mirror of
https://github.com/facebook/rocksdb.git
synced 2026-11-06 11:02:36 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
410c562319 | ||
|
|
49d0c21baf | ||
|
|
b72fe4f2b9 | ||
|
|
8725d9014b | ||
|
|
3df9173a8c | ||
|
|
c3edaf57f3 | ||
|
|
f340a2eccc | ||
|
|
78c83ac1ec | ||
|
|
190bb0bd24 | ||
|
|
fdc2970d37 | ||
|
|
d55655a423 | ||
|
|
c8aafdba33 | ||
|
|
1601da4049 | ||
|
|
34d8f03af4 | ||
|
|
05996cd497 | ||
|
|
25837eeee5 | ||
|
|
c6cfbc2919 | ||
|
|
d27b47f439 | ||
|
|
9d490593d0 | ||
|
|
4bdfb7e7da | ||
|
|
2dcfc54752 | ||
|
|
504ff4ed55 | ||
|
|
e3a91ec1e3 | ||
|
|
58420b7c60 | ||
|
|
945fcbe820 | ||
|
|
02bce9b1af | ||
|
|
85910fb575 | ||
|
|
96305d9bb4 | ||
|
|
82586e293e | ||
|
|
873f7fe535 | ||
|
|
37a26591c7 | ||
|
|
ab1fb6cf8e | ||
|
|
6403642c02 | ||
|
|
de376be2ba | ||
|
|
adb750fdf4 | ||
|
|
20d051a00e | ||
|
|
7d80ea4544 | ||
|
|
eaa4f9d23b | ||
|
|
fccc881894 | ||
|
|
0119a8c78b | ||
|
|
9727956436 | ||
|
|
09175119d2 | ||
|
|
20d065d940 | ||
|
|
0c533e61bc | ||
|
|
6efdbe85e3 | ||
|
|
7b2b4b7c53 | ||
|
|
f2a8ee8ff2 | ||
|
|
7208116105 | ||
|
|
11631c0609 | ||
|
|
a00391c729 | ||
|
|
1d94aeea44 | ||
|
|
8dc3d77b59 | ||
|
|
09cd25f763 | ||
|
|
5bc8abc0ec | ||
|
|
f91f6bd78e | ||
|
|
7c9e50e37d | ||
|
|
2ea356d0be |
No files matched your search
@@ -4,8 +4,8 @@ runs:
|
||||
steps:
|
||||
- name: Install Maven
|
||||
run: |
|
||||
wget --no-check-certificate https://dlcdn.apache.org/maven/maven-3/3.9.6/binaries/apache-maven-3.9.6-bin.tar.gz
|
||||
tar zxf apache-maven-3.9.6-bin.tar.gz
|
||||
echo "export M2_HOME=$(pwd)/apache-maven-3.9.6" >> $GITHUB_ENV
|
||||
echo "$(pwd)/apache-maven-3.9.6/bin" >> $GITHUB_PATH
|
||||
wget --no-check-certificate https://dlcdn.apache.org/maven/maven-3/3.9.10/binaries/apache-maven-3.9.10-bin.tar.gz
|
||||
tar zxf apache-maven-3.9.10-bin.tar.gz
|
||||
echo "export M2_HOME=$(pwd)/apache-maven-3.9.10" >> $GITHUB_ENV
|
||||
echo "$(pwd)/apache-maven-3.9.10/bin" >> $GITHUB_PATH
|
||||
shell: bash
|
||||
@@ -38,7 +38,7 @@ runs:
|
||||
$env:Path = $env:JAVA_HOME + ";" + $env:Path
|
||||
mkdir build
|
||||
cd build
|
||||
& cmake -G "$Env:CMAKE_GENERATOR" -DCMAKE_BUILD_TYPE=Debug -DOPTDBG=1 -DPORTABLE="$Env:CMAKE_PORTABLE" -DSNAPPY=1 -DJNI=1 ..
|
||||
& cmake -G "$Env:CMAKE_GENERATOR" -DCMAKE_BUILD_TYPE=Debug -DOPTDBG=1 -DPORTABLE="$Env:CMAKE_PORTABLE" -DSNAPPY=1 -DXPRESS=1 -DJNI=1 ..
|
||||
if(!$?) { Exit $LASTEXITCODE }
|
||||
cd ..
|
||||
echo "Building with VS version: $Env:CMAKE_GENERATOR"
|
||||
|
||||
@@ -52,6 +52,14 @@ jobs:
|
||||
run: make check-buck-targets
|
||||
- name: Simple source code checks
|
||||
run: make check-sources
|
||||
- name: Sanity check check_format_compatible.sh
|
||||
run: |-
|
||||
export TEST_TMPDIR=/dev/shm/rocksdb
|
||||
rm -rf /dev/shm/rocksdb
|
||||
mkdir /dev/shm/rocksdb
|
||||
git reset --hard
|
||||
git config --global --add safe.directory /__w/rocksdb/rocksdb
|
||||
SANITY_CHECK=1 LONG_TEST=1 tools/check_format_compatible.sh
|
||||
# ========================= Linux With Tests ======================== #
|
||||
build-linux:
|
||||
if: ${{ github.repository_owner == 'facebook' }}
|
||||
@@ -476,7 +484,7 @@ jobs:
|
||||
runs-on:
|
||||
labels: 4-core-ubuntu
|
||||
container:
|
||||
image: evolvedbinary/rocksjava:centos6_x64-be
|
||||
image: evolvedbinary/rocksjava:centos7_x64-be
|
||||
options: --shm-size=16gb
|
||||
steps:
|
||||
# The docker image is intentionally based on an OS that has an older GLIBC version.
|
||||
@@ -504,7 +512,7 @@ jobs:
|
||||
runs-on:
|
||||
labels: 4-core-ubuntu
|
||||
container:
|
||||
image: evolvedbinary/rocksjava:centos6_x64-be
|
||||
image: evolvedbinary/rocksjava:centos7_x64-be
|
||||
options: --shm-size=16gb
|
||||
steps:
|
||||
# The docker image is intentionally based on an OS that has an older GLIBC version.
|
||||
@@ -599,7 +607,7 @@ jobs:
|
||||
runs-on:
|
||||
labels: 4-core-ubuntu
|
||||
container:
|
||||
image: evolvedbinary/rocksjava:rockylinux8_x64-be
|
||||
image: evolvedbinary/rocksjava:alpine3_x64-be
|
||||
options: --shm-size=16gb
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
|
||||
@@ -249,6 +249,7 @@ cpp_library_wrapper(name="rocksdb_lib", srcs=[
|
||||
"trace_replay/trace_record_result.cc",
|
||||
"trace_replay/trace_replay.cc",
|
||||
"util/async_file_reader.cc",
|
||||
"util/auto_skip_compressor.cc",
|
||||
"util/build_version.cc",
|
||||
"util/cleanable.cc",
|
||||
"util/coding.cc",
|
||||
@@ -267,6 +268,7 @@ cpp_library_wrapper(name="rocksdb_lib", srcs=[
|
||||
"util/random.cc",
|
||||
"util/rate_limiter.cc",
|
||||
"util/ribbon_config.cc",
|
||||
"util/simple_mixed_compressor.cc",
|
||||
"util/slice.cc",
|
||||
"util/status.cc",
|
||||
"util/stderr_logger.cc",
|
||||
@@ -4709,6 +4711,12 @@ cpp_unittest_wrapper(name="compressed_secondary_cache_test",
|
||||
extra_compiler_flags=[])
|
||||
|
||||
|
||||
cpp_unittest_wrapper(name="compression_test",
|
||||
srcs=["util/compression_test.cc"],
|
||||
deps=[":rocksdb_test_lib"],
|
||||
extra_compiler_flags=[])
|
||||
|
||||
|
||||
cpp_unittest_wrapper(name="configurable_test",
|
||||
srcs=["options/configurable_test.cc"],
|
||||
deps=[":rocksdb_test_lib"],
|
||||
|
||||
@@ -874,11 +874,13 @@ set(SOURCES
|
||||
trace_replay/trace_record.cc
|
||||
trace_replay/trace_replay.cc
|
||||
util/async_file_reader.cc
|
||||
util/auto_skip_compressor.cc
|
||||
util/cleanable.cc
|
||||
util/coding.cc
|
||||
util/compaction_job_stats_impl.cc
|
||||
util/comparator.cc
|
||||
util/compression.cc
|
||||
util/simple_mixed_compressor.cc
|
||||
util/compression_context_cache.cc
|
||||
util/concurrent_task_limiter_impl.cc
|
||||
util/crc32c.cc
|
||||
@@ -1456,6 +1458,7 @@ if(WITH_TESTS)
|
||||
util/autovector_test.cc
|
||||
util/bloom_test.cc
|
||||
util/coding_test.cc
|
||||
util/compression_test.cc
|
||||
util/crc32c_test.cc
|
||||
util/defer_test.cc
|
||||
util/dynamic_bloom_test.cc
|
||||
|
||||
+43
@@ -1,6 +1,49 @@
|
||||
# Rocksdb Change Log
|
||||
> NOTE: Entries for next release do not go here. Follow instructions in `unreleased_history/README.txt`
|
||||
|
||||
## 10.4.2 (07/09/2025)
|
||||
### Bug Fixes
|
||||
* Fix a race condition between concurrent DB::Open sharing the same SstFileManager instance.
|
||||
|
||||
## 10.4.1 (07/01/2025)
|
||||
### Behavior Changes
|
||||
* RocksDB now triggers eligible compactions every 12 hours when periodic compaction is configured. This solves a limitation of the compaction trigger mechanism, which would only trigger compaction after specific events like flush, compaction, or SetOptions.
|
||||
### Bug Fixes
|
||||
* Fix a bug in BackupEngine that can crash backup due to a null FSWritableFile passed to WritableFileWriter.
|
||||
|
||||
## 10.4.0 (06/20/2025)
|
||||
### New Features
|
||||
* Add a new CF option `memtable_avg_op_scan_flush_trigger` that supports triggering memtable flush when an iterator scans through an expensive range of keys, with the average number of skipped keys from the active memtable exceeding the threshold.
|
||||
* Vector based memtable now supports concurrent writers (DBOptions::allow_concurrent_memtable_write) #13675.
|
||||
* Add new experimental `TransactionOptions::large_txn_commit_optimize_byte_threshold` to enable optimizations for large transaction commit by transaction batch data size.
|
||||
* Add a new option `CompactionOptionsUniversal::reduce_file_locking` and if it's true, auto universal compaction picking will adjust to minimize locking of input files when bottom priority compactions are waiting to run. This can increase the likelihood of existing L0s being selected for compaction, thereby improving write stall and reducing read regression.
|
||||
* Add new `format_version=7` to aid experimental support of custom compression algorithms with CompressionManager and block-based table. This format version includes changing the format of `TableProperties::compression_name`.
|
||||
|
||||
### Public API Changes
|
||||
* Change NewExternalTableFactory to return a unique_ptr instead of shared_ptr.
|
||||
* Add an optional min file size requirement for deletion triggered compaction. It can be specified when creating `CompactOnDeletionCollectorFactory`.
|
||||
|
||||
### Behavior Changes
|
||||
* `TransactionOptions::large_txn_commit_optimize_threshold` now has default value 0 for disabled. `TransactionDBOptions::txn_commit_bypass_memtable_threshold` now has no effect on transactions.
|
||||
|
||||
### Bug Fixes
|
||||
* Fix a bug where CreateColumnFamilyWithImport() could miss the SST file for the memtable flush it triggered. The exported CF then may not contain the updates in the memtable when CreateColumnFamilyWithImport() is called.
|
||||
* Fix iterator operations returning NotImplemented status if disallow_memtable_writes and paranoid_memory_checks CF options are both set.
|
||||
* Fixed handling of file checksums in IngestExternalFile() to allow providing checksums using recognized but not necessarily the DB's preferred checksum function, to ease migration between checksum functions.
|
||||
|
||||
## 10.3.0 (05/17/2025)
|
||||
### New Features
|
||||
* Add new experimental `CompactionOptionsFIFO::allow_trivial_copy_when_change_temperature` along with `CompactionOptionsFIFO::trivial_copy_buffer_size` to allow optimizing FIFO compactions with tiering when kChangeTemperature to move files from source tier FileSystem to another tier FileSystem via trivial and direct copying raw sst file instead of reading thru the content of the SST file then rebuilding the table files.
|
||||
* Add a new field to Compaction Stats in LOG files for the pre-compression size written to each level.
|
||||
* Add new experimental `TransactionOptions::large_txn_commit_optimize_threshold` to enable optimizations for large transaction commit with per transaction threshold. `TransactionDBOptions::txn_commit_bypass_memtable_threshold` is deprecated in favor of this transaction option.
|
||||
* [internal team use only] Allow an application-defined `request_id` to be passed to RocksDB and propagated to the filesystem via IODebugContext
|
||||
|
||||
### Bug Fixes
|
||||
* Fix a bug where transaction lock upgrade can incorrectly fail with a Deadlock status. This happens when a transaction has a non-zero timeout and tries to upgrade a shared lock that is also held by another transaction.
|
||||
* Pass wrapped WritableFileWriter pointer to ExternalTableBuilder so that the file checksum can be correctly calculated and returned by SstFileWriter for external table files.
|
||||
* Fix an infinite-loop bug in transaction locking. This can happen if a transaction reaches lock limit and its time out expires before it attempts to wait for it.
|
||||
* Fixed a potential data race with `CompressionOptions::parallel_threads > 1` and a `TablePropertiesCollector` overriding `BlockAdd()`.
|
||||
|
||||
## 10.2.0 (04/21/2025)
|
||||
### New Features
|
||||
* Provide histogram stats `COMPACTION_PREFETCH_BYTES` to measure number of bytes for RocksDB's prefetching (as opposed to file
|
||||
|
||||
@@ -1491,6 +1491,9 @@ db_test: $(OBJ_DIR)/db/db_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
db_test2: $(OBJ_DIR)/db/db_test2.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
compression_test: $(OBJ_DIR)/util/compression_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
db_logical_block_size_cache_test: $(OBJ_DIR)/db/db_logical_block_size_cache_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
@@ -2144,14 +2147,14 @@ ZLIB_DOWNLOAD_BASE ?= http://zlib.net
|
||||
BZIP2_VER ?= 1.0.8
|
||||
BZIP2_SHA256 ?= ab5a03176ee106d3f0fa90e381da478ddae405918153cca248e682cd0c4a2269
|
||||
BZIP2_DOWNLOAD_BASE ?= http://sourceware.org/pub/bzip2
|
||||
SNAPPY_VER ?= 1.2.1
|
||||
SNAPPY_SHA256 ?= 736aeb64d86566d2236ddffa2865ee5d7a82d26c9016b36218fcc27ea4f09f86
|
||||
SNAPPY_VER ?= 1.2.2
|
||||
SNAPPY_SHA256 ?= 90f74bc1fbf78a6c56b3c4a082a05103b3a56bb17bca1a27e052ea11723292dc
|
||||
SNAPPY_DOWNLOAD_BASE ?= https://github.com/google/snappy/archive
|
||||
LZ4_VER ?= 1.9.4
|
||||
LZ4_SHA256 ?= 0b0e3aa07c8c063ddf40b082bdf7e37a1562bda40a0ff5272957f3e987e0e54b
|
||||
LZ4_VER ?= 1.10.0
|
||||
LZ4_SHA256 ?= 537512904744b35e232912055ccf8ec66d768639ff3abe5788d90d792ec5f48b
|
||||
LZ4_DOWNLOAD_BASE ?= https://github.com/lz4/lz4/archive
|
||||
ZSTD_VER ?= 1.5.5
|
||||
ZSTD_SHA256 ?= 98e9c3d949d1b924e28e01eccb7deed865eefebf25c2f21c702e5cd5b63b85e1
|
||||
ZSTD_VER ?= 1.5.7
|
||||
ZSTD_SHA256 ?= 37d7284556b20954e56e1ca85b80226768902e2edabd3b649e9e72c0c9012ee3
|
||||
ZSTD_DOWNLOAD_BASE ?= https://github.com/facebook/zstd/archive
|
||||
CURL_SSL_OPTS ?= --tlsv1
|
||||
|
||||
@@ -2372,27 +2375,27 @@ rocksdbjavastaticreleasedocker: rocksdbjavastaticosx rocksdbjavastaticdockerx86
|
||||
|
||||
rocksdbjavastaticdockerx86:
|
||||
mkdir -p java/target
|
||||
docker run --rm --name rocksdb_linux_x86-be --platform linux/386 --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) --env J=$(J) evolvedbinary/rocksjava:centos6_x86-be /rocksdb-host/java/crossbuild/docker-build-linux.sh
|
||||
docker run --rm --name rocksdb_linux_x86-be --platform linux/386 --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) --env J=$(J) evolvedbinary/rocksjava:centos7_x86-be /rocksdb-host/java/crossbuild/docker-build-linux.sh
|
||||
|
||||
rocksdbjavastaticdockerx86_64:
|
||||
mkdir -p java/target
|
||||
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) --env J=$(J) evolvedbinary/rocksjava:centos6_x64-be /rocksdb-host/java/crossbuild/docker-build-linux.sh
|
||||
docker run --rm --name rocksdb_linux_x64-be --platform linux/amd64 --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) --env J=$(J) evolvedbinary/rocksjava:centos7_x64-be /rocksdb-host/java/crossbuild/docker-build-linux.sh
|
||||
|
||||
rocksdbjavastaticdockerppc64le:
|
||||
mkdir -p java/target
|
||||
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) --env J=$(J) evolvedbinary/rocksjava:centos7_ppc64le-be /rocksdb-host/java/crossbuild/docker-build-linux.sh
|
||||
docker run --rm --name rocksdb_linux_ppc64le-be --platform linux/ppc64le --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) --env J=$(J) evolvedbinary/rocksjava:centos7_ppc64le-be /rocksdb-host/java/crossbuild/docker-build-linux.sh
|
||||
|
||||
rocksdbjavastaticdockerarm64v8:
|
||||
mkdir -p java/target
|
||||
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) --env J=$(J) evolvedbinary/rocksjava:centos7_arm64v8-be /rocksdb-host/java/crossbuild/docker-build-linux.sh
|
||||
docker run --rm --name rocksdb_linux_arm64v8-be --platform linux/aarch64 --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) --env J=$(J) evolvedbinary/rocksjava:centos7_arm64v8-be /rocksdb-host/java/crossbuild/docker-build-linux.sh
|
||||
|
||||
rocksdbjavastaticdockers390x:
|
||||
mkdir -p java/target
|
||||
docker run --rm --name rocksdb_linux_s390x-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) --env J=$(J) evolvedbinary/rocksjava:ubuntu18_s390x-be /rocksdb-host/java/crossbuild/docker-build-linux.sh
|
||||
docker run --rm --name rocksdb_linux_s390x-be --platform linux/s390x --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) --env J=$(J) evolvedbinary/rocksjava:ubuntu18_s390x-be /rocksdb-host/java/crossbuild/docker-build-linux.sh
|
||||
|
||||
rocksdbjavastaticdockerriscv64:
|
||||
mkdir -p java/target
|
||||
docker run --rm --name rocksdb_linux_riscv64-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) --env J=$(J) evolvedbinary/rocksjava:ubuntu20_riscv64-be /rocksdb-host/java/crossbuild/docker-build-linux.sh
|
||||
docker run --rm --name rocksdb_linux_riscv64-be --platform linux/riscv64 --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) --env J=$(J) evolvedbinary/rocksjava:ubuntu20_riscv64-be /rocksdb-host/java/crossbuild/docker-build-linux.sh
|
||||
|
||||
rocksdbjavastaticdockerx86musl:
|
||||
mkdir -p java/target
|
||||
@@ -2400,19 +2403,19 @@ rocksdbjavastaticdockerx86musl:
|
||||
|
||||
rocksdbjavastaticdockerx86_64musl:
|
||||
mkdir -p java/target
|
||||
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) --env J=$(J) evolvedbinary/rocksjava:alpine3_x64-be /rocksdb-host/java/crossbuild/docker-build-linux.sh
|
||||
docker run --rm --name rocksdb_linux_x64-musl-be --platform linux/amd64 --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) --env J=$(J) evolvedbinary/rocksjava:alpine3_x64-be /rocksdb-host/java/crossbuild/docker-build-linux.sh
|
||||
|
||||
rocksdbjavastaticdockerppc64lemusl:
|
||||
mkdir -p java/target
|
||||
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) --env J=$(J) evolvedbinary/rocksjava:alpine3_ppc64le-be /rocksdb-host/java/crossbuild/docker-build-linux.sh
|
||||
docker run --rm --name rocksdb_linux_ppc64le-musl-be --platform linux/ppc64le --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) --env J=$(J) evolvedbinary/rocksjava:alpine3_ppc64le-be /rocksdb-host/java/crossbuild/docker-build-linux.sh
|
||||
|
||||
rocksdbjavastaticdockerarm64v8musl:
|
||||
mkdir -p java/target
|
||||
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) --env J=$(J) evolvedbinary/rocksjava:alpine3_arm64v8-be /rocksdb-host/java/crossbuild/docker-build-linux.sh
|
||||
docker run --rm --name rocksdb_linux_arm64v8-musl-be --platform linux/aarch64 --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) --env J=$(J) evolvedbinary/rocksjava:alpine3_arm64v8-be /rocksdb-host/java/crossbuild/docker-build-linux.sh
|
||||
|
||||
rocksdbjavastaticdockers390xmusl:
|
||||
mkdir -p java/target
|
||||
docker run --rm --name rocksdb_linux_s390x-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) --env J=$(J) evolvedbinary/rocksjava:alpine3_s390x-be /rocksdb-host/java/crossbuild/docker-build-linux.sh
|
||||
docker run --rm --name rocksdb_linux_s390x-musl-be --platform linux/s390x --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) --env J=$(J) evolvedbinary/rocksjava:alpine3_s390x-be /rocksdb-host/java/crossbuild/docker-build-linux.sh
|
||||
|
||||
rocksdbjavastaticpublish: rocksdbjavastaticrelease rocksdbjavastaticpublishcentral
|
||||
|
||||
@@ -2467,8 +2470,8 @@ jtest_run:
|
||||
jtest: rocksdbjava
|
||||
cd java;$(MAKE) sample test
|
||||
|
||||
jpmd: rocksdbjava rocksdbjavageneratepom
|
||||
cd java;$(MAKE) pmd
|
||||
jpmd: rocksdbjavageneratepom
|
||||
cd java;$(MAKE) java java_test pmd
|
||||
|
||||
jdb_bench:
|
||||
cd java;$(MAKE) db_bench;
|
||||
@@ -2489,11 +2492,13 @@ checkout_folly:
|
||||
fi
|
||||
@# Pin to a particular version for public CI, so that PR authors don't
|
||||
@# need to worry about folly breaking our integration. Update periodically
|
||||
cd third-party/folly && git reset --hard 8e8186f67de7a23d3a07366946b1617343927d84
|
||||
@# NOTE: this hack is required for clang in some cases
|
||||
perl -pi -e 's/int rv = syscall/int rv = (int)syscall/' third-party/folly/folly/detail/Futex.cpp
|
||||
@# NOTE: this hack is required for gcc in some cases
|
||||
perl -pi -e 's/(__has_include.<experimental.memory_resource>.)/__cpp_rtti && $$1/' third-party/folly/folly/memory/MemoryResource.h
|
||||
cd third-party/folly && git reset --hard d17bf897cb5bbf8f07b122a614e8cffdc38edcde
|
||||
@# Apparently missing include
|
||||
perl -pi -e 's/(#include <atomic>)/$$1\n#include <cstring>/' third-party/folly/folly/lang/Exception.h
|
||||
@# Warning-as-error on memcpy
|
||||
perl -pi -e 's/memcpy.&ptr/memcpy((void*)&ptr/' third-party/folly/folly/lang/Exception.cpp
|
||||
@# const mismatch
|
||||
perl -pi -e 's/: environ/: (const char**)(environ)/' third-party/folly/folly/Subprocess.cpp
|
||||
@# NOTE: boost source will be needed for any build including `USE_FOLLY_LITE` builds as those depend on boost headers
|
||||
cd third-party/folly && $(PYTHON) build/fbcode_builder/getdeps.py fetch boost
|
||||
|
||||
|
||||
@@ -118,6 +118,9 @@ fi
|
||||
# fi
|
||||
set -e
|
||||
|
||||
# Exclude third-party from formatting
|
||||
EXCLUDE=':!third-party/'
|
||||
|
||||
uncommitted_code=`git diff HEAD`
|
||||
|
||||
# If there's no uncommitted changes, we assume user are doing post-commit
|
||||
@@ -137,11 +140,11 @@ then
|
||||
# should be relevant for formatting fixes.
|
||||
FORMAT_UPSTREAM_MERGE_BASE="$(git merge-base "$FORMAT_UPSTREAM" HEAD)"
|
||||
# Get the differences
|
||||
diffs=$(git diff -U0 "$FORMAT_UPSTREAM_MERGE_BASE" | $CLANG_FORMAT_DIFF -p 1) || true
|
||||
diffs=$(git diff -U0 "$FORMAT_UPSTREAM_MERGE_BASE" -- $EXCLUDE | $CLANG_FORMAT_DIFF -p 1) || true
|
||||
echo "Checking format of changes not yet in $FORMAT_UPSTREAM..."
|
||||
else
|
||||
# Check the format of uncommitted lines,
|
||||
diffs=$(git diff -U0 HEAD | $CLANG_FORMAT_DIFF -p 1) || true
|
||||
diffs=$(git diff -U0 HEAD -- $EXCLUDE | $CLANG_FORMAT_DIFF -p 1) || true
|
||||
echo "Checking format of uncommitted changes..."
|
||||
fi
|
||||
|
||||
@@ -187,9 +190,9 @@ fi
|
||||
# Do in-place format adjustment.
|
||||
if [ -z "$uncommitted_code" ]
|
||||
then
|
||||
git diff -U0 "$FORMAT_UPSTREAM_MERGE_BASE" | $CLANG_FORMAT_DIFF -i -p 1
|
||||
git diff -U0 "$FORMAT_UPSTREAM_MERGE_BASE" -- $EXCLUDE | $CLANG_FORMAT_DIFF -i -p 1
|
||||
else
|
||||
git diff -U0 HEAD | $CLANG_FORMAT_DIFF -i -p 1
|
||||
git diff -U0 HEAD -- $EXCLUDE | $CLANG_FORMAT_DIFF -i -p 1
|
||||
fi
|
||||
echo "Files reformatted!"
|
||||
|
||||
|
||||
Vendored
+7
-6
@@ -80,15 +80,16 @@ std::unique_ptr<SecondaryCacheResultHandle> CompressedSecondaryCache::Lookup(
|
||||
ptr = reinterpret_cast<CacheAllocationPtr*>(handle_value);
|
||||
handle_value_charge = cache_->GetCharge(lru_handle);
|
||||
data_ptr = ptr->get();
|
||||
data_ptr = GetVarint32Ptr(data_ptr, data_ptr + 1,
|
||||
static_cast<uint32_t*>(&type_32));
|
||||
const char* limit = ptr->get() + handle_value_charge;
|
||||
data_ptr =
|
||||
GetVarint32Ptr(data_ptr, limit, static_cast<uint32_t*>(&type_32));
|
||||
type = static_cast<CompressionType>(type_32);
|
||||
data_ptr = GetVarint32Ptr(data_ptr, data_ptr + 1,
|
||||
static_cast<uint32_t*>(&source_32));
|
||||
data_ptr =
|
||||
GetVarint32Ptr(data_ptr, limit, static_cast<uint32_t*>(&source_32));
|
||||
source = static_cast<CacheTier>(source_32);
|
||||
uint64_t data_size = 0;
|
||||
data_ptr = GetVarint64Ptr(data_ptr, ptr->get() + handle_value_charge,
|
||||
static_cast<uint64_t*>(&data_size));
|
||||
data_ptr =
|
||||
GetVarint64Ptr(data_ptr, limit, static_cast<uint64_t*>(&data_size));
|
||||
assert(handle_value_charge > data_size);
|
||||
handle_value_charge = data_size;
|
||||
}
|
||||
|
||||
+2
-2
@@ -218,8 +218,7 @@ Status BuildTable(
|
||||
const Slice& key = c_iter.key();
|
||||
const Slice& value = c_iter.value();
|
||||
ParsedInternalKey ikey = c_iter.ikey();
|
||||
key_after_flush_buf.assign(key.data(), key.size());
|
||||
Slice key_after_flush = key_after_flush_buf;
|
||||
Slice key_after_flush = key;
|
||||
Slice value_after_flush = value;
|
||||
|
||||
if (ikey.type == kTypeValuePreferredSeqno) {
|
||||
@@ -237,6 +236,7 @@ Status BuildTable(
|
||||
std::min(smallest_preferred_seqno, preferred_seqno);
|
||||
} else {
|
||||
// Cannot get a useful preferred seqno, convert it to a kTypeValue.
|
||||
key_after_flush_buf.assign(key.data(), key.size());
|
||||
UpdateInternalKey(&key_after_flush_buf, ikey.sequence, kTypeValue);
|
||||
ikey = ParsedInternalKey(ikey.user_key, ikey.sequence, kTypeValue);
|
||||
key_after_flush = key_after_flush_buf;
|
||||
|
||||
@@ -3748,6 +3748,16 @@ uint32_t rocksdb_options_get_memtable_op_scan_flush_trigger(
|
||||
return opt->rep.memtable_op_scan_flush_trigger;
|
||||
}
|
||||
|
||||
void rocksdb_options_set_memtable_avg_op_scan_flush_trigger(
|
||||
rocksdb_options_t* opt, uint32_t n) {
|
||||
opt->rep.memtable_avg_op_scan_flush_trigger = n;
|
||||
}
|
||||
|
||||
uint32_t rocksdb_options_get_memtable_avg_op_scan_flush_trigger(
|
||||
rocksdb_options_t* opt) {
|
||||
return opt->rep.memtable_avg_op_scan_flush_trigger;
|
||||
}
|
||||
|
||||
void rocksdb_options_enable_statistics(rocksdb_options_t* opt) {
|
||||
opt->rep.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics();
|
||||
}
|
||||
|
||||
+10
@@ -2220,6 +2220,10 @@ int main(int argc, char** argv) {
|
||||
CheckCondition(100 ==
|
||||
rocksdb_options_get_memtable_op_scan_flush_trigger(o));
|
||||
|
||||
rocksdb_options_set_memtable_avg_op_scan_flush_trigger(o, 150);
|
||||
CheckCondition(150 ==
|
||||
rocksdb_options_get_memtable_avg_op_scan_flush_trigger(o));
|
||||
|
||||
rocksdb_options_set_ttl(o, 5000);
|
||||
CheckCondition(5000 == rocksdb_options_get_ttl(o));
|
||||
|
||||
@@ -2663,6 +2667,12 @@ int main(int argc, char** argv) {
|
||||
CheckCondition(100 ==
|
||||
rocksdb_options_get_memtable_op_scan_flush_trigger(o));
|
||||
|
||||
rocksdb_options_set_memtable_avg_op_scan_flush_trigger(copy, 900);
|
||||
CheckCondition(
|
||||
900 == rocksdb_options_get_memtable_avg_op_scan_flush_trigger(copy));
|
||||
CheckCondition(150 ==
|
||||
rocksdb_options_get_memtable_avg_op_scan_flush_trigger(o));
|
||||
|
||||
rocksdb_options_set_ttl(copy, 8000);
|
||||
CheckCondition(8000 == rocksdb_options_get_ttl(copy));
|
||||
CheckCondition(5000 == rocksdb_options_get_ttl(o));
|
||||
|
||||
+55
-18
@@ -110,23 +110,48 @@ void GetInternalTblPropCollFactory(
|
||||
}
|
||||
}
|
||||
|
||||
Status CheckCompressionSupportedWithManager(
|
||||
CompressionType type, UnownedPtr<CompressionManager> mgr) {
|
||||
if (mgr) {
|
||||
if (!mgr->SupportsCompressionType(type)) {
|
||||
return Status::NotSupported("Compression type " +
|
||||
CompressionTypeToString(type) +
|
||||
" is not recognized/supported by this "
|
||||
"version of CompressionManager " +
|
||||
mgr->GetId());
|
||||
}
|
||||
} else {
|
||||
if (!CompressionTypeSupported(type)) {
|
||||
if (type <= kLastBuiltinCompression) {
|
||||
return Status::InvalidArgument("Compression type " +
|
||||
CompressionTypeToString(type) +
|
||||
" is not linked with the binary.");
|
||||
} else {
|
||||
return Status::NotSupported(
|
||||
"Compression type " + CompressionTypeToString(type) +
|
||||
" is not recognized/supported by built-in CompressionManager.");
|
||||
}
|
||||
}
|
||||
}
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status CheckCompressionSupported(const ColumnFamilyOptions& cf_options) {
|
||||
if (!cf_options.compression_per_level.empty()) {
|
||||
for (size_t level = 0; level < cf_options.compression_per_level.size();
|
||||
++level) {
|
||||
if (!CompressionTypeSupported(cf_options.compression_per_level[level])) {
|
||||
return Status::InvalidArgument(
|
||||
"Compression type " +
|
||||
CompressionTypeToString(cf_options.compression_per_level[level]) +
|
||||
" is not linked with the binary.");
|
||||
Status s = CheckCompressionSupportedWithManager(
|
||||
cf_options.compression_per_level[level],
|
||||
cf_options.compression_manager.get());
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (!CompressionTypeSupported(cf_options.compression)) {
|
||||
return Status::InvalidArgument(
|
||||
"Compression type " +
|
||||
CompressionTypeToString(cf_options.compression) +
|
||||
" is not linked with the binary.");
|
||||
Status s = CheckCompressionSupportedWithManager(
|
||||
cf_options.compression, cf_options.compression_manager.get());
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
}
|
||||
if (cf_options.compression_opts.zstd_max_train_bytes > 0) {
|
||||
@@ -448,13 +473,21 @@ ColumnFamilyOptions SanitizeCfOptions(const ImmutableDBOptions& db_options,
|
||||
result.preclude_last_level_data_seconds = 0;
|
||||
}
|
||||
|
||||
if (read_only && result.memtable_op_scan_flush_trigger != 0) {
|
||||
ROCKS_LOG_WARN(db_options.info_log.get(),
|
||||
"option memtable_op_scan_flush_trigger is sanitized to "
|
||||
"0(disabled) for read only DB.");
|
||||
result.memtable_op_scan_flush_trigger = 0;
|
||||
if (read_only) {
|
||||
if (result.memtable_op_scan_flush_trigger) {
|
||||
ROCKS_LOG_WARN(db_options.info_log.get(),
|
||||
"option memtable_op_scan_flush_trigger is sanitized to "
|
||||
"0(disabled) for read only DB.");
|
||||
result.memtable_op_scan_flush_trigger = 0;
|
||||
}
|
||||
if (result.memtable_avg_op_scan_flush_trigger) {
|
||||
ROCKS_LOG_WARN(
|
||||
db_options.info_log.get(),
|
||||
"option memtable_avg_op_scan_flush_trigger is sanitized to "
|
||||
"0(disabled) for read only DB.");
|
||||
result.memtable_avg_op_scan_flush_trigger = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -1209,10 +1242,12 @@ Compaction* ColumnFamilyData::PickCompaction(
|
||||
const MutableCFOptions& mutable_options,
|
||||
const MutableDBOptions& mutable_db_options,
|
||||
const std::vector<SequenceNumber>& existing_snapshots,
|
||||
const SnapshotChecker* snapshot_checker, LogBuffer* log_buffer) {
|
||||
const SnapshotChecker* snapshot_checker, LogBuffer* log_buffer,
|
||||
bool require_max_output_level) {
|
||||
auto* result = compaction_picker_->PickCompaction(
|
||||
GetName(), mutable_options, mutable_db_options, existing_snapshots,
|
||||
snapshot_checker, current_->storage_info(), log_buffer);
|
||||
snapshot_checker, current_->storage_info(), log_buffer,
|
||||
require_max_output_level);
|
||||
if (result != nullptr) {
|
||||
result->FinalizeInputInfo(current_);
|
||||
}
|
||||
@@ -1598,6 +1633,8 @@ Status ColumnFamilyData::SetOptions(
|
||||
Status s = GetColumnFamilyOptionsFromMap(config_opts, cf_opts, options_map,
|
||||
&cf_opts);
|
||||
if (s.ok()) {
|
||||
// FIXME: we should call SanitizeOptions() too or consolidate it with
|
||||
// ValidateOptions().
|
||||
s = ValidateOptions(db_opts, cf_opts);
|
||||
}
|
||||
if (s.ok()) {
|
||||
|
||||
+2
-1
@@ -424,7 +424,8 @@ class ColumnFamilyData {
|
||||
const MutableCFOptions& mutable_options,
|
||||
const MutableDBOptions& mutable_db_options,
|
||||
const std::vector<SequenceNumber>& existing_snapshots,
|
||||
const SnapshotChecker* snapshot_checker, LogBuffer* log_buffer);
|
||||
const SnapshotChecker* snapshot_checker, LogBuffer* log_buffer,
|
||||
bool require_max_output_level = false);
|
||||
|
||||
// Check if the passed range overlap with any running compactions.
|
||||
// REQUIRES: DB mutex held
|
||||
|
||||
@@ -463,6 +463,11 @@ class Compaction {
|
||||
const int start_level,
|
||||
const int output_level);
|
||||
|
||||
static bool OutputToNonZeroMaxOutputLevel(int output_level,
|
||||
int max_output_level) {
|
||||
return output_level > 0 && output_level == max_output_level;
|
||||
}
|
||||
|
||||
// If some data cannot be safely migrated "up" the LSM tree due to a change
|
||||
// in the preclude_last_level_data_seconds setting, this indicates a sequence
|
||||
// number for the newest data that must be kept in the last level.
|
||||
|
||||
@@ -133,10 +133,7 @@ CompactionJob::CompactionJob(
|
||||
LogBuffer* log_buffer, FSDirectory* db_directory,
|
||||
FSDirectory* output_directory, FSDirectory* blob_output_directory,
|
||||
Statistics* stats, InstrumentedMutex* db_mutex,
|
||||
ErrorHandler* db_error_handler,
|
||||
std::vector<SequenceNumber> existing_snapshots,
|
||||
SequenceNumber earliest_write_conflict_snapshot,
|
||||
const SnapshotChecker* snapshot_checker, JobContext* job_context,
|
||||
ErrorHandler* db_error_handler, JobContext* job_context,
|
||||
std::shared_ptr<Cache> table_cache, EventLogger* event_logger,
|
||||
bool paranoid_file_checks, bool measure_io_stats, const std::string& dbname,
|
||||
CompactionJobStats* compaction_job_stats, Env::Priority thread_pri,
|
||||
@@ -173,12 +170,7 @@ CompactionJob::CompactionJob(
|
||||
blob_output_directory_(blob_output_directory),
|
||||
db_mutex_(db_mutex),
|
||||
db_error_handler_(db_error_handler),
|
||||
existing_snapshots_(std::move(existing_snapshots)),
|
||||
earliest_snapshot_(existing_snapshots_.empty()
|
||||
? kMaxSequenceNumber
|
||||
: existing_snapshots_.at(0)),
|
||||
earliest_write_conflict_snapshot_(earliest_write_conflict_snapshot),
|
||||
snapshot_checker_(snapshot_checker),
|
||||
earliest_snapshot_(job_context->GetEarliestSnapshotSequence()),
|
||||
job_context_(job_context),
|
||||
table_cache_(std::move(table_cache)),
|
||||
event_logger_(event_logger),
|
||||
@@ -193,6 +185,7 @@ CompactionJob::CompactionJob(
|
||||
bg_bottom_compaction_scheduled_(bg_bottom_compaction_scheduled) {
|
||||
assert(job_stats_ != nullptr);
|
||||
assert(log_buffer_ != nullptr);
|
||||
assert(job_context->snapshot_context_initialized);
|
||||
|
||||
const auto* cfd = compact_->compaction->column_family_data();
|
||||
ThreadStatusUtil::SetEnableTracking(db_options_.enable_thread_tracking);
|
||||
@@ -224,10 +217,9 @@ void CompactionJob::ReportStartedCompaction(Compaction* compaction) {
|
||||
ThreadStatus::COMPACTION_PROP_FLAGS,
|
||||
compaction->is_manual_compaction() +
|
||||
(compaction->deletion_compaction() << 1));
|
||||
|
||||
auto total_input_bytes = compaction->CalculateTotalInputSize();
|
||||
ThreadStatusUtil::SetThreadOperationProperty(
|
||||
ThreadStatus::COMPACTION_TOTAL_INPUT_BYTES,
|
||||
compaction->CalculateTotalInputSize());
|
||||
ThreadStatus::COMPACTION_TOTAL_INPUT_BYTES, total_input_bytes);
|
||||
|
||||
IOSTATS_RESET(bytes_written);
|
||||
IOSTATS_RESET(bytes_read);
|
||||
@@ -242,6 +234,16 @@ void CompactionJob::ReportStartedCompaction(Compaction* compaction) {
|
||||
|
||||
job_stats_->is_manual_compaction = compaction->is_manual_compaction();
|
||||
job_stats_->is_full_compaction = compaction->is_full_compaction();
|
||||
// populate compaction stats num_input_files and total_num_of_bytes
|
||||
size_t num_input_files = 0;
|
||||
for (int input_level = 0;
|
||||
input_level < static_cast<int>(compaction->num_input_levels());
|
||||
++input_level) {
|
||||
const LevelFilesBrief* flevel = compaction->input_levels(input_level);
|
||||
num_input_files += flevel->num_files;
|
||||
}
|
||||
job_stats_->CompactionJobStats::num_input_files = num_input_files;
|
||||
job_stats_->total_input_bytes = total_input_bytes;
|
||||
}
|
||||
|
||||
void CompactionJob::Prepare(
|
||||
@@ -837,8 +839,7 @@ Status CompactionJob::Run() {
|
||||
}
|
||||
|
||||
ReleaseSubcompactionResources();
|
||||
TEST_SYNC_POINT("CompactionJob::ReleaseSubcompactionResources:0");
|
||||
TEST_SYNC_POINT("CompactionJob::ReleaseSubcompactionResources:1");
|
||||
TEST_SYNC_POINT("CompactionJob::ReleaseSubcompactionResources");
|
||||
|
||||
for (const auto& state : compact_->sub_compact_states) {
|
||||
for (const auto& output : state.GetOutputs()) {
|
||||
@@ -1183,7 +1184,7 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
|
||||
// creation across both CompactionJob and CompactionServiceCompactionJob
|
||||
sub_compact->AssignRangeDelAggregator(
|
||||
std::make_unique<CompactionRangeDelAggregator>(
|
||||
&cfd->internal_comparator(), existing_snapshots_,
|
||||
&cfd->internal_comparator(), job_context_->snapshot_seqs,
|
||||
&full_history_ts_low_, &trim_ts_));
|
||||
|
||||
// TODO: since we already use C++17, should use
|
||||
@@ -1324,8 +1325,8 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
|
||||
env_, cfd->user_comparator(), cfd->ioptions().merge_operator.get(),
|
||||
compaction_filter, db_options_.info_log.get(),
|
||||
false /* internal key corruption is expected */,
|
||||
existing_snapshots_.empty() ? 0 : existing_snapshots_.back(),
|
||||
snapshot_checker_, compact_->compaction->level(), db_options_.stats);
|
||||
job_context_->GetLatestSnapshotSequence(), job_context_->snapshot_checker,
|
||||
compact_->compaction->level(), db_options_.stats);
|
||||
|
||||
const auto& mutable_cf_options =
|
||||
sub_compact->compaction->mutable_cf_options();
|
||||
@@ -1361,9 +1362,10 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
|
||||
|
||||
auto c_iter = std::make_unique<CompactionIterator>(
|
||||
input, cfd->user_comparator(), &merge, versions_->LastSequence(),
|
||||
&existing_snapshots_, earliest_snapshot_,
|
||||
earliest_write_conflict_snapshot_, job_snapshot_seq, snapshot_checker_,
|
||||
env_, ShouldReportDetailedTime(env_, stats_),
|
||||
&(job_context_->snapshot_seqs), earliest_snapshot_,
|
||||
job_context_->earliest_write_conflict_snapshot, job_snapshot_seq,
|
||||
job_context_->snapshot_checker, env_,
|
||||
ShouldReportDetailedTime(env_, stats_),
|
||||
/*expect_valid_internal_key=*/true, sub_compact->RangeDelAgg(),
|
||||
blob_file_builder.get(), db_options_.allow_data_in_errors,
|
||||
db_options_.enforce_single_del_contracts, manual_compaction_canceled_,
|
||||
@@ -1652,10 +1654,6 @@ Status CompactionJob::FinishCompactionOutputFile(
|
||||
Status s = input_status;
|
||||
|
||||
// Add range tombstones
|
||||
auto earliest_snapshot = kMaxSequenceNumber;
|
||||
if (existing_snapshots_.size() > 0) {
|
||||
earliest_snapshot = existing_snapshots_[0];
|
||||
}
|
||||
if (s.ok()) {
|
||||
// Inclusive lower bound, exclusive upper bound
|
||||
std::pair<SequenceNumber, SequenceNumber> keep_seqno_range{
|
||||
@@ -1681,7 +1679,7 @@ Status CompactionJob::FinishCompactionOutputFile(
|
||||
s = outputs.AddRangeDels(*sub_compact->RangeDelAgg(), comp_start_user_key,
|
||||
comp_end_user_key, range_del_out_stats,
|
||||
bottommost_level_, cfd->internal_comparator(),
|
||||
earliest_snapshot, keep_seqno_range,
|
||||
earliest_snapshot_, keep_seqno_range,
|
||||
next_table_min_key, full_history_ts_low_);
|
||||
}
|
||||
RecordDroppedKeys(range_del_out_stats, &sub_compact->compaction_job_stats);
|
||||
@@ -2313,9 +2311,10 @@ void CompactionJob::LogCompaction() {
|
||||
}
|
||||
stream << "score" << compaction->score() << "input_data_size"
|
||||
<< compaction->CalculateTotalInputSize() << "oldest_snapshot_seqno"
|
||||
<< (existing_snapshots_.empty()
|
||||
<< (job_context_->snapshot_seqs.empty()
|
||||
? int64_t{-1} // Use -1 for "none"
|
||||
: static_cast<int64_t>(existing_snapshots_[0]));
|
||||
: static_cast<int64_t>(
|
||||
job_context_->GetEarliestSnapshotSequence()));
|
||||
if (compaction->SupportsPerKeyPlacement()) {
|
||||
stream << "proximal_after_seqno" << proximal_after_seqno_;
|
||||
stream << "preserve_seqno_after" << preserve_seqno_after_;
|
||||
|
||||
@@ -142,27 +142,27 @@ class SubcompactionState;
|
||||
|
||||
class CompactionJob {
|
||||
public:
|
||||
CompactionJob(
|
||||
int job_id, Compaction* compaction, const ImmutableDBOptions& db_options,
|
||||
const MutableDBOptions& mutable_db_options,
|
||||
const FileOptions& file_options, VersionSet* versions,
|
||||
const std::atomic<bool>* shutting_down, LogBuffer* log_buffer,
|
||||
FSDirectory* db_directory, FSDirectory* output_directory,
|
||||
FSDirectory* blob_output_directory, Statistics* stats,
|
||||
InstrumentedMutex* db_mutex, ErrorHandler* db_error_handler,
|
||||
std::vector<SequenceNumber> existing_snapshots,
|
||||
SequenceNumber earliest_write_conflict_snapshot,
|
||||
const SnapshotChecker* snapshot_checker, JobContext* job_context,
|
||||
std::shared_ptr<Cache> table_cache, EventLogger* event_logger,
|
||||
bool paranoid_file_checks, bool measure_io_stats,
|
||||
const std::string& dbname, CompactionJobStats* compaction_job_stats,
|
||||
Env::Priority thread_pri, const std::shared_ptr<IOTracer>& io_tracer,
|
||||
const std::atomic<bool>& manual_compaction_canceled,
|
||||
const std::string& db_id = "", const std::string& db_session_id = "",
|
||||
std::string full_history_ts_low = "", std::string trim_ts = "",
|
||||
BlobFileCompletionCallback* blob_callback = nullptr,
|
||||
int* bg_compaction_scheduled = nullptr,
|
||||
int* bg_bottom_compaction_scheduled = nullptr);
|
||||
CompactionJob(int job_id, Compaction* compaction,
|
||||
const ImmutableDBOptions& db_options,
|
||||
const MutableDBOptions& mutable_db_options,
|
||||
const FileOptions& file_options, VersionSet* versions,
|
||||
const std::atomic<bool>* shutting_down, LogBuffer* log_buffer,
|
||||
FSDirectory* db_directory, FSDirectory* output_directory,
|
||||
FSDirectory* blob_output_directory, Statistics* stats,
|
||||
InstrumentedMutex* db_mutex, ErrorHandler* db_error_handler,
|
||||
JobContext* job_context, std::shared_ptr<Cache> table_cache,
|
||||
EventLogger* event_logger, bool paranoid_file_checks,
|
||||
bool measure_io_stats, const std::string& dbname,
|
||||
CompactionJobStats* compaction_job_stats,
|
||||
Env::Priority thread_pri,
|
||||
const std::shared_ptr<IOTracer>& io_tracer,
|
||||
const std::atomic<bool>& manual_compaction_canceled,
|
||||
const std::string& db_id = "",
|
||||
const std::string& db_session_id = "",
|
||||
std::string full_history_ts_low = "", std::string trim_ts = "",
|
||||
BlobFileCompletionCallback* blob_callback = nullptr,
|
||||
int* bg_compaction_scheduled = nullptr,
|
||||
int* bg_bottom_compaction_scheduled = nullptr);
|
||||
|
||||
virtual ~CompactionJob();
|
||||
|
||||
@@ -321,21 +321,8 @@ class CompactionJob {
|
||||
FSDirectory* blob_output_directory_;
|
||||
InstrumentedMutex* db_mutex_;
|
||||
ErrorHandler* db_error_handler_;
|
||||
// If there were two snapshots with seq numbers s1 and
|
||||
// s2 and s1 < s2, and if we find two instances of a key k1 then lies
|
||||
// entirely within s1 and s2, then the earlier version of k1 can be safely
|
||||
// deleted because that version is not visible in any snapshot.
|
||||
std::vector<SequenceNumber> existing_snapshots_;
|
||||
|
||||
SequenceNumber earliest_snapshot_;
|
||||
|
||||
// This is the earliest snapshot that could be used for write-conflict
|
||||
// checking by a transaction. For any user-key newer than this snapshot, we
|
||||
// should make sure not to remove evidence that a write occurred.
|
||||
SequenceNumber earliest_write_conflict_snapshot_;
|
||||
|
||||
const SnapshotChecker* const snapshot_checker_;
|
||||
|
||||
JobContext* job_context_;
|
||||
|
||||
std::shared_ptr<Cache> table_cache_;
|
||||
@@ -427,8 +414,9 @@ struct CompactionServiceInput {
|
||||
// CompactionServiceOutputFile is the metadata for the output SST file
|
||||
struct CompactionServiceOutputFile {
|
||||
std::string file_name;
|
||||
SequenceNumber smallest_seqno;
|
||||
SequenceNumber largest_seqno;
|
||||
uint64_t file_size{};
|
||||
SequenceNumber smallest_seqno{};
|
||||
SequenceNumber largest_seqno{};
|
||||
std::string smallest_internal_key;
|
||||
std::string largest_internal_key;
|
||||
uint64_t oldest_ancester_time = kUnknownOldestAncesterTime;
|
||||
@@ -436,24 +424,26 @@ struct CompactionServiceOutputFile {
|
||||
uint64_t epoch_number = kUnknownEpochNumber;
|
||||
std::string file_checksum = kUnknownFileChecksum;
|
||||
std::string file_checksum_func_name = kUnknownFileChecksumFuncName;
|
||||
uint64_t paranoid_hash;
|
||||
uint64_t paranoid_hash{};
|
||||
bool marked_for_compaction;
|
||||
UniqueId64x2 unique_id{};
|
||||
TableProperties table_properties;
|
||||
bool is_proximal_level_output;
|
||||
Temperature file_temperature;
|
||||
Temperature file_temperature = Temperature::kUnknown;
|
||||
|
||||
CompactionServiceOutputFile() = default;
|
||||
CompactionServiceOutputFile(
|
||||
const std::string& name, SequenceNumber smallest, SequenceNumber largest,
|
||||
std::string _smallest_internal_key, std::string _largest_internal_key,
|
||||
uint64_t _oldest_ancester_time, uint64_t _file_creation_time,
|
||||
uint64_t _epoch_number, const std::string& _file_checksum,
|
||||
const std::string& name, uint64_t size, SequenceNumber smallest,
|
||||
SequenceNumber largest, std::string _smallest_internal_key,
|
||||
std::string _largest_internal_key, uint64_t _oldest_ancester_time,
|
||||
uint64_t _file_creation_time, uint64_t _epoch_number,
|
||||
const std::string& _file_checksum,
|
||||
const std::string& _file_checksum_func_name, uint64_t _paranoid_hash,
|
||||
bool _marked_for_compaction, UniqueId64x2 _unique_id,
|
||||
const TableProperties& _table_properties, bool _is_proximal_level_output,
|
||||
Temperature _file_temperature)
|
||||
: file_name(name),
|
||||
file_size(size),
|
||||
smallest_seqno(smallest),
|
||||
largest_seqno(largest),
|
||||
smallest_internal_key(std::move(_smallest_internal_key)),
|
||||
@@ -521,9 +511,9 @@ class CompactionServiceCompactionJob : private CompactionJob {
|
||||
const std::atomic<bool>* shutting_down, LogBuffer* log_buffer,
|
||||
FSDirectory* output_directory, Statistics* stats,
|
||||
InstrumentedMutex* db_mutex, ErrorHandler* db_error_handler,
|
||||
std::vector<SequenceNumber> existing_snapshots,
|
||||
std::shared_ptr<Cache> table_cache, EventLogger* event_logger,
|
||||
const std::string& dbname, const std::shared_ptr<IOTracer>& io_tracer,
|
||||
JobContext* job_context, std::shared_ptr<Cache> table_cache,
|
||||
EventLogger* event_logger, const std::string& dbname,
|
||||
const std::shared_ptr<IOTracer>& io_tracer,
|
||||
const std::atomic<bool>& manual_compaction_canceled,
|
||||
const std::string& db_id, const std::string& db_session_id,
|
||||
std::string output_path,
|
||||
|
||||
@@ -43,7 +43,6 @@ void VerifyInitializationOfCompactionJobStats(
|
||||
ASSERT_EQ(compaction_job_stats.elapsed_micros, 0U);
|
||||
|
||||
ASSERT_EQ(compaction_job_stats.num_input_records, 0U);
|
||||
ASSERT_EQ(compaction_job_stats.num_input_files, 0U);
|
||||
ASSERT_EQ(compaction_job_stats.num_input_files_at_output_level, 0U);
|
||||
|
||||
ASSERT_EQ(compaction_job_stats.num_output_records, 0U);
|
||||
@@ -52,7 +51,6 @@ void VerifyInitializationOfCompactionJobStats(
|
||||
ASSERT_TRUE(compaction_job_stats.is_manual_compaction);
|
||||
ASSERT_FALSE(compaction_job_stats.is_remote_compaction);
|
||||
|
||||
ASSERT_EQ(compaction_job_stats.total_input_bytes, 0U);
|
||||
ASSERT_EQ(compaction_job_stats.total_output_bytes, 0U);
|
||||
|
||||
ASSERT_EQ(compaction_job_stats.total_input_raw_key_bytes, 0U);
|
||||
@@ -460,9 +458,10 @@ class CompactionJobTestBase : public testing::Test {
|
||||
ReadOptions read_opts;
|
||||
Status s = cf_options_.table_factory->NewTableReader(
|
||||
read_opts,
|
||||
TableReaderOptions(cfd->ioptions(), nullptr, FileOptions(),
|
||||
TableReaderOptions(cfd->ioptions(), /*prefix_extractor=*/nullptr,
|
||||
/*compression_manager=*/nullptr, FileOptions(),
|
||||
cfd_->internal_comparator(),
|
||||
0 /* block_protection_bytes_per_key */),
|
||||
/*block_protection_bytes_per_key=*/0),
|
||||
std::move(freader), file_size, &table_reader, false);
|
||||
ASSERT_OK(s);
|
||||
assert(table_reader);
|
||||
@@ -595,11 +594,11 @@ class CompactionJobTestBase : public testing::Test {
|
||||
const std::vector<std::vector<FileMetaData*>>& input_files,
|
||||
const std::vector<int> input_levels,
|
||||
std::function<void(Compaction& comp)>&& verify_func,
|
||||
const std::vector<SequenceNumber>& snapshots = {}) {
|
||||
std::vector<SequenceNumber>&& snapshots = {}) {
|
||||
const int kLastLevel = cf_options_.num_levels - 1;
|
||||
verify_per_key_placement_ = std::move(verify_func);
|
||||
mock::KVVector empty_map;
|
||||
RunCompaction(input_files, input_levels, {empty_map}, snapshots,
|
||||
RunCompaction(input_files, input_levels, {empty_map}, std::move(snapshots),
|
||||
kMaxSequenceNumber, kLastLevel, false);
|
||||
}
|
||||
|
||||
@@ -608,7 +607,7 @@ class CompactionJobTestBase : public testing::Test {
|
||||
const std::vector<std::vector<FileMetaData*>>& input_files,
|
||||
const std::vector<int>& input_levels,
|
||||
const std::vector<mock::KVVector>& expected_results,
|
||||
const std::vector<SequenceNumber>& snapshots = {},
|
||||
std::vector<SequenceNumber>&& snapshots = {},
|
||||
SequenceNumber earliest_write_conflict_snapshot = kMaxSequenceNumber,
|
||||
int output_level = 1, bool verify = true,
|
||||
std::vector<uint64_t> expected_oldest_blob_file_numbers = {},
|
||||
@@ -665,13 +664,15 @@ class CompactionJobTestBase : public testing::Test {
|
||||
ucmp_->timestamp_size() == full_history_ts_low_.size());
|
||||
const std::atomic<bool> kManualCompactionCanceledFalse{false};
|
||||
JobContext job_context(1, false /* create_superversion */);
|
||||
job_context.InitSnapshotContext(snapshot_checker, nullptr,
|
||||
earliest_write_conflict_snapshot,
|
||||
std::move(snapshots));
|
||||
CompactionJob compaction_job(
|
||||
0, &compaction, db_options_, mutable_db_options_, env_options_,
|
||||
versions_.get(), &shutting_down_, &log_buffer, nullptr, nullptr,
|
||||
nullptr, nullptr, &mutex_, &error_handler_, snapshots,
|
||||
earliest_write_conflict_snapshot, snapshot_checker, &job_context,
|
||||
table_cache_, &event_logger, false, false, dbname_,
|
||||
&compaction_job_stats_, Env::Priority::USER, nullptr /* IOTracer */,
|
||||
nullptr, nullptr, &mutex_, &error_handler_, &job_context, table_cache_,
|
||||
&event_logger, false, false, dbname_, &compaction_job_stats_,
|
||||
Env::Priority::USER, nullptr /* IOTracer */,
|
||||
/*manual_compaction_canceled=*/kManualCompactionCanceledFalse,
|
||||
env_->GenerateUniqueId(), DBImpl::GenerateDbSessionId(nullptr),
|
||||
full_history_ts_low_);
|
||||
@@ -1669,6 +1670,7 @@ TEST_F(CompactionJobTest, ResultSerialization) {
|
||||
UniqueId64x2 id{rnd64.Uniform(UINT64_MAX), rnd64.Uniform(UINT64_MAX)};
|
||||
result.output_files.emplace_back(
|
||||
rnd.RandomString(rnd.Uniform(kStrMaxLen)) /* file_name */,
|
||||
rnd64.Uniform(UINT64_MAX) /* file_size */,
|
||||
rnd64.Uniform(UINT64_MAX) /* smallest_seqno */,
|
||||
rnd64.Uniform(UINT64_MAX) /* largest_seqno */,
|
||||
rnd.RandomBinaryString(
|
||||
@@ -2035,7 +2037,7 @@ TEST_F(CompactionJobTest, CutToAlignGrandparentBoundarySameKey) {
|
||||
snapshots.emplace_back(i);
|
||||
}
|
||||
RunCompaction({lvl0_files, lvl1_files}, input_levels,
|
||||
{expected_file1, expected_file2}, snapshots);
|
||||
{expected_file1, expected_file2}, std::move(snapshots));
|
||||
}
|
||||
|
||||
TEST_F(CompactionJobTest, CutForMaxCompactionBytesSameKey) {
|
||||
@@ -2094,7 +2096,8 @@ TEST_F(CompactionJobTest, CutForMaxCompactionBytesSameKey) {
|
||||
snapshots.emplace_back(i);
|
||||
}
|
||||
RunCompaction({lvl0_files, lvl1_files}, input_levels,
|
||||
{expected_file1, expected_file2, expected_file3}, snapshots);
|
||||
{expected_file1, expected_file2, expected_file3},
|
||||
std::move(snapshots));
|
||||
}
|
||||
|
||||
class CompactionJobTimestampTest : public CompactionJobTestBase {
|
||||
|
||||
@@ -65,7 +65,7 @@ class CompactionPicker {
|
||||
const MutableDBOptions& mutable_db_options,
|
||||
const std::vector<SequenceNumber>& existing_snapshots,
|
||||
const SnapshotChecker* snapshot_checker, VersionStorageInfo* vstorage,
|
||||
LogBuffer* log_buffer) = 0;
|
||||
LogBuffer* log_buffer, bool require_max_output_level) = 0;
|
||||
|
||||
// The returned Compaction might not include the whole requested range.
|
||||
// In that case, compaction_end will be set to the next key that needs
|
||||
@@ -272,7 +272,8 @@ class NullCompactionPicker : public CompactionPicker {
|
||||
const MutableDBOptions& /*mutable_db_options*/,
|
||||
const std::vector<SequenceNumber>& /*existing_snapshots*/,
|
||||
const SnapshotChecker* /*snapshot_checker*/,
|
||||
VersionStorageInfo* /*vstorage*/, LogBuffer* /* log_buffer */) override {
|
||||
VersionStorageInfo* /*vstorage*/, LogBuffer* /* log_buffer */,
|
||||
bool /*require_max_output_level*/ = false) override {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
@@ -428,7 +428,7 @@ Compaction* FIFOCompactionPicker::PickCompaction(
|
||||
const MutableDBOptions& mutable_db_options,
|
||||
const std::vector<SequenceNumber>& /* existing_snapshots */,
|
||||
const SnapshotChecker* /* snapshot_checker */, VersionStorageInfo* vstorage,
|
||||
LogBuffer* log_buffer) {
|
||||
LogBuffer* log_buffer, bool /* require_max_output_level*/) {
|
||||
Compaction* c = nullptr;
|
||||
if (mutable_cf_options.ttl > 0) {
|
||||
c = PickTTLCompaction(cf_name, mutable_cf_options, mutable_db_options,
|
||||
|
||||
@@ -23,7 +23,8 @@ class FIFOCompactionPicker : public CompactionPicker {
|
||||
const MutableDBOptions& mutable_db_options,
|
||||
const std::vector<SequenceNumber>& /* existing_snapshots */,
|
||||
const SnapshotChecker* /* snapshot_checker */,
|
||||
VersionStorageInfo* version, LogBuffer* log_buffer) override;
|
||||
VersionStorageInfo* version, LogBuffer* log_buffer,
|
||||
bool /* require_max_output_level*/ = false) override;
|
||||
|
||||
Compaction* CompactRange(const std::string& cf_name,
|
||||
const MutableCFOptions& mutable_cf_options,
|
||||
|
||||
@@ -978,7 +978,7 @@ Compaction* LevelCompactionPicker::PickCompaction(
|
||||
const MutableDBOptions& mutable_db_options,
|
||||
const std::vector<SequenceNumber>& /*existing_snapshots */,
|
||||
const SnapshotChecker* /*snapshot_checker*/, VersionStorageInfo* vstorage,
|
||||
LogBuffer* log_buffer) {
|
||||
LogBuffer* log_buffer, bool /* require_max_output_level*/) {
|
||||
LevelCompactionBuilder builder(cf_name, vstorage, this, log_buffer,
|
||||
mutable_cf_options, ioptions_,
|
||||
mutable_db_options);
|
||||
|
||||
@@ -25,7 +25,8 @@ class LevelCompactionPicker : public CompactionPicker {
|
||||
const MutableDBOptions& mutable_db_options,
|
||||
const std::vector<SequenceNumber>& /* existing_snapshots */,
|
||||
const SnapshotChecker* /* snapshot_checker */,
|
||||
VersionStorageInfo* vstorage, LogBuffer* log_buffer) override;
|
||||
VersionStorageInfo* vstorage, LogBuffer* log_buffer,
|
||||
bool /*require_max_output_level*/ = false) override;
|
||||
|
||||
bool NeedsCompaction(const VersionStorageInfo* vstorage) const override;
|
||||
};
|
||||
|
||||
@@ -38,7 +38,8 @@ class UniversalCompactionBuilder {
|
||||
const MutableDBOptions& mutable_db_options,
|
||||
const std::vector<SequenceNumber>& existing_snapshots,
|
||||
const SnapshotChecker* snapshot_checker, VersionStorageInfo* vstorage,
|
||||
UniversalCompactionPicker* picker, LogBuffer* log_buffer)
|
||||
UniversalCompactionPicker* picker, LogBuffer* log_buffer,
|
||||
bool require_max_output_level)
|
||||
: ioptions_(ioptions),
|
||||
icmp_(icmp),
|
||||
cf_name_(cf_name),
|
||||
@@ -46,7 +47,8 @@ class UniversalCompactionBuilder {
|
||||
mutable_db_options_(mutable_db_options),
|
||||
vstorage_(vstorage),
|
||||
picker_(picker),
|
||||
log_buffer_(log_buffer) {
|
||||
log_buffer_(log_buffer),
|
||||
require_max_output_level_(require_max_output_level) {
|
||||
assert(icmp_);
|
||||
const auto* ucmp = icmp_->user_comparator();
|
||||
assert(ucmp);
|
||||
@@ -102,6 +104,174 @@ class UniversalCompactionBuilder {
|
||||
bool level_has_marked_standalone_rangedel;
|
||||
};
|
||||
|
||||
unsigned int GetMaxNumFilesToCompactBasedOnMaxReadAmp(
|
||||
const int file_num_compaction_trigger, const unsigned int ratio,
|
||||
int* num_sr_not_compacted_output, int* max_num_runs_output) const {
|
||||
assert(num_sr_not_compacted_output);
|
||||
assert(max_num_runs_output);
|
||||
int max_num_runs =
|
||||
mutable_cf_options_.compaction_options_universal.max_read_amp;
|
||||
if (max_num_runs < 0) {
|
||||
// any value < -1 is not valid
|
||||
assert(max_num_runs == -1);
|
||||
// By default, fall back to `level0_file_num_compaction_trigger`
|
||||
max_num_runs = file_num_compaction_trigger;
|
||||
} else if (max_num_runs == 0) {
|
||||
if (mutable_cf_options_.compaction_options_universal.stop_style ==
|
||||
kCompactionStopStyleTotalSize) {
|
||||
// 0 means auto-tuning by RocksDB. We estimate max num run based on
|
||||
// max_run_size, size_ratio and write buffer size:
|
||||
// Assume the size of the lowest level size is equal to
|
||||
// write_buffer_size. Each subsequent level is the max size without
|
||||
// triggering size_ratio compaction. `max_num_runs` is the minimum
|
||||
// number of levels required such that the target size of the
|
||||
// largest level is at least `max_run_size_`.
|
||||
max_num_runs = 1;
|
||||
double cur_level_max_size =
|
||||
static_cast<double>(mutable_cf_options_.write_buffer_size);
|
||||
double total_run_size = 0;
|
||||
while (cur_level_max_size < static_cast<double>(max_run_size_)) {
|
||||
// This loop should not take too many iterations since
|
||||
// cur_level_max_size at least doubles each iteration.
|
||||
total_run_size += cur_level_max_size;
|
||||
cur_level_max_size = (100.0 + ratio) / 100.0 * total_run_size;
|
||||
++max_num_runs;
|
||||
}
|
||||
} else {
|
||||
// TODO: implement the auto-tune logic for this stop style
|
||||
max_num_runs = file_num_compaction_trigger;
|
||||
}
|
||||
} else {
|
||||
// max_num_runs > 0, it's the limit on the number of sorted run
|
||||
}
|
||||
|
||||
// Get the total number of sorted runs that are not being compacted
|
||||
int num_sr_not_compacted = 0;
|
||||
for (size_t i = 0; i < sorted_runs_.size(); i++) {
|
||||
if (sorted_runs_[i].being_compacted == false &&
|
||||
!sorted_runs_[i].level_has_marked_standalone_rangedel) {
|
||||
num_sr_not_compacted++;
|
||||
}
|
||||
}
|
||||
|
||||
*num_sr_not_compacted_output = num_sr_not_compacted;
|
||||
*max_num_runs_output = max_num_runs;
|
||||
|
||||
if (num_sr_not_compacted > max_num_runs) {
|
||||
return num_sr_not_compacted - max_num_runs + 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
Compaction* MaybePickPeriodicCompaction(Compaction* const prev_picked_c) {
|
||||
if (prev_picked_c != nullptr ||
|
||||
vstorage_->FilesMarkedForPeriodicCompaction().empty()) {
|
||||
return prev_picked_c;
|
||||
}
|
||||
// Always need to do a full compaction for periodic compaction.
|
||||
Compaction* c = PickPeriodicCompaction();
|
||||
TEST_SYNC_POINT_CALLBACK("PostPickPeriodicCompaction", c);
|
||||
if (c != nullptr) {
|
||||
ROCKS_LOG_BUFFER(log_buffer_,
|
||||
"[%s] Universal: picked for periodic compaction\n",
|
||||
cf_name_.c_str());
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
Compaction* MaybePickSizeAmpCompaction(Compaction* const prev_picked_c,
|
||||
int file_num_compaction_trigger) {
|
||||
if (prev_picked_c != nullptr ||
|
||||
sorted_runs_.size() <
|
||||
static_cast<size_t>(file_num_compaction_trigger)) {
|
||||
return prev_picked_c;
|
||||
}
|
||||
Compaction* c = PickCompactionToReduceSizeAmp();
|
||||
if (c != nullptr) {
|
||||
TEST_SYNC_POINT("PickCompactionToReduceSizeAmpReturnNonnullptr");
|
||||
ROCKS_LOG_BUFFER(log_buffer_,
|
||||
"[%s] Universal: picked for size amp compaction \n",
|
||||
cf_name_.c_str());
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
Compaction* MaybePickCompactionToReduceSortedRunsBasedFileRatio(
|
||||
Compaction* const prev_picked_c, int file_num_compaction_trigger,
|
||||
unsigned int ratio) {
|
||||
if (prev_picked_c != nullptr ||
|
||||
sorted_runs_.size() <
|
||||
static_cast<size_t>(file_num_compaction_trigger)) {
|
||||
return prev_picked_c;
|
||||
}
|
||||
Compaction* c = PickCompactionToReduceSortedRuns(ratio, UINT_MAX);
|
||||
if (c != nullptr) {
|
||||
TEST_SYNC_POINT("PickCompactionToReduceSortedRunsReturnNonnullptr");
|
||||
ROCKS_LOG_BUFFER(log_buffer_,
|
||||
"[%s] Universal: picked for size ratio compaction to "
|
||||
"reduce sorted run\n",
|
||||
cf_name_.c_str());
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
Compaction* MaybePickCompactionToReduceSortedRuns(
|
||||
Compaction* const prev_picked_c, int file_num_compaction_trigger,
|
||||
unsigned int ratio) {
|
||||
if (prev_picked_c != nullptr ||
|
||||
sorted_runs_.size() <
|
||||
static_cast<size_t>(file_num_compaction_trigger)) {
|
||||
return prev_picked_c;
|
||||
}
|
||||
|
||||
int num_sr_not_compacted = 0;
|
||||
int max_num_runs = 0;
|
||||
const unsigned int max_num_files_to_compact =
|
||||
GetMaxNumFilesToCompactBasedOnMaxReadAmp(file_num_compaction_trigger,
|
||||
ratio, &num_sr_not_compacted,
|
||||
&max_num_runs);
|
||||
if (max_num_files_to_compact == 0) {
|
||||
ROCKS_LOG_BUFFER(
|
||||
log_buffer_,
|
||||
"[%s] Universal: skipping compaction to reduce sorted run, num "
|
||||
"sorted runs not "
|
||||
"being compacted -- %u, max num runs allowed -- %d, max_run_size "
|
||||
"-- %" PRIu64 "\n",
|
||||
cf_name_.c_str(), num_sr_not_compacted, max_num_runs, max_run_size_);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Compaction* c =
|
||||
PickCompactionToReduceSortedRuns(UINT_MAX, max_num_files_to_compact);
|
||||
if (c != nullptr) {
|
||||
ROCKS_LOG_BUFFER(log_buffer_,
|
||||
"[%s] Universal: picked for sorted run num compaction "
|
||||
"to reduce sorted run, to "
|
||||
"compact file num -- %u, max num runs allowed"
|
||||
"-- %d, max_run_size -- %" PRIu64 "\n",
|
||||
cf_name_.c_str(), max_num_files_to_compact, max_num_runs,
|
||||
max_run_size_);
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
Compaction* MaybePickDeleteTriggeredCompaction(
|
||||
Compaction* const prev_picked_c) {
|
||||
if (prev_picked_c != nullptr) {
|
||||
return prev_picked_c;
|
||||
}
|
||||
Compaction* c = PickDeleteTriggeredCompaction();
|
||||
if (c != nullptr) {
|
||||
TEST_SYNC_POINT("PickDeleteTriggeredCompactionReturnNonnullptr");
|
||||
ROCKS_LOG_BUFFER(
|
||||
log_buffer_,
|
||||
"[%s] Universal: picked for delete triggered compaction\n",
|
||||
cf_name_.c_str());
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
// Pick Universal compaction to limit read amplification
|
||||
Compaction* PickCompactionToReduceSortedRuns(
|
||||
unsigned int ratio, unsigned int max_number_of_files_to_compact);
|
||||
@@ -249,6 +419,13 @@ class UniversalCompactionBuilder {
|
||||
return num_l0_to_exclude;
|
||||
}
|
||||
|
||||
bool MeetsOutputLevelRequirements(int output_level) const {
|
||||
return !require_max_output_level_ ||
|
||||
Compaction::OutputToNonZeroMaxOutputLevel(
|
||||
output_level,
|
||||
vstorage_->MaxOutputLevel(ioptions_.allow_ingest_behind));
|
||||
}
|
||||
|
||||
const ImmutableOptions& ioptions_;
|
||||
const InternalKeyComparator* icmp_;
|
||||
double score_;
|
||||
@@ -260,6 +437,7 @@ class UniversalCompactionBuilder {
|
||||
VersionStorageInfo* vstorage_;
|
||||
UniversalCompactionPicker* picker_;
|
||||
LogBuffer* log_buffer_;
|
||||
bool require_max_output_level_;
|
||||
// Optional earliest snapshot at time of compaction picking. This is only
|
||||
// provided if the column family doesn't enable user-defined timestamps.
|
||||
// And this information is only passed to `Compaction` picked by deletion
|
||||
@@ -420,10 +598,11 @@ Compaction* UniversalCompactionPicker::PickCompaction(
|
||||
const MutableDBOptions& mutable_db_options,
|
||||
const std::vector<SequenceNumber>& existing_snapshots,
|
||||
const SnapshotChecker* snapshot_checker, VersionStorageInfo* vstorage,
|
||||
LogBuffer* log_buffer) {
|
||||
LogBuffer* log_buffer, bool require_max_output_level) {
|
||||
UniversalCompactionBuilder builder(
|
||||
ioptions_, icmp_, cf_name, mutable_cf_options, mutable_db_options,
|
||||
existing_snapshots, snapshot_checker, vstorage, this, log_buffer);
|
||||
existing_snapshots, snapshot_checker, vstorage, this, log_buffer,
|
||||
require_max_output_level);
|
||||
return builder.PickCompaction();
|
||||
}
|
||||
|
||||
@@ -554,13 +733,21 @@ bool UniversalCompactionBuilder::ShouldSkipMarkedFile(
|
||||
Compaction* UniversalCompactionBuilder::PickCompaction() {
|
||||
const int kLevel0 = 0;
|
||||
score_ = vstorage_->CompactionScore(kLevel0);
|
||||
int max_output_level =
|
||||
const int max_output_level =
|
||||
vstorage_->MaxOutputLevel(ioptions_.allow_ingest_behind);
|
||||
const int file_num_compaction_trigger =
|
||||
mutable_cf_options_.level0_file_num_compaction_trigger;
|
||||
const unsigned int ratio =
|
||||
mutable_cf_options_.compaction_options_universal.size_ratio;
|
||||
|
||||
if (max_output_level == 0 &&
|
||||
!MeetsOutputLevelRequirements(0 /* output_level */)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
max_run_size_ = 0;
|
||||
sorted_runs_ =
|
||||
CalculateSortedRuns(*vstorage_, max_output_level, &max_run_size_);
|
||||
int file_num_compaction_trigger =
|
||||
mutable_cf_options_.level0_file_num_compaction_trigger;
|
||||
|
||||
if (sorted_runs_.size() == 0 ||
|
||||
(vstorage_->FilesMarkedForPeriodicCompaction().empty() &&
|
||||
@@ -572,6 +759,7 @@ Compaction* UniversalCompactionBuilder::PickCompaction() {
|
||||
"UniversalCompactionBuilder::PickCompaction:Return", nullptr);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
VersionStorageInfo::LevelSummaryStorage tmp;
|
||||
ROCKS_LOG_BUFFER_MAX_SZ(
|
||||
log_buffer_, 3072,
|
||||
@@ -579,119 +767,14 @@ Compaction* UniversalCompactionBuilder::PickCompaction() {
|
||||
cf_name_.c_str(), sorted_runs_.size(), vstorage_->LevelSummary(&tmp));
|
||||
|
||||
Compaction* c = nullptr;
|
||||
// Periodic compaction has higher priority than other type of compaction
|
||||
// because it's a hard requirement.
|
||||
if (!vstorage_->FilesMarkedForPeriodicCompaction().empty()) {
|
||||
// Always need to do a full compaction for periodic compaction.
|
||||
c = PickPeriodicCompaction();
|
||||
TEST_SYNC_POINT_CALLBACK("PostPickPeriodicCompaction", c);
|
||||
}
|
||||
|
||||
if (c == nullptr &&
|
||||
sorted_runs_.size() >= static_cast<size_t>(file_num_compaction_trigger)) {
|
||||
// Check for size amplification.
|
||||
if ((c = PickCompactionToReduceSizeAmp()) != nullptr) {
|
||||
TEST_SYNC_POINT("PickCompactionToReduceSizeAmpReturnNonnullptr");
|
||||
ROCKS_LOG_BUFFER(log_buffer_, "[%s] Universal: compacting for size amp\n",
|
||||
cf_name_.c_str());
|
||||
} else {
|
||||
// Size amplification is within limits. Try reducing read
|
||||
// amplification while maintaining file size ratios.
|
||||
unsigned int ratio =
|
||||
mutable_cf_options_.compaction_options_universal.size_ratio;
|
||||
|
||||
if ((c = PickCompactionToReduceSortedRuns(ratio, UINT_MAX)) != nullptr) {
|
||||
TEST_SYNC_POINT("PickCompactionToReduceSortedRunsReturnNonnullptr");
|
||||
ROCKS_LOG_BUFFER(log_buffer_,
|
||||
"[%s] Universal: compacting for size ratio\n",
|
||||
cf_name_.c_str());
|
||||
} else {
|
||||
// Size amplification and file size ratios are within configured limits.
|
||||
// If max read amplification exceeds configured limits, then force
|
||||
// compaction to reduce the number sorted runs without looking at file
|
||||
// size ratios.
|
||||
|
||||
// This is guaranteed by NeedsCompaction()
|
||||
assert(sorted_runs_.size() >=
|
||||
static_cast<size_t>(file_num_compaction_trigger));
|
||||
int max_num_runs =
|
||||
mutable_cf_options_.compaction_options_universal.max_read_amp;
|
||||
if (max_num_runs < 0) {
|
||||
// any value < -1 is not valid
|
||||
assert(max_num_runs == -1);
|
||||
// By default, fall back to `level0_file_num_compaction_trigger`
|
||||
max_num_runs = file_num_compaction_trigger;
|
||||
} else if (max_num_runs == 0) {
|
||||
if (mutable_cf_options_.compaction_options_universal.stop_style ==
|
||||
kCompactionStopStyleTotalSize) {
|
||||
// 0 means auto-tuning by RocksDB. We estimate max num run based on
|
||||
// max_run_size, size_ratio and write buffer size:
|
||||
// Assume the size of the lowest level size is equal to
|
||||
// write_buffer_size. Each subsequent level is the max size without
|
||||
// triggering size_ratio compaction. `max_num_runs` is the minimum
|
||||
// number of levels required such that the target size of the
|
||||
// largest level is at least `max_run_size_`.
|
||||
max_num_runs = 1;
|
||||
double cur_level_max_size =
|
||||
static_cast<double>(mutable_cf_options_.write_buffer_size);
|
||||
double total_run_size = 0;
|
||||
while (cur_level_max_size < static_cast<double>(max_run_size_)) {
|
||||
// This loop should not take too many iterations since
|
||||
// cur_level_max_size at least doubles each iteration.
|
||||
total_run_size += cur_level_max_size;
|
||||
cur_level_max_size = (100.0 + ratio) / 100.0 * total_run_size;
|
||||
++max_num_runs;
|
||||
}
|
||||
} else {
|
||||
// TODO: implement the auto-tune logic for this stop style
|
||||
max_num_runs = file_num_compaction_trigger;
|
||||
}
|
||||
} else {
|
||||
// max_num_runs > 0, it's the limit on the number of sorted run
|
||||
}
|
||||
// Get the total number of sorted runs that are not being compacted
|
||||
int num_sr_not_compacted = 0;
|
||||
for (size_t i = 0; i < sorted_runs_.size(); i++) {
|
||||
if (sorted_runs_[i].being_compacted == false &&
|
||||
!sorted_runs_[i].level_has_marked_standalone_rangedel) {
|
||||
num_sr_not_compacted++;
|
||||
}
|
||||
}
|
||||
|
||||
// The number of sorted runs that are not being compacted is greater
|
||||
// than the maximum allowed number of sorted runs
|
||||
if (num_sr_not_compacted > max_num_runs) {
|
||||
unsigned int num_files = num_sr_not_compacted - max_num_runs + 1;
|
||||
if ((c = PickCompactionToReduceSortedRuns(UINT_MAX, num_files)) !=
|
||||
nullptr) {
|
||||
ROCKS_LOG_BUFFER(log_buffer_,
|
||||
"[%s] Universal: compacting for file num, to "
|
||||
"compact file num -- %u, max num runs allowed"
|
||||
"-- %d, max_run_size -- %" PRIu64 "\n",
|
||||
cf_name_.c_str(), num_files, max_num_runs,
|
||||
max_run_size_);
|
||||
}
|
||||
} else {
|
||||
ROCKS_LOG_BUFFER(
|
||||
log_buffer_,
|
||||
"[%s] Universal: skipping compaction for file num, num runs not "
|
||||
"being compacted -- %u, max num runs allowed -- %d, max_run_size "
|
||||
"-- %" PRIu64 "\n",
|
||||
cf_name_.c_str(), num_sr_not_compacted, max_num_runs,
|
||||
max_run_size_);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (c == nullptr) {
|
||||
if ((c = PickDeleteTriggeredCompaction()) != nullptr) {
|
||||
TEST_SYNC_POINT("PickDeleteTriggeredCompactionReturnNonnullptr");
|
||||
ROCKS_LOG_BUFFER(log_buffer_,
|
||||
"[%s] Universal: delete triggered compaction\n",
|
||||
cf_name_.c_str());
|
||||
}
|
||||
}
|
||||
c = MaybePickPeriodicCompaction(c);
|
||||
c = MaybePickSizeAmpCompaction(c, file_num_compaction_trigger);
|
||||
c = MaybePickCompactionToReduceSortedRunsBasedFileRatio(
|
||||
c, file_num_compaction_trigger, ratio);
|
||||
c = MaybePickCompactionToReduceSortedRuns(c, file_num_compaction_trigger,
|
||||
ratio);
|
||||
c = MaybePickDeleteTriggeredCompaction(c);
|
||||
|
||||
if (c == nullptr) {
|
||||
TEST_SYNC_POINT_CALLBACK(
|
||||
@@ -700,6 +783,7 @@ Compaction* UniversalCompactionBuilder::PickCompaction() {
|
||||
}
|
||||
assert(c->output_level() <=
|
||||
vstorage_->MaxOutputLevel(ioptions_.allow_ingest_behind));
|
||||
assert(MeetsOutputLevelRequirements(c->output_level()));
|
||||
|
||||
if (mutable_cf_options_.compaction_options_universal.allow_trivial_move ==
|
||||
true &&
|
||||
@@ -825,14 +909,16 @@ Compaction* UniversalCompactionBuilder::PickCompactionToReduceSortedRuns(
|
||||
if (sr->being_compacted) {
|
||||
ROCKS_LOG_BUFFER(log_buffer_,
|
||||
"[%s] Universal: %s"
|
||||
"[%d] being compacted, skipping",
|
||||
"[%d] being compacted, skipping for compaction to "
|
||||
"reduce sorted runs",
|
||||
cf_name_.c_str(), file_num_buf, loop);
|
||||
} else if (sr->level_has_marked_standalone_rangedel) {
|
||||
ROCKS_LOG_BUFFER(log_buffer_,
|
||||
"[%s] Universal: %s"
|
||||
"[%d] has standalone range tombstone files marked for "
|
||||
"compaction, skipping",
|
||||
cf_name_.c_str(), file_num_buf, loop);
|
||||
ROCKS_LOG_BUFFER(
|
||||
log_buffer_,
|
||||
"[%s] Universal: %s"
|
||||
"[%d] has standalone range tombstone files marked for "
|
||||
"compaction, skipping for compaction to reduce sorted runs",
|
||||
cf_name_.c_str(), file_num_buf, loop);
|
||||
}
|
||||
|
||||
sr = nullptr;
|
||||
@@ -845,7 +931,8 @@ Compaction* UniversalCompactionBuilder::PickCompactionToReduceSortedRuns(
|
||||
char file_num_buf[kFormatFileNumberBufSize];
|
||||
sr->Dump(file_num_buf, sizeof(file_num_buf), true);
|
||||
ROCKS_LOG_BUFFER(log_buffer_,
|
||||
"[%s] Universal: Possible candidate %s[%d].",
|
||||
"[%s] Universal: Possible candidate for compaction to "
|
||||
"reduce sorted runs %s[%d].",
|
||||
cf_name_.c_str(), file_num_buf, loop);
|
||||
}
|
||||
|
||||
@@ -947,6 +1034,10 @@ Compaction* UniversalCompactionBuilder::PickCompactionToReduceSortedRuns(
|
||||
output_level = sorted_runs_[first_index_after].level - 1;
|
||||
}
|
||||
|
||||
if (!MeetsOutputLevelRequirements(output_level)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::vector<CompactionInputFiles> inputs(max_output_level + 1);
|
||||
for (size_t i = 0; i < inputs.size(); ++i) {
|
||||
inputs[i].level = start_level + static_cast<int>(i);
|
||||
@@ -1039,18 +1130,19 @@ Compaction* UniversalCompactionBuilder::PickCompactionToReduceSizeAmp() {
|
||||
char file_num_buf[kFormatFileNumberBufSize];
|
||||
sr->Dump(file_num_buf, sizeof(file_num_buf), true);
|
||||
if (sr->being_compacted) {
|
||||
ROCKS_LOG_BUFFER(
|
||||
log_buffer_,
|
||||
"[%s] Universal: stopping at sorted run undergoing compaction: "
|
||||
"%s[%" ROCKSDB_PRIszt "]",
|
||||
cf_name_.c_str(), file_num_buf, start_index - 1);
|
||||
ROCKS_LOG_BUFFER(log_buffer_,
|
||||
"[%s] Universal: stopping for size amp compaction at "
|
||||
"sorted run undergoing compaction: "
|
||||
"%s[%" ROCKSDB_PRIszt "]",
|
||||
cf_name_.c_str(), file_num_buf, start_index - 1);
|
||||
} else if (sr->level_has_marked_standalone_rangedel) {
|
||||
ROCKS_LOG_BUFFER(
|
||||
log_buffer_,
|
||||
"[%s] Universal: stopping at sorted run that has standalone range "
|
||||
"tombstone files marked for compaction: "
|
||||
"%s[%" ROCKSDB_PRIszt "]",
|
||||
cf_name_.c_str(), file_num_buf, start_index - 1);
|
||||
ROCKS_LOG_BUFFER(log_buffer_,
|
||||
"[%s] Universal: stopping for size amp compaction at "
|
||||
"sorted run that has "
|
||||
"standalone range "
|
||||
"tombstone files marked for compaction: "
|
||||
"%s[%" ROCKSDB_PRIszt "]",
|
||||
cf_name_.c_str(), file_num_buf, start_index - 1);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -1066,11 +1158,12 @@ Compaction* UniversalCompactionBuilder::PickCompactionToReduceSizeAmp() {
|
||||
{
|
||||
const size_t num_l0_to_exclude = MightExcludeNewL0sToReduceWriteStop(
|
||||
num_l0_files, end_index, start_index, candidate_size);
|
||||
ROCKS_LOG_BUFFER(log_buffer_,
|
||||
"[%s] Universal: Excluding %" ROCKSDB_PRIszt
|
||||
" latest L0 files to reduce potential write stop "
|
||||
"triggered by `level0_stop_writes_trigger`",
|
||||
cf_name_.c_str(), num_l0_to_exclude);
|
||||
ROCKS_LOG_BUFFER(
|
||||
log_buffer_,
|
||||
"[%s] Universal: Excluding for size amp compaction %" ROCKSDB_PRIszt
|
||||
" latest L0 files to reduce potential write stop "
|
||||
"triggered by `level0_stop_writes_trigger`",
|
||||
cf_name_.c_str(), num_l0_to_exclude);
|
||||
}
|
||||
|
||||
{
|
||||
@@ -1088,18 +1181,18 @@ Compaction* UniversalCompactionBuilder::PickCompactionToReduceSizeAmp() {
|
||||
|
||||
// size amplification = percentage of additional size
|
||||
if (candidate_size * 100 < ratio * base_sr_size) {
|
||||
ROCKS_LOG_BUFFER(
|
||||
log_buffer_,
|
||||
"[%s] Universal: size amp not needed. newer-files-total-size %" PRIu64
|
||||
" earliest-file-size %" PRIu64,
|
||||
cf_name_.c_str(), candidate_size, base_sr_size);
|
||||
ROCKS_LOG_BUFFER(log_buffer_,
|
||||
"[%s] Universal: size amp compction not needed. "
|
||||
"newer-files-total-size %" PRIu64
|
||||
" earliest-file-size %" PRIu64,
|
||||
cf_name_.c_str(), candidate_size, base_sr_size);
|
||||
return nullptr;
|
||||
} else {
|
||||
ROCKS_LOG_BUFFER(
|
||||
log_buffer_,
|
||||
"[%s] Universal: size amp needed. newer-files-total-size %" PRIu64
|
||||
" earliest-file-size %" PRIu64,
|
||||
cf_name_.c_str(), candidate_size, base_sr_size);
|
||||
ROCKS_LOG_BUFFER(log_buffer_,
|
||||
"[%s] Universal: size amp compaction needed. "
|
||||
"newer-files-total-size %" PRIu64
|
||||
" earliest-file-size %" PRIu64,
|
||||
cf_name_.c_str(), candidate_size, base_sr_size);
|
||||
}
|
||||
// Since incremental compaction can't include more than second last
|
||||
// level, it can introduce penalty, compared to full compaction. We
|
||||
@@ -1450,6 +1543,10 @@ Compaction* UniversalCompactionBuilder::PickDeleteTriggeredCompaction() {
|
||||
}
|
||||
assert(output_level <= max_output_level);
|
||||
|
||||
if (!MeetsOutputLevelRequirements(output_level)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (output_level != 0) {
|
||||
if (start_level == 0) {
|
||||
if (!picker_->GetOverlappingL0Files(vstorage_, &start_level_inputs,
|
||||
@@ -1574,6 +1671,10 @@ Compaction* UniversalCompactionBuilder::PickCompactionWithSortedRunRange(
|
||||
output_level = sorted_runs_[end_index + 1].level - 1;
|
||||
}
|
||||
|
||||
if (!MeetsOutputLevelRequirements(output_level)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// intra L0 compactions outputs could have overlap
|
||||
if (output_level != 0 && picker_->FilesRangeOverlapWithCompaction(
|
||||
inputs, output_level,
|
||||
|
||||
@@ -18,12 +18,15 @@ class UniversalCompactionPicker : public CompactionPicker {
|
||||
UniversalCompactionPicker(const ImmutableOptions& ioptions,
|
||||
const InternalKeyComparator* icmp)
|
||||
: CompactionPicker(ioptions, icmp) {}
|
||||
|
||||
// If `require_max_output_level` is true, only pick compaction
|
||||
// with max output level or return nullptr if no such compaction exists.
|
||||
Compaction* PickCompaction(
|
||||
const std::string& cf_name, const MutableCFOptions& mutable_cf_options,
|
||||
const MutableDBOptions& mutable_db_options,
|
||||
const std::vector<SequenceNumber>& existing_snapshots,
|
||||
const SnapshotChecker* snapshot_checker, VersionStorageInfo* vstorage,
|
||||
LogBuffer* log_buffer) override;
|
||||
LogBuffer* log_buffer, bool require_max_output_level = false) override;
|
||||
int MaxOutputLevel() const override { return NumberLevels() - 1; }
|
||||
|
||||
bool NeedsCompaction(const VersionStorageInfo* vstorage) const override;
|
||||
|
||||
@@ -41,7 +41,7 @@ CompactionJob::ProcessKeyValueCompactionWithCompactionService(
|
||||
}
|
||||
|
||||
compaction_input.cf_name = compaction->column_family_data()->GetName();
|
||||
compaction_input.snapshots = existing_snapshots_;
|
||||
compaction_input.snapshots = job_context_->snapshot_seqs;
|
||||
compaction_input.has_begin = sub_compact->start.has_value();
|
||||
compaction_input.begin =
|
||||
compaction_input.has_begin ? sub_compact->start->ToString() : "";
|
||||
@@ -221,18 +221,24 @@ CompactionJob::ProcessKeyValueCompactionWithCompactionService(
|
||||
}
|
||||
|
||||
FileMetaData meta;
|
||||
uint64_t file_size;
|
||||
// FIXME: file_size should be part of CompactionServiceOutputFile so that
|
||||
// we don't get DB corruption if the full file size has not been propagated
|
||||
// back to the caller through the file system (which could have metadata
|
||||
// lag or caching bugs).
|
||||
s = fs_->GetFileSize(tgt_file, IOOptions(), &file_size, nullptr);
|
||||
uint64_t file_size = file.file_size;
|
||||
|
||||
// TODO - Clean this up in the next release.
|
||||
// For backward compatibility - in case the remote worker does not populate
|
||||
// the file_size yet. If missing, continue to populate this from the file
|
||||
// system.
|
||||
if (file_size == 0) {
|
||||
s = fs_->GetFileSize(tgt_file, IOOptions(), &file_size, nullptr);
|
||||
}
|
||||
|
||||
if (!s.ok()) {
|
||||
sub_compact->status = s;
|
||||
db_options_.compaction_service->OnInstallation(
|
||||
response.scheduled_job_id, CompactionServiceJobStatus::kFailure);
|
||||
return CompactionServiceJobStatus::kFailure;
|
||||
}
|
||||
assert(file_size > 0);
|
||||
|
||||
meta.fd = FileDescriptor(file_num, compaction->output_path_id(), file_size,
|
||||
file.smallest_seqno, file.largest_seqno);
|
||||
meta.smallest.DecodeFrom(file.smallest_internal_key);
|
||||
@@ -298,9 +304,9 @@ CompactionServiceCompactionJob::CompactionServiceCompactionJob(
|
||||
VersionSet* versions, const std::atomic<bool>* shutting_down,
|
||||
LogBuffer* log_buffer, FSDirectory* output_directory, Statistics* stats,
|
||||
InstrumentedMutex* db_mutex, ErrorHandler* db_error_handler,
|
||||
std::vector<SequenceNumber> existing_snapshots,
|
||||
std::shared_ptr<Cache> table_cache, EventLogger* event_logger,
|
||||
const std::string& dbname, const std::shared_ptr<IOTracer>& io_tracer,
|
||||
JobContext* job_context, std::shared_ptr<Cache> table_cache,
|
||||
EventLogger* event_logger, const std::string& dbname,
|
||||
const std::shared_ptr<IOTracer>& io_tracer,
|
||||
const std::atomic<bool>& manual_compaction_canceled,
|
||||
const std::string& db_id, const std::string& db_session_id,
|
||||
std::string output_path,
|
||||
@@ -309,9 +315,8 @@ CompactionServiceCompactionJob::CompactionServiceCompactionJob(
|
||||
: CompactionJob(job_id, compaction, db_options, mutable_db_options,
|
||||
file_options, versions, shutting_down, log_buffer, nullptr,
|
||||
output_directory, nullptr, stats, db_mutex,
|
||||
db_error_handler, std::move(existing_snapshots),
|
||||
kMaxSequenceNumber, nullptr, nullptr,
|
||||
std::move(table_cache), event_logger,
|
||||
db_error_handler, job_context, std::move(table_cache),
|
||||
event_logger,
|
||||
compaction->mutable_cf_options().paranoid_file_checks,
|
||||
compaction->mutable_cf_options().report_bg_io_stats, dbname,
|
||||
&(compaction_service_result->stats), Env::Priority::USER,
|
||||
@@ -421,14 +426,14 @@ Status CompactionServiceCompactionJob::Run() {
|
||||
for (const auto& output_file : sub_compact->GetOutputs()) {
|
||||
auto& meta = output_file.meta;
|
||||
compaction_result_->output_files.emplace_back(
|
||||
MakeTableFileName(meta.fd.GetNumber()), meta.fd.smallest_seqno,
|
||||
meta.fd.largest_seqno, meta.smallest.Encode().ToString(),
|
||||
meta.largest.Encode().ToString(), meta.oldest_ancester_time,
|
||||
meta.file_creation_time, meta.epoch_number, meta.file_checksum,
|
||||
meta.file_checksum_func_name, output_file.validator.GetHash(),
|
||||
meta.marked_for_compaction, meta.unique_id,
|
||||
*output_file.table_properties, output_file.is_proximal_level,
|
||||
meta.temperature);
|
||||
MakeTableFileName(meta.fd.GetNumber()), meta.fd.GetFileSize(),
|
||||
meta.fd.smallest_seqno, meta.fd.largest_seqno,
|
||||
meta.smallest.Encode().ToString(), meta.largest.Encode().ToString(),
|
||||
meta.oldest_ancester_time, meta.file_creation_time, meta.epoch_number,
|
||||
meta.file_checksum, meta.file_checksum_func_name,
|
||||
output_file.validator.GetHash(), meta.marked_for_compaction,
|
||||
meta.unique_id, *output_file.table_properties,
|
||||
output_file.is_proximal_level, meta.temperature);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -528,6 +533,10 @@ static std::unordered_map<std::string, OptionTypeInfo>
|
||||
{offsetof(struct CompactionServiceOutputFile, file_name),
|
||||
OptionType::kEncodedString, OptionVerificationType::kNormal,
|
||||
OptionTypeFlags::kNone}},
|
||||
{"file_size",
|
||||
{offsetof(struct CompactionServiceOutputFile, file_size),
|
||||
OptionType::kUInt64T, OptionVerificationType::kNormal,
|
||||
OptionTypeFlags::kNone}},
|
||||
{"smallest_seqno",
|
||||
{offsetof(struct CompactionServiceOutputFile, smallest_seqno),
|
||||
OptionType::kUInt64T, OptionVerificationType::kNormal,
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "port/stack_trace.h"
|
||||
#include "rocksdb/utilities/options_util.h"
|
||||
#include "table/unique_id_impl.h"
|
||||
#include "utilities/merge_operators/string_append/stringappend.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
@@ -1222,6 +1223,32 @@ TEST_F(CompactionServiceTest, CompactionFilter) {
|
||||
ASSERT_GE(my_cs->GetCompactionNum(), 1);
|
||||
}
|
||||
|
||||
TEST_F(CompactionServiceTest, MergeOperator) {
|
||||
Options options = CurrentOptions();
|
||||
options.merge_operator.reset(new StringAppendOperator(','));
|
||||
ReopenWithCompactionService(&options);
|
||||
GenerateTestData();
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
for (int i = 0; i < 200; i++) {
|
||||
ASSERT_OK(db_->Merge(WriteOptions(), Key(i),
|
||||
"merge_op_append_" + std::to_string(i)));
|
||||
}
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
||||
// verify result
|
||||
for (int i = 0; i < 200; i++) {
|
||||
auto result = Get(Key(i));
|
||||
if (i % 2) {
|
||||
ASSERT_EQ(result, "value" + std::to_string(i) + ",merge_op_append_" +
|
||||
std::to_string(i));
|
||||
} else {
|
||||
ASSERT_EQ(result, "value_new" + std::to_string(i) + ",merge_op_append_" +
|
||||
std::to_string(i));
|
||||
}
|
||||
}
|
||||
auto my_cs = GetCompactionService();
|
||||
ASSERT_GE(my_cs->GetCompactionNum(), 1);
|
||||
}
|
||||
|
||||
TEST_F(CompactionServiceTest, Snapshot) {
|
||||
Options options = CurrentOptions();
|
||||
ReopenWithCompactionService(&options);
|
||||
|
||||
@@ -106,7 +106,11 @@ class SubcompactionState {
|
||||
subcompaction_job_info.subcompaction_job_id = static_cast<int>(sub_job_id);
|
||||
subcompaction_job_info.base_input_level = c->start_level();
|
||||
subcompaction_job_info.output_level = c->output_level();
|
||||
subcompaction_job_info.compaction_reason = c->compaction_reason();
|
||||
subcompaction_job_info.compression = c->output_compression();
|
||||
subcompaction_job_info.stats = compaction_job_stats;
|
||||
subcompaction_job_info.blob_compression_type =
|
||||
c->mutable_cf_options().blob_compression_type;
|
||||
}
|
||||
|
||||
SubcompactionState() = delete;
|
||||
|
||||
+4
-3
@@ -93,9 +93,10 @@ Status VerifySstFileChecksumInternal(const Options& options,
|
||||
nullptr /* file_read_hist */, ioptions.rate_limiter.get()));
|
||||
const bool kImmortal = true;
|
||||
auto reader_options = TableReaderOptions(
|
||||
ioptions, options.prefix_extractor, env_options, internal_comparator,
|
||||
options.block_protection_bytes_per_key, false /* skip_filters */,
|
||||
!kImmortal, false /* force_direct_prefetch */, -1 /* level */);
|
||||
ioptions, options.prefix_extractor, options.compression_manager.get(),
|
||||
env_options, internal_comparator, options.block_protection_bytes_per_key,
|
||||
false /* skip_filters */, !kImmortal, false /* force_direct_prefetch */,
|
||||
-1 /* level */);
|
||||
reader_options.largest_seqno = largest_seqno;
|
||||
s = options.table_factory->NewTableReader(
|
||||
read_options, reader_options, std::move(file_reader), file_size,
|
||||
|
||||
@@ -5086,6 +5086,7 @@ TEST_F(DBBasicTest, DisallowMemtableWrite) {
|
||||
options_allow.create_if_missing = true;
|
||||
Options options_disallow = options_allow;
|
||||
options_disallow.disallow_memtable_writes = true;
|
||||
options_disallow.paranoid_memory_checks = true;
|
||||
|
||||
DestroyAndReopen(options_allow);
|
||||
// CFs allowing and disallowing memtable write
|
||||
@@ -5125,6 +5126,11 @@ TEST_F(DBBasicTest, DisallowMemtableWrite) {
|
||||
EXPECT_EQ(Get(2, "b2"), "2");
|
||||
EXPECT_EQ(Get(3, "b3"), "NOT_FOUND");
|
||||
|
||||
std::unique_ptr<Iterator> iter(
|
||||
dbfull()->NewIterator(ReadOptions(), handles_[3]));
|
||||
iter->Seek("a3");
|
||||
ASSERT_OK(iter->status());
|
||||
iter.reset();
|
||||
// When the DB is re-opened with WAL entries for a CF that is newly setting
|
||||
// disallow_memtable_writes, we detect that and fail the open gracefully.
|
||||
ASSERT_EQ(TryReopenWithColumnFamilies(
|
||||
|
||||
+67
-57
@@ -829,68 +829,78 @@ TEST_F(DBBlockCacheTest, CacheCompressionDict) {
|
||||
const int kNumEntriesPerFile = 128;
|
||||
const int kNumBytesPerEntry = 1024;
|
||||
|
||||
// Try all the available libraries that support dictionary compression
|
||||
std::vector<CompressionType> compression_types;
|
||||
if (Zlib_Supported()) {
|
||||
compression_types.push_back(kZlibCompression);
|
||||
}
|
||||
if (LZ4_Supported()) {
|
||||
compression_types.push_back(kLZ4Compression);
|
||||
compression_types.push_back(kLZ4HCCompression);
|
||||
}
|
||||
if (ZSTD_Supported()) {
|
||||
compression_types.push_back(kZSTD);
|
||||
}
|
||||
std::vector<CompressionType> dict_compressions =
|
||||
GetSupportedDictCompressions();
|
||||
Random rnd(301);
|
||||
for (auto compression_type : compression_types) {
|
||||
Options options = CurrentOptions();
|
||||
options.bottommost_compression = compression_type;
|
||||
options.bottommost_compression_opts.max_dict_bytes = 4096;
|
||||
options.bottommost_compression_opts.enabled = true;
|
||||
options.create_if_missing = true;
|
||||
options.num_levels = 2;
|
||||
options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics();
|
||||
options.target_file_size_base = kNumEntriesPerFile * kNumBytesPerEntry;
|
||||
BlockBasedTableOptions table_options;
|
||||
table_options.cache_index_and_filter_blocks = true;
|
||||
table_options.block_cache.reset(new MockCache());
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
DestroyAndReopen(options);
|
||||
// Format version before and after compression handling changes
|
||||
for (int format_version : {6, 7}) {
|
||||
// Test all supported compression types because (at least historically)
|
||||
// dictionary compression could be enabled and a dictionary block saved
|
||||
// but ignored by some compression types. Ensure we at least don't crash
|
||||
// or return corruption for those.
|
||||
for (auto compression_type : GetSupportedCompressions()) {
|
||||
// Extra handling checks only for types actually supporting dictionary
|
||||
// compression.
|
||||
bool dict_supported =
|
||||
std::count(dict_compressions.begin(), dict_compressions.end(),
|
||||
compression_type) > 0;
|
||||
|
||||
RecordCacheCountersForCompressionDict(options);
|
||||
Options options = CurrentOptions();
|
||||
options.bottommost_compression = compression_type;
|
||||
options.bottommost_compression_opts.max_dict_bytes = 4096;
|
||||
options.bottommost_compression_opts.enabled = true;
|
||||
options.create_if_missing = true;
|
||||
options.num_levels = 2;
|
||||
options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics();
|
||||
options.target_file_size_base = kNumEntriesPerFile * kNumBytesPerEntry;
|
||||
BlockBasedTableOptions table_options;
|
||||
table_options.cache_index_and_filter_blocks = true;
|
||||
table_options.block_cache.reset(new MockCache());
|
||||
table_options.format_version = format_version;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
DestroyAndReopen(options);
|
||||
|
||||
for (int i = 0; i < kNumFiles; ++i) {
|
||||
ASSERT_EQ(i, NumTableFilesAtLevel(0, 0));
|
||||
for (int j = 0; j < kNumEntriesPerFile; ++j) {
|
||||
std::string value = rnd.RandomString(kNumBytesPerEntry);
|
||||
ASSERT_OK(Put(Key(j * kNumFiles + i), value.c_str()));
|
||||
RecordCacheCountersForCompressionDict(options);
|
||||
|
||||
for (int i = 0; i < kNumFiles; ++i) {
|
||||
ASSERT_EQ(i, NumTableFilesAtLevel(0, 0));
|
||||
for (int j = 0; j < kNumEntriesPerFile; ++j) {
|
||||
std::string value = rnd.RandomString(kNumBytesPerEntry);
|
||||
ASSERT_OK(Put(Key(j * kNumFiles + i), value.c_str()));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
ASSERT_EQ(0, NumTableFilesAtLevel(0));
|
||||
ASSERT_EQ(kNumFiles, NumTableFilesAtLevel(1));
|
||||
|
||||
if (dict_supported) {
|
||||
// Compression dictionary blocks are preloaded.
|
||||
CheckCacheCountersForCompressionDict(
|
||||
options, kNumFiles /* expected_compression_dict_misses */,
|
||||
0 /* expected_compression_dict_hits */,
|
||||
kNumFiles /* expected_compression_dict_inserts */);
|
||||
}
|
||||
|
||||
// Seek to a key in a file. It should cause the SST's dictionary
|
||||
// meta-block to be read.
|
||||
RecordCacheCounters(options);
|
||||
RecordCacheCountersForCompressionDict(options);
|
||||
ReadOptions read_options;
|
||||
ASSERT_NE("NOT_FOUND", Get(Key(kNumFiles * kNumEntriesPerFile - 1)));
|
||||
|
||||
if (dict_supported) {
|
||||
// Two block hits: index and dictionary since they are prefetched
|
||||
// One block missed/added: data block
|
||||
CheckCacheCounters(options, 1 /* expected_misses */,
|
||||
2 /* expected_hits */, 1 /* expected_inserts */,
|
||||
0 /* expected_failures */);
|
||||
CheckCacheCountersForCompressionDict(
|
||||
options, 0 /* expected_compression_dict_misses */,
|
||||
1 /* expected_compression_dict_hits */,
|
||||
0 /* expected_compression_dict_inserts */);
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
ASSERT_EQ(0, NumTableFilesAtLevel(0));
|
||||
ASSERT_EQ(kNumFiles, NumTableFilesAtLevel(1));
|
||||
|
||||
// Compression dictionary blocks are preloaded.
|
||||
CheckCacheCountersForCompressionDict(
|
||||
options, kNumFiles /* expected_compression_dict_misses */,
|
||||
0 /* expected_compression_dict_hits */,
|
||||
kNumFiles /* expected_compression_dict_inserts */);
|
||||
|
||||
// Seek to a key in a file. It should cause the SST's dictionary meta-block
|
||||
// to be read.
|
||||
RecordCacheCounters(options);
|
||||
RecordCacheCountersForCompressionDict(options);
|
||||
ReadOptions read_options;
|
||||
ASSERT_NE("NOT_FOUND", Get(Key(kNumFiles * kNumEntriesPerFile - 1)));
|
||||
// Two block hits: index and dictionary since they are prefetched
|
||||
// One block missed/added: data block
|
||||
CheckCacheCounters(options, 1 /* expected_misses */, 2 /* expected_hits */,
|
||||
1 /* expected_inserts */, 0 /* expected_failures */);
|
||||
CheckCacheCountersForCompressionDict(
|
||||
options, 0 /* expected_compression_dict_misses */,
|
||||
1 /* expected_compression_dict_hits */,
|
||||
0 /* expected_compression_dict_inserts */);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+322
-143
@@ -455,6 +455,72 @@ TEST_P(DBCompactionTestWithParam, CompactionDeletionTrigger) {
|
||||
}
|
||||
}
|
||||
#endif // !defined(ROCKSDB_VALGRIND_RUN) || defined(ROCKSDB_FULL_VALGRIND_RUN)
|
||||
TEST_F(DBCompactionTest, UniversalReduceFileLockingRepickNothing) {
|
||||
const int kFileNumCompactionTrigger = 3;
|
||||
|
||||
Options options = CurrentOptions();
|
||||
options.compaction_options_universal.reduce_file_locking = true;
|
||||
// Set `max_background_jobs` to be 3 to allow low and bottom priority thread
|
||||
// to run compaction together
|
||||
options.max_background_jobs = 3;
|
||||
Env::Default()->SetBackgroundThreads(1, Env::Priority::BOTTOM);
|
||||
options.num_levels = 3;
|
||||
options.compaction_style = kCompactionStyleUniversal;
|
||||
options.level0_file_num_compaction_trigger = kFileNumCompactionTrigger;
|
||||
options.compaction_options_universal.max_size_amplification_percent = 1;
|
||||
|
||||
DestroyAndReopen(options);
|
||||
|
||||
// Need to get a token to enable compaction parallelism up to
|
||||
// `max_background_compactions` jobs.
|
||||
auto pressure_token =
|
||||
dbfull()->TEST_write_controler().GetCompactionPressureToken();
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency(
|
||||
{// Wait for the full (bottom-priority) compaction to be pre-picked as an
|
||||
// intent (that is allowing files to be picked by other compactions and
|
||||
// will pick later when the bottom-priority thread is available to
|
||||
// execute the compaction) before triggering the low-priority compaction.
|
||||
{"DBImpl::BackgroundCompaction:ForwardToBottomPriPool",
|
||||
"LowPriCompaction"},
|
||||
// Wait for low-priority compaction to start before
|
||||
// repicking for the full compaction intent (bottom-priority), enabling
|
||||
// them to run in parallel.
|
||||
{"DBImpl::BackgroundCompaction:NonTrivial",
|
||||
"DBImpl::BGWorkBottomCompaction"}});
|
||||
|
||||
bool bottom_pri_compaction_attempt_repick = false;
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::BackgroundCompaction():AfterPickCompactionBottomPri",
|
||||
[&](void* arg) {
|
||||
bottom_pri_compaction_attempt_repick = true;
|
||||
Compaction* c = static_cast<Compaction*>(arg);
|
||||
// Verify the intended full compaction for bottom priority thread does
|
||||
// not get to run (i.e, output to bottommost level) since when it
|
||||
// repicks its files, some of the the intended input files are already
|
||||
// compacted by the low priority thread
|
||||
assert(c == nullptr);
|
||||
});
|
||||
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
for (int i = 0; i < kFileNumCompactionTrigger; ++i) {
|
||||
if (i == 0) {
|
||||
ASSERT_OK(Put("file_locked_for_bottom_pri_compaction", "value"));
|
||||
} else {
|
||||
ASSERT_OK(
|
||||
Put("file_not_locked_for_bottom_pri_compaction" + std::to_string(i),
|
||||
"value"));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
|
||||
TEST_SYNC_POINT("LowPriCompaction");
|
||||
ASSERT_OK(Put("a_new_file_to_pick_for_low_pri_compaction", "value"));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
ASSERT_TRUE(bottom_pri_compaction_attempt_repick);
|
||||
}
|
||||
|
||||
TEST_F(DBCompactionTest, SkipStatsUpdateTest) {
|
||||
// This test verify UpdateAccumulatedStats is not on
|
||||
@@ -2824,46 +2890,99 @@ TEST_F(DBCompactionTest, L0_CompactionBug_Issue44_b) {
|
||||
}
|
||||
|
||||
TEST_F(DBCompactionTest, ManualAutoRace) {
|
||||
CreateAndReopenWithCF({"pikachu"}, CurrentOptions());
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency(
|
||||
{{"DBImpl::BGWorkCompaction", "DBCompactionTest::ManualAutoRace:1"},
|
||||
{"DBImpl::RunManualCompaction:WaitScheduled",
|
||||
"BackgroundCallCompaction:0"}});
|
||||
const int kNumL0FilesTrigger = 4;
|
||||
// Verify that the auto compaction is retried after the conflicting exclusive
|
||||
// manual compaction finishes for:
|
||||
// 1. Non-bottom-priority compactions (tested with level compaction)
|
||||
// 2. Bottom-priority compactions (tested with universal compaction)
|
||||
for (auto compaction_style :
|
||||
{kCompactionStyleLevel, kCompactionStyleUniversal}) {
|
||||
Env::Default()->SetBackgroundThreads(
|
||||
compaction_style == kCompactionStyleUniversal ? 2 : 0,
|
||||
Env::Priority::BOTTOM);
|
||||
for (auto universal_reduce_file_locking : {false, true}) {
|
||||
if (compaction_style != kCompactionStyleUniversal &&
|
||||
universal_reduce_file_locking) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
|
||||
Options options = CurrentOptions();
|
||||
options.num_levels = 3;
|
||||
options.level0_file_num_compaction_trigger = kNumL0FilesTrigger;
|
||||
options.compaction_style = compaction_style;
|
||||
options.compaction_options_universal.reduce_file_locking =
|
||||
universal_reduce_file_locking;
|
||||
|
||||
ASSERT_OK(Put(1, "foo", ""));
|
||||
ASSERT_OK(Put(1, "bar", ""));
|
||||
ASSERT_OK(Flush(1));
|
||||
ASSERT_OK(Put(1, "foo", ""));
|
||||
ASSERT_OK(Put(1, "bar", ""));
|
||||
// Generate four files in CF 0, which should trigger an auto compaction
|
||||
ASSERT_OK(Put("foo", ""));
|
||||
ASSERT_OK(Put("bar", ""));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(Put("foo", ""));
|
||||
ASSERT_OK(Put("bar", ""));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(Put("foo", ""));
|
||||
ASSERT_OK(Put("bar", ""));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(Put("foo", ""));
|
||||
ASSERT_OK(Put("bar", ""));
|
||||
ASSERT_OK(Flush());
|
||||
DestroyAndReopen(options);
|
||||
CreateAndReopenWithCF({"exclusive_manual_compaction_cf"}, options);
|
||||
|
||||
// The auto compaction is scheduled but waited until here
|
||||
TEST_SYNC_POINT("DBCompactionTest::ManualAutoRace:1");
|
||||
// The auto compaction will wait until the manual compaction is registerd
|
||||
// before processing so that it will be cancelled.
|
||||
CompactRangeOptions cro;
|
||||
cro.exclusive_manual_compaction = true;
|
||||
ASSERT_OK(dbfull()->CompactRange(cro, handles_[1], nullptr, nullptr));
|
||||
ASSERT_EQ("0,1", FilesPerLevel(1));
|
||||
// Set up sync points to ensure that the auto compaction
|
||||
// encounters a conflict from exclusive manual compaction before the auto
|
||||
// compaction gets to pick files, This will trigger a retry later.
|
||||
//
|
||||
// Specifically, the sync points are set up as following:
|
||||
// 1. Wait until background low-pri scheduled (not picking files yet) or
|
||||
// bottom-pri scheduled (not repicking files yet) for
|
||||
// `universal_reduce_file_locking = true` before triggering
|
||||
// CompactRange()
|
||||
//
|
||||
// 2. Wait until the triggered CompactRange()
|
||||
// registers its compaction and creates conflict before the auto
|
||||
// compaction picks or repicks files for the background compaction.
|
||||
if (compaction_style == kCompactionStyleLevel ||
|
||||
!universal_reduce_file_locking) {
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency(
|
||||
{{"DBImpl::BGWorkCompaction", "DBCompactionTest::ManualAutoRace:1"},
|
||||
{"DBImpl::RunManualCompaction:WaitScheduled",
|
||||
"BackgroundCallCompaction:0"}});
|
||||
} else {
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency(
|
||||
{{"DBImpl::BackgroundCompaction:ForwardToBottomPriPool",
|
||||
"DBCompactionTest::ManualAutoRace:1"},
|
||||
{"DBImpl::RunManualCompaction:WaitScheduled",
|
||||
"BackgroundCallCompaction:0:BottomPri"}});
|
||||
}
|
||||
|
||||
// Eventually the cancelled compaction will be rescheduled and executed.
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
ASSERT_EQ("0,1", FilesPerLevel(0));
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
|
||||
bool encounter_conflict = false;
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::BackgroundCompaction()::Conflict",
|
||||
[&](void* /*arg*/) { encounter_conflict = true; });
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
// Generate files in CF 1 for exclusive CompactRange()
|
||||
ASSERT_OK(Put(1, "foo", ""));
|
||||
ASSERT_OK(Put(1, "bar", ""));
|
||||
ASSERT_OK(Flush(1));
|
||||
ASSERT_OK(Put(1, "foo", ""));
|
||||
ASSERT_OK(Put(1, "bar", ""));
|
||||
// Generate files in CF0 to trigger full compaction
|
||||
for (int i = 0; i < kNumL0FilesTrigger; ++i) {
|
||||
ASSERT_OK(Put("foo", ""));
|
||||
ASSERT_OK(Put("bar", ""));
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
|
||||
TEST_SYNC_POINT("DBCompactionTest::ManualAutoRace:1");
|
||||
CompactRangeOptions cro;
|
||||
cro.exclusive_manual_compaction = true;
|
||||
ASSERT_OK(dbfull()->CompactRange(cro, handles_[1], nullptr, nullptr));
|
||||
ASSERT_EQ(compaction_style == kCompactionStyleLevel ? "0,1" : "0,0,1",
|
||||
FilesPerLevel(1));
|
||||
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
|
||||
ASSERT_TRUE(encounter_conflict);
|
||||
|
||||
// Verify that the auto compaction is eventually executed after the
|
||||
// exclusive CompactRange() finishes.
|
||||
ASSERT_EQ(compaction_style == kCompactionStyleLevel ? "0,1" : "0,0,1",
|
||||
FilesPerLevel(0));
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
}
|
||||
Env::Default()->SetBackgroundThreads(0, Env::Priority::BOTTOM);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(DBCompactionTestWithParam, ManualCompaction) {
|
||||
@@ -3986,41 +4105,51 @@ TEST_P(DBCompactionTestWithParam, IntraL0CompactionDoesNotObsoleteDeletions) {
|
||||
TEST_P(DBCompactionTestWithParam, FullCompactionInBottomPriThreadPool) {
|
||||
const int kNumFilesTrigger = 3;
|
||||
Env::Default()->SetBackgroundThreads(1, Env::Priority::BOTTOM);
|
||||
for (bool use_universal_compaction : {false, true}) {
|
||||
Options options = CurrentOptions();
|
||||
if (use_universal_compaction) {
|
||||
options.compaction_style = kCompactionStyleUniversal;
|
||||
} else {
|
||||
options.compaction_style = kCompactionStyleLevel;
|
||||
options.level_compaction_dynamic_level_bytes = true;
|
||||
for (auto compaction_style :
|
||||
{kCompactionStyleLevel, kCompactionStyleUniversal}) {
|
||||
for (auto universal_reduce_file_locking : {false, true}) {
|
||||
if (compaction_style != kCompactionStyleUniversal &&
|
||||
universal_reduce_file_locking) {
|
||||
continue;
|
||||
}
|
||||
Options options = CurrentOptions();
|
||||
options.compaction_style = compaction_style;
|
||||
if (compaction_style == kCompactionStyleLevel) {
|
||||
options.level_compaction_dynamic_level_bytes = true;
|
||||
} else {
|
||||
options.compaction_options_universal.reduce_file_locking =
|
||||
universal_reduce_file_locking;
|
||||
// Trigger compaction if size amplification exceeds 110%
|
||||
options.compaction_options_universal.max_size_amplification_percent =
|
||||
110;
|
||||
}
|
||||
options.num_levels = 4;
|
||||
options.write_buffer_size = 100 << 10; // 100KB
|
||||
options.target_file_size_base = 32 << 10; // 32KB
|
||||
options.level0_file_num_compaction_trigger = kNumFilesTrigger;
|
||||
|
||||
DestroyAndReopen(options);
|
||||
|
||||
int num_bottom_pri_compactions = 0;
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::BGWorkBottomCompaction",
|
||||
[&](void* /*arg*/) { ++num_bottom_pri_compactions; });
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
Random rnd(301);
|
||||
for (int num = 0; num < kNumFilesTrigger; num++) {
|
||||
ASSERT_EQ(NumSortedRuns(), num);
|
||||
int key_idx = 0;
|
||||
GenerateNewFile(&rnd, &key_idx);
|
||||
}
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
|
||||
ASSERT_EQ(1, num_bottom_pri_compactions);
|
||||
|
||||
// Verify that size amplification did occur
|
||||
ASSERT_EQ(NumSortedRuns(), 1);
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
options.num_levels = 4;
|
||||
options.write_buffer_size = 100 << 10; // 100KB
|
||||
options.target_file_size_base = 32 << 10; // 32KB
|
||||
options.level0_file_num_compaction_trigger = kNumFilesTrigger;
|
||||
// Trigger compaction if size amplification exceeds 110%
|
||||
options.compaction_options_universal.max_size_amplification_percent = 110;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
int num_bottom_pri_compactions = 0;
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::BGWorkBottomCompaction",
|
||||
[&](void* /*arg*/) { ++num_bottom_pri_compactions; });
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
Random rnd(301);
|
||||
for (int num = 0; num < kNumFilesTrigger; num++) {
|
||||
ASSERT_EQ(NumSortedRuns(), num);
|
||||
int key_idx = 0;
|
||||
GenerateNewFile(&rnd, &key_idx);
|
||||
}
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
|
||||
ASSERT_EQ(1, num_bottom_pri_compactions);
|
||||
|
||||
// Verify that size amplification did occur
|
||||
ASSERT_EQ(NumSortedRuns(), 1);
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
Env::Default()->SetBackgroundThreads(0, Env::Priority::BOTTOM);
|
||||
}
|
||||
@@ -6634,7 +6763,7 @@ TEST_P(RoundRobinSubcompactionsAgainstResources, SubcompactionsUsingResources) {
|
||||
// compaction is enough to make post-compaction L1 size less than
|
||||
// the maximum size (this test assumes only one round-robin compaction
|
||||
// is triggered by kLevelMaxLevelSize)
|
||||
options.max_compaction_bytes = 100000000;
|
||||
options.max_compaction_bytes = std::numeric_limits<uint64_t>::max();
|
||||
|
||||
DestroyAndReopen(options);
|
||||
env_->SetBackgroundThreads(total_low_pri_threads_, Env::LOW);
|
||||
@@ -6667,41 +6796,33 @@ TEST_P(RoundRobinSubcompactionsAgainstResources, SubcompactionsUsingResources) {
|
||||
// More than 10 files are selected for round-robin under auto
|
||||
// compaction. The number of planned subcompaction is restricted by
|
||||
// the minimum number between available threads and compaction limits
|
||||
ASSERT_EQ(num_planned_subcompactions - options.max_subcompactions,
|
||||
std::min(total_low_pri_threads_, max_compaction_limits_) - 1);
|
||||
auto actual_reserved_threads =
|
||||
num_planned_subcompactions - options.max_subcompactions;
|
||||
auto expected_reserved_threads =
|
||||
std::min(total_low_pri_threads_, max_compaction_limits_) - 1;
|
||||
ASSERT_EQ(actual_reserved_threads, expected_reserved_threads);
|
||||
num_planned_subcompactions_verified = true;
|
||||
});
|
||||
SyncPoint::GetInstance()->LoadDependency(
|
||||
{{"RoundRobinSubcompactionsAgainstResources:0",
|
||||
"BackgroundCallCompaction:0"},
|
||||
{"CompactionJob::AcquireSubcompactionResources:0",
|
||||
"RoundRobinSubcompactionsAgainstResources:1"},
|
||||
{"RoundRobinSubcompactionsAgainstResources:2",
|
||||
"CompactionJob::AcquireSubcompactionResources:1"},
|
||||
{"CompactionJob::ReleaseSubcompactionResources:0",
|
||||
"RoundRobinSubcompactionsAgainstResources:3"},
|
||||
{"RoundRobinSubcompactionsAgainstResources:4",
|
||||
"CompactionJob::ReleaseSubcompactionResources:1"}});
|
||||
|
||||
int acquire_count = 0;
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"CompactionJob::AcquireSubcompactionResources:0",
|
||||
[&](void* /*arg*/) { acquire_count++; });
|
||||
int release_count = 0;
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"CompactionJob::ReleaseSubcompactionResources",
|
||||
[&](void* /*arg*/) { release_count++; });
|
||||
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
ASSERT_OK(dbfull()->EnableAutoCompaction({dbfull()->DefaultColumnFamily()}));
|
||||
TEST_SYNC_POINT("RoundRobinSubcompactionsAgainstResources:0");
|
||||
TEST_SYNC_POINT("RoundRobinSubcompactionsAgainstResources:1");
|
||||
auto pressure_token =
|
||||
dbfull()->TEST_write_controler().GetCompactionPressureToken();
|
||||
|
||||
TEST_SYNC_POINT("RoundRobinSubcompactionsAgainstResources:2");
|
||||
TEST_SYNC_POINT("RoundRobinSubcompactionsAgainstResources:3");
|
||||
// We can reserve more threads now except one is being used
|
||||
ASSERT_EQ(total_low_pri_threads_ - 1,
|
||||
env_->ReserveThreads(total_low_pri_threads_, Env::Priority::LOW));
|
||||
ASSERT_EQ(
|
||||
total_low_pri_threads_ - 1,
|
||||
env_->ReleaseThreads(total_low_pri_threads_ - 1, Env::Priority::LOW));
|
||||
TEST_SYNC_POINT("RoundRobinSubcompactionsAgainstResources:4");
|
||||
ASSERT_OK(dbfull()->EnableAutoCompaction({dbfull()->DefaultColumnFamily()}));
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
|
||||
ASSERT_TRUE(num_planned_subcompactions_verified);
|
||||
ASSERT_EQ(acquire_count, release_count);
|
||||
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
}
|
||||
@@ -10006,55 +10127,60 @@ TEST_F(DBCompactionTest, BottomPriCompactionCountsTowardConcurrencyLimit) {
|
||||
|
||||
env_->SetBackgroundThreads(1, Env::Priority::BOTTOM);
|
||||
|
||||
Options options = CurrentOptions();
|
||||
options.level0_file_num_compaction_trigger = kNumL0Files;
|
||||
options.num_levels = kNumLevels;
|
||||
DestroyAndReopen(options);
|
||||
for (bool universal_reduce_file_locking : {false, true}) {
|
||||
Options options = CurrentOptions();
|
||||
options.level0_file_num_compaction_trigger = kNumL0Files;
|
||||
options.num_levels = kNumLevels;
|
||||
options.compaction_style = kCompactionStyleUniversal;
|
||||
options.compaction_options_universal.reduce_file_locking =
|
||||
universal_reduce_file_locking;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
// Setup last level to be non-empty since it's a bit unclear whether
|
||||
// compaction to an empty level would be considered "bottommost".
|
||||
ASSERT_OK(Put(Key(0), "val"));
|
||||
ASSERT_OK(Flush());
|
||||
MoveFilesToLevel(kNumLevels - 1);
|
||||
|
||||
SyncPoint::GetInstance()->LoadDependency(
|
||||
{{"DBImpl::BGWorkBottomCompaction",
|
||||
"DBCompactionTest::BottomPriCompactionCountsTowardConcurrencyLimit:"
|
||||
"PreTriggerCompaction"},
|
||||
{"DBCompactionTest::BottomPriCompactionCountsTowardConcurrencyLimit:"
|
||||
"PostTriggerCompaction",
|
||||
"BackgroundCallCompaction:0"}});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
port::Thread compact_range_thread([&] {
|
||||
CompactRangeOptions cro;
|
||||
cro.bottommost_level_compaction = BottommostLevelCompaction::kForce;
|
||||
cro.exclusive_manual_compaction = false;
|
||||
ASSERT_OK(dbfull()->CompactRange(cro, nullptr, nullptr));
|
||||
});
|
||||
|
||||
// Sleep in the low-pri thread so any newly scheduled compaction will be
|
||||
// queued. Otherwise it might finish before we check its existence.
|
||||
test::SleepingBackgroundTask sleeping_task_low;
|
||||
env_->Schedule(&test::SleepingBackgroundTask::DoSleepTask, &sleeping_task_low,
|
||||
Env::Priority::LOW);
|
||||
sleeping_task_low.WaitUntilSleeping();
|
||||
|
||||
TEST_SYNC_POINT(
|
||||
"DBCompactionTest::BottomPriCompactionCountsTowardConcurrencyLimit:"
|
||||
"PreTriggerCompaction");
|
||||
for (int i = 0; i < kNumL0Files; ++i) {
|
||||
// Setup last level to be non-empty since it's a bit unclear whether
|
||||
// compaction to an empty level would be considered "bottommost".
|
||||
ASSERT_OK(Put(Key(0), "val"));
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
ASSERT_EQ(0u, env_->GetThreadPoolQueueLen(Env::Priority::LOW));
|
||||
TEST_SYNC_POINT(
|
||||
"DBCompactionTest::BottomPriCompactionCountsTowardConcurrencyLimit:"
|
||||
"PostTriggerCompaction");
|
||||
MoveFilesToLevel(kNumLevels - 1);
|
||||
|
||||
sleeping_task_low.WakeUp();
|
||||
sleeping_task_low.WaitUntilDone();
|
||||
compact_range_thread.join();
|
||||
SyncPoint::GetInstance()->LoadDependency(
|
||||
{{"DBImpl::BGWorkBottomCompaction",
|
||||
"DBCompactionTest::BottomPriCompactionCountsTowardConcurrencyLimit:"
|
||||
"PreTriggerCompaction"},
|
||||
{"DBCompactionTest::BottomPriCompactionCountsTowardConcurrencyLimit:"
|
||||
"PostTriggerCompaction",
|
||||
"BackgroundCallCompaction:0"}});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
port::Thread compact_range_thread([&] {
|
||||
CompactRangeOptions cro;
|
||||
cro.bottommost_level_compaction = BottommostLevelCompaction::kForce;
|
||||
cro.exclusive_manual_compaction = false;
|
||||
ASSERT_OK(dbfull()->CompactRange(cro, nullptr, nullptr));
|
||||
});
|
||||
|
||||
// Sleep in the low-pri thread so any newly scheduled compaction will be
|
||||
// queued. Otherwise it might finish before we check its existence.
|
||||
test::SleepingBackgroundTask sleeping_task_low;
|
||||
env_->Schedule(&test::SleepingBackgroundTask::DoSleepTask,
|
||||
&sleeping_task_low, Env::Priority::LOW);
|
||||
sleeping_task_low.WaitUntilSleeping();
|
||||
|
||||
TEST_SYNC_POINT(
|
||||
"DBCompactionTest::BottomPriCompactionCountsTowardConcurrencyLimit:"
|
||||
"PreTriggerCompaction");
|
||||
for (int i = 0; i < kNumL0Files; ++i) {
|
||||
ASSERT_OK(Put(Key(0), "val"));
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
ASSERT_EQ(0u, env_->GetThreadPoolQueueLen(Env::Priority::LOW));
|
||||
TEST_SYNC_POINT(
|
||||
"DBCompactionTest::BottomPriCompactionCountsTowardConcurrencyLimit:"
|
||||
"PostTriggerCompaction");
|
||||
|
||||
sleeping_task_low.WakeUp();
|
||||
sleeping_task_low.WaitUntilDone();
|
||||
compact_range_thread.join();
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBCompactionTest, BottommostFileCompactionAllowIngestBehind) {
|
||||
@@ -10967,6 +11093,59 @@ TEST_F(DBCompactionTest, RecordNewestKeyTimeForTtlCompaction) {
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
ASSERT_EQ(NumTableFilesAtLevel(0), 0);
|
||||
}
|
||||
|
||||
class PeriodicCompactionListener : public EventListener {
|
||||
public:
|
||||
explicit PeriodicCompactionListener() {}
|
||||
void OnCompactionBegin(DB* /*db*/, const CompactionJobInfo& ci) override {
|
||||
if (ci.compaction_reason == CompactionReason::kPeriodicCompaction) {
|
||||
++num_periodic_compactions;
|
||||
}
|
||||
}
|
||||
|
||||
std::atomic<int> num_periodic_compactions = 0;
|
||||
};
|
||||
|
||||
TEST_F(DBCompactionTest, PeriodicTask) {
|
||||
// Tests that when no trigger event is fired (flush/compaction/setoptions),
|
||||
// periodic compaction is still triggered by a scheduled periodic function.
|
||||
auto mock_clock = std::make_shared<MockSystemClock>(env_->GetSystemClock());
|
||||
mock_clock->SetCurrentTime(100);
|
||||
mock_clock->InstallTimedWaitFixCallback();
|
||||
auto mock_env = std::make_unique<CompositeEnvWrapper>(env_, mock_clock);
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::StartPeriodicTaskScheduler:Init", [&](void* arg) {
|
||||
auto periodic_task_scheduler_ptr =
|
||||
static_cast<PeriodicTaskScheduler*>(arg);
|
||||
periodic_task_scheduler_ptr->TEST_OverrideTimer(mock_clock.get());
|
||||
});
|
||||
|
||||
Options options;
|
||||
options.env = mock_env.get();
|
||||
options.compaction_style = kCompactionStyleUniversal;
|
||||
options.statistics = CreateDBStatistics();
|
||||
int kPeriodicCompactionSeconds = 7 * 24 * 60 * 60; // 1 week
|
||||
options.periodic_compaction_seconds = kPeriodicCompactionSeconds;
|
||||
options.num_levels = 50;
|
||||
auto listener = std::make_shared<PeriodicCompactionListener>();
|
||||
options.listeners.push_back(listener);
|
||||
ASSERT_OK(TryReopen(options));
|
||||
|
||||
Random* rnd = Random::GetTLSInstance();
|
||||
for (int k = 0; k < 10; ++k) {
|
||||
ASSERT_OK(Put(Key(k), rnd->RandomString(100)));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(db_->CompactRange({}, nullptr, nullptr));
|
||||
ASSERT_EQ(1, NumTableFilesAtLevel(49));
|
||||
|
||||
dbfull()->TEST_WaitForPeriodicTaskRun(
|
||||
[&] { mock_clock->MockSleepForSeconds(kPeriodicCompactionSeconds + 1); });
|
||||
ASSERT_OK(db_->WaitForCompact({}));
|
||||
|
||||
ASSERT_EQ(listener->num_periodic_compactions, 1);
|
||||
Close();
|
||||
}
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
+49
-9
@@ -253,6 +253,9 @@ DBImpl::DBImpl(const DBOptions& options, const std::string& dbname,
|
||||
periodic_task_functions_.emplace(
|
||||
PeriodicTaskType::kRecordSeqnoTime,
|
||||
[this]() { this->RecordSeqnoToTimeMapping(); });
|
||||
periodic_task_functions_.emplace(
|
||||
PeriodicTaskType::kTriggerCompaction,
|
||||
[this]() { this->TriggerPeriodicCompaction(); });
|
||||
|
||||
versions_.reset(new VersionSet(
|
||||
dbname_, &immutable_db_options_, file_options_, table_cache_.get(),
|
||||
@@ -787,7 +790,8 @@ Status DBImpl::StartPeriodicTaskScheduler() {
|
||||
Status s = periodic_task_scheduler_.Register(
|
||||
PeriodicTaskType::kDumpStats,
|
||||
periodic_task_functions_.at(PeriodicTaskType::kDumpStats),
|
||||
mutable_db_options_.stats_dump_period_sec);
|
||||
mutable_db_options_.stats_dump_period_sec,
|
||||
/*run_immediately=*/true);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
@@ -796,7 +800,8 @@ Status DBImpl::StartPeriodicTaskScheduler() {
|
||||
Status s = periodic_task_scheduler_.Register(
|
||||
PeriodicTaskType::kPersistStats,
|
||||
periodic_task_functions_.at(PeriodicTaskType::kPersistStats),
|
||||
mutable_db_options_.stats_persist_period_sec);
|
||||
mutable_db_options_.stats_persist_period_sec,
|
||||
/*run_immediately=*/true);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
@@ -804,7 +809,15 @@ Status DBImpl::StartPeriodicTaskScheduler() {
|
||||
|
||||
Status s = periodic_task_scheduler_.Register(
|
||||
PeriodicTaskType::kFlushInfoLog,
|
||||
periodic_task_functions_.at(PeriodicTaskType::kFlushInfoLog));
|
||||
periodic_task_functions_.at(PeriodicTaskType::kFlushInfoLog),
|
||||
/*run_immediately=*/true);
|
||||
|
||||
if (s.ok()) {
|
||||
s = periodic_task_scheduler_.Register(
|
||||
PeriodicTaskType::kTriggerCompaction,
|
||||
periodic_task_functions_.at(PeriodicTaskType::kTriggerCompaction),
|
||||
/*run_immediately=*/false);
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
@@ -855,7 +868,7 @@ Status DBImpl::RegisterRecordSeqnoTimeWorker() {
|
||||
s = periodic_task_scheduler_.Register(
|
||||
PeriodicTaskType::kRecordSeqnoTime,
|
||||
periodic_task_functions_.at(PeriodicTaskType::kRecordSeqnoTime),
|
||||
seqno_time_cadence);
|
||||
seqno_time_cadence, /*run_immediately=*/true);
|
||||
}
|
||||
|
||||
return s;
|
||||
@@ -1365,7 +1378,7 @@ Status DBImpl::SetDBOptions(
|
||||
s = periodic_task_scheduler_.Register(
|
||||
PeriodicTaskType::kDumpStats,
|
||||
periodic_task_functions_.at(PeriodicTaskType::kDumpStats),
|
||||
new_options.stats_dump_period_sec);
|
||||
new_options.stats_dump_period_sec, /*run_immediately=*/true);
|
||||
}
|
||||
if (new_options.max_total_wal_size !=
|
||||
mutable_db_options_.max_total_wal_size) {
|
||||
@@ -1380,7 +1393,7 @@ Status DBImpl::SetDBOptions(
|
||||
s = periodic_task_scheduler_.Register(
|
||||
PeriodicTaskType::kPersistStats,
|
||||
periodic_task_functions_.at(PeriodicTaskType::kPersistStats),
|
||||
new_options.stats_persist_period_sec);
|
||||
new_options.stats_persist_period_sec, /*run_immediately=*/true);
|
||||
}
|
||||
}
|
||||
mutex_.Lock();
|
||||
@@ -5018,7 +5031,6 @@ void DBImpl::GetColumnFamilyMetaData(ColumnFamilyHandle* column_family,
|
||||
assert(column_family);
|
||||
auto* cfd =
|
||||
static_cast_with_check<ColumnFamilyHandleImpl>(column_family)->cfd();
|
||||
auto* sv = GetAndRefSuperVersion(cfd);
|
||||
{
|
||||
// Without mutex, Version::GetColumnFamilyMetaData will have data race
|
||||
// with Compaction::MarkFilesBeingCompacted. One solution is to use mutex,
|
||||
@@ -5030,9 +5042,8 @@ void DBImpl::GetColumnFamilyMetaData(ColumnFamilyHandle* column_family,
|
||||
// DB::GetColumnFamilyMetaData is not called frequently, the regression
|
||||
// should not be big. We still need to keep an eye on it.
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
sv->current->GetColumnFamilyMetaData(cf_meta);
|
||||
cfd->current()->GetColumnFamilyMetaData(cf_meta);
|
||||
}
|
||||
ReturnAndCleanupSuperVersion(cfd, sv);
|
||||
}
|
||||
|
||||
void DBImpl::GetAllColumnFamilyMetaData(
|
||||
@@ -6886,6 +6897,35 @@ void DBImpl::RecordSeqnoToTimeMapping() {
|
||||
sv_context.Clean();
|
||||
}
|
||||
|
||||
void DBImpl::TriggerPeriodicCompaction() {
|
||||
TEST_SYNC_POINT("DBImpl::TriggerPeriodicCompaction:StartRunning");
|
||||
{
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
ROCKS_LOG_INFO(immutable_db_options_.info_log,
|
||||
"Running the periodic task to trigger compactions.");
|
||||
|
||||
for (ColumnFamilyData* cfd : *versions_->GetColumnFamilySet()) {
|
||||
if (cfd->IsDropped()) {
|
||||
continue;
|
||||
}
|
||||
if (cfd->GetLatestCFOptions().periodic_compaction_seconds &&
|
||||
!cfd->queued_for_compaction()) {
|
||||
cfd->current()->storage_info()->ComputeCompactionScore(
|
||||
cfd->ioptions(), cfd->GetLatestMutableCFOptions());
|
||||
EnqueuePendingCompaction(cfd);
|
||||
if (cfd->queued_for_compaction()) {
|
||||
ROCKS_LOG_INFO(immutable_db_options_.info_log,
|
||||
"Periodic task to trigger compaction queued Column "
|
||||
"family [%s] for compaction.",
|
||||
cfd->GetName().c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
MaybeScheduleFlushOrCompaction();
|
||||
bg_cv_.SignalAll();
|
||||
}
|
||||
}
|
||||
|
||||
void DBImpl::TrackOrUntrackFiles(
|
||||
const std::vector<std::string>& existing_data_files, bool track) {
|
||||
auto sfm = static_cast_with_check<SstFileManagerImpl>(
|
||||
|
||||
+28
-12
@@ -1090,10 +1090,7 @@ class DBImpl : public DB {
|
||||
void SetSnapshotChecker(SnapshotChecker* snapshot_checker);
|
||||
|
||||
// Fill JobContext with snapshot information needed by flush and compaction.
|
||||
void GetSnapshotContext(JobContext* job_context,
|
||||
std::vector<SequenceNumber>* snapshot_seqs,
|
||||
SequenceNumber* earliest_write_conflict_snapshot,
|
||||
SnapshotChecker** snapshot_checker);
|
||||
void InitSnapshotContext(JobContext* job_context);
|
||||
|
||||
// Not thread-safe.
|
||||
void SetRecoverableStatePreReleaseCallback(PreReleaseCallback* callback);
|
||||
@@ -1289,6 +1286,12 @@ class DBImpl : public DB {
|
||||
// For the background timer job
|
||||
void RecordSeqnoToTimeMapping();
|
||||
|
||||
// Compactions rely on an event triggers like flush/compaction/SetOptions.
|
||||
// We need to trigger periodic compactions even when there is no such trigger.
|
||||
// This function checks and schedules available compactions and will run
|
||||
// periodically.
|
||||
void TriggerPeriodicCompaction();
|
||||
|
||||
// REQUIRES: DB mutex held
|
||||
std::pair<SequenceNumber, uint64_t> GetSeqnoToTimeSample() const;
|
||||
|
||||
@@ -1955,12 +1958,19 @@ class DBImpl : public DB {
|
||||
};
|
||||
struct PrepickedCompaction {
|
||||
// background compaction takes ownership of `compaction`.
|
||||
// TODO(hx235): consider using std::shared_ptr for easier ownership
|
||||
// management
|
||||
Compaction* compaction;
|
||||
// caller retains ownership of `manual_compaction_state` as it is reused
|
||||
// across background compactions.
|
||||
ManualCompactionState* manual_compaction_state; // nullptr if non-manual
|
||||
// task limiter token is requested during compaction picking.
|
||||
std::unique_ptr<TaskLimiterToken> task_token;
|
||||
// If true, `compaction` is picked temporarily to express compaction intent
|
||||
// and will be released before re-picking a real compaction based on the
|
||||
// updated LSM shape when thread associated with `compaction` is ready to
|
||||
// run
|
||||
bool need_repick;
|
||||
};
|
||||
|
||||
struct CompactionArg {
|
||||
@@ -2051,14 +2061,13 @@ class DBImpl : public DB {
|
||||
// Flush the in-memory write buffer to storage. Switches to a new
|
||||
// log-file/memtable and writes a new descriptor iff successful. Then
|
||||
// installs a new super version for the column family.
|
||||
Status FlushMemTableToOutputFile(
|
||||
ColumnFamilyData* cfd, const MutableCFOptions& mutable_cf_options,
|
||||
bool* madeProgress, JobContext* job_context, FlushReason flush_reason,
|
||||
SuperVersionContext* superversion_context,
|
||||
std::vector<SequenceNumber>& snapshot_seqs,
|
||||
SequenceNumber earliest_write_conflict_snapshot,
|
||||
SnapshotChecker* snapshot_checker, LogBuffer* log_buffer,
|
||||
Env::Priority thread_pri);
|
||||
Status FlushMemTableToOutputFile(ColumnFamilyData* cfd,
|
||||
const MutableCFOptions& mutable_cf_options,
|
||||
bool* madeProgress, JobContext* job_context,
|
||||
FlushReason flush_reason,
|
||||
SuperVersionContext* superversion_context,
|
||||
LogBuffer* log_buffer,
|
||||
Env::Priority thread_pri);
|
||||
|
||||
// Flush the memtables of (multiple) column families to multiple files on
|
||||
// persistent storage.
|
||||
@@ -2460,6 +2469,8 @@ class DBImpl : public DB {
|
||||
bool* flush_rescheduled_to_retain_udt,
|
||||
Env::Priority thread_pri);
|
||||
|
||||
Compaction* CreateIntendedCompactionForwardedToBottomPriorityPool(
|
||||
Compaction* c);
|
||||
bool EnoughRoomForCompaction(ColumnFamilyData* cfd,
|
||||
const std::vector<CompactionInputFiles>& inputs,
|
||||
bool* sfm_bookkeeping, LogBuffer* log_buffer);
|
||||
@@ -2727,6 +2738,11 @@ class DBImpl : public DB {
|
||||
const std::vector<ColumnFamilyHandle*>& column_families,
|
||||
ErrorIteratorFuncType error_iterator_func);
|
||||
|
||||
bool ShouldPickCompaction(bool is_prepicked,
|
||||
const PrepickedCompaction* prepicked_compaction);
|
||||
|
||||
void ResetBottomPriCompactionIntent(ColumnFamilyData* cfd,
|
||||
std::unique_ptr<Compaction>& c);
|
||||
// Lock over the persistent DB state. Non-nullptr iff successfully acquired.
|
||||
FileLock* db_lock_ = nullptr;
|
||||
|
||||
|
||||
@@ -145,10 +145,7 @@ IOStatus DBImpl::SyncClosedWals(const WriteOptions& write_options,
|
||||
Status DBImpl::FlushMemTableToOutputFile(
|
||||
ColumnFamilyData* cfd, const MutableCFOptions& mutable_cf_options,
|
||||
bool* made_progress, JobContext* job_context, FlushReason flush_reason,
|
||||
SuperVersionContext* superversion_context,
|
||||
std::vector<SequenceNumber>& snapshot_seqs,
|
||||
SequenceNumber earliest_write_conflict_snapshot,
|
||||
SnapshotChecker* snapshot_checker, LogBuffer* log_buffer,
|
||||
SuperVersionContext* superversion_context, LogBuffer* log_buffer,
|
||||
Env::Priority thread_pri) {
|
||||
mutex_.AssertHeld();
|
||||
assert(cfd);
|
||||
@@ -212,7 +209,6 @@ Status DBImpl::FlushMemTableToOutputFile(
|
||||
FlushJob flush_job(
|
||||
dbname_, cfd, immutable_db_options_, mutable_cf_options, max_memtable_id,
|
||||
file_options_for_compaction_, versions_.get(), &mutex_, &shutting_down_,
|
||||
snapshot_seqs, earliest_write_conflict_snapshot, snapshot_checker,
|
||||
job_context, flush_reason, log_buffer, directories_.GetDbDir(),
|
||||
GetDataDir(cfd, 0U),
|
||||
GetCompressionFlush(cfd->ioptions(), mutable_cf_options), stats_,
|
||||
@@ -397,11 +393,8 @@ Status DBImpl::FlushMemTablesToOutputFiles(
|
||||
bg_flush_args, made_progress, job_context, log_buffer, thread_pri);
|
||||
}
|
||||
assert(bg_flush_args.size() == 1);
|
||||
std::vector<SequenceNumber> snapshot_seqs;
|
||||
SequenceNumber earliest_write_conflict_snapshot;
|
||||
SnapshotChecker* snapshot_checker;
|
||||
GetSnapshotContext(job_context, &snapshot_seqs,
|
||||
&earliest_write_conflict_snapshot, &snapshot_checker);
|
||||
InitSnapshotContext(job_context);
|
||||
|
||||
const auto& bg_flush_arg = bg_flush_args[0];
|
||||
ColumnFamilyData* cfd = bg_flush_arg.cfd_;
|
||||
// intentional infrequent copy for each flush
|
||||
@@ -412,8 +405,7 @@ Status DBImpl::FlushMemTablesToOutputFiles(
|
||||
FlushReason flush_reason = bg_flush_arg.flush_reason_;
|
||||
Status s = FlushMemTableToOutputFile(
|
||||
cfd, mutable_cf_options_copy, made_progress, job_context, flush_reason,
|
||||
superversion_context, snapshot_seqs, earliest_write_conflict_snapshot,
|
||||
snapshot_checker, log_buffer, thread_pri);
|
||||
superversion_context, log_buffer, thread_pri);
|
||||
return s;
|
||||
}
|
||||
|
||||
@@ -448,12 +440,7 @@ Status DBImpl::AtomicFlushMemTablesToOutputFiles(
|
||||
}
|
||||
#endif /* !NDEBUG */
|
||||
|
||||
std::vector<SequenceNumber> snapshot_seqs;
|
||||
SequenceNumber earliest_write_conflict_snapshot;
|
||||
SnapshotChecker* snapshot_checker;
|
||||
GetSnapshotContext(job_context, &snapshot_seqs,
|
||||
&earliest_write_conflict_snapshot, &snapshot_checker);
|
||||
|
||||
InitSnapshotContext(job_context);
|
||||
autovector<FSDirectory*> distinct_output_dirs;
|
||||
autovector<std::string> distinct_output_dir_paths;
|
||||
std::vector<std::unique_ptr<FlushJob>> jobs;
|
||||
@@ -487,8 +474,7 @@ Status DBImpl::AtomicFlushMemTablesToOutputFiles(
|
||||
jobs.emplace_back(new FlushJob(
|
||||
dbname_, cfd, immutable_db_options_, mutable_cf_options,
|
||||
max_memtable_id, file_options_for_compaction_, versions_.get(), &mutex_,
|
||||
&shutting_down_, snapshot_seqs, earliest_write_conflict_snapshot,
|
||||
snapshot_checker, job_context, flush_reason, log_buffer,
|
||||
&shutting_down_, job_context, flush_reason, log_buffer,
|
||||
directories_.GetDbDir(), data_dir,
|
||||
GetCompressionFlush(cfd->ioptions(), mutable_cf_options), stats_,
|
||||
&event_logger_, mutable_cf_options.report_bg_io_stats,
|
||||
@@ -1395,6 +1381,9 @@ Status DBImpl::CompactFiles(const CompactionOptions& compact_options,
|
||||
TEST_SYNC_POINT_CALLBACK("TestCompactFiles:PausingManualCompaction:3",
|
||||
static_cast<void*>(const_cast<std::atomic<int>*>(
|
||||
&manual_compaction_paused_)));
|
||||
TEST_SYNC_POINT_CALLBACK("TestCancelCompactFiles:SuccessfulCompaction",
|
||||
static_cast<void*>(const_cast<std::atomic<int>*>(
|
||||
&manual_compaction_paused_)));
|
||||
{
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
auto* current = cfd->current();
|
||||
@@ -1447,7 +1436,12 @@ Status DBImpl::CompactFilesImpl(
|
||||
if (shutting_down_.load(std::memory_order_acquire)) {
|
||||
return Status::ShutdownInProgress();
|
||||
}
|
||||
if (manual_compaction_paused_.load(std::memory_order_acquire) > 0) {
|
||||
|
||||
// triggered by DisableManualCompactions or by user-set canceled flag in
|
||||
// CompactionOptions
|
||||
if (manual_compaction_paused_.load(std::memory_order_acquire) > 0 ||
|
||||
(compact_options.canceled &&
|
||||
compact_options.canceled->load(std::memory_order_acquire))) {
|
||||
return Status::Incomplete(Status::SubCode::kManualCompactionPaused);
|
||||
}
|
||||
|
||||
@@ -1518,11 +1512,7 @@ Status DBImpl::CompactFilesImpl(
|
||||
// deletion compaction currently not allowed in CompactFiles.
|
||||
assert(!c->deletion_compaction());
|
||||
|
||||
std::vector<SequenceNumber> snapshot_seqs;
|
||||
SequenceNumber earliest_write_conflict_snapshot;
|
||||
SnapshotChecker* snapshot_checker;
|
||||
GetSnapshotContext(job_context, &snapshot_seqs,
|
||||
&earliest_write_conflict_snapshot, &snapshot_checker);
|
||||
InitSnapshotContext(job_context);
|
||||
|
||||
std::unique_ptr<std::list<uint64_t>::iterator> pending_outputs_inserted_elem(
|
||||
new std::list<uint64_t>::iterator(
|
||||
@@ -1536,7 +1526,6 @@ Status DBImpl::CompactFilesImpl(
|
||||
log_buffer, directories_.GetDbDir(),
|
||||
GetDataDir(c->column_family_data(), c->output_path_id()),
|
||||
GetDataDir(c->column_family_data(), 0), stats_, &mutex_, &error_handler_,
|
||||
snapshot_seqs, earliest_write_conflict_snapshot, snapshot_checker,
|
||||
job_context, table_cache_, &event_logger_,
|
||||
c->mutable_cf_options().paranoid_file_checks,
|
||||
c->mutable_cf_options().report_bg_io_stats, dbname_,
|
||||
@@ -2189,6 +2178,7 @@ Status DBImpl::RunManualCompaction(
|
||||
// Don't throttle manual compaction, only count outstanding tasks.
|
||||
assert(false);
|
||||
}
|
||||
ca->prepicked_compaction->need_repick = false;
|
||||
manual.incomplete = false;
|
||||
if (compaction->bottommost_level() &&
|
||||
env_->GetBackgroundThreads(Env::Priority::BOTTOM) > 0) {
|
||||
@@ -2718,6 +2708,10 @@ Status DBImpl::WaitUntilFlushWouldNotStallWrites(ColumnFamilyData* cfd,
|
||||
// Finish waiting when ALL column families finish flushing memtables.
|
||||
// resuming_from_bg_err indicates whether the caller is trying to resume from
|
||||
// background error or in normal processing.
|
||||
// Note that the wait finishes when the flush result is installed to column
|
||||
// families' Versions and persisted in MANIFEST. It doesn't wait until
|
||||
// SuperVersion to reflect the flush result, except for the case when
|
||||
// flush_reason is `kExternalFileIngestion`.
|
||||
Status DBImpl::WaitForFlushMemTables(
|
||||
const autovector<ColumnFamilyData*>& cfds,
|
||||
const autovector<const uint64_t*>& flush_memtable_ids,
|
||||
@@ -3411,6 +3405,10 @@ void DBImpl::BackgroundCallCompaction(PrepickedCompaction* prepicked_compaction,
|
||||
bool made_progress = false;
|
||||
JobContext job_context(next_job_id_.fetch_add(1), true);
|
||||
TEST_SYNC_POINT("BackgroundCallCompaction:0");
|
||||
if (bg_thread_pri == Env::Priority::BOTTOM) {
|
||||
TEST_SYNC_POINT("BackgroundCallCompaction:0:BottomPri");
|
||||
}
|
||||
|
||||
LogBuffer log_buffer(InfoLogLevel::INFO_LEVEL,
|
||||
immutable_db_options_.info_log.get());
|
||||
{
|
||||
@@ -3646,34 +3644,54 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
|
||||
: m->manual_end->DebugString(true).c_str()));
|
||||
}
|
||||
}
|
||||
} else if (!is_prepicked && !compaction_queue_.empty()) {
|
||||
} else if (ShouldPickCompaction(is_prepicked, prepicked_compaction)) {
|
||||
bool need_repick = is_prepicked && prepicked_compaction->need_repick;
|
||||
if (HasExclusiveManualCompaction()) {
|
||||
// Can't compact right now, but try again later
|
||||
TEST_SYNC_POINT("DBImpl::BackgroundCompaction()::Conflict");
|
||||
|
||||
// Stay in the compaction queue.
|
||||
unscheduled_compactions_++;
|
||||
// TODO(hx235): Resolve conflict between intended
|
||||
// bottom-priority compaction (requiring repick, i.e., need_repick = true)
|
||||
// and exclusive manual compaction by releasing the intended
|
||||
// bottom-priority compaction.
|
||||
if (!need_repick) {
|
||||
// Can't compact right now, but try again later
|
||||
//
|
||||
// Increase `unscheduled_compactions_` directly so we
|
||||
// don't need to
|
||||
// dequeue and enqueue the CFD again in the compaction queue and thus
|
||||
// keep the CFD's position in the queue
|
||||
unscheduled_compactions_++;
|
||||
|
||||
return Status::OK();
|
||||
return Status::OK();
|
||||
}
|
||||
}
|
||||
|
||||
auto cfd = PickCompactionFromQueue(&task_token, log_buffer);
|
||||
if (cfd == nullptr) {
|
||||
// Can't find any executable task from the compaction queue.
|
||||
// All tasks have been throttled by compaction thread limiter.
|
||||
++unscheduled_compactions_;
|
||||
return Status::Busy();
|
||||
}
|
||||
ColumnFamilyData* cfd = nullptr;
|
||||
|
||||
// We unreference here because the following code will take a Ref() on
|
||||
// this cfd if it is going to use it (Compaction class holds a
|
||||
// reference).
|
||||
// This will all happen under a mutex so we don't have to be afraid of
|
||||
// somebody else deleting it.
|
||||
if (cfd->UnrefAndTryDelete()) {
|
||||
// This was the last reference of the column family, so no need to
|
||||
// compact.
|
||||
return Status::OK();
|
||||
if (!need_repick) {
|
||||
cfd = PickCompactionFromQueue(&task_token, log_buffer);
|
||||
if (cfd == nullptr) {
|
||||
// Can't find any executable task from the compaction queue.
|
||||
// All tasks have been throttled by compaction thread limiter.
|
||||
++unscheduled_compactions_;
|
||||
return Status::Busy();
|
||||
}
|
||||
|
||||
// We unreference here because the following code will take a Ref() on
|
||||
// this cfd if it is going to use it (Compaction class holds a
|
||||
// reference).
|
||||
// This will all happen under a mutex so we don't have to be afraid of
|
||||
// somebody else deleting it.
|
||||
if (cfd->UnrefAndTryDelete()) {
|
||||
// This was the last reference of the column family, so no need to
|
||||
// compact.
|
||||
return Status::OK();
|
||||
}
|
||||
} else {
|
||||
cfd = c->column_family_data();
|
||||
assert(cfd);
|
||||
ResetBottomPriCompactionIntent(cfd, c);
|
||||
assert(c == nullptr);
|
||||
}
|
||||
|
||||
// Pick up latest mutable CF Options and use it throughout the
|
||||
@@ -3687,21 +3705,24 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
|
||||
// compaction is not necessary. Need to make sure mutex is held
|
||||
// until we make a copy in the following code
|
||||
TEST_SYNC_POINT("DBImpl::BackgroundCompaction():BeforePickCompaction");
|
||||
SnapshotChecker* snapshot_checker = nullptr;
|
||||
std::vector<SequenceNumber> snapshot_seqs;
|
||||
// This info is not useful for other scenarios, so save querying existing
|
||||
// snapshots for those cases.
|
||||
if (cfd->ioptions().compaction_style == kCompactionStyleUniversal &&
|
||||
cfd->user_comparator()->timestamp_size() == 0) {
|
||||
SequenceNumber earliest_write_conflict_snapshot;
|
||||
GetSnapshotContext(job_context, &snapshot_seqs,
|
||||
&earliest_write_conflict_snapshot,
|
||||
&snapshot_checker);
|
||||
InitSnapshotContext(job_context);
|
||||
assert(is_snapshot_supported_ || snapshots_.empty());
|
||||
}
|
||||
c.reset(cfd->PickCompaction(mutable_cf_options, mutable_db_options_,
|
||||
snapshot_seqs, snapshot_checker, log_buffer));
|
||||
TEST_SYNC_POINT("DBImpl::BackgroundCompaction():AfterPickCompaction");
|
||||
c.reset(cfd->PickCompaction(
|
||||
mutable_cf_options, mutable_db_options_, job_context->snapshot_seqs,
|
||||
job_context->snapshot_checker, log_buffer,
|
||||
thread_pri == Env::Priority::BOTTOM /* require_max_output_level */));
|
||||
if (thread_pri == Env::Priority::LOW) {
|
||||
TEST_SYNC_POINT("DBImpl::BackgroundCompaction():AfterPickCompaction");
|
||||
} else if (thread_pri == Env::Priority::BOTTOM) {
|
||||
TEST_SYNC_POINT_CALLBACK(
|
||||
"DBImpl::BackgroundCompaction():AfterPickCompactionBottomPri",
|
||||
c.get());
|
||||
}
|
||||
|
||||
if (c != nullptr) {
|
||||
bool enough_room = EnoughRoomForCompaction(
|
||||
@@ -3715,7 +3736,7 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
|
||||
->storage_info()
|
||||
->ComputeCompactionScore(c->immutable_options(),
|
||||
c->mutable_cf_options());
|
||||
AddToCompactionQueue(cfd);
|
||||
EnqueuePendingCompaction(cfd);
|
||||
|
||||
c.reset();
|
||||
// Don't need to sleep here, because BackgroundCallCompaction
|
||||
@@ -3737,16 +3758,21 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
|
||||
// options take effect.
|
||||
// 3) When we Pick a new compaction, we "remove" those files being
|
||||
// compacted from the calculation, which then influences compaction
|
||||
// score. Here we check if we need the new compaction even without the
|
||||
// files that are currently being compacted. If we need another
|
||||
// compaction, we might be able to execute it in parallel, so we add
|
||||
// it to the queue and schedule a new thread.
|
||||
if (cfd->NeedsCompaction()) {
|
||||
// Yes, we need more compactions!
|
||||
AddToCompactionQueue(cfd);
|
||||
MaybeScheduleFlushOrCompaction();
|
||||
}
|
||||
// score. Inside EnqueuePendingCompaction(), we check if we need
|
||||
// the new compaction even without the files that are currently being
|
||||
// compacted. If we need another compaction, we might be able to
|
||||
// execute it in parallel, so we add it to the queue and schedule a
|
||||
// new thread.
|
||||
EnqueuePendingCompaction(cfd);
|
||||
MaybeScheduleFlushOrCompaction();
|
||||
}
|
||||
} else if (is_prepicked) {
|
||||
ROCKS_LOG_BUFFER(
|
||||
log_buffer,
|
||||
"[%s] Pre-picked compaction repicked files for compaction as "
|
||||
"required, "
|
||||
"but upon re-evaluation, no compaction was found necessary \n",
|
||||
cfd->GetName().c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4123,14 +4149,16 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
|
||||
ThreadStatusUtil::ResetThreadStatus();
|
||||
TEST_SYNC_POINT_CALLBACK("DBImpl::BackgroundCompaction:AfterCompaction",
|
||||
c->column_family_data());
|
||||
} else if (!is_prepicked && c->output_level() > 0 &&
|
||||
c->output_level() ==
|
||||
} else if (!is_prepicked &&
|
||||
Compaction::OutputToNonZeroMaxOutputLevel(
|
||||
c->output_level(),
|
||||
c->column_family_data()
|
||||
->current()
|
||||
->storage_info()
|
||||
->MaxOutputLevel(
|
||||
immutable_db_options_.allow_ingest_behind) &&
|
||||
immutable_db_options_.allow_ingest_behind)) &&
|
||||
env_->GetBackgroundThreads(Env::Priority::BOTTOM) > 0) {
|
||||
assert(thread_pri == Env::Priority::LOW);
|
||||
// Forward compactions involving last level to the bottom pool if it exists,
|
||||
// such that compactions unlikely to contribute to write stalls can be
|
||||
// delayed or deprioritized.
|
||||
@@ -4139,7 +4167,23 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
|
||||
ca->db = this;
|
||||
ca->compaction_pri_ = Env::Priority::BOTTOM;
|
||||
ca->prepicked_compaction = new PrepickedCompaction;
|
||||
ca->prepicked_compaction->compaction = c.release();
|
||||
|
||||
// If `universal_reduce_file_locking` is true, we only lock a limited set of
|
||||
// input files by creating an intended compaction to forward to bottom
|
||||
// priority pool and repicking files when bottom priority thread
|
||||
// gets to execute this intended compaction
|
||||
const bool need_repick =
|
||||
c->mutable_cf_options()
|
||||
.compaction_options_universal.reduce_file_locking;
|
||||
if (need_repick) {
|
||||
ca->prepicked_compaction->compaction =
|
||||
CreateIntendedCompactionForwardedToBottomPriorityPool(c.get());
|
||||
c.reset();
|
||||
ca->prepicked_compaction->need_repick = true;
|
||||
} else {
|
||||
ca->prepicked_compaction->compaction = c.release();
|
||||
ca->prepicked_compaction->need_repick = false;
|
||||
}
|
||||
ca->prepicked_compaction->manual_compaction_state = nullptr;
|
||||
// Transfer requested token, so it doesn't need to do it again.
|
||||
ca->prepicked_compaction->task_token = std::move(task_token);
|
||||
@@ -4154,11 +4198,7 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
|
||||
output_level = c->output_level();
|
||||
TEST_SYNC_POINT_CALLBACK("DBImpl::BackgroundCompaction:NonTrivial",
|
||||
&output_level);
|
||||
std::vector<SequenceNumber> snapshot_seqs;
|
||||
SequenceNumber earliest_write_conflict_snapshot;
|
||||
SnapshotChecker* snapshot_checker;
|
||||
GetSnapshotContext(job_context, &snapshot_seqs,
|
||||
&earliest_write_conflict_snapshot, &snapshot_checker);
|
||||
InitSnapshotContext(job_context);
|
||||
assert(is_snapshot_supported_ || snapshots_.empty());
|
||||
|
||||
CompactionJob compaction_job(
|
||||
@@ -4167,8 +4207,7 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
|
||||
&shutting_down_, log_buffer, directories_.GetDbDir(),
|
||||
GetDataDir(c->column_family_data(), c->output_path_id()),
|
||||
GetDataDir(c->column_family_data(), 0), stats_, &mutex_,
|
||||
&error_handler_, snapshot_seqs, earliest_write_conflict_snapshot,
|
||||
snapshot_checker, job_context, table_cache_, &event_logger_,
|
||||
&error_handler_, job_context, table_cache_, &event_logger_,
|
||||
c->mutable_cf_options().paranoid_file_checks,
|
||||
c->mutable_cf_options().report_bg_io_stats, dbname_,
|
||||
&compaction_job_stats, thread_pri, io_tracer_,
|
||||
@@ -4188,8 +4227,15 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
|
||||
NotifyOnCompactionBegin(c->column_family_data(), c.get(), status,
|
||||
compaction_job_stats, job_context->job_id);
|
||||
mutex_.Unlock();
|
||||
TEST_SYNC_POINT_CALLBACK(
|
||||
"DBImpl::BackgroundCompaction:NonTrivial:BeforeRun", nullptr);
|
||||
if (thread_pri == Env::Priority::LOW) {
|
||||
TEST_SYNC_POINT_CALLBACK(
|
||||
"DBImpl::BackgroundCompaction:NonTrivial:BeforeRun", nullptr);
|
||||
} else {
|
||||
assert(thread_pri == Env::Priority::BOTTOM);
|
||||
TEST_SYNC_POINT(
|
||||
"DBImpl::BackgroundCompaction:NonTrivial:BeforeRunBottomPri");
|
||||
}
|
||||
|
||||
// Should handle error?
|
||||
compaction_job.Run().PermitUncheckedError();
|
||||
TEST_SYNC_POINT("DBImpl::BackgroundCompaction:NonTrivial:AfterRun");
|
||||
@@ -4283,9 +4329,7 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
|
||||
->storage_info()
|
||||
->ComputeCompactionScore(c->immutable_options(),
|
||||
c->mutable_cf_options());
|
||||
if (!cfd->queued_for_compaction()) {
|
||||
AddToCompactionQueue(cfd);
|
||||
}
|
||||
EnqueuePendingCompaction(cfd);
|
||||
}
|
||||
}
|
||||
// this will unref its input_version and column_family_data
|
||||
@@ -4330,6 +4374,71 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
|
||||
return status;
|
||||
}
|
||||
|
||||
// Create an intended compaction to forward based on the original picked
|
||||
// compaction. It serves two purposes while it is waiting
|
||||
// for a bottom-priority thread becomes available to run:
|
||||
// - Prevent the last input file (or sorted run if non-L0) from
|
||||
// being included in compaction score calculations unnecessarily since the
|
||||
// intended compaction is already scheduled to compact it
|
||||
// - Allow other input files to be picked by low-priority compactions that can
|
||||
// run right away
|
||||
//
|
||||
// Once a bottom-priority available to run this intended compaction, it will
|
||||
// repick files to consider the LSM updates that occurred during the waiting
|
||||
// period.
|
||||
Compaction* DBImpl::CreateIntendedCompactionForwardedToBottomPriorityPool(
|
||||
Compaction* c) {
|
||||
auto* cfd = c->column_family_data();
|
||||
const auto& io = c->immutable_options();
|
||||
const auto& mo = c->mutable_cf_options();
|
||||
auto* vstorage = c->input_version()->storage_info();
|
||||
|
||||
std::vector<CompactionInputFiles> inputs(1);
|
||||
|
||||
const std::vector<FileMetaData*>* max_intput_level_files = nullptr;
|
||||
int max_intput_level = 0;
|
||||
|
||||
for (size_t i = c->num_input_levels(); i >= 1; --i) {
|
||||
size_t level = i - 1;
|
||||
if (c->num_input_files(level) > 0) {
|
||||
max_intput_level = static_cast<int>(level);
|
||||
max_intput_level_files = c->inputs(level);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
assert(max_intput_level_files);
|
||||
assert(!max_intput_level_files->empty());
|
||||
inputs[0].level = max_intput_level;
|
||||
|
||||
if (max_intput_level == 0) {
|
||||
// The last input file
|
||||
inputs[0].files.push_back(
|
||||
(*max_intput_level_files)[max_intput_level_files->size() - 1]);
|
||||
} else {
|
||||
// The last input sorted run
|
||||
for (FileMetaData* f : (*max_intput_level_files)) {
|
||||
inputs[0].files.push_back(f);
|
||||
}
|
||||
}
|
||||
|
||||
c->ReleaseCompactionFiles(Status::OK());
|
||||
|
||||
Compaction* intended_compaction = new Compaction(
|
||||
vstorage, io, mo, mutable_db_options_, std::move(inputs),
|
||||
c->output_level(), c->target_output_file_size(),
|
||||
c->max_compaction_bytes(), c->output_path_id(), c->output_compression(),
|
||||
c->output_compression_opts(), c->output_temperature(),
|
||||
c->max_subcompactions(), c->grandparents(),
|
||||
std::nullopt /* earliest_snapshot */, nullptr /* snapshot_checker */);
|
||||
|
||||
cfd->compaction_picker()->RegisterCompaction(intended_compaction);
|
||||
vstorage->ComputeCompactionScore(io, mo);
|
||||
intended_compaction->FinalizeInputInfo(cfd->current());
|
||||
|
||||
return intended_compaction;
|
||||
}
|
||||
|
||||
bool DBImpl::HasPendingManualCompaction() {
|
||||
return (!manual_compaction_dequeue_.empty());
|
||||
}
|
||||
@@ -4601,31 +4710,33 @@ void DBImpl::SetSnapshotChecker(SnapshotChecker* snapshot_checker) {
|
||||
snapshot_checker_.reset(snapshot_checker);
|
||||
}
|
||||
|
||||
void DBImpl::GetSnapshotContext(
|
||||
JobContext* job_context, std::vector<SequenceNumber>* snapshot_seqs,
|
||||
SequenceNumber* earliest_write_conflict_snapshot,
|
||||
SnapshotChecker** snapshot_checker_ptr) {
|
||||
void DBImpl::InitSnapshotContext(JobContext* job_context) {
|
||||
mutex_.AssertHeld();
|
||||
assert(job_context != nullptr);
|
||||
assert(snapshot_seqs != nullptr);
|
||||
assert(earliest_write_conflict_snapshot != nullptr);
|
||||
assert(snapshot_checker_ptr != nullptr);
|
||||
|
||||
*snapshot_checker_ptr = snapshot_checker_.get();
|
||||
if (use_custom_gc_ && *snapshot_checker_ptr == nullptr) {
|
||||
*snapshot_checker_ptr = DisableGCSnapshotChecker::Instance();
|
||||
if (job_context->snapshot_context_initialized) {
|
||||
return;
|
||||
}
|
||||
if (*snapshot_checker_ptr != nullptr) {
|
||||
SnapshotChecker* snapshot_checker = snapshot_checker_.get();
|
||||
if (use_custom_gc_ && !snapshot_checker) {
|
||||
snapshot_checker = DisableGCSnapshotChecker::Instance();
|
||||
}
|
||||
std::unique_ptr<ManagedSnapshot> managed_snapshot = nullptr;
|
||||
if (snapshot_checker) {
|
||||
// If snapshot_checker is used, that means the flush/compaction may
|
||||
// contain values not visible to snapshot taken after
|
||||
// flush/compaction job starts. Take a snapshot and it will appear
|
||||
// in snapshot_seqs and force compaction iterator to consider such
|
||||
// snapshots.
|
||||
const Snapshot* job_snapshot =
|
||||
GetSnapshotImpl(false /*write_conflict_boundary*/, false /*lock*/);
|
||||
job_context->job_snapshot.reset(new ManagedSnapshot(this, job_snapshot));
|
||||
const Snapshot* snapshot =
|
||||
GetSnapshotImpl(/*is_write_conflict_boundary=*/false, /*lock=*/false);
|
||||
managed_snapshot.reset(new ManagedSnapshot(this, snapshot));
|
||||
}
|
||||
*snapshot_seqs = snapshots_.GetAll(earliest_write_conflict_snapshot);
|
||||
SequenceNumber earliest_write_conflict_snapshot = kMaxSequenceNumber;
|
||||
std::vector<SequenceNumber> snapshot_seqs =
|
||||
snapshots_.GetAll(&earliest_write_conflict_snapshot);
|
||||
job_context->InitSnapshotContext(
|
||||
snapshot_checker, std::move(managed_snapshot),
|
||||
earliest_write_conflict_snapshot, std::move(snapshot_seqs));
|
||||
}
|
||||
|
||||
Status DBImpl::WaitForCompact(
|
||||
@@ -4684,4 +4795,18 @@ Status DBImpl::WaitForCompact(
|
||||
}
|
||||
}
|
||||
|
||||
bool DBImpl::ShouldPickCompaction(
|
||||
bool is_prepicked, const PrepickedCompaction* prepicked_compaction) {
|
||||
return (!is_prepicked && !compaction_queue_.empty()) ||
|
||||
(is_prepicked && prepicked_compaction->need_repick);
|
||||
}
|
||||
|
||||
void DBImpl::ResetBottomPriCompactionIntent(ColumnFamilyData* cfd,
|
||||
std::unique_ptr<Compaction>& c) {
|
||||
c->ReleaseCompactionFiles(Status::OK());
|
||||
cfd->current()->storage_info()->ComputeCompactionScore(
|
||||
c->immutable_options(), c->mutable_cf_options());
|
||||
c.reset();
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
@@ -933,7 +933,10 @@ Status DBImplSecondary::CompactWithoutInstallation(
|
||||
immutable_db_options_.info_log.get());
|
||||
|
||||
const int job_id = next_job_id_.fetch_add(1);
|
||||
|
||||
JobContext job_context(0, true /*create_superversion*/);
|
||||
std::vector<SequenceNumber> snapshots = input.snapshots;
|
||||
job_context.InitSnapshotContext(nullptr, nullptr, kMaxSequenceNumber,
|
||||
std::move(snapshots));
|
||||
// use primary host's db_id for running the compaction, but db_session_id is
|
||||
// using the local one, which is to make sure the unique id is unique from
|
||||
// the remote compactors. Because the id is generated from db_id,
|
||||
@@ -944,7 +947,7 @@ Status DBImplSecondary::CompactWithoutInstallation(
|
||||
job_id, c.get(), immutable_db_options_, mutable_db_options_,
|
||||
file_options_for_compaction_, versions_.get(), &shutting_down_,
|
||||
&log_buffer, output_dir.get(), stats_, &mutex_, &error_handler_,
|
||||
input.snapshots, table_cache_, &event_logger_, dbname_, io_tracer_,
|
||||
&job_context, table_cache_, &event_logger_, dbname_, io_tracer_,
|
||||
options.canceled ? *options.canceled : kManualCompactionCanceledFalse_,
|
||||
input.db_id, db_session_id_, secondary_path_, input, result);
|
||||
|
||||
|
||||
@@ -856,8 +856,7 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options,
|
||||
write_group, current_sequence, column_family_memtables_.get(),
|
||||
&flush_scheduler_, &trim_history_scheduler_,
|
||||
write_options.ignore_missing_column_families,
|
||||
0 /*recovery_log_number*/, this, parallel, seq_per_batch_,
|
||||
batch_per_txn_);
|
||||
0 /*recovery_log_number*/, this, seq_per_batch_, batch_per_txn_);
|
||||
} else {
|
||||
write_group.last_sequence = last_sequence;
|
||||
write_thread_.LaunchParallelMemTableWriters(&write_group);
|
||||
@@ -1115,7 +1114,7 @@ Status DBImpl::PipelinedWriteImpl(const WriteOptions& write_options,
|
||||
memtable_write_group, w.sequence, column_family_memtables_.get(),
|
||||
&flush_scheduler_, &trim_history_scheduler_,
|
||||
write_options.ignore_missing_column_families, 0 /*log_number*/, this,
|
||||
false /*concurrent_memtable_writes*/, seq_per_batch_, batch_per_txn_);
|
||||
seq_per_batch_, batch_per_txn_);
|
||||
if (memtable_write_group.status
|
||||
.ok()) { // Don't publish a partial batch write
|
||||
versions_->SetLastSequence(memtable_write_group.last_sequence);
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
|
||||
#include <iomanip>
|
||||
|
||||
#include "db/db_test_util.h"
|
||||
#include "port/stack_trace.h"
|
||||
#include "test_util/testutil.h"
|
||||
|
||||
+32
-22
@@ -66,11 +66,11 @@ DBIter::DBIter(Env* _env, const ReadOptions& read_options,
|
||||
timestamp_lb_(read_options.iter_start_ts),
|
||||
timestamp_size_(timestamp_ub_ ? timestamp_ub_->size() : 0),
|
||||
active_mem_(active_mem),
|
||||
memtable_seqno_lb_((active_mem_ && !active_mem_->IsEmpty())
|
||||
? active_mem_->GetFirstSequenceNumber()
|
||||
: kMaxSequenceNumber),
|
||||
memtable_op_scan_flush_trigger_(
|
||||
mutable_cf_options.memtable_op_scan_flush_trigger),
|
||||
memtable_seqno_lb_(kMaxSequenceNumber),
|
||||
memtable_op_scan_flush_trigger_(0),
|
||||
avg_op_scan_flush_trigger_(0),
|
||||
iter_step_since_seek_(1),
|
||||
mem_hidden_op_scanned_since_seek_(0),
|
||||
direction_(kForward),
|
||||
valid_(false),
|
||||
current_entry_is_merged_(false),
|
||||
@@ -98,6 +98,25 @@ DBIter::DBIter(Env* _env, const ReadOptions& read_options,
|
||||
// prefix_seek_opt_in_only should force total_order_seek whereever the caller
|
||||
// is duplicating the original ReadOptions
|
||||
assert(!ioptions.prefix_seek_opt_in_only || read_options.total_order_seek);
|
||||
if (active_mem_) {
|
||||
// FIXME: GetEarliestSequenceNumber() may return a seqno that is one smaller
|
||||
// than the smallest seqno in the memtable. This violates its comment and
|
||||
// entries with that seqno may not be in the active memtable. Before it's
|
||||
// fixed, we use GetFirstSequenceNumber() for more accurate result.
|
||||
memtable_seqno_lb_ = active_mem_->IsEmpty()
|
||||
? active_mem_->GetEarliestSequenceNumber()
|
||||
: active_mem_->GetFirstSequenceNumber();
|
||||
memtable_op_scan_flush_trigger_ =
|
||||
mutable_cf_options.memtable_op_scan_flush_trigger;
|
||||
if (memtable_op_scan_flush_trigger_) {
|
||||
// avg_op_scan_flush_trigger_ requires memtable_op_scan_flush_trigger_ > 0
|
||||
avg_op_scan_flush_trigger_ =
|
||||
mutable_cf_options.memtable_avg_op_scan_flush_trigger;
|
||||
}
|
||||
} else {
|
||||
// memtable_op_scan_flush_trigger_ and avg_op_scan_flush_trigger_ are
|
||||
// initialized to 0(disabled) as default.
|
||||
}
|
||||
}
|
||||
|
||||
Status DBIter::GetProperty(std::string prop_name, std::string* prop) {
|
||||
@@ -159,6 +178,7 @@ void DBIter::Next() {
|
||||
local_stats_.skip_count_ += num_internal_keys_skipped_;
|
||||
local_stats_.skip_count_--;
|
||||
num_internal_keys_skipped_ = 0;
|
||||
iter_step_since_seek_++;
|
||||
bool ok = true;
|
||||
if (direction_ == kReverse) {
|
||||
is_key_seqnum_zero_ = false;
|
||||
@@ -373,8 +393,7 @@ bool DBIter::FindNextUserEntryInternal(bool skipping_saved_key,
|
||||
// to one.
|
||||
bool reseek_done = false;
|
||||
|
||||
uint64_t mem_ops_scanned = 0;
|
||||
bool marked_for_flush = false;
|
||||
uint64_t mem_hidden_op_scanned = 0;
|
||||
do {
|
||||
// Will update is_key_seqnum_zero_ as soon as we parsed the current key
|
||||
// but we need to save the previous value to be used in the loop.
|
||||
@@ -431,12 +450,7 @@ bool DBIter::FindNextUserEntryInternal(bool skipping_saved_key,
|
||||
CompareKeyForSkip(ikey_.user_key, saved_key_.GetUserKey()) <= 0) {
|
||||
num_skipped++; // skip this entry
|
||||
PERF_COUNTER_ADD(internal_key_skipped_count, 1);
|
||||
if (memtable_op_scan_flush_trigger_ && active_mem_ &&
|
||||
ikey_.sequence >= memtable_seqno_lb_ && !marked_for_flush &&
|
||||
++mem_ops_scanned >= memtable_op_scan_flush_trigger_) {
|
||||
active_mem_->MarkForFlush();
|
||||
marked_for_flush = true;
|
||||
}
|
||||
MarkMemtableForFlushForPerOpTrigger(mem_hidden_op_scanned);
|
||||
} else {
|
||||
assert(!skipping_saved_key ||
|
||||
CompareKeyForSkip(ikey_.user_key, saved_key_.GetUserKey()) > 0);
|
||||
@@ -458,12 +472,7 @@ bool DBIter::FindNextUserEntryInternal(bool skipping_saved_key,
|
||||
!iter_.iter()->IsKeyPinned() /* copy */);
|
||||
skipping_saved_key = true;
|
||||
PERF_COUNTER_ADD(internal_delete_skipped_count, 1);
|
||||
if (memtable_op_scan_flush_trigger_ && active_mem_ &&
|
||||
ikey_.sequence >= memtable_seqno_lb_ && !marked_for_flush &&
|
||||
++mem_ops_scanned >= memtable_op_scan_flush_trigger_) {
|
||||
active_mem_->MarkForFlush();
|
||||
marked_for_flush = true;
|
||||
}
|
||||
MarkMemtableForFlushForPerOpTrigger(mem_hidden_op_scanned);
|
||||
}
|
||||
break;
|
||||
case kTypeValue:
|
||||
@@ -502,7 +511,6 @@ bool DBIter::FindNextUserEntryInternal(bool skipping_saved_key,
|
||||
|
||||
valid_ = true;
|
||||
return true;
|
||||
break;
|
||||
case kTypeMerge:
|
||||
if (!PrepareValueInternal()) {
|
||||
return false;
|
||||
@@ -514,7 +522,6 @@ bool DBIter::FindNextUserEntryInternal(bool skipping_saved_key,
|
||||
current_entry_is_merged_ = true;
|
||||
valid_ = true;
|
||||
return MergeValuesNewToOld(); // Go to a different state machine
|
||||
break;
|
||||
default:
|
||||
valid_ = false;
|
||||
status_ = Status::Corruption(
|
||||
@@ -1115,7 +1122,6 @@ bool DBIter::FindValueForCurrentKey() {
|
||||
}
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
case kTypeValue:
|
||||
case kTypeValuePreferredSeqno:
|
||||
SetValueAndColumnsFromPlain(pinned_value_);
|
||||
@@ -1588,6 +1594,7 @@ void DBIter::Seek(const Slice& target) {
|
||||
ResetBlobData();
|
||||
ResetValueAndColumns();
|
||||
ResetInternalKeysSkippedCounter();
|
||||
MarkMemtableForFlushForAvgTrigger();
|
||||
|
||||
// Seek the inner iterator based on the target key.
|
||||
{
|
||||
@@ -1664,6 +1671,7 @@ void DBIter::SeekForPrev(const Slice& target) {
|
||||
ResetBlobData();
|
||||
ResetValueAndColumns();
|
||||
ResetInternalKeysSkippedCounter();
|
||||
MarkMemtableForFlushForAvgTrigger();
|
||||
|
||||
// Seek the inner iterator based on the target key.
|
||||
{
|
||||
@@ -1725,6 +1733,7 @@ void DBIter::SeekToFirst() {
|
||||
ResetBlobData();
|
||||
ResetValueAndColumns();
|
||||
ResetInternalKeysSkippedCounter();
|
||||
MarkMemtableForFlushForAvgTrigger();
|
||||
ClearSavedValue();
|
||||
is_key_seqnum_zero_ = false;
|
||||
|
||||
@@ -1788,6 +1797,7 @@ void DBIter::SeekToLast() {
|
||||
ResetBlobData();
|
||||
ResetValueAndColumns();
|
||||
ResetInternalKeysSkippedCounter();
|
||||
MarkMemtableForFlushForAvgTrigger();
|
||||
ClearSavedValue();
|
||||
is_key_seqnum_zero_ = false;
|
||||
|
||||
|
||||
+38
-4
@@ -12,7 +12,6 @@
|
||||
#include <string>
|
||||
|
||||
#include "db/db_impl/db_impl.h"
|
||||
#include "db/range_del_aggregator.h"
|
||||
#include "memory/arena.h"
|
||||
#include "options/cf_options.h"
|
||||
#include "rocksdb/db.h"
|
||||
@@ -62,7 +61,8 @@ class DBIter final : public Iterator {
|
||||
//
|
||||
// @param active_mem Pointer to the active memtable that `internal_iter`
|
||||
// is reading from. If not null, the memtable can be marked for flush
|
||||
// according to option mutable_cf_options.memtable_op_scan_flush_trigger.
|
||||
// according to options mutable_cf_options.memtable_op_scan_flush_trigger
|
||||
// and mutable_cf_options.memtable_avg_op_scan_flush_trigger.
|
||||
// @param arena_mode If true, the DBIter will be allocated from the arena.
|
||||
static DBIter* NewIter(Env* env, const ReadOptions& read_options,
|
||||
const ImmutableOptions& ioptions,
|
||||
@@ -145,6 +145,7 @@ class DBIter final : public Iterator {
|
||||
void operator=(const DBIter&) = delete;
|
||||
|
||||
~DBIter() override {
|
||||
MarkMemtableForFlushForAvgTrigger();
|
||||
ThreadStatus::OperationType cur_op_type =
|
||||
ThreadStatusUtil::GetThreadOperation();
|
||||
ThreadStatusUtil::SetThreadOperation(
|
||||
@@ -417,6 +418,36 @@ class DBIter final : public Iterator {
|
||||
return true;
|
||||
}
|
||||
|
||||
void MarkMemtableForFlushForAvgTrigger() {
|
||||
if (avg_op_scan_flush_trigger_ &&
|
||||
mem_hidden_op_scanned_since_seek_ >= memtable_op_scan_flush_trigger_ &&
|
||||
mem_hidden_op_scanned_since_seek_ >=
|
||||
static_cast<uint64_t>(iter_step_since_seek_) *
|
||||
avg_op_scan_flush_trigger_) {
|
||||
assert(memtable_op_scan_flush_trigger_ > 0);
|
||||
active_mem_->MarkForFlush();
|
||||
avg_op_scan_flush_trigger_ = 0;
|
||||
memtable_op_scan_flush_trigger_ = 0;
|
||||
}
|
||||
iter_step_since_seek_ = 1;
|
||||
mem_hidden_op_scanned_since_seek_ = 0;
|
||||
}
|
||||
|
||||
void MarkMemtableForFlushForPerOpTrigger(uint64_t& mem_hidden_op_scanned) {
|
||||
if (memtable_op_scan_flush_trigger_ &&
|
||||
ikey_.sequence >= memtable_seqno_lb_) {
|
||||
if (++mem_hidden_op_scanned >= memtable_op_scan_flush_trigger_) {
|
||||
active_mem_->MarkForFlush();
|
||||
// Turn off the flush trigger checks.
|
||||
memtable_op_scan_flush_trigger_ = 0;
|
||||
avg_op_scan_flush_trigger_ = 0;
|
||||
}
|
||||
if (avg_op_scan_flush_trigger_) {
|
||||
++mem_hidden_op_scanned_since_seek_;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const SliceTransform* prefix_extractor_;
|
||||
Env* const env_;
|
||||
SystemClock* clock_;
|
||||
@@ -476,8 +507,11 @@ class DBIter final : public Iterator {
|
||||
std::string saved_timestamp_;
|
||||
std::optional<std::vector<ScanOptions>> scan_opts_;
|
||||
ReadOnlyMemTable* const active_mem_;
|
||||
const SequenceNumber memtable_seqno_lb_;
|
||||
const uint32_t memtable_op_scan_flush_trigger_;
|
||||
SequenceNumber memtable_seqno_lb_;
|
||||
uint32_t memtable_op_scan_flush_trigger_;
|
||||
uint32_t avg_op_scan_flush_trigger_;
|
||||
uint32_t iter_step_since_seek_;
|
||||
uint32_t mem_hidden_op_scanned_since_seek_;
|
||||
Direction direction_;
|
||||
bool valid_;
|
||||
bool current_entry_is_merged_;
|
||||
|
||||
@@ -3890,6 +3890,7 @@ TEST_P(DBIteratorTest, MemtableOpsScanFlushTriggerWithSeek) {
|
||||
db_->GetIntProperty("rocksdb.num-deletes-active-mem-table", &val));
|
||||
ASSERT_EQ(0, val);
|
||||
} else {
|
||||
ASSERT_EQ(0, NumTableFilesAtLevel(0));
|
||||
uint64_t val = 0;
|
||||
ASSERT_TRUE(
|
||||
db_->GetIntProperty("rocksdb.num-deletes-active-mem-table", &val));
|
||||
@@ -3984,6 +3985,160 @@ TEST_P(DBIteratorTest, MemtableOpsScanFlushTriggerWithNext) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(DBIteratorTest, AverageMemtableOpsScanFlushTrigger) {
|
||||
// Tests option memtable_avg_op_scan_flush_trigger with
|
||||
// long tombstone sequences.
|
||||
Random* r = Random::GetTLSInstance();
|
||||
|
||||
const int kAvgTrigger = 10;
|
||||
const int kMaxTrigger = 500;
|
||||
Options options;
|
||||
options.create_if_missing = true;
|
||||
options.memtable_op_scan_flush_trigger = kMaxTrigger;
|
||||
options.memtable_avg_op_scan_flush_trigger = kAvgTrigger;
|
||||
options.level_compaction_dynamic_level_bytes = true;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
const int kNumKeys = 1000;
|
||||
// Base data that will be covered by a consecutive sequence of tombstones.
|
||||
for (int i = 0; i < kNumKeys; ++i) {
|
||||
ASSERT_OK(Put(Key(i), r->RandomString(50)));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(db_->CompactRange({}, nullptr, nullptr));
|
||||
ASSERT_EQ(1, NumTableFilesAtLevel(6));
|
||||
|
||||
for (int i = 0; i < kNumKeys; ++i) {
|
||||
// We issue slightly more deletions than kAvgTrigger between visible keys
|
||||
// to ensure avg skipped entries exceed kAvgTrigger.
|
||||
if (i % (kAvgTrigger + 2) != 0) {
|
||||
ASSERT_OK(SingleDelete(Key(i)));
|
||||
}
|
||||
}
|
||||
|
||||
// Each operation, except the first Seek, is expected to see kAvgTrigger + 1
|
||||
// tombstones (from the active memtable) before it finds the next visible key.
|
||||
SetPerfLevel(PerfLevel::kEnableCount);
|
||||
get_perf_context()->Reset();
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(ReadOptions()));
|
||||
iter->Seek(Key(1));
|
||||
ASSERT_EQ(get_perf_context()->next_on_memtable_count, kAvgTrigger + 1);
|
||||
iter.reset();
|
||||
// Should not flush since total entries skipped is below
|
||||
// memtable_op_scan_flush_trigger
|
||||
ASSERT_OK(Put(Key(0), "dummy write"));
|
||||
ASSERT_OK(Put(Key(0), "dummy write"));
|
||||
ASSERT_OK(db_->WaitForCompact({}));
|
||||
ASSERT_EQ(0, NumTableFilesAtLevel(0));
|
||||
|
||||
get_perf_context()->Reset();
|
||||
iter.reset(db_->NewIterator(ReadOptions()));
|
||||
int num_ops = 1;
|
||||
uint64_t num_skipped = 0;
|
||||
iter->Seek(Key(0));
|
||||
ASSERT_EQ(iter->key(), Key(0));
|
||||
uint64_t last_memtable_next_count =
|
||||
get_perf_context()->next_on_memtable_count;
|
||||
iter->Next();
|
||||
num_ops++;
|
||||
while (iter->Valid()) {
|
||||
ASSERT_OK(iter->status());
|
||||
uint64_t num_skipped_in_op =
|
||||
get_perf_context()->next_on_memtable_count - last_memtable_next_count;
|
||||
ASSERT_GE(num_skipped_in_op, kAvgTrigger + 1);
|
||||
last_memtable_next_count = get_perf_context()->next_on_memtable_count;
|
||||
num_skipped += num_skipped_in_op;
|
||||
iter->Next();
|
||||
num_ops++;
|
||||
}
|
||||
// During iterator destruction we mark memtable for flush
|
||||
iter.reset();
|
||||
|
||||
// avg trigger
|
||||
ASSERT_GE(num_skipped, kAvgTrigger * num_ops);
|
||||
// memtable_op_scan_flush_trigger
|
||||
ASSERT_GE(num_skipped, kMaxTrigger);
|
||||
// Average hidden entries scanned from memtable per operation is more than
|
||||
// kAvgTrigger and the total skipped is more than
|
||||
// memtable_op_scan_flush_trigger, the current memtable should be marked for
|
||||
// flush. The following two writes will trigger the flush.
|
||||
ASSERT_OK(Put(Key(0), "dummy write"));
|
||||
// Before a write, we schedule memtables for flush if requested.
|
||||
ASSERT_OK(Put(Key(0), "dummy write"));
|
||||
ASSERT_OK(db_->WaitForCompact({}));
|
||||
ASSERT_EQ(1, NumTableFilesAtLevel(0));
|
||||
}
|
||||
|
||||
TEST_P(DBIteratorTest, AverageMemtableOpsScanFlushTriggerByOverwrites) {
|
||||
// Tests option memtable_avg_op_scan_flush_trigger with overwrites to keys.
|
||||
Random* r = Random::GetTLSInstance();
|
||||
|
||||
const int kAvgTrigger = 25;
|
||||
Options options;
|
||||
options.create_if_missing = true;
|
||||
options.memtable_op_scan_flush_trigger = 250;
|
||||
options.memtable_avg_op_scan_flush_trigger = kAvgTrigger;
|
||||
options.level_compaction_dynamic_level_bytes = true;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
const int kNumKeys = 100;
|
||||
// Base data that will be covered by a consecutive sequence of tombstones.
|
||||
for (int i = 0; i < kNumKeys; ++i) {
|
||||
ASSERT_OK(Put(Key(i), r->RandomString(50)));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(db_->CompactRange({}, nullptr, nullptr));
|
||||
ASSERT_EQ(1, NumTableFilesAtLevel(6));
|
||||
|
||||
// One visible key every 10 keys.
|
||||
// Each non-visible user key has 3 non-visible entries in the active memtable.
|
||||
for (int i = 0; i < kNumKeys; ++i) {
|
||||
if (i % 10 != 0) {
|
||||
ASSERT_OK(Put(Key(i), r->RandomString(50)));
|
||||
ASSERT_OK(Put(Key(i), r->RandomString(50)));
|
||||
ASSERT_OK(Delete(Key(i)));
|
||||
}
|
||||
}
|
||||
|
||||
SetPerfLevel(PerfLevel::kEnableCount);
|
||||
get_perf_context()->Reset();
|
||||
ReadOptions ro;
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(ro));
|
||||
iter->Seek(Key(1));
|
||||
ASSERT_GT(get_perf_context()->next_on_memtable_count, kAvgTrigger);
|
||||
// Re-seek to trigger check for flush trigger
|
||||
iter->Seek(Key(1));
|
||||
// Should not flush since total entries skipped is below
|
||||
// memtable_op_scan_flush_trigger
|
||||
ASSERT_FALSE(static_cast<ColumnFamilyHandleImpl*>(db_->DefaultColumnFamily())
|
||||
->cfd()
|
||||
->mem()
|
||||
->IsMarkedForFlush());
|
||||
ASSERT_OK(Put(Key(0), "dummy write"));
|
||||
ASSERT_OK(Put(Key(0), "dummy write"));
|
||||
ASSERT_OK(db_->WaitForCompact({}));
|
||||
ASSERT_EQ(0, NumTableFilesAtLevel(0));
|
||||
get_perf_context()->Reset();
|
||||
|
||||
int num_ops = 1;
|
||||
iter->Seek(Key(1));
|
||||
while (iter->Valid()) {
|
||||
num_ops++;
|
||||
iter->Next();
|
||||
}
|
||||
ASSERT_GT(get_perf_context()->next_on_memtable_count, num_ops * kAvgTrigger);
|
||||
|
||||
// Re-seek should check conditions for marking memtable for flush
|
||||
iter->Seek(Key(80));
|
||||
|
||||
// Average hidden entries scanned from memtable per operation is 2.
|
||||
ASSERT_OK(Put(Key(0), "dummy write"));
|
||||
// Before a write, we schedule memtables for flush if requested.
|
||||
ASSERT_OK(Put(Key(0), "dummy write"));
|
||||
ASSERT_OK(db_->WaitForCompact({}));
|
||||
ASSERT_EQ(1, NumTableFilesAtLevel(0));
|
||||
}
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
@@ -424,6 +424,96 @@ TEST_F(DBMemTableTest, IntegrityChecks) {
|
||||
ASSERT_FALSE(iter->Valid());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBMemTableTest, VectorConcurrentInsert) {
|
||||
Options options;
|
||||
options.create_if_missing = true;
|
||||
options.create_missing_column_families = true;
|
||||
options.allow_concurrent_memtable_write = true;
|
||||
options.memtable_factory.reset(new VectorRepFactory());
|
||||
DestroyAndReopen(options);
|
||||
CreateAndReopenWithCF({"cf1"}, options);
|
||||
|
||||
// Multi-threaded writes
|
||||
{
|
||||
WriteOptions write_options;
|
||||
std::vector<port::Thread> threads;
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
threads.emplace_back([&, i]() {
|
||||
int start = i * 100;
|
||||
int end = start + 100;
|
||||
WriteBatch batch;
|
||||
for (int j = start; j < end; ++j) {
|
||||
ASSERT_OK(
|
||||
batch.Put(handles_[0], Key(j), "value" + std::to_string(j)));
|
||||
}
|
||||
ASSERT_OK(db_->Write(write_options, &batch));
|
||||
});
|
||||
}
|
||||
for (auto& t : threads) {
|
||||
t.join();
|
||||
}
|
||||
|
||||
std::unique_ptr<Iterator> iter(
|
||||
db_->NewIterator(ReadOptions(), handles_[0]));
|
||||
iter->SeekToFirst();
|
||||
for (int i = 0; i < 1000; ++i) {
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ(iter->key().ToString(), Key(i));
|
||||
ASSERT_EQ(iter->value().ToString(), "value" + std::to_string(i));
|
||||
iter->Next();
|
||||
}
|
||||
ASSERT_FALSE(iter->Valid());
|
||||
ASSERT_OK(iter->status());
|
||||
}
|
||||
|
||||
// Multi-threaded writes, multi CF
|
||||
{
|
||||
WriteOptions write_options;
|
||||
std::vector<port::Thread> threads;
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
threads.emplace_back([&, i]() {
|
||||
int start = i * 100;
|
||||
int end = start + 100;
|
||||
WriteBatch batch;
|
||||
for (int j = start; j < end; ++j) {
|
||||
ASSERT_OK(batch.Put(handles_[0], Key(j), "CF0" + std::to_string(j)));
|
||||
ASSERT_OK(batch.Put(handles_[1], Key(j), "CF1" + std::to_string(j)));
|
||||
}
|
||||
ASSERT_OK(db_->Write(write_options, &batch));
|
||||
});
|
||||
}
|
||||
|
||||
for (auto& t : threads) {
|
||||
t.join();
|
||||
}
|
||||
|
||||
std::unique_ptr<Iterator> iter0(
|
||||
db_->NewIterator(ReadOptions(), handles_[0]));
|
||||
std::unique_ptr<Iterator> iter1(
|
||||
db_->NewIterator(ReadOptions(), handles_[1]));
|
||||
iter0->SeekToFirst();
|
||||
iter1->SeekToFirst();
|
||||
for (int i = 0; i < 1000; ++i) {
|
||||
ASSERT_TRUE(iter0->Valid());
|
||||
ASSERT_EQ(iter0->key().ToString(), Key(i));
|
||||
ASSERT_EQ(iter0->value().ToString(), "CF0" + std::to_string(i));
|
||||
iter0->Next();
|
||||
|
||||
ASSERT_TRUE(iter1->Valid());
|
||||
ASSERT_EQ(iter1->key().ToString(), Key(i));
|
||||
ASSERT_EQ(iter1->value().ToString(), "CF1" + std::to_string(i));
|
||||
iter1->Next();
|
||||
}
|
||||
ASSERT_FALSE(iter0->Valid());
|
||||
ASSERT_OK(iter0->status());
|
||||
ASSERT_FALSE(iter1->Valid());
|
||||
ASSERT_OK(iter1->status());
|
||||
}
|
||||
|
||||
ASSERT_OK(Flush(0));
|
||||
ASSERT_OK(Flush(1));
|
||||
}
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
+20
-2
@@ -151,6 +151,7 @@ TEST_F(DBTest, RequestIdPlumbingTest) {
|
||||
options.env = env_;
|
||||
|
||||
// Create a mock environment to capture IODebugContext during reads
|
||||
IODebugContext dbgCopy;
|
||||
const std::string* captured_request_id_dbg;
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
@@ -160,6 +161,8 @@ TEST_F(DBTest, RequestIdPlumbingTest) {
|
||||
captured_request_id_dbg = nullptr;
|
||||
} else {
|
||||
captured_request_id_dbg = dbg->request_id;
|
||||
// Test IODebugContext assignment operator
|
||||
dbgCopy = *dbg;
|
||||
}
|
||||
});
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
|
||||
@@ -178,6 +181,10 @@ TEST_F(DBTest, RequestIdPlumbingTest) {
|
||||
// Verify the request_id was propagated to the file system
|
||||
ASSERT_NE(captured_request_id_dbg, nullptr);
|
||||
ASSERT_EQ(*captured_request_id_dbg, test_request_id);
|
||||
|
||||
ASSERT_NE(dbgCopy.request_id, nullptr);
|
||||
ASSERT_NE(dbgCopy.request_id, captured_request_id_dbg);
|
||||
ASSERT_EQ(*dbgCopy.request_id, test_request_id);
|
||||
}
|
||||
|
||||
captured_request_id_dbg = nullptr;
|
||||
@@ -197,6 +204,17 @@ TEST_F(DBTest, RequestIdPlumbingTest) {
|
||||
// Verify the request_id was propagated to the file system
|
||||
ASSERT_NE(captured_request_id_dbg, nullptr);
|
||||
ASSERT_EQ(*captured_request_id_dbg, request_id);
|
||||
|
||||
ASSERT_NE(dbgCopy.request_id, nullptr);
|
||||
ASSERT_NE(dbgCopy.request_id, captured_request_id_dbg);
|
||||
ASSERT_EQ(*dbgCopy.request_id, request_id);
|
||||
|
||||
// Test IODebugContext copy constructor
|
||||
IODebugContext dbgCopy2(dbgCopy);
|
||||
ASSERT_NE(dbgCopy2.request_id, nullptr);
|
||||
ASSERT_NE(dbgCopy2.request_id, captured_request_id_dbg);
|
||||
ASSERT_NE(dbgCopy2.request_id, dbgCopy.request_id);
|
||||
ASSERT_EQ(*dbgCopy2.request_id, request_id);
|
||||
}
|
||||
|
||||
// test request_id plumbing during multiget
|
||||
@@ -6191,8 +6209,8 @@ TEST_F(DBTest, L0L1L2AndUpHitCounter) {
|
||||
}
|
||||
|
||||
TEST_F(DBTest, EncodeDecompressedBlockSizeTest) {
|
||||
bool& allow_unsupported_fv =
|
||||
BlockBasedTableFactory::AllowUnsupportedFormatVersion();
|
||||
// Allow testing format_version=1
|
||||
bool& allow_unsupported_fv = TEST_AllowUnsupportedFormatVersion();
|
||||
SaveAndRestore guard(&allow_unsupported_fv);
|
||||
ASSERT_FALSE(allow_unsupported_fv);
|
||||
|
||||
|
||||
+637
-2
@@ -10,7 +10,6 @@
|
||||
#include <atomic>
|
||||
#include <cstdlib>
|
||||
#include <functional>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
|
||||
#include "db/db_test_util.h"
|
||||
@@ -25,11 +24,13 @@
|
||||
#include "rocksdb/persistent_cache.h"
|
||||
#include "rocksdb/trace_record.h"
|
||||
#include "rocksdb/trace_record_result.h"
|
||||
#include "rocksdb/utilities/object_registry.h"
|
||||
#include "rocksdb/utilities/replayer.h"
|
||||
#include "rocksdb/wal_filter.h"
|
||||
#include "test_util/testutil.h"
|
||||
#include "util/defer.h"
|
||||
#include "util/random.h"
|
||||
#include "util/simple_mixed_compressor.h"
|
||||
#include "utilities/fault_injection_env.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
@@ -1883,6 +1884,542 @@ TEST_F(DBTest2, CompressionOptions) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBTest2, RoundRobinManager) {
|
||||
if (ZSTD_Supported()) {
|
||||
auto mgr =
|
||||
std::make_shared<RoundRobinManager>(GetBuiltinV2CompressionManager());
|
||||
|
||||
std::vector<std::string> values;
|
||||
for (bool use_wrapper : {true}) {
|
||||
SCOPED_TRACE((use_wrapper ? "With " : "No ") + std::string("wrapper"));
|
||||
|
||||
Options options = CurrentOptions();
|
||||
options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics();
|
||||
options.statistics->set_stats_level(StatsLevel::kExceptTimeForMutex);
|
||||
BlockBasedTableOptions bbto;
|
||||
bbto.enable_index_compression = false;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(bbto));
|
||||
options.compression_manager = use_wrapper ? mgr : nullptr;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
Random rnd(301);
|
||||
constexpr int kCount = 13;
|
||||
|
||||
// Highly compressible blocks, except 1 non-compressible. Half of the
|
||||
// compressible are morked for bypass and 1 marked for rejection. Values
|
||||
// are large enough to ensure just 1 k-v per block.
|
||||
for (int i = 0; i < kCount; ++i) {
|
||||
std::string value;
|
||||
if (i == 6) {
|
||||
// One non-compressible block
|
||||
value = rnd.RandomBinaryString(20000);
|
||||
} else {
|
||||
test::CompressibleString(&rnd, 0.1, 20000, &value);
|
||||
}
|
||||
values.push_back(value);
|
||||
ASSERT_OK(Put(Key(i), value));
|
||||
ASSERT_EQ(Get(Key(i)), value);
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
// Ensure well-formed for reads
|
||||
for (int i = 0; i < kCount; ++i) {
|
||||
ASSERT_NE(Get(Key(i)), "NOT_FOUND");
|
||||
ASSERT_EQ(Get(Key(i)), values[i]);
|
||||
}
|
||||
ASSERT_EQ(Get(Key(kCount)), "NOT_FOUND");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBTest2, RandomMixedCompressionManager) {
|
||||
if (ZSTD_Supported()) {
|
||||
auto mgr = std::make_shared<RandomMixedCompressionManager>(
|
||||
GetBuiltinV2CompressionManager());
|
||||
std::vector<std::string> values;
|
||||
for (bool use_wrapper : {true}) {
|
||||
SCOPED_TRACE((use_wrapper ? "With " : "No ") + std::string("wrapper"));
|
||||
|
||||
Options options = CurrentOptions();
|
||||
options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics();
|
||||
options.statistics->set_stats_level(StatsLevel::kExceptTimeForMutex);
|
||||
BlockBasedTableOptions bbto;
|
||||
bbto.enable_index_compression = false;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(bbto));
|
||||
options.compression_manager = use_wrapper ? mgr : nullptr;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
Random rnd(301);
|
||||
constexpr int kCount = 13;
|
||||
|
||||
// Highly compressible blocks, except 1 non-compressible. Half of the
|
||||
// compressible are morked for bypass and 1 marked for rejection. Values
|
||||
// are large enough to ensure just 1 k-v per block.
|
||||
for (int i = 0; i < kCount; ++i) {
|
||||
std::string value;
|
||||
if (i == 6) {
|
||||
// One non-compressible block
|
||||
value = rnd.RandomBinaryString(20000);
|
||||
} else {
|
||||
test::CompressibleString(&rnd, 0.1, 20000, &value);
|
||||
}
|
||||
values.push_back(value);
|
||||
ASSERT_OK(Put(Key(i), value));
|
||||
ASSERT_EQ(Get(Key(i)), value);
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
// Ensure well-formed for reads
|
||||
for (int i = 0; i < kCount; ++i) {
|
||||
ASSERT_NE(Get(Key(i)), "NOT_FOUND");
|
||||
ASSERT_EQ(Get(Key(i)), values[i]);
|
||||
}
|
||||
ASSERT_EQ(Get(Key(kCount)), "NOT_FOUND");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBTest2, CompressionManagerWrapper) {
|
||||
// Test that we can use a custom CompressionManager to wrap the built-in
|
||||
// CompressionManager, thus adopting a custom *strategy* based on existing
|
||||
// algorithms. This will "mark" some blocks (in their contents) as "do not
|
||||
// compress", i.e. no attempt to compress, and some blocks as "reject
|
||||
// compression", i.e. compression attempted but rejected because of ratio
|
||||
// or otherwise. These cases are distinguishable for statistics that
|
||||
// approximate "wasted effort".
|
||||
static std::string kDoNotCompress = "do_not_compress";
|
||||
static std::string kRejectCompression = "reject_compression";
|
||||
|
||||
struct MyCompressor : public CompressorWrapper {
|
||||
using CompressorWrapper::CompressorWrapper;
|
||||
const char* Name() const override { return "MyCompressor"; }
|
||||
|
||||
Status CompressBlock(Slice uncompressed_data,
|
||||
std::string* compressed_output,
|
||||
CompressionType* out_compression_type,
|
||||
ManagedWorkingArea* working_area) override {
|
||||
auto begin = uncompressed_data.data();
|
||||
auto end = uncompressed_data.data() + uncompressed_data.size();
|
||||
if (std::search(begin, end, kDoNotCompress.begin(),
|
||||
kDoNotCompress.end()) != end) {
|
||||
// Do not attempt compression
|
||||
EXPECT_EQ(*out_compression_type, kNoCompression);
|
||||
return Status::OK();
|
||||
} else if (std::search(begin, end, kRejectCompression.begin(),
|
||||
kRejectCompression.end()) != end) {
|
||||
// Simulate attempted & rejected compression
|
||||
*compressed_output = "blah";
|
||||
EXPECT_EQ(*out_compression_type, kNoCompression);
|
||||
return Status::OK();
|
||||
} else {
|
||||
return wrapped_->CompressBlock(uncompressed_data, compressed_output,
|
||||
out_compression_type, working_area);
|
||||
}
|
||||
}
|
||||
};
|
||||
struct MyManager : public CompressionManagerWrapper {
|
||||
using CompressionManagerWrapper::CompressionManagerWrapper;
|
||||
const char* Name() const override { return "MyManager"; }
|
||||
std::unique_ptr<Compressor> GetCompressorForSST(
|
||||
const FilterBuildingContext& context, const CompressionOptions& opts,
|
||||
CompressionType preferred) override {
|
||||
return std::make_unique<MyCompressor>(
|
||||
wrapped_->GetCompressorForSST(context, opts, preferred));
|
||||
}
|
||||
};
|
||||
auto mgr = std::make_shared<MyManager>(GetBuiltinV2CompressionManager());
|
||||
|
||||
for (CompressionType type : GetSupportedCompressions()) {
|
||||
for (bool use_wrapper : {false, true}) {
|
||||
if (type == kNoCompression) {
|
||||
continue;
|
||||
}
|
||||
SCOPED_TRACE("Compression type: " + std::to_string(type) +
|
||||
(use_wrapper ? " with " : " no ") + "wrapper");
|
||||
|
||||
Options options = CurrentOptions();
|
||||
options.compression = type;
|
||||
options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics();
|
||||
options.statistics->set_stats_level(StatsLevel::kExceptTimeForMutex);
|
||||
BlockBasedTableOptions bbto;
|
||||
bbto.enable_index_compression = false;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(bbto));
|
||||
options.compression_manager = use_wrapper ? mgr : nullptr;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
auto PopStat = [&](Tickers t) -> uint64_t {
|
||||
return options.statistics->getAndResetTickerCount(t);
|
||||
};
|
||||
|
||||
Random rnd(301);
|
||||
constexpr int kCount = 13;
|
||||
|
||||
// Highly compressible blocks, except 1 non-compressible. Half of the
|
||||
// compressible are morked for bypass and 1 marked for rejection. Values
|
||||
// are large enough to ensure just 1 k-v per block.
|
||||
for (int i = 0; i < kCount; ++i) {
|
||||
std::string value;
|
||||
if (i == 6) {
|
||||
// One non-compressible block
|
||||
value = rnd.RandomBinaryString(20000);
|
||||
} else {
|
||||
test::CompressibleString(&rnd, 0.1, 20000, &value);
|
||||
if ((i % 2) == 0) {
|
||||
// Half for bypass
|
||||
value += kDoNotCompress;
|
||||
} else if (i == 7) {
|
||||
// One for rejection
|
||||
value += kRejectCompression;
|
||||
}
|
||||
}
|
||||
ASSERT_OK(Put(Key(i), value));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
if (use_wrapper) {
|
||||
EXPECT_EQ(kCount / 2 - 1, PopStat(NUMBER_BLOCK_COMPRESSED));
|
||||
EXPECT_EQ(kCount / 2, PopStat(NUMBER_BLOCK_COMPRESSION_BYPASSED));
|
||||
EXPECT_EQ(1 + 1, PopStat(NUMBER_BLOCK_COMPRESSION_REJECTED));
|
||||
} else {
|
||||
EXPECT_EQ(kCount - 1, PopStat(NUMBER_BLOCK_COMPRESSED));
|
||||
EXPECT_EQ(0, PopStat(NUMBER_BLOCK_COMPRESSION_BYPASSED));
|
||||
EXPECT_EQ(1, PopStat(NUMBER_BLOCK_COMPRESSION_REJECTED));
|
||||
}
|
||||
|
||||
// Ensure well-formed for reads
|
||||
for (int i = 0; i < kCount; ++i) {
|
||||
ASSERT_NE(Get(Key(i)), "NOT_FOUND");
|
||||
}
|
||||
ASSERT_EQ(Get(Key(kCount)), "NOT_FOUND");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBTest2, CompressionManagerCustomCompression) {
|
||||
// Test that we can use a custom CompressionManager to implement custom
|
||||
// compression algorithms, and that there are appropriate schema guard rails
|
||||
// to ensure data is not processed by the wrong algorithm.
|
||||
using Compressor8A = test::CompressorCustomAlg<kCustomCompression8A>;
|
||||
using Compressor8B = test::CompressorCustomAlg<kCustomCompression8B>;
|
||||
using Compressor8C = test::CompressorCustomAlg<kCustomCompression8C>;
|
||||
|
||||
if (!Compressor8A::Supported() || !LZ4_Supported()) {
|
||||
fprintf(stderr,
|
||||
"Prerequisite compression library not supported. Skipping\n");
|
||||
return;
|
||||
}
|
||||
|
||||
class MyManager : public CompressionManager {
|
||||
public:
|
||||
explicit MyManager(const char* compat_name) : compat_name_(compat_name) {}
|
||||
const char* Name() const override { return name_.c_str(); }
|
||||
const char* CompatibilityName() const override { return compat_name_; }
|
||||
|
||||
bool SupportsCompressionType(CompressionType type) const override {
|
||||
return type == kCustomCompression8A || type == kCustomCompression8B ||
|
||||
type == kCustomCompression8C ||
|
||||
GetBuiltinV2CompressionManager()->SupportsCompressionType(type);
|
||||
}
|
||||
|
||||
int used_compressor8A_count_ = 0;
|
||||
int used_compressor8B_count_ = 0;
|
||||
int used_compressor8C_count_ = 0;
|
||||
|
||||
std::unique_ptr<Compressor> GetCompressor(const CompressionOptions& opts,
|
||||
CompressionType type) override {
|
||||
switch (static_cast<unsigned char>(type)) {
|
||||
case kCustomCompression8A:
|
||||
used_compressor8A_count_++;
|
||||
return std::make_unique<Compressor8A>();
|
||||
case kCustomCompression8B:
|
||||
used_compressor8B_count_++;
|
||||
return std::make_unique<Compressor8B>();
|
||||
case kCustomCompression8C:
|
||||
used_compressor8C_count_++;
|
||||
return std::make_unique<Compressor8C>();
|
||||
// Also support built-in compression algorithms
|
||||
default:
|
||||
return GetBuiltinV2CompressionManager()->GetCompressor(opts, type);
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<Decompressor> GetDecompressor() override {
|
||||
return std::make_shared<test::DecompressorCustomAlg>();
|
||||
}
|
||||
|
||||
RelaxedAtomic<CompressionType> last_specific_decompressor_type_{
|
||||
kNoCompression};
|
||||
|
||||
std::shared_ptr<Decompressor> GetDecompressorForTypes(
|
||||
const CompressionType* types_begin,
|
||||
const CompressionType* types_end) override {
|
||||
assert(types_end > types_begin);
|
||||
last_specific_decompressor_type_.StoreRelaxed(*types_begin);
|
||||
auto decomp = std::make_shared<test::DecompressorCustomAlg>();
|
||||
decomp->SetAllowedTypes(types_begin, types_end);
|
||||
return decomp;
|
||||
}
|
||||
|
||||
void AddFriend(const std::shared_ptr<CompressionManager>& mgr) {
|
||||
friends_[mgr->CompatibilityName()] = mgr;
|
||||
}
|
||||
std::shared_ptr<CompressionManager> FindCompatibleCompressionManager(
|
||||
Slice compatibility_name) override {
|
||||
std::shared_ptr<CompressionManager> rv =
|
||||
CompressionManager::FindCompatibleCompressionManager(
|
||||
compatibility_name);
|
||||
if (!rv) {
|
||||
auto it = friends_.find(compatibility_name.ToString());
|
||||
if (it != friends_.end()) {
|
||||
return it->second.lock();
|
||||
}
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
private:
|
||||
const char* compat_name_;
|
||||
std::string name_;
|
||||
// weak_ptr to avoid cycles
|
||||
std::map<std::string, std::weak_ptr<CompressionManager>> friends_;
|
||||
};
|
||||
|
||||
for (bool use_dict : {false, true}) {
|
||||
SCOPED_TRACE(use_dict ? "With dict" : "No dict");
|
||||
|
||||
// Although these compression managers are actually compatible, we must
|
||||
// respect their distinct compatibility names and treat them as incompatible
|
||||
// (or else risk processing data incorrectly)
|
||||
// NOTE: these are not registered in ObjectRegistry to test what happens
|
||||
// when the original CompressionManager might not be available, but
|
||||
// mgr_bar will be registered during the test, with different names to
|
||||
// prevent interference between iterations.
|
||||
auto mgr_foo = std::make_shared<MyManager>("Foo");
|
||||
auto mgr_bar = std::make_shared<MyManager>(use_dict ? "Bar1" : "Bar2");
|
||||
|
||||
// And this one claims to be fully compatible with the built-in compression
|
||||
// manager when it's not fully compatible (for custom CompressionTypes)
|
||||
auto mgr_claim_compatible = std::make_shared<MyManager>("BuiltinV2");
|
||||
|
||||
constexpr uint16_t kValueSize = 10000;
|
||||
|
||||
Options options = CurrentOptions();
|
||||
options.level0_file_num_compaction_trigger = 20;
|
||||
BlockBasedTableOptions bbto;
|
||||
bbto.enable_index_compression = false;
|
||||
bbto.format_version = 6; // Before custom compression alg support
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(bbto));
|
||||
// Claims not to use custom compression (and doesn't unless setting a custom
|
||||
// CompressionType)
|
||||
options.compression_manager = mgr_claim_compatible;
|
||||
// Use a built-in compression type with dictionary support
|
||||
options.compression = kLZ4Compression;
|
||||
options.compression_opts.max_dict_bytes = kValueSize / 2;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
Random rnd(404);
|
||||
std::string value;
|
||||
ASSERT_OK(
|
||||
Put("a", test::CompressibleString(&rnd, 0.1, kValueSize, &value)));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
// That data should be readable without access to the original compression
|
||||
// manager, because it used the built-in CompatibilityName and a built-in
|
||||
// CompressionType
|
||||
options.compression_manager = nullptr;
|
||||
Reopen(options);
|
||||
ASSERT_EQ(Get("a"), value);
|
||||
|
||||
// Verify it was compressed
|
||||
Range r = {"a", "a0"};
|
||||
TablePropertiesCollection tables_properties;
|
||||
ASSERT_OK(db_->GetPropertiesOfTablesInRange(db_->DefaultColumnFamily(), &r,
|
||||
1, &tables_properties));
|
||||
ASSERT_EQ(tables_properties.size(), 1U);
|
||||
EXPECT_LT(tables_properties.begin()->second->data_size, kValueSize / 2);
|
||||
EXPECT_EQ(tables_properties.begin()->second->compression_name, "LZ4");
|
||||
|
||||
// Disallow setting a custom CompressionType with a CompressionManager
|
||||
// claiming to be built-in compatible.
|
||||
options.compression_manager = mgr_claim_compatible;
|
||||
options.compression = kCustomCompression8A;
|
||||
ASSERT_EQ(TryReopen(options).code(), Status::Code::kInvalidArgument);
|
||||
|
||||
options.compression_manager = nullptr;
|
||||
options.compression = kCustomCompressionFE;
|
||||
ASSERT_EQ(TryReopen(options).code(), Status::Code::kInvalidArgument);
|
||||
options.compression =
|
||||
static_cast<CompressionType>(kLastBuiltinCompression + 1);
|
||||
ASSERT_EQ(TryReopen(options).code(), Status::Code::kInvalidArgument);
|
||||
|
||||
// Custom compression schema (different CompatibilityName) not supported
|
||||
// before format_version=7
|
||||
options.compression_manager = mgr_foo;
|
||||
options.compression = kLZ4Compression;
|
||||
ASSERT_EQ(TryReopen(options).code(), Status::Code::kInvalidArgument);
|
||||
|
||||
// Set format version supporting custom compression
|
||||
bbto.format_version = 7;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(bbto));
|
||||
|
||||
// Custom compression type not supported with built-in schema name, even
|
||||
// with format_version=7
|
||||
options.compression_manager = mgr_claim_compatible;
|
||||
options.compression = kCustomCompression8B;
|
||||
ASSERT_EQ(TryReopen(options).code(), Status::Code::kInvalidArgument);
|
||||
|
||||
// Custom compression schema, but specifying a custom compression type it
|
||||
// doesn't support.
|
||||
options.compression_manager = mgr_foo;
|
||||
options.compression = kCustomCompressionF0;
|
||||
ASSERT_EQ(TryReopen(options).code(), Status::Code::kNotSupported);
|
||||
|
||||
// Using a built-in compression type with fv=7 but named custom schema
|
||||
options.compression = kLZ4Compression;
|
||||
Reopen(options);
|
||||
ASSERT_OK(
|
||||
Put("b", test::CompressibleString(&rnd, 0.1, kValueSize, &value)));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_EQ(NumTableFilesAtLevel(0), 2);
|
||||
ASSERT_EQ(Get("b"), value);
|
||||
|
||||
// Verify it was compressed with LZ4
|
||||
r = {"b", "b0"};
|
||||
tables_properties.clear();
|
||||
ASSERT_OK(db_->GetPropertiesOfTablesInRange(db_->DefaultColumnFamily(), &r,
|
||||
1, &tables_properties));
|
||||
ASSERT_EQ(tables_properties.size(), 1U);
|
||||
EXPECT_LT(tables_properties.begin()->second->data_size, kValueSize / 2);
|
||||
// Uses new format for "compression_name" property
|
||||
EXPECT_EQ(tables_properties.begin()->second->compression_name, "Foo;04;");
|
||||
EXPECT_EQ(mgr_foo->last_specific_decompressor_type_.LoadRelaxed(),
|
||||
kLZ4Compression);
|
||||
|
||||
// Custom compression type
|
||||
options.compression = kCustomCompression8A;
|
||||
Reopen(options);
|
||||
ASSERT_OK(
|
||||
Put("c", test::CompressibleString(&rnd, 0.1, kValueSize, &value)));
|
||||
EXPECT_EQ(mgr_foo->used_compressor8A_count_, 0);
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_EQ(NumTableFilesAtLevel(0), 3);
|
||||
ASSERT_EQ(Get("c"), value);
|
||||
EXPECT_EQ(mgr_foo->used_compressor8A_count_, 1);
|
||||
|
||||
// Verify it was compressed with custom format
|
||||
r = {"c", "c0"};
|
||||
tables_properties.clear();
|
||||
ASSERT_OK(db_->GetPropertiesOfTablesInRange(db_->DefaultColumnFamily(), &r,
|
||||
1, &tables_properties));
|
||||
ASSERT_EQ(tables_properties.size(), 1U);
|
||||
EXPECT_LT(tables_properties.begin()->second->data_size, kValueSize / 2);
|
||||
EXPECT_EQ(tables_properties.begin()->second->compression_name, "Foo;8A;");
|
||||
EXPECT_EQ(mgr_foo->last_specific_decompressor_type_.LoadRelaxed(),
|
||||
kCustomCompression8A);
|
||||
|
||||
// Also dynamically changeable, because the compression manager will respect
|
||||
// the current setting as reported under the legacy logic
|
||||
ASSERT_OK(dbfull()->SetOptions({{"compression", "kLZ4Compression"}}));
|
||||
ASSERT_OK(
|
||||
Put("d", test::CompressibleString(&rnd, 0.1, kValueSize, &value)));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_EQ(NumTableFilesAtLevel(0), 4);
|
||||
ASSERT_EQ(Get("d"), value);
|
||||
|
||||
// Verify it was compressed with LZ4
|
||||
r = {"d", "d0"};
|
||||
tables_properties.clear();
|
||||
ASSERT_OK(db_->GetPropertiesOfTablesInRange(db_->DefaultColumnFamily(), &r,
|
||||
1, &tables_properties));
|
||||
ASSERT_EQ(tables_properties.size(), 1U);
|
||||
EXPECT_LT(tables_properties.begin()->second->data_size, kValueSize / 2);
|
||||
EXPECT_EQ(tables_properties.begin()->second->compression_name, "Foo;04;");
|
||||
EXPECT_EQ(mgr_foo->last_specific_decompressor_type_.LoadRelaxed(),
|
||||
kLZ4Compression);
|
||||
|
||||
// Dynamically changeable to custom compressions also
|
||||
ASSERT_OK(dbfull()->SetOptions({{"compression", "kCustomCompression8B"}}));
|
||||
ASSERT_OK(
|
||||
Put("e", test::CompressibleString(&rnd, 0.1, kValueSize, &value)));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_EQ(NumTableFilesAtLevel(0), 5);
|
||||
ASSERT_EQ(Get("e"), value);
|
||||
|
||||
// Verify it was compressed with custom format
|
||||
r = {"e", "e0"};
|
||||
tables_properties.clear();
|
||||
ASSERT_OK(db_->GetPropertiesOfTablesInRange(db_->DefaultColumnFamily(), &r,
|
||||
1, &tables_properties));
|
||||
ASSERT_EQ(tables_properties.size(), 1U);
|
||||
EXPECT_LT(tables_properties.begin()->second->data_size, kValueSize / 2);
|
||||
EXPECT_EQ(tables_properties.begin()->second->compression_name, "Foo;8B;");
|
||||
EXPECT_EQ(mgr_foo->last_specific_decompressor_type_.LoadRelaxed(),
|
||||
kCustomCompression8B);
|
||||
|
||||
// Fails to re-open with incompatible compression manager (can't find
|
||||
// compression manager Foo because it's not registered nor known by Bar)
|
||||
options.compression_manager = mgr_bar;
|
||||
options.compression = kLZ4Compression;
|
||||
ASSERT_EQ(TryReopen(options).code(), Status::Code::kNotSupported);
|
||||
|
||||
// But should re-open if we make Bar aware of the Foo compression manager
|
||||
mgr_bar->AddFriend(mgr_foo);
|
||||
Reopen(options);
|
||||
|
||||
// Can still read everything
|
||||
ASSERT_EQ(Get("a").size(), kValueSize);
|
||||
ASSERT_EQ(Get("b").size(), kValueSize);
|
||||
ASSERT_EQ(Get("c").size(), kValueSize);
|
||||
ASSERT_EQ(Get("d").size(), kValueSize);
|
||||
ASSERT_EQ(Get("e").size(), kValueSize);
|
||||
|
||||
// Add a file using mgr_bar
|
||||
ASSERT_OK(
|
||||
Put("f", test::CompressibleString(&rnd, 0.1, kValueSize, &value)));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_EQ(NumTableFilesAtLevel(0), 6);
|
||||
ASSERT_EQ(Get("f"), value);
|
||||
|
||||
// Verify it was compressed appropriately
|
||||
r = {"f", "f0"};
|
||||
tables_properties.clear();
|
||||
ASSERT_OK(db_->GetPropertiesOfTablesInRange(db_->DefaultColumnFamily(), &r,
|
||||
1, &tables_properties));
|
||||
ASSERT_EQ(tables_properties.size(), 1U);
|
||||
EXPECT_LT(tables_properties.begin()->second->data_size, kValueSize / 2);
|
||||
EXPECT_EQ(mgr_bar->last_specific_decompressor_type_.LoadRelaxed(),
|
||||
kLZ4Compression);
|
||||
|
||||
// Fails to re-open with incompatible compression manager (can't find
|
||||
// compression manager Bar because it's not registered nor known by Foo)
|
||||
options.compression_manager = mgr_foo;
|
||||
ASSERT_EQ(TryReopen(options).code(), Status::Code::kNotSupported);
|
||||
|
||||
// Register and re-open
|
||||
auto& library = *ObjectLibrary::Default();
|
||||
library.AddFactory<CompressionManager>(
|
||||
mgr_bar->CompatibilityName(),
|
||||
[mgr_bar](const std::string& /*uri*/,
|
||||
std::unique_ptr<CompressionManager>* guard,
|
||||
std::string* /*errmsg*/) {
|
||||
*guard = std::make_unique<MyManager>(mgr_bar->CompatibilityName());
|
||||
return guard->get();
|
||||
});
|
||||
Reopen(options);
|
||||
|
||||
// Can still read everything
|
||||
ASSERT_EQ(Get("a").size(), kValueSize);
|
||||
ASSERT_EQ(Get("b").size(), kValueSize);
|
||||
ASSERT_EQ(Get("c").size(), kValueSize);
|
||||
ASSERT_EQ(Get("d").size(), kValueSize);
|
||||
ASSERT_EQ(Get("e").size(), kValueSize);
|
||||
ASSERT_EQ(Get("f").size(), kValueSize);
|
||||
|
||||
// TODO: test old version of a compression manager unable to read a
|
||||
// compression type
|
||||
}
|
||||
}
|
||||
|
||||
class CompactionStallTestListener : public EventListener {
|
||||
public:
|
||||
CompactionStallTestListener()
|
||||
@@ -3008,7 +3545,7 @@ TEST_F(DBTest2, PausingManualCompaction1) {
|
||||
"TestCompactFiles:PausingManualCompaction:3", [&](void* arg) {
|
||||
auto paused = static_cast<std::atomic<int>*>(arg);
|
||||
// CompactFiles() relies on manual_compactions_paused to
|
||||
// determine if thie compaction should be paused or not
|
||||
// determine if this compaction should be paused or not
|
||||
ASSERT_EQ(0, paused->load(std::memory_order_acquire));
|
||||
paused->fetch_add(1, std::memory_order_release);
|
||||
});
|
||||
@@ -3120,6 +3657,7 @@ TEST_F(DBTest2, PausingManualCompaction3) {
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
dbfull()->DisableManualCompaction();
|
||||
|
||||
ASSERT_TRUE(dbfull()
|
||||
->CompactRange(compact_options, nullptr, nullptr)
|
||||
.IsManualCompactionPaused());
|
||||
@@ -5419,6 +5957,103 @@ TEST_F(DBTest2, TestCompactFiles) {
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
}
|
||||
|
||||
TEST_F(DBTest2, TestCancelCompactFiles) {
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
Options options;
|
||||
options.env = env_;
|
||||
options.num_levels = 2;
|
||||
options.disable_auto_compactions = true;
|
||||
Reopen(options);
|
||||
|
||||
auto* handle = db_->DefaultColumnFamily();
|
||||
ASSERT_EQ(db_->NumberLevels(handle), 2);
|
||||
|
||||
ROCKSDB_NAMESPACE::SstFileWriter sst_file_writer{
|
||||
ROCKSDB_NAMESPACE::EnvOptions(), options};
|
||||
|
||||
// ingest large SST files
|
||||
std::vector<std::string> external_sst_file_names;
|
||||
int key_counter = 0;
|
||||
const int num_keys_per_file = 100000;
|
||||
const int num_files = 10;
|
||||
for (int i = 0; i < num_files; ++i) {
|
||||
std::string file_name =
|
||||
dbname_ + "/test_compact_files" + std::to_string(i) + ".sst_t";
|
||||
external_sst_file_names.push_back(file_name);
|
||||
ASSERT_OK(sst_file_writer.Open(file_name));
|
||||
for (int j = 0; j < num_keys_per_file; ++j) {
|
||||
ASSERT_OK(sst_file_writer.Put(Key(j + num_keys_per_file * key_counter),
|
||||
std::to_string(j)));
|
||||
}
|
||||
key_counter += 1;
|
||||
ASSERT_OK(sst_file_writer.Finish());
|
||||
}
|
||||
|
||||
ASSERT_OK(db_->IngestExternalFile(handle, external_sst_file_names,
|
||||
IngestExternalFileOptions()));
|
||||
ASSERT_EQ(NumTableFilesAtLevel(1, 0), num_files);
|
||||
std::vector<std::string> files;
|
||||
GetSstFiles(env_, dbname_, &files);
|
||||
ASSERT_EQ(files.size(), num_files);
|
||||
|
||||
// Test that 0 compactions happen - canceled is set to True initially
|
||||
CompactionOptions compaction_options;
|
||||
std::atomic<bool> canceled(true);
|
||||
compaction_options.canceled = &canceled;
|
||||
|
||||
ASSERT_TRUE(db_->CompactFiles(compaction_options, handle, files, 1)
|
||||
.IsManualCompactionPaused());
|
||||
ASSERT_EQ(NumTableFilesAtLevel(1, 0), num_files);
|
||||
|
||||
// Test cancellation before the check to cancel compaction happens -
|
||||
// compaction should not occur
|
||||
bool disable_compaction = false;
|
||||
compaction_options.canceled->store(false, std::memory_order_release);
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"TestCancelCompactFiles:SuccessfulCompaction", [&](void* arg) {
|
||||
auto paused = static_cast<std::atomic<int>*>(arg);
|
||||
if (disable_compaction) {
|
||||
db_->DisableManualCompaction();
|
||||
ASSERT_EQ(1, paused->load(std::memory_order_acquire));
|
||||
} else {
|
||||
compaction_options.canceled->store(true, std::memory_order_release);
|
||||
ASSERT_EQ(0, paused->load(std::memory_order_acquire));
|
||||
}
|
||||
});
|
||||
|
||||
ASSERT_TRUE(db_->CompactFiles(compaction_options, handle, files, 1)
|
||||
.IsManualCompactionPaused());
|
||||
ASSERT_EQ(NumTableFilesAtLevel(1, 0), num_files);
|
||||
|
||||
// DisableManualCompaction() should successfully cancel compaction
|
||||
disable_compaction = true;
|
||||
compaction_options.canceled->store(false, std::memory_order_release);
|
||||
ASSERT_TRUE(db_->CompactFiles(compaction_options, handle, files, 1)
|
||||
.IsManualCompactionPaused());
|
||||
ASSERT_EQ(NumTableFilesAtLevel(1, 0), num_files);
|
||||
// unlike CompactRange, value of compaction_options.canceled will be
|
||||
// unaffected by calling DisableManualCompactions()
|
||||
ASSERT_FALSE(compaction_options.canceled->load());
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
db_->EnableManualCompaction();
|
||||
|
||||
// Test cancelation after the check to cancel compaction - compaction should
|
||||
// occur, leaving only 1 file
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"CompactFilesImpl:0", [&](void* /*arg*/) {
|
||||
compaction_options.canceled->store(true, std::memory_order_release);
|
||||
});
|
||||
|
||||
compaction_options.canceled->store(false, std::memory_order_release);
|
||||
ASSERT_OK(db_->CompactFiles(compaction_options, handle, files, 1));
|
||||
ASSERT_EQ(NumTableFilesAtLevel(1, 0), 1);
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
}
|
||||
|
||||
TEST_F(DBTest2, MultiDBParallelOpenTest) {
|
||||
const int kNumDbs = 2;
|
||||
Options options = CurrentOptions();
|
||||
|
||||
@@ -1672,55 +1672,75 @@ TEST_P(DBTestUniversalCompaction, ConcurrentBottomPriLowPriCompactions) {
|
||||
}
|
||||
const int kNumFilesTrigger = 3;
|
||||
Env::Default()->SetBackgroundThreads(1, Env::Priority::BOTTOM);
|
||||
Options options = CurrentOptions();
|
||||
options.compaction_style = kCompactionStyleUniversal;
|
||||
options.max_background_compactions = 2;
|
||||
options.num_levels = num_levels_;
|
||||
options.write_buffer_size = 100 << 10; // 100KB
|
||||
options.target_file_size_base = 32 << 10; // 32KB
|
||||
options.level0_file_num_compaction_trigger = kNumFilesTrigger;
|
||||
// Trigger compaction if size amplification exceeds 110%
|
||||
options.compaction_options_universal.max_size_amplification_percent = 110;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
// Need to get a token to enable compaction parallelism up to
|
||||
// `max_background_compactions` jobs.
|
||||
auto pressure_token =
|
||||
dbfull()->TEST_write_controler().GetCompactionPressureToken();
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency(
|
||||
{// wait for the full compaction to be picked before adding files intended
|
||||
// for the second one.
|
||||
{"DBImpl::BackgroundCompaction:ForwardToBottomPriPool",
|
||||
"DBTestUniversalCompaction:ConcurrentBottomPriLowPriCompactions:0"},
|
||||
// the full (bottom-pri) compaction waits until a partial (low-pri)
|
||||
// compaction has started to verify they can run in parallel.
|
||||
{"DBImpl::BackgroundCompaction:NonTrivial",
|
||||
"DBImpl::BGWorkBottomCompaction"}});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
for (bool universal_reduce_file_locking : {true, false}) {
|
||||
Options options = CurrentOptions();
|
||||
options.compaction_style = kCompactionStyleUniversal;
|
||||
options.compaction_options_universal.reduce_file_locking =
|
||||
universal_reduce_file_locking;
|
||||
options.max_background_compactions = 2;
|
||||
options.num_levels = num_levels_;
|
||||
options.write_buffer_size = 100 << 10; // 100KB
|
||||
options.target_file_size_base = 32 << 10; // 32KB
|
||||
options.level0_file_num_compaction_trigger = kNumFilesTrigger;
|
||||
// Trigger compaction if size amplification exceeds 110%
|
||||
options.compaction_options_universal.max_size_amplification_percent = 110;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
Random rnd(301);
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
for (int num = 0; num < kNumFilesTrigger; num++) {
|
||||
int key_idx = 0;
|
||||
GenerateNewFile(&rnd, &key_idx, true /* no_wait */);
|
||||
// use no_wait above because that one waits for flush and compaction. We
|
||||
// don't want to wait for compaction because the full compaction is
|
||||
// intentionally blocked while more files are flushed.
|
||||
ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable());
|
||||
// Need to get a token to enable compaction parallelism up to
|
||||
// `max_background_compactions` jobs.
|
||||
auto pressure_token =
|
||||
dbfull()->TEST_write_controler().GetCompactionPressureToken();
|
||||
if (universal_reduce_file_locking) {
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency(
|
||||
{// Wait for the full compaction to be repicked before adding files
|
||||
// intended for the second compaction.
|
||||
{"DBImpl::BackgroundCompaction():AfterPickCompactionBottomPri",
|
||||
"DBTestUniversalCompaction:ConcurrentBottomPriLowPriCompactions:0"},
|
||||
// Wait for the second compaction to run before running the full
|
||||
// compaction to verify they can run in parallel
|
||||
{"DBImpl::BackgroundCompaction:NonTrivial:BeforeRun",
|
||||
"DBImpl::BackgroundCompaction:NonTrivial:BeforeRunBottomPri"}});
|
||||
} else {
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency(
|
||||
{// Wait for the full compaction to be forwarded before adding files
|
||||
// intended for the second compaction.
|
||||
{"DBImpl::BackgroundCompaction:ForwardToBottomPriPool",
|
||||
"DBTestUniversalCompaction:ConcurrentBottomPriLowPriCompactions:0"},
|
||||
// Wait for the second compaction to run before running the full
|
||||
// compaction to verify they can run in parallel
|
||||
{"DBImpl::BackgroundCompaction:NonTrivial:BeforeRun",
|
||||
"DBImpl::BackgroundCompaction:NonTrivial:BeforeRunBottomPri"}});
|
||||
}
|
||||
if (i == 0) {
|
||||
TEST_SYNC_POINT(
|
||||
"DBTestUniversalCompaction:ConcurrentBottomPriLowPriCompactions:0");
|
||||
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
Random rnd(301);
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
for (int num = 0; num < kNumFilesTrigger; num++) {
|
||||
int key_idx = 0;
|
||||
GenerateNewFile(&rnd, &key_idx, true /* no_wait */);
|
||||
// use no_wait above because that one waits for flush and compaction. We
|
||||
// don't want to wait for compaction because the full compaction is
|
||||
// intentionally blocked while more files are flushed.
|
||||
ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable());
|
||||
}
|
||||
if (i == 0) {
|
||||
TEST_SYNC_POINT(
|
||||
"DBTestUniversalCompaction:ConcurrentBottomPriLowPriCompactions:0");
|
||||
}
|
||||
}
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
|
||||
// First compaction should output to bottom level. Second should output to
|
||||
// L0 since older L0 files pending compaction prevent it from being placed
|
||||
// lower.
|
||||
ASSERT_EQ(NumSortedRuns(), 2);
|
||||
ASSERT_GT(NumTableFilesAtLevel(0), 0);
|
||||
ASSERT_GT(NumTableFilesAtLevel(num_levels_ - 1), 0);
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
|
||||
// First compaction should output to bottom level. Second should output to L0
|
||||
// since older L0 files pending compaction prevent it from being placed lower.
|
||||
ASSERT_EQ(NumSortedRuns(), 2);
|
||||
ASSERT_GT(NumTableFilesAtLevel(0), 0);
|
||||
ASSERT_GT(NumTableFilesAtLevel(num_levels_ - 1), 0);
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
|
||||
Env::Default()->SetBackgroundThreads(0, Env::Priority::BOTTOM);
|
||||
}
|
||||
|
||||
|
||||
+33
-15
@@ -77,7 +77,12 @@ void EventHelpers::LogAndNotifyTableFileCreationFinished(
|
||||
TableFileCreationReason reason, const Status& s,
|
||||
const std::string& file_checksum,
|
||||
const std::string& file_checksum_func_name) {
|
||||
if (s.ok() && event_logger) {
|
||||
if (!event_logger && listeners.empty()) {
|
||||
s.PermitUncheckedError();
|
||||
return;
|
||||
}
|
||||
|
||||
if (event_logger) {
|
||||
JSONWriter jwriter;
|
||||
AppendCurrentTime(&jwriter);
|
||||
jwriter << "cf_name" << cf_name << "job" << job_id << "event"
|
||||
@@ -165,6 +170,8 @@ void EventHelpers::LogAndNotifyTableFileCreationFinished(
|
||||
jwriter << "oldest_blob_file_number" << oldest_blob_file_number;
|
||||
}
|
||||
|
||||
jwriter << "status" << s.ToString();
|
||||
|
||||
jwriter.EndObject();
|
||||
|
||||
event_logger->Log(jwriter);
|
||||
@@ -195,18 +202,22 @@ void EventHelpers::LogAndNotifyTableFileDeletion(
|
||||
const std::string& file_path, const Status& status,
|
||||
const std::string& dbname,
|
||||
const std::vector<std::shared_ptr<EventListener>>& listeners) {
|
||||
JSONWriter jwriter;
|
||||
AppendCurrentTime(&jwriter);
|
||||
|
||||
jwriter << "job" << job_id << "event" << "table_file_deletion"
|
||||
<< "file_number" << file_number;
|
||||
if (!status.ok()) {
|
||||
jwriter << "status" << status.ToString();
|
||||
if (!event_logger && listeners.empty()) {
|
||||
status.PermitUncheckedError();
|
||||
return;
|
||||
}
|
||||
|
||||
jwriter.EndObject();
|
||||
if (event_logger) {
|
||||
JSONWriter jwriter;
|
||||
AppendCurrentTime(&jwriter);
|
||||
|
||||
event_logger->Log(jwriter);
|
||||
jwriter << "job" << job_id << "event" << "table_file_deletion"
|
||||
<< "file_number" << file_number << "status" << status.ToString();
|
||||
|
||||
jwriter.EndObject();
|
||||
|
||||
event_logger->Log(jwriter);
|
||||
}
|
||||
|
||||
if (listeners.empty()) {
|
||||
return;
|
||||
@@ -274,7 +285,12 @@ void EventHelpers::LogAndNotifyBlobFileCreationFinished(
|
||||
const std::string& file_checksum,
|
||||
const std::string& file_checksum_func_name, uint64_t total_blob_count,
|
||||
uint64_t total_blob_bytes) {
|
||||
if (s.ok() && event_logger) {
|
||||
if (!event_logger && listeners.empty()) {
|
||||
s.PermitUncheckedError();
|
||||
return;
|
||||
}
|
||||
|
||||
if (event_logger) {
|
||||
JSONWriter jwriter;
|
||||
AppendCurrentTime(&jwriter);
|
||||
jwriter << "cf_name" << cf_name << "job" << job_id << "event"
|
||||
@@ -305,15 +321,17 @@ void EventHelpers::LogAndNotifyBlobFileDeletion(
|
||||
const std::vector<std::shared_ptr<EventListener>>& listeners, int job_id,
|
||||
uint64_t file_number, const std::string& file_path, const Status& status,
|
||||
const std::string& dbname) {
|
||||
if (!event_logger && listeners.empty()) {
|
||||
status.PermitUncheckedError();
|
||||
return;
|
||||
}
|
||||
|
||||
if (event_logger) {
|
||||
JSONWriter jwriter;
|
||||
AppendCurrentTime(&jwriter);
|
||||
|
||||
jwriter << "job" << job_id << "event" << "blob_file_deletion"
|
||||
<< "file_number" << file_number;
|
||||
if (!status.ok()) {
|
||||
jwriter << "status" << status.ToString();
|
||||
}
|
||||
<< "file_number" << file_number << "status" << status.ToString();
|
||||
|
||||
jwriter.EndObject();
|
||||
event_logger->Log(jwriter);
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
#include "test_util/testharness.h"
|
||||
#include "test_util/testutil.h"
|
||||
#include "util/defer.h"
|
||||
#include "util/file_checksum_helper.h"
|
||||
#include "util/random.h"
|
||||
#include "utilities/fault_injection_env.h"
|
||||
|
||||
@@ -346,7 +347,8 @@ class ChecksumVerifyHelper {
|
||||
|
||||
Status GetSingleFileChecksumAndFuncName(
|
||||
const std::string& file_path, std::string* file_checksum,
|
||||
std::string* file_checksum_func_name) {
|
||||
std::string* file_checksum_func_name,
|
||||
const std::string& requested_func_name = {}) {
|
||||
Status s;
|
||||
EnvOptions soptions;
|
||||
std::unique_ptr<SequentialFile> file_reader;
|
||||
@@ -364,6 +366,8 @@ class ChecksumVerifyHelper {
|
||||
return Status::OK();
|
||||
} else {
|
||||
FileChecksumGenContext gen_context;
|
||||
gen_context.file_name = file_path;
|
||||
gen_context.requested_checksum_func_name = requested_func_name;
|
||||
std::unique_ptr<FileChecksumGenerator> file_checksum_gen =
|
||||
file_checksum_gen_factory->CreateFileChecksumGenerator(gen_context);
|
||||
*file_checksum_func_name = file_checksum_gen->Name();
|
||||
@@ -439,10 +443,50 @@ TEST_F(ExternalSSTFileBasicTest, BasicWithFileChecksumCrc32c) {
|
||||
DestroyAndRecreateExternalSSTFilesDir();
|
||||
}
|
||||
|
||||
namespace {
|
||||
class VariousFileChecksumGenerator : public FileChecksumGenCrc32c {
|
||||
public:
|
||||
explicit VariousFileChecksumGenerator(const std::string& name)
|
||||
: FileChecksumGenCrc32c({}), name_(name) {}
|
||||
|
||||
const char* Name() const override { return name_.c_str(); }
|
||||
|
||||
std::string GetChecksum() const override {
|
||||
return FileChecksumGenCrc32c::GetChecksum() + "_" + name_;
|
||||
}
|
||||
|
||||
private:
|
||||
const std::string name_;
|
||||
};
|
||||
|
||||
class VariousFileChecksumGenFactory : public FileChecksumGenFactory {
|
||||
public:
|
||||
std::unique_ptr<FileChecksumGenerator> CreateFileChecksumGenerator(
|
||||
const FileChecksumGenContext& context) override {
|
||||
static RelaxedAtomic<int> counter{0};
|
||||
if (Slice(context.requested_checksum_func_name).starts_with("Various")) {
|
||||
return std::make_unique<VariousFileChecksumGenerator>(
|
||||
context.requested_checksum_func_name);
|
||||
} else if (context.requested_checksum_func_name.empty()) {
|
||||
// Lacking a specific request, use a different function name for each
|
||||
// result.
|
||||
return std::make_unique<VariousFileChecksumGenerator>(
|
||||
"Various" + std::to_string(counter.FetchAddRelaxed(1)));
|
||||
} else {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
static const char* kClassName() { return "VariousFileChecksumGenFactory"; }
|
||||
const char* Name() const override { return kClassName(); }
|
||||
};
|
||||
} // namespace
|
||||
|
||||
TEST_F(ExternalSSTFileBasicTest, IngestFileWithFileChecksum) {
|
||||
Options old_options = CurrentOptions();
|
||||
Options options = CurrentOptions();
|
||||
options.file_checksum_gen_factory = GetFileChecksumGenCrc32cFactory();
|
||||
options.file_checksum_gen_factory =
|
||||
std::make_shared<VariousFileChecksumGenFactory>();
|
||||
const ImmutableCFOptions ioptions(options);
|
||||
ChecksumVerifyHelper checksum_helper(options);
|
||||
|
||||
@@ -463,7 +507,8 @@ TEST_F(ExternalSSTFileBasicTest, IngestFileWithFileChecksum) {
|
||||
ASSERT_EQ(file1_info.largest_key, Key(1099));
|
||||
std::string file_checksum1, file_checksum_func_name1;
|
||||
ASSERT_OK(checksum_helper.GetSingleFileChecksumAndFuncName(
|
||||
file1, &file_checksum1, &file_checksum_func_name1));
|
||||
file1, &file_checksum1, &file_checksum_func_name1,
|
||||
file1_info.file_checksum_func_name));
|
||||
ASSERT_EQ(file1_info.file_checksum, file_checksum1);
|
||||
ASSERT_EQ(file1_info.file_checksum_func_name, file_checksum_func_name1);
|
||||
|
||||
@@ -482,7 +527,8 @@ TEST_F(ExternalSSTFileBasicTest, IngestFileWithFileChecksum) {
|
||||
ASSERT_EQ(file2_info.largest_key, Key(1299));
|
||||
std::string file_checksum2, file_checksum_func_name2;
|
||||
ASSERT_OK(checksum_helper.GetSingleFileChecksumAndFuncName(
|
||||
file2, &file_checksum2, &file_checksum_func_name2));
|
||||
file2, &file_checksum2, &file_checksum_func_name2,
|
||||
file2_info.file_checksum_func_name));
|
||||
ASSERT_EQ(file2_info.file_checksum, file_checksum2);
|
||||
ASSERT_EQ(file2_info.file_checksum_func_name, file_checksum_func_name2);
|
||||
|
||||
@@ -501,7 +547,8 @@ TEST_F(ExternalSSTFileBasicTest, IngestFileWithFileChecksum) {
|
||||
ASSERT_EQ(file3_info.largest_key, Key(1499));
|
||||
std::string file_checksum3, file_checksum_func_name3;
|
||||
ASSERT_OK(checksum_helper.GetSingleFileChecksumAndFuncName(
|
||||
file3, &file_checksum3, &file_checksum_func_name3));
|
||||
file3, &file_checksum3, &file_checksum_func_name3,
|
||||
file3_info.file_checksum_func_name));
|
||||
ASSERT_EQ(file3_info.file_checksum, file_checksum3);
|
||||
ASSERT_EQ(file3_info.file_checksum_func_name, file_checksum_func_name3);
|
||||
|
||||
@@ -520,7 +567,8 @@ TEST_F(ExternalSSTFileBasicTest, IngestFileWithFileChecksum) {
|
||||
ASSERT_EQ(file4_info.largest_key, Key(1799));
|
||||
std::string file_checksum4, file_checksum_func_name4;
|
||||
ASSERT_OK(checksum_helper.GetSingleFileChecksumAndFuncName(
|
||||
file4, &file_checksum4, &file_checksum_func_name4));
|
||||
file4, &file_checksum4, &file_checksum_func_name4,
|
||||
file4_info.file_checksum_func_name));
|
||||
ASSERT_EQ(file4_info.file_checksum, file_checksum4);
|
||||
ASSERT_EQ(file4_info.file_checksum_func_name, file_checksum_func_name4);
|
||||
|
||||
@@ -539,7 +587,8 @@ TEST_F(ExternalSSTFileBasicTest, IngestFileWithFileChecksum) {
|
||||
ASSERT_EQ(file5_info.largest_key, Key(1999));
|
||||
std::string file_checksum5, file_checksum_func_name5;
|
||||
ASSERT_OK(checksum_helper.GetSingleFileChecksumAndFuncName(
|
||||
file5, &file_checksum5, &file_checksum_func_name5));
|
||||
file5, &file_checksum5, &file_checksum_func_name5,
|
||||
file5_info.file_checksum_func_name));
|
||||
ASSERT_EQ(file5_info.file_checksum, file_checksum5);
|
||||
ASSERT_EQ(file5_info.file_checksum_func_name, file_checksum_func_name5);
|
||||
|
||||
@@ -558,7 +607,8 @@ TEST_F(ExternalSSTFileBasicTest, IngestFileWithFileChecksum) {
|
||||
ASSERT_EQ(file6_info.largest_key, Key(2199));
|
||||
std::string file_checksum6, file_checksum_func_name6;
|
||||
ASSERT_OK(checksum_helper.GetSingleFileChecksumAndFuncName(
|
||||
file6, &file_checksum6, &file_checksum_func_name6));
|
||||
file6, &file_checksum6, &file_checksum_func_name6,
|
||||
file6_info.file_checksum_func_name));
|
||||
ASSERT_EQ(file6_info.file_checksum, file_checksum6);
|
||||
ASSERT_EQ(file6_info.file_checksum_func_name, file_checksum_func_name6);
|
||||
|
||||
@@ -628,18 +678,23 @@ TEST_F(ExternalSSTFileBasicTest, IngestFileWithFileChecksum) {
|
||||
}
|
||||
ASSERT_OK(env_->FileExists(file2));
|
||||
|
||||
// Enable verify_file_checksum option
|
||||
// No checksum information is provided, generate it when ingesting
|
||||
std::vector<std::string> checksum, checksum_func;
|
||||
s = AddFileWithFileChecksum({file3}, checksum, checksum_func, true, false,
|
||||
false, false);
|
||||
// Enable verify_file_checksum option. No checksum information is provided,
|
||||
// so it is generated when ingesting. The configured checksum factory will
|
||||
// use a different function than before.
|
||||
s = AddFileWithFileChecksum({file3}, {}, {}, true, false, false, false);
|
||||
ASSERT_OK(s) << s.ToString();
|
||||
std::vector<LiveFileMetaData> live_files2;
|
||||
dbfull()->GetLiveFilesMetaData(&live_files2);
|
||||
for (const auto& f : live_files2) {
|
||||
if (set1.find(f.name) == set1.end()) {
|
||||
ASSERT_EQ(f.file_checksum, file_checksum3);
|
||||
ASSERT_EQ(f.file_checksum_func_name, file_checksum_func_name3);
|
||||
// Recomputed checksum, different function
|
||||
EXPECT_NE(f.file_checksum_func_name, file_checksum_func_name3);
|
||||
std::string cur_checksum3, cur_checksum_func_name3;
|
||||
ASSERT_OK(checksum_helper.GetSingleFileChecksumAndFuncName(
|
||||
dbname_ + f.name, &cur_checksum3, &cur_checksum_func_name3,
|
||||
f.file_checksum_func_name));
|
||||
EXPECT_EQ(f.file_checksum, cur_checksum3);
|
||||
EXPECT_EQ(f.file_checksum_func_name, cur_checksum_func_name3);
|
||||
set1.insert(f.name);
|
||||
}
|
||||
}
|
||||
@@ -653,8 +708,9 @@ TEST_F(ExternalSSTFileBasicTest, IngestFileWithFileChecksum) {
|
||||
ASSERT_NOK(s) << s.ToString();
|
||||
|
||||
// Does not enable verify_file_checksum options
|
||||
// Checksum function name matches, store the checksum being ingested.
|
||||
s = AddFileWithFileChecksum({file4}, {"asd"}, {file_checksum_func_name4},
|
||||
// Checksum function name is recognized, so store the checksum being ingested.
|
||||
std::string file_checksum_func_name4alt = "VariousABCD";
|
||||
s = AddFileWithFileChecksum({file4}, {"asd"}, {file_checksum_func_name4alt},
|
||||
false, false, false, false);
|
||||
ASSERT_OK(s) << s.ToString();
|
||||
std::vector<LiveFileMetaData> live_files3;
|
||||
@@ -663,7 +719,7 @@ TEST_F(ExternalSSTFileBasicTest, IngestFileWithFileChecksum) {
|
||||
if (set1.find(f.name) == set1.end()) {
|
||||
ASSERT_FALSE(f.file_checksum == file_checksum4);
|
||||
ASSERT_EQ(f.file_checksum, "asd");
|
||||
ASSERT_EQ(f.file_checksum_func_name, file_checksum_func_name4);
|
||||
ASSERT_EQ(f.file_checksum_func_name, file_checksum_func_name4alt);
|
||||
set1.insert(f.name);
|
||||
}
|
||||
}
|
||||
@@ -672,7 +728,8 @@ TEST_F(ExternalSSTFileBasicTest, IngestFileWithFileChecksum) {
|
||||
|
||||
// enable verify_file_checksum options, DB enable checksum, and enable
|
||||
// write_global_seq. So the checksum stored is different from the one
|
||||
// ingested due to the sequence number changes.
|
||||
// ingested due to the sequence number changes. The checksum function name
|
||||
// may also change since the checksum is recomputed.
|
||||
s = AddFileWithFileChecksum({file5}, {file_checksum5},
|
||||
{file_checksum_func_name5}, true, false, false,
|
||||
true);
|
||||
@@ -681,11 +738,14 @@ TEST_F(ExternalSSTFileBasicTest, IngestFileWithFileChecksum) {
|
||||
dbfull()->GetLiveFilesMetaData(&live_files4);
|
||||
for (const auto& f : live_files4) {
|
||||
if (set1.find(f.name) == set1.end()) {
|
||||
// Recomputed checksum, different function
|
||||
EXPECT_NE(f.file_checksum_func_name, file_checksum_func_name5);
|
||||
std::string cur_checksum5, cur_checksum_func_name5;
|
||||
ASSERT_OK(checksum_helper.GetSingleFileChecksumAndFuncName(
|
||||
dbname_ + f.name, &cur_checksum5, &cur_checksum_func_name5));
|
||||
ASSERT_EQ(f.file_checksum, cur_checksum5);
|
||||
ASSERT_EQ(f.file_checksum_func_name, file_checksum_func_name5);
|
||||
dbname_ + f.name, &cur_checksum5, &cur_checksum_func_name5,
|
||||
f.file_checksum_func_name));
|
||||
EXPECT_EQ(f.file_checksum, cur_checksum5);
|
||||
EXPECT_EQ(f.file_checksum_func_name, cur_checksum_func_name5);
|
||||
set1.insert(f.name);
|
||||
}
|
||||
}
|
||||
@@ -693,18 +753,22 @@ TEST_F(ExternalSSTFileBasicTest, IngestFileWithFileChecksum) {
|
||||
ASSERT_OK(env_->FileExists(file5));
|
||||
|
||||
// Does not enable verify_file_checksum options and also the ingested file
|
||||
// checksum information is empty. DB will generate and store the checksum
|
||||
// in Manifest.
|
||||
std::vector<std::string> files_c6, files_name6;
|
||||
s = AddFileWithFileChecksum({file6}, files_c6, files_name6, false, false,
|
||||
false, false);
|
||||
// checksum information is empty. DB will generate and store file checksum
|
||||
// in Manifest, which could be different from the previous invocation.
|
||||
s = AddFileWithFileChecksum({file6}, {}, {}, false, false, false, false);
|
||||
ASSERT_OK(s) << s.ToString();
|
||||
std::vector<LiveFileMetaData> live_files6;
|
||||
dbfull()->GetLiveFilesMetaData(&live_files6);
|
||||
for (const auto& f : live_files6) {
|
||||
if (set1.find(f.name) == set1.end()) {
|
||||
ASSERT_EQ(f.file_checksum, file_checksum6);
|
||||
ASSERT_EQ(f.file_checksum_func_name, file_checksum_func_name6);
|
||||
// Recomputed checksum, different function
|
||||
EXPECT_NE(f.file_checksum_func_name, file_checksum_func_name6);
|
||||
std::string cur_checksum6, cur_checksum_func_name6;
|
||||
ASSERT_OK(checksum_helper.GetSingleFileChecksumAndFuncName(
|
||||
dbname_ + f.name, &cur_checksum6, &cur_checksum_func_name6,
|
||||
f.file_checksum_func_name));
|
||||
EXPECT_EQ(f.file_checksum, cur_checksum6);
|
||||
EXPECT_EQ(f.file_checksum_func_name, cur_checksum_func_name6);
|
||||
set1.insert(f.name);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -260,10 +260,6 @@ Status ExternalSstFileIngestionJob::Prepare(
|
||||
} else {
|
||||
need_generate_file_checksum_ = true;
|
||||
}
|
||||
FileChecksumGenContext gen_context;
|
||||
std::unique_ptr<FileChecksumGenerator> file_checksum_gen =
|
||||
db_options_.file_checksum_gen_factory->CreateFileChecksumGenerator(
|
||||
gen_context);
|
||||
std::vector<std::string> generated_checksums;
|
||||
std::vector<std::string> generated_checksum_func_names;
|
||||
// Step 1: generate the checksum for ingested sst file.
|
||||
@@ -271,7 +267,9 @@ Status ExternalSstFileIngestionJob::Prepare(
|
||||
for (size_t i = 0; i < files_to_ingest_.size(); i++) {
|
||||
std::string generated_checksum;
|
||||
std::string generated_checksum_func_name;
|
||||
std::string requested_checksum_func_name;
|
||||
std::string requested_checksum_func_name =
|
||||
i < files_checksum_func_names.size() ? files_checksum_func_names[i]
|
||||
: "";
|
||||
// TODO: rate limit file reads for checksum calculation during file
|
||||
// ingestion.
|
||||
// TODO: plumb Env::IOActivity
|
||||
@@ -314,40 +312,50 @@ Status ExternalSstFileIngestionJob::Prepare(
|
||||
if (files_checksum_func_names[i] !=
|
||||
generated_checksum_func_names[i]) {
|
||||
status = Status::InvalidArgument(
|
||||
"Checksum function name does not match with the checksum "
|
||||
"function name of this DB");
|
||||
ROCKS_LOG_WARN(
|
||||
db_options_.info_log,
|
||||
"Sst file checksum verification of file: %s failed: %s",
|
||||
external_files_paths[i].c_str(), status.ToString().c_str());
|
||||
"DB file checksum gen factory " +
|
||||
std::string(db_options_.file_checksum_gen_factory->Name()) +
|
||||
" generated checksum function name " +
|
||||
generated_checksum_func_names[i] + " for file " +
|
||||
external_files_paths[i] +
|
||||
" which does not match requested/provided " +
|
||||
files_checksum_func_names[i]);
|
||||
break;
|
||||
}
|
||||
if (files_checksums[i] != generated_checksums[i]) {
|
||||
status = Status::Corruption(
|
||||
"Ingested checksum does not match with the generated "
|
||||
"checksum");
|
||||
ROCKS_LOG_WARN(
|
||||
db_options_.info_log,
|
||||
"Sst file checksum verification of file: %s failed: %s",
|
||||
files_to_ingest_[i].internal_file_path.c_str(),
|
||||
status.ToString().c_str());
|
||||
"Checksum verification mismatch for ingestion file " +
|
||||
external_files_paths[i] + " using function " +
|
||||
generated_checksum_func_names[i] + ". Expected: " +
|
||||
Slice(files_checksums[i]).ToString(/*hex=*/true) +
|
||||
" Computed: " +
|
||||
Slice(generated_checksums[i]).ToString(/*hex=*/true));
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// If verify_file_checksum is not enabled, we only verify the
|
||||
// checksum function name. If it does not match, fail the ingestion.
|
||||
// If matches, we trust the ingested checksum information and store
|
||||
// in the Manifest.
|
||||
// If verify_file_checksum is not enabled, we only verify the factory
|
||||
// recognizes the checksum function name. If it does not match, fail
|
||||
// the ingestion. If matches, we trust the ingested checksum
|
||||
// information and store in the Manifest.
|
||||
for (size_t i = 0; i < files_to_ingest_.size(); i++) {
|
||||
if (files_checksum_func_names[i] != file_checksum_gen->Name()) {
|
||||
FileChecksumGenContext gen_context;
|
||||
gen_context.file_name = files_to_ingest_[i].internal_file_path;
|
||||
gen_context.requested_checksum_func_name =
|
||||
files_checksum_func_names[i];
|
||||
auto file_checksum_gen =
|
||||
db_options_.file_checksum_gen_factory
|
||||
->CreateFileChecksumGenerator(gen_context);
|
||||
|
||||
if (file_checksum_gen == nullptr ||
|
||||
files_checksum_func_names[i] != file_checksum_gen->Name()) {
|
||||
status = Status::InvalidArgument(
|
||||
"Checksum function name does not match with the checksum "
|
||||
"function name of this DB");
|
||||
ROCKS_LOG_WARN(
|
||||
db_options_.info_log,
|
||||
"Sst file checksum verification of file: %s failed: %s",
|
||||
external_files_paths[i].c_str(), status.ToString().c_str());
|
||||
"Checksum function name " + files_checksum_func_names[i] +
|
||||
" for file " + external_files_paths[i] +
|
||||
" not recognized by DB checksum gen factory" +
|
||||
db_options_.file_checksum_gen_factory->Name() +
|
||||
(file_checksum_gen ? (" Returned function " +
|
||||
std::string(file_checksum_gen->Name()))
|
||||
: ""));
|
||||
break;
|
||||
}
|
||||
files_to_ingest_[i].file_checksum = files_checksums[i];
|
||||
@@ -362,12 +370,11 @@ Status ExternalSstFileIngestionJob::Prepare(
|
||||
status = Status::InvalidArgument(
|
||||
"The checksum information of ingested sst files are nonempty and "
|
||||
"the size of checksums or the size of the checksum function "
|
||||
"names "
|
||||
"does not match with the number of ingested sst files");
|
||||
ROCKS_LOG_WARN(
|
||||
db_options_.info_log,
|
||||
"The ingested sst files checksum information is incomplete: %s",
|
||||
status.ToString().c_str());
|
||||
"names does not match with the number of ingested sst files");
|
||||
}
|
||||
if (!status.ok()) {
|
||||
ROCKS_LOG_WARN(db_options_.info_log, "Ingestion failed: %s",
|
||||
status.ToString().c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -834,7 +841,8 @@ Status ExternalSstFileIngestionJob::ResetTableReader(
|
||||
ro,
|
||||
TableReaderOptions(
|
||||
cfd_->ioptions(), sv->mutable_cf_options.prefix_extractor,
|
||||
env_options_, cfd_->internal_comparator(),
|
||||
sv->mutable_cf_options.compression_manager.get(), env_options_,
|
||||
cfd_->internal_comparator(),
|
||||
sv->mutable_cf_options.block_protection_bytes_per_key,
|
||||
/*skip_filters*/ false, /*immortal*/ false,
|
||||
/*force_direct_prefetch*/ false, /*level*/ -1,
|
||||
|
||||
+27
-34
@@ -92,12 +92,10 @@ FlushJob::FlushJob(
|
||||
const MutableCFOptions& mutable_cf_options, uint64_t max_memtable_id,
|
||||
const FileOptions& file_options, VersionSet* versions,
|
||||
InstrumentedMutex* db_mutex, std::atomic<bool>* shutting_down,
|
||||
std::vector<SequenceNumber> existing_snapshots,
|
||||
SequenceNumber earliest_write_conflict_snapshot,
|
||||
SnapshotChecker* snapshot_checker, JobContext* job_context,
|
||||
FlushReason flush_reason, LogBuffer* log_buffer, FSDirectory* db_directory,
|
||||
FSDirectory* output_file_directory, CompressionType output_compression,
|
||||
Statistics* stats, EventLogger* event_logger, bool measure_io_stats,
|
||||
JobContext* job_context, FlushReason flush_reason, LogBuffer* log_buffer,
|
||||
FSDirectory* db_directory, FSDirectory* output_file_directory,
|
||||
CompressionType output_compression, Statistics* stats,
|
||||
EventLogger* event_logger, bool measure_io_stats,
|
||||
const bool sync_output_directory, const bool write_manifest,
|
||||
Env::Priority thread_pri, const std::shared_ptr<IOTracer>& io_tracer,
|
||||
std::shared_ptr<const SeqnoToTimeMapping> seqno_to_time_mapping,
|
||||
@@ -114,12 +112,7 @@ FlushJob::FlushJob(
|
||||
versions_(versions),
|
||||
db_mutex_(db_mutex),
|
||||
shutting_down_(shutting_down),
|
||||
existing_snapshots_(std::move(existing_snapshots)),
|
||||
earliest_snapshot_(existing_snapshots_.empty()
|
||||
? kMaxSequenceNumber
|
||||
: existing_snapshots_.at(0)),
|
||||
earliest_write_conflict_snapshot_(earliest_write_conflict_snapshot),
|
||||
snapshot_checker_(snapshot_checker),
|
||||
earliest_snapshot_(job_context->GetEarliestSnapshotSequence()),
|
||||
job_context_(job_context),
|
||||
flush_reason_(flush_reason),
|
||||
log_buffer_(log_buffer),
|
||||
@@ -140,6 +133,7 @@ FlushJob::FlushJob(
|
||||
full_history_ts_low_(std::move(full_history_ts_low)),
|
||||
blob_callback_(blob_callback),
|
||||
seqno_to_time_mapping_(std::move(seqno_to_time_mapping)) {
|
||||
assert(job_context->snapshot_context_initialized);
|
||||
// Update the thread status to indicate flush.
|
||||
ReportStartedFlush();
|
||||
TEST_SYNC_POINT("FlushJob::FlushJob()");
|
||||
@@ -456,7 +450,7 @@ Status FlushJob::MemPurge() {
|
||||
const std::string* const full_history_ts_low = &(cfd_->GetFullHistoryTsLow());
|
||||
std::unique_ptr<CompactionRangeDelAggregator> range_del_agg(
|
||||
new CompactionRangeDelAggregator(&(cfd_->internal_comparator()),
|
||||
existing_snapshots_,
|
||||
job_context_->snapshot_seqs,
|
||||
full_history_ts_low));
|
||||
for (auto& rd_iter : range_del_iters) {
|
||||
range_del_agg->AddTombstones(std::move(rd_iter));
|
||||
@@ -495,19 +489,19 @@ Status FlushJob::MemPurge() {
|
||||
|
||||
Env* env = db_options_.env;
|
||||
assert(env);
|
||||
MergeHelper merge(
|
||||
env, (cfd_->internal_comparator()).user_comparator(),
|
||||
(ioptions.merge_operator).get(), compaction_filter.get(),
|
||||
ioptions.logger, true /* internal key corruption is not ok */,
|
||||
existing_snapshots_.empty() ? 0 : existing_snapshots_.back(),
|
||||
snapshot_checker_);
|
||||
MergeHelper merge(env, (cfd_->internal_comparator()).user_comparator(),
|
||||
(ioptions.merge_operator).get(), compaction_filter.get(),
|
||||
ioptions.logger,
|
||||
true /* internal key corruption is not ok */,
|
||||
job_context_->GetLatestSnapshotSequence(),
|
||||
job_context_->snapshot_checker);
|
||||
assert(job_context_);
|
||||
SequenceNumber job_snapshot_seq = job_context_->GetJobSnapshotSequence();
|
||||
const std::atomic<bool> kManualCompactionCanceledFalse{false};
|
||||
CompactionIterator c_iter(
|
||||
iter.get(), (cfd_->internal_comparator()).user_comparator(), &merge,
|
||||
kMaxSequenceNumber, &existing_snapshots_, earliest_snapshot_,
|
||||
earliest_write_conflict_snapshot_, job_snapshot_seq, snapshot_checker_,
|
||||
kMaxSequenceNumber, &job_context_->snapshot_seqs, earliest_snapshot_,
|
||||
job_context_->earliest_write_conflict_snapshot,
|
||||
job_context_->GetJobSnapshotSequence(), job_context_->snapshot_checker,
|
||||
env, ShouldReportDetailedTime(env, ioptions.stats),
|
||||
true /* internal key corruption is not ok */, range_del_agg.get(),
|
||||
nullptr, ioptions.allow_data_in_errors,
|
||||
@@ -761,7 +755,7 @@ bool FlushJob::MemPurgeDecider(double threshold) {
|
||||
// Pick the oldest existing snapshot that is more recent
|
||||
// than the sequence number of the sampled entry.
|
||||
min_seqno_snapshot = kMaxSequenceNumber;
|
||||
for (SequenceNumber seq_num : existing_snapshots_) {
|
||||
for (SequenceNumber seq_num : job_context_->snapshot_seqs) {
|
||||
if (seq_num > res.sequence && seq_num < min_seqno_snapshot) {
|
||||
min_seqno_snapshot = seq_num;
|
||||
}
|
||||
@@ -1000,20 +994,19 @@ Status FlushJob::WriteLevel0Table() {
|
||||
preclude_last_level_min_seqno_ == kMaxSequenceNumber
|
||||
? preclude_last_level_min_seqno_
|
||||
: std::min(earliest_snapshot_, preclude_last_level_min_seqno_));
|
||||
const SequenceNumber job_snapshot_seq =
|
||||
job_context_->GetJobSnapshotSequence();
|
||||
|
||||
s = BuildTable(
|
||||
dbname_, versions_, db_options_, tboptions, file_options_,
|
||||
cfd_->table_cache(), iter.get(), std::move(range_del_iters), &meta_,
|
||||
&blob_file_additions, existing_snapshots_, earliest_snapshot_,
|
||||
earliest_write_conflict_snapshot_, job_snapshot_seq,
|
||||
snapshot_checker_, mutable_cf_options_.paranoid_file_checks,
|
||||
cfd_->internal_stats(), &io_s, io_tracer_,
|
||||
BlobFileCreationReason::kFlush, seqno_to_time_mapping_.get(),
|
||||
event_logger_, job_context_->job_id, &table_properties_, write_hint,
|
||||
full_history_ts_low, blob_callback_, base_, &memtable_payload_bytes,
|
||||
&memtable_garbage_bytes, &flush_stats);
|
||||
&blob_file_additions, job_context_->snapshot_seqs, earliest_snapshot_,
|
||||
job_context_->earliest_write_conflict_snapshot,
|
||||
job_context_->GetJobSnapshotSequence(),
|
||||
job_context_->snapshot_checker,
|
||||
mutable_cf_options_.paranoid_file_checks, cfd_->internal_stats(),
|
||||
&io_s, io_tracer_, BlobFileCreationReason::kFlush,
|
||||
seqno_to_time_mapping_.get(), event_logger_, job_context_->job_id,
|
||||
&table_properties_, write_hint, full_history_ts_low, blob_callback_,
|
||||
base_, &memtable_payload_bytes, &memtable_garbage_bytes,
|
||||
&flush_stats);
|
||||
TEST_SYNC_POINT_CALLBACK("FlushJob::WriteLevel0Table:s", &s);
|
||||
// TODO: Cleanup io_status in BuildTable and table builders
|
||||
assert(!s.ok() || io_s.ok());
|
||||
|
||||
+3
-8
@@ -63,11 +63,9 @@ class FlushJob {
|
||||
const MutableCFOptions& mutable_cf_options, uint64_t max_memtable_id,
|
||||
const FileOptions& file_options, VersionSet* versions,
|
||||
InstrumentedMutex* db_mutex, std::atomic<bool>* shutting_down,
|
||||
std::vector<SequenceNumber> existing_snapshots,
|
||||
SequenceNumber earliest_write_conflict_snapshot,
|
||||
SnapshotChecker* snapshot_checker, JobContext* job_context,
|
||||
FlushReason flush_reason, LogBuffer* log_buffer,
|
||||
FSDirectory* db_directory, FSDirectory* output_file_directory,
|
||||
JobContext* job_context, FlushReason flush_reason,
|
||||
LogBuffer* log_buffer, FSDirectory* db_directory,
|
||||
FSDirectory* output_file_directory,
|
||||
CompressionType output_compression, Statistics* stats,
|
||||
EventLogger* event_logger, bool measure_io_stats,
|
||||
const bool sync_output_directory, const bool write_manifest,
|
||||
@@ -167,10 +165,7 @@ class FlushJob {
|
||||
VersionSet* versions_;
|
||||
InstrumentedMutex* db_mutex_;
|
||||
std::atomic<bool>* shutting_down_;
|
||||
std::vector<SequenceNumber> existing_snapshots_;
|
||||
SequenceNumber earliest_snapshot_;
|
||||
SequenceNumber earliest_write_conflict_snapshot_;
|
||||
SnapshotChecker* snapshot_checker_;
|
||||
JobContext* job_context_;
|
||||
FlushReason flush_reason_;
|
||||
LogBuffer* log_buffer_;
|
||||
|
||||
+65
-65
@@ -186,16 +186,16 @@ TEST_F(FlushJobTest, Empty) {
|
||||
JobContext job_context(0);
|
||||
auto cfd = versions_->GetColumnFamilySet()->GetDefault();
|
||||
EventLogger event_logger(db_options_.info_log.get());
|
||||
SnapshotChecker* snapshot_checker = nullptr; // not relavant
|
||||
FlushJob flush_job(
|
||||
dbname_, versions_->GetColumnFamilySet()->GetDefault(), db_options_,
|
||||
cfd->GetLatestMutableCFOptions(),
|
||||
std::numeric_limits<uint64_t>::max() /* memtable_id */, env_options_,
|
||||
versions_.get(), &mutex_, &shutting_down_, {}, kMaxSequenceNumber,
|
||||
snapshot_checker, &job_context, FlushReason::kTest, nullptr, nullptr,
|
||||
nullptr, kNoCompression, nullptr, &event_logger, false,
|
||||
true /* sync_output_directory */, true /* write_manifest */,
|
||||
Env::Priority::USER, nullptr /*IOTracer*/, empty_seqno_to_time_mapping_);
|
||||
job_context.InitSnapshotContext(nullptr, nullptr, kMaxSequenceNumber, {});
|
||||
FlushJob flush_job(dbname_, versions_->GetColumnFamilySet()->GetDefault(),
|
||||
db_options_, cfd->GetLatestMutableCFOptions(),
|
||||
std::numeric_limits<uint64_t>::max() /* memtable_id */,
|
||||
env_options_, versions_.get(), &mutex_, &shutting_down_,
|
||||
&job_context, FlushReason::kTest, nullptr, nullptr,
|
||||
nullptr, kNoCompression, nullptr, &event_logger, false,
|
||||
true /* sync_output_directory */,
|
||||
true /* write_manifest */, Env::Priority::USER,
|
||||
nullptr /*IOTracer*/, empty_seqno_to_time_mapping_);
|
||||
{
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
flush_job.PickMemTable();
|
||||
@@ -272,16 +272,16 @@ TEST_F(FlushJobTest, NonEmpty) {
|
||||
}
|
||||
|
||||
EventLogger event_logger(db_options_.info_log.get());
|
||||
SnapshotChecker* snapshot_checker = nullptr; // not relavant
|
||||
FlushJob flush_job(
|
||||
dbname_, versions_->GetColumnFamilySet()->GetDefault(), db_options_,
|
||||
cfd->GetLatestMutableCFOptions(),
|
||||
std::numeric_limits<uint64_t>::max() /* memtable_id */, env_options_,
|
||||
versions_.get(), &mutex_, &shutting_down_, {}, kMaxSequenceNumber,
|
||||
snapshot_checker, &job_context, FlushReason::kTest, nullptr, nullptr,
|
||||
nullptr, kNoCompression, db_options_.statistics.get(), &event_logger,
|
||||
true, true /* sync_output_directory */, true /* write_manifest */,
|
||||
Env::Priority::USER, nullptr /*IOTracer*/, empty_seqno_to_time_mapping_);
|
||||
job_context.InitSnapshotContext(nullptr, nullptr, kMaxSequenceNumber, {});
|
||||
FlushJob flush_job(dbname_, versions_->GetColumnFamilySet()->GetDefault(),
|
||||
db_options_, cfd->GetLatestMutableCFOptions(),
|
||||
std::numeric_limits<uint64_t>::max() /* memtable_id */,
|
||||
env_options_, versions_.get(), &mutex_, &shutting_down_,
|
||||
&job_context, FlushReason::kTest, nullptr, nullptr,
|
||||
nullptr, kNoCompression, db_options_.statistics.get(),
|
||||
&event_logger, true, true /* sync_output_directory */,
|
||||
true /* write_manifest */, Env::Priority::USER,
|
||||
nullptr /*IOTracer*/, empty_seqno_to_time_mapping_);
|
||||
|
||||
HistogramData hist;
|
||||
FileMetaData file_meta;
|
||||
@@ -332,18 +332,18 @@ TEST_F(FlushJobTest, FlushMemTablesSingleColumnFamily) {
|
||||
}
|
||||
|
||||
EventLogger event_logger(db_options_.info_log.get());
|
||||
SnapshotChecker* snapshot_checker = nullptr; // not relavant
|
||||
|
||||
assert(memtable_ids.size() == num_mems);
|
||||
uint64_t smallest_memtable_id = memtable_ids.front();
|
||||
uint64_t flush_memtable_id = smallest_memtable_id + num_mems_to_flush - 1;
|
||||
job_context.InitSnapshotContext(nullptr, nullptr, kMaxSequenceNumber, {});
|
||||
FlushJob flush_job(
|
||||
dbname_, versions_->GetColumnFamilySet()->GetDefault(), db_options_,
|
||||
cfd->GetLatestMutableCFOptions(), flush_memtable_id, env_options_,
|
||||
versions_.get(), &mutex_, &shutting_down_, {}, kMaxSequenceNumber,
|
||||
snapshot_checker, &job_context, FlushReason::kTest, nullptr, nullptr,
|
||||
nullptr, kNoCompression, db_options_.statistics.get(), &event_logger,
|
||||
true, true /* sync_output_directory */, true /* write_manifest */,
|
||||
versions_.get(), &mutex_, &shutting_down_, &job_context,
|
||||
FlushReason::kTest, nullptr, nullptr, nullptr, kNoCompression,
|
||||
db_options_.statistics.get(), &event_logger, true,
|
||||
true /* sync_output_directory */, true /* write_manifest */,
|
||||
Env::Priority::USER, nullptr /*IOTracer*/, empty_seqno_to_time_mapping_);
|
||||
HistogramData hist;
|
||||
FileMetaData file_meta;
|
||||
@@ -405,18 +405,17 @@ TEST_F(FlushJobTest, FlushMemtablesMultipleColumnFamilies) {
|
||||
}
|
||||
|
||||
EventLogger event_logger(db_options_.info_log.get());
|
||||
SnapshotChecker* snapshot_checker = nullptr; // not relevant
|
||||
std::vector<std::unique_ptr<FlushJob>> flush_jobs;
|
||||
k = 0;
|
||||
job_context.InitSnapshotContext(nullptr, nullptr, kMaxSequenceNumber, {});
|
||||
for (auto cfd : all_cfds) {
|
||||
std::vector<SequenceNumber> snapshot_seqs;
|
||||
flush_jobs.emplace_back(new FlushJob(
|
||||
dbname_, cfd, db_options_, cfd->GetLatestMutableCFOptions(),
|
||||
memtable_ids[k], env_options_, versions_.get(), &mutex_,
|
||||
&shutting_down_, snapshot_seqs, kMaxSequenceNumber, snapshot_checker,
|
||||
&job_context, FlushReason::kTest, nullptr, nullptr, nullptr,
|
||||
kNoCompression, db_options_.statistics.get(), &event_logger, true,
|
||||
false /* sync_output_directory */, false /* write_manifest */,
|
||||
&shutting_down_, &job_context, FlushReason::kTest, nullptr, nullptr,
|
||||
nullptr, kNoCompression, db_options_.statistics.get(), &event_logger,
|
||||
true, false /* sync_output_directory */, false /* write_manifest */,
|
||||
Env::Priority::USER, nullptr /*IOTracer*/,
|
||||
empty_seqno_to_time_mapping_));
|
||||
k++;
|
||||
@@ -532,16 +531,17 @@ TEST_F(FlushJobTest, Snapshots) {
|
||||
}
|
||||
|
||||
EventLogger event_logger(db_options_.info_log.get());
|
||||
SnapshotChecker* snapshot_checker = nullptr; // not relavant
|
||||
FlushJob flush_job(
|
||||
dbname_, versions_->GetColumnFamilySet()->GetDefault(), db_options_,
|
||||
cfd->GetLatestMutableCFOptions(),
|
||||
std::numeric_limits<uint64_t>::max() /* memtable_id */, env_options_,
|
||||
versions_.get(), &mutex_, &shutting_down_, snapshots, kMaxSequenceNumber,
|
||||
snapshot_checker, &job_context, FlushReason::kTest, nullptr, nullptr,
|
||||
nullptr, kNoCompression, db_options_.statistics.get(), &event_logger,
|
||||
true, true /* sync_output_directory */, true /* write_manifest */,
|
||||
Env::Priority::USER, nullptr /*IOTracer*/, empty_seqno_to_time_mapping_);
|
||||
job_context.InitSnapshotContext(nullptr, nullptr, kMaxSequenceNumber,
|
||||
std::move(snapshots));
|
||||
FlushJob flush_job(dbname_, versions_->GetColumnFamilySet()->GetDefault(),
|
||||
db_options_, cfd->GetLatestMutableCFOptions(),
|
||||
std::numeric_limits<uint64_t>::max() /* memtable_id */,
|
||||
env_options_, versions_.get(), &mutex_, &shutting_down_,
|
||||
&job_context, FlushReason::kTest, nullptr, nullptr,
|
||||
nullptr, kNoCompression, db_options_.statistics.get(),
|
||||
&event_logger, true, true /* sync_output_directory */,
|
||||
true /* write_manifest */, Env::Priority::USER,
|
||||
nullptr /*IOTracer*/, empty_seqno_to_time_mapping_);
|
||||
mutex_.Lock();
|
||||
flush_job.PickMemTable();
|
||||
ASSERT_OK(flush_job.Run());
|
||||
@@ -585,18 +585,18 @@ TEST_F(FlushJobTest, GetRateLimiterPriorityForWrite) {
|
||||
}
|
||||
|
||||
EventLogger event_logger(db_options_.info_log.get());
|
||||
SnapshotChecker* snapshot_checker = nullptr; // not relavant
|
||||
|
||||
assert(memtable_ids.size() == num_mems);
|
||||
uint64_t smallest_memtable_id = memtable_ids.front();
|
||||
uint64_t flush_memtable_id = smallest_memtable_id + num_mems_to_flush - 1;
|
||||
job_context.InitSnapshotContext(nullptr, nullptr, kMaxSequenceNumber, {});
|
||||
FlushJob flush_job(
|
||||
dbname_, versions_->GetColumnFamilySet()->GetDefault(), db_options_,
|
||||
cfd->GetLatestMutableCFOptions(), flush_memtable_id, env_options_,
|
||||
versions_.get(), &mutex_, &shutting_down_, {}, kMaxSequenceNumber,
|
||||
snapshot_checker, &job_context, FlushReason::kTest, nullptr, nullptr,
|
||||
nullptr, kNoCompression, db_options_.statistics.get(), &event_logger,
|
||||
true, true /* sync_output_directory */, true /* write_manifest */,
|
||||
versions_.get(), &mutex_, &shutting_down_, &job_context,
|
||||
FlushReason::kTest, nullptr, nullptr, nullptr, kNoCompression,
|
||||
db_options_.statistics.get(), &event_logger, true,
|
||||
true /* sync_output_directory */, true /* write_manifest */,
|
||||
Env::Priority::USER, nullptr /*IOTracer*/, empty_seqno_to_time_mapping_);
|
||||
|
||||
// When the state from WriteController is normal.
|
||||
@@ -658,16 +658,16 @@ TEST_F(FlushJobTest, ReplaceTimedPutWriteTimeWithPreferredSeqno) {
|
||||
}
|
||||
|
||||
EventLogger event_logger(db_options_.info_log.get());
|
||||
SnapshotChecker* snapshot_checker = nullptr; // not relevant
|
||||
FlushJob flush_job(
|
||||
dbname_, versions_->GetColumnFamilySet()->GetDefault(), db_options_,
|
||||
cfd->GetLatestMutableCFOptions(),
|
||||
std::numeric_limits<uint64_t>::max() /* memtable_id */, env_options_,
|
||||
versions_.get(), &mutex_, &shutting_down_, {}, kMaxSequenceNumber,
|
||||
snapshot_checker, &job_context, FlushReason::kTest, nullptr, nullptr,
|
||||
nullptr, kNoCompression, db_options_.statistics.get(), &event_logger,
|
||||
true, true /* sync_output_directory */, true /* write_manifest */,
|
||||
Env::Priority::USER, nullptr /*IOTracer*/, seqno_to_time_mapping);
|
||||
job_context.InitSnapshotContext(nullptr, nullptr, kMaxSequenceNumber, {});
|
||||
FlushJob flush_job(dbname_, versions_->GetColumnFamilySet()->GetDefault(),
|
||||
db_options_, cfd->GetLatestMutableCFOptions(),
|
||||
std::numeric_limits<uint64_t>::max() /* memtable_id */,
|
||||
env_options_, versions_.get(), &mutex_, &shutting_down_,
|
||||
&job_context, FlushReason::kTest, nullptr, nullptr,
|
||||
nullptr, kNoCompression, db_options_.statistics.get(),
|
||||
&event_logger, true, true /* sync_output_directory */,
|
||||
true /* write_manifest */, Env::Priority::USER,
|
||||
nullptr /*IOTracer*/, seqno_to_time_mapping);
|
||||
|
||||
FileMetaData file_meta;
|
||||
mutex_.Lock();
|
||||
@@ -761,19 +761,19 @@ TEST_P(FlushJobTimestampTest, AllKeysExpired) {
|
||||
}
|
||||
|
||||
std::vector<SequenceNumber> snapshots;
|
||||
constexpr SnapshotChecker* const snapshot_checker = nullptr;
|
||||
JobContext job_context(0);
|
||||
EventLogger event_logger(db_options_.info_log.get());
|
||||
std::string full_history_ts_low;
|
||||
PutFixed64(&full_history_ts_low, std::numeric_limits<uint64_t>::max());
|
||||
cfd->SetFullHistoryTsLow(full_history_ts_low);
|
||||
job_context.InitSnapshotContext(nullptr, nullptr, kMaxSequenceNumber, {});
|
||||
FlushJob flush_job(
|
||||
dbname_, cfd, db_options_, cfd->GetLatestMutableCFOptions(),
|
||||
std::numeric_limits<uint64_t>::max() /* memtable_id */, env_options_,
|
||||
versions_.get(), &mutex_, &shutting_down_, snapshots, kMaxSequenceNumber,
|
||||
snapshot_checker, &job_context, FlushReason::kTest, nullptr, nullptr,
|
||||
nullptr, kNoCompression, db_options_.statistics.get(), &event_logger,
|
||||
true, true /* sync_output_directory */, true /* write_manifest */,
|
||||
versions_.get(), &mutex_, &shutting_down_, &job_context,
|
||||
FlushReason::kTest, nullptr, nullptr, nullptr, kNoCompression,
|
||||
db_options_.statistics.get(), &event_logger, true,
|
||||
true /* sync_output_directory */, true /* write_manifest */,
|
||||
Env::Priority::USER, nullptr /*IOTracer*/, empty_seqno_to_time_mapping_,
|
||||
/*db_id=*/"",
|
||||
/*db_session_id=*/"", full_history_ts_low);
|
||||
@@ -823,8 +823,8 @@ TEST_P(FlushJobTimestampTest, NoKeyExpired) {
|
||||
}
|
||||
|
||||
std::vector<SequenceNumber> snapshots;
|
||||
SnapshotChecker* const snapshot_checker = nullptr;
|
||||
JobContext job_context(0);
|
||||
job_context.InitSnapshotContext(nullptr, nullptr, kMaxSequenceNumber, {});
|
||||
EventLogger event_logger(db_options_.info_log.get());
|
||||
std::string full_history_ts_low;
|
||||
PutFixed64(&full_history_ts_low, 0);
|
||||
@@ -832,10 +832,10 @@ TEST_P(FlushJobTimestampTest, NoKeyExpired) {
|
||||
FlushJob flush_job(
|
||||
dbname_, cfd, db_options_, cfd->GetLatestMutableCFOptions(),
|
||||
std::numeric_limits<uint64_t>::max() /* memtable_id */, env_options_,
|
||||
versions_.get(), &mutex_, &shutting_down_, snapshots, kMaxSequenceNumber,
|
||||
snapshot_checker, &job_context, FlushReason::kTest, nullptr, nullptr,
|
||||
nullptr, kNoCompression, db_options_.statistics.get(), &event_logger,
|
||||
true, true /* sync_output_directory */, true /* write_manifest */,
|
||||
versions_.get(), &mutex_, &shutting_down_, &job_context,
|
||||
FlushReason::kTest, nullptr, nullptr, nullptr, kNoCompression,
|
||||
db_options_.statistics.get(), &event_logger, true,
|
||||
true /* sync_output_directory */, true /* write_manifest */,
|
||||
Env::Priority::USER, nullptr /*IOTracer*/, empty_seqno_to_time_mapping_,
|
||||
/*db_id=*/"",
|
||||
/*db_session_id=*/"", full_history_ts_low);
|
||||
|
||||
@@ -324,7 +324,8 @@ Status ImportColumnFamilyJob::GetIngestedFileInfo(
|
||||
status = sv->mutable_cf_options.table_factory->NewTableReader(
|
||||
TableReaderOptions(
|
||||
cfd_->ioptions(), sv->mutable_cf_options.prefix_extractor,
|
||||
env_options_, cfd_->internal_comparator(),
|
||||
sv->mutable_cf_options.compression_manager.get(), env_options_,
|
||||
cfd_->internal_comparator(),
|
||||
sv->mutable_cf_options.block_protection_bytes_per_key,
|
||||
/*skip_filters*/ false, /*immortal*/ false,
|
||||
/*force_direct_prefetch*/ false, /*level*/ -1,
|
||||
|
||||
+49
-11
@@ -135,6 +135,37 @@ struct JobContext {
|
||||
return kMaxSequenceNumber;
|
||||
}
|
||||
|
||||
SequenceNumber GetLatestSnapshotSequence() const {
|
||||
assert(snapshot_context_initialized);
|
||||
if (snapshot_seqs.empty()) {
|
||||
return 0;
|
||||
}
|
||||
return snapshot_seqs.back();
|
||||
}
|
||||
|
||||
SequenceNumber GetEarliestSnapshotSequence() const {
|
||||
assert(snapshot_context_initialized);
|
||||
if (snapshot_seqs.empty()) {
|
||||
return kMaxSequenceNumber;
|
||||
}
|
||||
return snapshot_seqs.front();
|
||||
}
|
||||
|
||||
void InitSnapshotContext(SnapshotChecker* checker,
|
||||
std::unique_ptr<ManagedSnapshot> managed_snapshot,
|
||||
SequenceNumber earliest_write_conflict,
|
||||
std::vector<SequenceNumber>&& snapshots) {
|
||||
if (snapshot_context_initialized) {
|
||||
return;
|
||||
}
|
||||
snapshot_context_initialized = true;
|
||||
snapshot_checker = checker;
|
||||
assert(!job_snapshot);
|
||||
job_snapshot = std::move(managed_snapshot);
|
||||
earliest_write_conflict_snapshot = earliest_write_conflict;
|
||||
snapshot_seqs = std::move(snapshots);
|
||||
}
|
||||
|
||||
// Structure to store information for candidate files to delete.
|
||||
struct CandidateFileInfo {
|
||||
std::string file_name;
|
||||
@@ -146,9 +177,6 @@ struct JobContext {
|
||||
}
|
||||
};
|
||||
|
||||
// Unique job id
|
||||
int job_id;
|
||||
|
||||
// a list of all files that we'll consider deleting
|
||||
// (every once in a while this is filled up with all files
|
||||
// in the DB directory)
|
||||
@@ -197,14 +225,14 @@ struct JobContext {
|
||||
|
||||
// the current manifest_file_number, log_number and prev_log_number
|
||||
// that corresponds to the set of files in 'live'.
|
||||
uint64_t manifest_file_number;
|
||||
uint64_t pending_manifest_file_number;
|
||||
uint64_t manifest_file_number = 0;
|
||||
uint64_t pending_manifest_file_number = 0;
|
||||
|
||||
// Used for remote compaction. To prevent OPTIONS files from getting
|
||||
// purged by PurgeObsoleteFiles() of the primary host
|
||||
uint64_t min_options_file_number;
|
||||
uint64_t log_number;
|
||||
uint64_t prev_log_number;
|
||||
uint64_t log_number = 0;
|
||||
uint64_t prev_log_number = 0;
|
||||
|
||||
uint64_t min_pending_output = 0;
|
||||
uint64_t prev_wals_total_size = 0;
|
||||
@@ -213,17 +241,27 @@ struct JobContext {
|
||||
|
||||
// Snapshot taken before flush/compaction job.
|
||||
std::unique_ptr<ManagedSnapshot> job_snapshot;
|
||||
SnapshotChecker* snapshot_checker = nullptr;
|
||||
std::vector<SequenceNumber> snapshot_seqs;
|
||||
// This is the earliest snapshot that could be used for write-conflict
|
||||
// checking by a transaction. For any user-key newer than this snapshot, we
|
||||
// should make sure not to remove evidence that a write occurred.
|
||||
SequenceNumber earliest_write_conflict_snapshot = kMaxSequenceNumber;
|
||||
|
||||
// Unique job id
|
||||
int job_id;
|
||||
|
||||
bool snapshot_context_initialized = false;
|
||||
|
||||
explicit JobContext(int _job_id, bool create_superversion = false) {
|
||||
job_id = _job_id;
|
||||
manifest_file_number = 0;
|
||||
pending_manifest_file_number = 0;
|
||||
log_number = 0;
|
||||
prev_log_number = 0;
|
||||
superversion_contexts.emplace_back(
|
||||
SuperVersionContext(create_superversion));
|
||||
}
|
||||
|
||||
// Delete the default constructor
|
||||
JobContext() = delete;
|
||||
|
||||
// For non-empty JobContext Clean() has to be called at least once before
|
||||
// before destruction (see asserts in ~JobContext()). Should be called with
|
||||
// unlocked DB mutex. Destructor doesn't call Clean() to avoid accidentally
|
||||
|
||||
@@ -537,6 +537,47 @@ TEST_F(EventListenerTest, DisableBGCompaction) {
|
||||
ASSERT_GE(listener->slowdown_count, kSlowdownTrigger * 9);
|
||||
}
|
||||
|
||||
class TestNumInputFilesTotalInputBytesPouplatedInListener
|
||||
: public EventListener {
|
||||
public:
|
||||
void OnCompactionCompleted(DB* /*db*/, const CompactionJobInfo& ci) override {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
num_input_files = ci.stats.num_input_files;
|
||||
total_num_of_bytes = ci.stats.total_input_bytes;
|
||||
}
|
||||
size_t num_input_files = 0;
|
||||
size_t total_num_of_bytes = 0;
|
||||
std::mutex mutex_;
|
||||
};
|
||||
|
||||
TEST_F(EventListenerTest, NumInputFilesTotalBytesPopulated) {
|
||||
Options options;
|
||||
options.level_compaction_dynamic_level_bytes = false;
|
||||
options.env = CurrentOptions().env;
|
||||
options.create_if_missing = true;
|
||||
options.memtable_factory.reset(test::NewSpecialSkipListFactory(
|
||||
DBTestBase::kNumKeysByGenerateNewRandomFile));
|
||||
|
||||
TestNumInputFilesTotalInputBytesPouplatedInListener* listener =
|
||||
new TestNumInputFilesTotalInputBytesPouplatedInListener();
|
||||
options.listeners.emplace_back(listener);
|
||||
|
||||
options.level0_file_num_compaction_trigger = 4;
|
||||
options.compaction_style = kCompactionStyleLevel;
|
||||
|
||||
DestroyAndReopen(options);
|
||||
Random rnd(301);
|
||||
ASSERT_EQ(listener->num_input_files, 0);
|
||||
ASSERT_EQ(listener->total_num_of_bytes, 0);
|
||||
// Write 4 files in L0
|
||||
for (int i = 0; i < 4; i++) {
|
||||
GenerateNewRandomFile(&rnd);
|
||||
}
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
ASSERT_EQ(listener->num_input_files, 4);
|
||||
ASSERT_NE(listener->total_num_of_bytes, 0);
|
||||
}
|
||||
|
||||
class TestCompactionReasonListener : public EventListener {
|
||||
public:
|
||||
void OnCompactionCompleted(DB* /*db*/, const CompactionJobInfo& ci) override {
|
||||
|
||||
+2
-1
@@ -196,10 +196,11 @@ bool MemTable::ShouldFlushNow() {
|
||||
// allocate one more block.
|
||||
const double kAllowOverAllocationRatio = 0.6;
|
||||
|
||||
// range deletion use skip list which allocates all memeory through `arena_`
|
||||
assert(range_del_table_->ApproximateMemoryUsage() == 0);
|
||||
// If arena still have room for new block allocation, we can safely say it
|
||||
// shouldn't flush.
|
||||
auto allocated_memory = table_->ApproximateMemoryUsage() +
|
||||
range_del_table_->ApproximateMemoryUsage() +
|
||||
arena_.MemoryAllocatedBytes();
|
||||
|
||||
approximate_memory_usage_.store(allocated_memory, std::memory_order_relaxed);
|
||||
|
||||
@@ -688,6 +688,7 @@ class MemTable final : public ReadOnlyMemTable {
|
||||
// Update counters and flush status after inserting a whole write batch
|
||||
// Used in concurrent memtable inserts.
|
||||
void BatchPostProcess(const MemTablePostProcessInfo& update_counters) {
|
||||
table_->BatchPostProcess();
|
||||
num_entries_.fetch_add(update_counters.num_entries,
|
||||
std::memory_order_relaxed);
|
||||
data_size_.fetch_add(update_counters.data_size, std::memory_order_relaxed);
|
||||
@@ -698,6 +699,10 @@ class MemTable final : public ReadOnlyMemTable {
|
||||
if (update_counters.num_range_deletes > 0) {
|
||||
num_range_deletes_.fetch_add(update_counters.num_range_deletes,
|
||||
std::memory_order_relaxed);
|
||||
// noop for skip-list memtable
|
||||
// Besides correctness test in stress test, memtable flush record count
|
||||
// check will catch this if it were not noop.
|
||||
// range_del_table_->BatchPostProcess();
|
||||
}
|
||||
UpdateFlushState();
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ static const std::map<PeriodicTaskType, uint64_t> kDefaultPeriodSeconds = {
|
||||
{PeriodicTaskType::kPersistStats, kInvalidPeriodSec},
|
||||
{PeriodicTaskType::kFlushInfoLog, 10},
|
||||
{PeriodicTaskType::kRecordSeqnoTime, kInvalidPeriodSec},
|
||||
{PeriodicTaskType::kTriggerCompaction, 12 * 60 * 60} // 12 hours
|
||||
};
|
||||
|
||||
static const std::map<PeriodicTaskType, std::string> kPeriodicTaskTypeNames = {
|
||||
@@ -33,16 +34,20 @@ static const std::map<PeriodicTaskType, std::string> kPeriodicTaskTypeNames = {
|
||||
{PeriodicTaskType::kPersistStats, "pst_st"},
|
||||
{PeriodicTaskType::kFlushInfoLog, "flush_info_log"},
|
||||
{PeriodicTaskType::kRecordSeqnoTime, "record_seq_time"},
|
||||
{PeriodicTaskType::kTriggerCompaction, "trigger_compaction"},
|
||||
};
|
||||
|
||||
Status PeriodicTaskScheduler::Register(PeriodicTaskType task_type,
|
||||
const PeriodicTaskFunc& fn) {
|
||||
return Register(task_type, fn, kDefaultPeriodSeconds.at(task_type));
|
||||
const PeriodicTaskFunc& fn,
|
||||
bool run_immediately) {
|
||||
return Register(task_type, fn, kDefaultPeriodSeconds.at(task_type),
|
||||
run_immediately);
|
||||
}
|
||||
|
||||
Status PeriodicTaskScheduler::Register(PeriodicTaskType task_type,
|
||||
const PeriodicTaskFunc& fn,
|
||||
uint64_t repeat_period_seconds) {
|
||||
uint64_t repeat_period_seconds,
|
||||
bool run_immediately) {
|
||||
MutexLock l(&timer_mutex);
|
||||
static std::atomic<uint64_t> initial_delay(0);
|
||||
|
||||
@@ -65,10 +70,13 @@ Status PeriodicTaskScheduler::Register(PeriodicTaskType task_type,
|
||||
std::string unique_id =
|
||||
kPeriodicTaskTypeNames.at(task_type) + std::to_string(id_++);
|
||||
|
||||
bool succeeded = timer_->Add(
|
||||
fn, unique_id,
|
||||
(initial_delay.fetch_add(1) % repeat_period_seconds) * kMicrosInSecond,
|
||||
repeat_period_seconds * kMicrosInSecond);
|
||||
uint64_t initial_delay_micros =
|
||||
(initial_delay.fetch_add(1) % repeat_period_seconds) * kMicrosInSecond;
|
||||
if (!run_immediately) {
|
||||
initial_delay_micros += repeat_period_seconds * kMicrosInSecond;
|
||||
}
|
||||
bool succeeded = timer_->Add(fn, unique_id, initial_delay_micros,
|
||||
repeat_period_seconds * kMicrosInSecond);
|
||||
if (!succeeded) {
|
||||
return Status::Aborted("Failed to register periodic task");
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ enum class PeriodicTaskType : uint8_t {
|
||||
kPersistStats,
|
||||
kFlushInfoLog,
|
||||
kRecordSeqnoTime,
|
||||
kTriggerCompaction,
|
||||
kMax,
|
||||
};
|
||||
|
||||
@@ -42,13 +43,16 @@ class PeriodicTaskScheduler {
|
||||
PeriodicTaskScheduler& operator=(PeriodicTaskScheduler&&) = delete;
|
||||
|
||||
// Register a task with its default repeat period. Thread safe call.
|
||||
Status Register(PeriodicTaskType task_type, const PeriodicTaskFunc& fn);
|
||||
// @param run_immediately If true, the task will run soon after it's
|
||||
// scheduled, instead of waiting for the repeat period.
|
||||
Status Register(PeriodicTaskType task_type, const PeriodicTaskFunc& fn,
|
||||
bool run_immediately);
|
||||
|
||||
// Register a task with specified repeat period. 0 is an invalid argument
|
||||
// (kInvalidPeriodSec). To stop the task, please use Unregister().
|
||||
// Thread safe call.
|
||||
Status Register(PeriodicTaskType task_type, const PeriodicTaskFunc& fn,
|
||||
uint64_t repeat_period_seconds);
|
||||
uint64_t repeat_period_seconds, bool run_immediately);
|
||||
|
||||
// Unregister the task. Thread safe call.
|
||||
Status Unregister(PeriodicTaskType task_type);
|
||||
|
||||
@@ -56,6 +56,12 @@ TEST_F(PeriodicTaskSchedulerTest, Basic) {
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::FlushInfoLog:StartRunning",
|
||||
[&](void*) { flush_info_log_counter++; });
|
||||
|
||||
int trigger_compaction_counter = 0;
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::TriggerPeriodicCompaction:StartRunning",
|
||||
[&](void*) { trigger_compaction_counter++; });
|
||||
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
Reopen(options);
|
||||
@@ -70,7 +76,7 @@ TEST_F(PeriodicTaskSchedulerTest, Basic) {
|
||||
|
||||
const PeriodicTaskScheduler& scheduler =
|
||||
dbfull()->TEST_GetPeriodicTaskScheduler();
|
||||
ASSERT_EQ(3, scheduler.TEST_GetValidTaskNum());
|
||||
ASSERT_EQ((int)PeriodicTaskType::kMax - 1, scheduler.TEST_GetValidTaskNum());
|
||||
|
||||
ASSERT_EQ(1, dump_st_counter);
|
||||
ASSERT_EQ(1, pst_st_counter);
|
||||
@@ -103,14 +109,14 @@ TEST_F(PeriodicTaskSchedulerTest, Basic) {
|
||||
ASSERT_EQ(3, pst_st_counter);
|
||||
ASSERT_EQ(4, flush_info_log_counter);
|
||||
|
||||
ASSERT_EQ(1u, scheduler.TEST_GetValidTaskNum());
|
||||
ASSERT_EQ(2u, scheduler.TEST_GetValidTaskNum());
|
||||
|
||||
// Re-enable one task
|
||||
ASSERT_OK(dbfull()->SetDBOptions({{"stats_dump_period_sec", "5"}}));
|
||||
ASSERT_EQ(5u, dbfull()->GetDBOptions().stats_dump_period_sec);
|
||||
ASSERT_EQ(0u, dbfull()->GetDBOptions().stats_persist_period_sec);
|
||||
|
||||
ASSERT_EQ(2, scheduler.TEST_GetValidTaskNum());
|
||||
ASSERT_EQ(3, scheduler.TEST_GetValidTaskNum());
|
||||
|
||||
dbfull()->TEST_WaitForPeriodicTaskRun(
|
||||
[&] { mock_clock_->MockSleepForSeconds(static_cast<int>(kPeriodSec)); });
|
||||
@@ -118,6 +124,16 @@ TEST_F(PeriodicTaskSchedulerTest, Basic) {
|
||||
ASSERT_EQ(3, pst_st_counter);
|
||||
ASSERT_EQ(5, flush_info_log_counter);
|
||||
|
||||
ASSERT_EQ(0, trigger_compaction_counter);
|
||||
dbfull()->TEST_WaitForPeriodicTaskRun([&] {
|
||||
mock_clock_->MockSleepForSeconds(static_cast<int>(12 * 60 * 60));
|
||||
});
|
||||
ASSERT_EQ(1, trigger_compaction_counter);
|
||||
dbfull()->TEST_WaitForPeriodicTaskRun([&] {
|
||||
mock_clock_->MockSleepForSeconds(static_cast<int>(12 * 60 * 60));
|
||||
});
|
||||
ASSERT_EQ(2, trigger_compaction_counter);
|
||||
|
||||
Close();
|
||||
}
|
||||
|
||||
@@ -150,7 +166,9 @@ TEST_F(PeriodicTaskSchedulerTest, MultiInstances) {
|
||||
auto dbi = static_cast_with_check<DBImpl>(dbs[kInstanceNum - 1]);
|
||||
|
||||
const PeriodicTaskScheduler& scheduler = dbi->TEST_GetPeriodicTaskScheduler();
|
||||
ASSERT_EQ(kInstanceNum * 3, scheduler.TEST_GetValidTaskNum());
|
||||
// kRecordSeqnoTime is not registered since the feature is not enabled
|
||||
ASSERT_EQ(kInstanceNum * ((int)PeriodicTaskType::kMax - 1),
|
||||
scheduler.TEST_GetValidTaskNum());
|
||||
|
||||
int expected_run = kInstanceNum;
|
||||
dbi->TEST_WaitForPeriodicTaskRun(
|
||||
|
||||
+2
-1
@@ -146,7 +146,8 @@ Status TableCache::GetTableReader(
|
||||
s = mutable_cf_options.table_factory->NewTableReader(
|
||||
ro,
|
||||
TableReaderOptions(
|
||||
ioptions_, mutable_cf_options.prefix_extractor, file_options,
|
||||
ioptions_, mutable_cf_options.prefix_extractor,
|
||||
mutable_cf_options.compression_manager.get(), file_options,
|
||||
internal_comparator,
|
||||
mutable_cf_options.block_protection_bytes_per_key, skip_filters,
|
||||
immortal_tables_, false /* force_direct_prefetch */, level,
|
||||
|
||||
@@ -1135,6 +1135,15 @@ void DumpManifestHandler::CheckIterationResult(const log::Reader& reader,
|
||||
// Print out DebugStrings. Can include non-terminating null characters.
|
||||
fwrite(cfd->current()->DebugString(hex_).data(), sizeof(char),
|
||||
cfd->current()->DebugString(hex_).size(), stdout);
|
||||
|
||||
fprintf(stdout,
|
||||
"By default, manifest file dump prints LSM trees as if %d levels "
|
||||
"were configured, "
|
||||
"which is not necessarily true for the column family (CF) this "
|
||||
"manifest is associated with. "
|
||||
"Please consult other DB files, such as the OPTIONS file, to "
|
||||
"confirm.\n",
|
||||
cfd->ioptions().num_levels);
|
||||
}
|
||||
fprintf(stdout,
|
||||
"next_file_number %" PRIu64 " last_sequence %" PRIu64
|
||||
|
||||
+2
-6
@@ -551,9 +551,6 @@ Status WriteBatchInternal::Iterate(const WriteBatch* wb,
|
||||
|
||||
if (LIKELY(!s.IsTryAgain())) {
|
||||
last_was_try_again = false;
|
||||
tag = 0;
|
||||
column_family = 0; // default
|
||||
|
||||
s = ReadRecordFromWriteBatch(&input, &tag, &column_family, &key, &value,
|
||||
&blob, &xid, &write_unix_time);
|
||||
if (!s.ok()) {
|
||||
@@ -1897,7 +1894,6 @@ Status WriteBatch::VerifyChecksum() const {
|
||||
// ReadRecordFromWriteBatch
|
||||
key.clear();
|
||||
value.clear();
|
||||
column_family = 0;
|
||||
s = ReadRecordFromWriteBatch(&input, &tag, &column_family, &key, &value,
|
||||
&blob, &xid, /*write_unix_time=*/nullptr);
|
||||
if (!s.ok()) {
|
||||
@@ -3214,11 +3210,11 @@ Status WriteBatchInternal::InsertInto(
|
||||
ColumnFamilyMemTables* memtables, FlushScheduler* flush_scheduler,
|
||||
TrimHistoryScheduler* trim_history_scheduler,
|
||||
bool ignore_missing_column_families, uint64_t recovery_log_number, DB* db,
|
||||
bool concurrent_memtable_writes, bool seq_per_batch, bool batch_per_txn) {
|
||||
bool seq_per_batch, bool batch_per_txn) {
|
||||
MemTableInserter inserter(
|
||||
sequence, memtables, flush_scheduler, trim_history_scheduler,
|
||||
ignore_missing_column_families, recovery_log_number, db,
|
||||
concurrent_memtable_writes, nullptr /* prot_info */,
|
||||
/*concurrent_memtable_writes=*/false, nullptr /* prot_info */,
|
||||
nullptr /*has_valid_writes*/, seq_per_batch, batch_per_txn);
|
||||
for (auto w : write_group) {
|
||||
if (w->CallbackFailed()) {
|
||||
|
||||
@@ -185,18 +185,19 @@ class WriteBatchInternal {
|
||||
// If flush_scheduler is non-null, it will be invoked if the memtable
|
||||
// should be flushed.
|
||||
//
|
||||
// Under concurrent use, the caller is responsible for making sure that
|
||||
// the memtables object itself is thread-local.
|
||||
// This overload is for non-concurrent insertion only.
|
||||
static Status InsertInto(
|
||||
WriteThread::WriteGroup& write_group, SequenceNumber sequence,
|
||||
ColumnFamilyMemTables* memtables, FlushScheduler* flush_scheduler,
|
||||
TrimHistoryScheduler* trim_history_scheduler,
|
||||
bool ignore_missing_column_families = false, uint64_t log_number = 0,
|
||||
DB* db = nullptr, bool concurrent_memtable_writes = false,
|
||||
bool seq_per_batch = false, bool batch_per_txn = true);
|
||||
DB* db = nullptr, bool seq_per_batch = false, bool batch_per_txn = true);
|
||||
|
||||
// Convenience form of InsertInto when you have only one batch
|
||||
// next_seq returns the seq after last sequence number used in MemTable insert
|
||||
//
|
||||
// Under concurrent use, the caller is responsible for making sure that
|
||||
// the memtables object itself is thread-local.
|
||||
static Status InsertInto(
|
||||
const WriteBatch* batch, ColumnFamilyMemTables* memtables,
|
||||
FlushScheduler* flush_scheduler,
|
||||
|
||||
@@ -398,6 +398,7 @@ DECLARE_bool(use_adaptive_mutex_lru);
|
||||
DECLARE_uint32(compress_format_version);
|
||||
DECLARE_uint64(manifest_preallocation_size);
|
||||
DECLARE_bool(enable_checksum_handoff);
|
||||
DECLARE_string(compression_manager);
|
||||
DECLARE_uint64(max_total_wal_size);
|
||||
DECLARE_double(high_pri_pool_ratio);
|
||||
DECLARE_double(low_pri_pool_ratio);
|
||||
@@ -424,7 +425,9 @@ DECLARE_bool(track_and_verify_wals);
|
||||
DECLARE_bool(enable_remote_compaction);
|
||||
DECLARE_bool(auto_refresh_iterator_with_snapshot);
|
||||
DECLARE_uint32(memtable_op_scan_flush_trigger);
|
||||
DECLARE_uint32(memtable_avg_op_scan_flush_trigger);
|
||||
DECLARE_uint32(ingest_wbwi_one_in);
|
||||
DECLARE_bool(universal_reduce_file_locking);
|
||||
|
||||
constexpr long KB = 1024;
|
||||
constexpr int kRandomValueMaxFactor = 3;
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
// This source code is licensed under both the GPLv2 (found in the
|
||||
// COPYING file in the root directory) and Apache 2.0 License
|
||||
// (found in the LICENSE.Apache file in the root directory).
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "test_util/testutil.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
class DbStressCustomCompressionManager : public CompressionManager {
|
||||
public:
|
||||
const char* Name() const override {
|
||||
return "DbStressCustomCompressionManager";
|
||||
}
|
||||
const char* CompatibilityName() const override { return "DbStressCustom1"; }
|
||||
|
||||
bool SupportsCompressionType(CompressionType type) const override {
|
||||
return default_->SupportsCompressionType(type) ||
|
||||
type == kCustomCompressionAA || type == kCustomCompressionAB ||
|
||||
type == kCustomCompressionAC;
|
||||
}
|
||||
|
||||
std::unique_ptr<Compressor> GetCompressor(const CompressionOptions& opts,
|
||||
CompressionType type) override {
|
||||
// db_stress never specifies a custom type, so we randomly use them anyway
|
||||
// when this compression manager is used.
|
||||
std::array<CompressionType, 4> choices = {
|
||||
type, kCustomCompressionAA, kCustomCompressionAB, kCustomCompressionAC};
|
||||
type = choices[Random::GetTLSInstance()->Uniform(4)];
|
||||
switch (static_cast<unsigned char>(type)) {
|
||||
case kCustomCompressionAA:
|
||||
return std::make_unique<
|
||||
test::CompressorCustomAlg<kCustomCompressionAA>>();
|
||||
case kCustomCompressionAB:
|
||||
return std::make_unique<
|
||||
test::CompressorCustomAlg<kCustomCompressionAB>>();
|
||||
case kCustomCompressionAC:
|
||||
return std::make_unique<
|
||||
test::CompressorCustomAlg<kCustomCompressionAC>>();
|
||||
// Also support built-in compression algorithms
|
||||
default:
|
||||
return GetBuiltinV2CompressionManager()->GetCompressor(opts, type);
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<Decompressor> GetDecompressor() override {
|
||||
return std::make_shared<test::DecompressorCustomAlg>();
|
||||
}
|
||||
|
||||
std::shared_ptr<Decompressor> GetDecompressorForTypes(
|
||||
const CompressionType* types_begin,
|
||||
const CompressionType* types_end) override {
|
||||
auto decomp = std::make_shared<test::DecompressorCustomAlg>();
|
||||
decomp->SetAllowedTypes(types_begin, types_end);
|
||||
return decomp;
|
||||
}
|
||||
|
||||
protected:
|
||||
std::shared_ptr<CompressionManager> default_ =
|
||||
GetBuiltinV2CompressionManager();
|
||||
};
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
@@ -467,7 +467,9 @@ DEFINE_uint64(blob_file_size,
|
||||
DEFINE_string(blob_compression_type, "none",
|
||||
"[Integrated BlobDB] The compression algorithm to use for large "
|
||||
"values stored in blob files.");
|
||||
|
||||
DEFINE_string(compression_manager, "mixed",
|
||||
"Ability to change compression manager specified in "
|
||||
"simple_mixed_manager.h (mixed -> roundRobin)");
|
||||
DEFINE_bool(enable_blob_garbage_collection,
|
||||
ROCKSDB_NAMESPACE::AdvancedColumnFamilyOptions()
|
||||
.enable_blob_garbage_collection,
|
||||
@@ -1482,4 +1484,15 @@ DEFINE_uint32(
|
||||
ROCKSDB_NAMESPACE::ColumnFamilyOptions().memtable_op_scan_flush_trigger,
|
||||
"Sets CF option memtable_op_scan_flush_trigger.");
|
||||
|
||||
DEFINE_uint32(
|
||||
memtable_avg_op_scan_flush_trigger,
|
||||
ROCKSDB_NAMESPACE::ColumnFamilyOptions().memtable_avg_op_scan_flush_trigger,
|
||||
"Sets CF option memtable_avg_op_scan_flush_trigger.");
|
||||
|
||||
DEFINE_bool(
|
||||
universal_reduce_file_locking,
|
||||
ROCKSDB_NAMESPACE::ColumnFamilyOptions()
|
||||
.compaction_options_universal.reduce_file_locking,
|
||||
"Sets "
|
||||
"ColumnFamilyOptions().compaciton_options_universal.reduce_file_locking.");
|
||||
#endif // GFLAGS
|
||||
@@ -11,6 +11,7 @@
|
||||
#include <ios>
|
||||
#include <thread>
|
||||
|
||||
#include "db_stress_tool/db_stress_compression_manager.h"
|
||||
#include "db_stress_tool/db_stress_listener.h"
|
||||
#include "rocksdb/io_status.h"
|
||||
#include "rocksdb/options.h"
|
||||
@@ -34,6 +35,7 @@
|
||||
#include "rocksdb/utilities/write_batch_with_index.h"
|
||||
#include "test_util/testutil.h"
|
||||
#include "util/cast_util.h"
|
||||
#include "util/simple_mixed_compressor.h"
|
||||
#include "utilities/backup/backup_engine_impl.h"
|
||||
#include "utilities/fault_injection_fs.h"
|
||||
#include "utilities/fault_injection_secondary_cache.h"
|
||||
@@ -849,9 +851,15 @@ Status StressTest::NewTxn(WriteOptions& write_opts, ThreadState* thread,
|
||||
assert(FLAGS_user_timestamp_size == 0);
|
||||
if (thread->rand.OneIn(2)) {
|
||||
txn_options.commit_bypass_memtable = true;
|
||||
} else {
|
||||
}
|
||||
if (thread->rand.OneIn(2)) {
|
||||
txn_options.large_txn_commit_optimize_threshold = 1;
|
||||
}
|
||||
if (thread->rand.OneIn(2) ||
|
||||
(!txn_options.commit_bypass_memtable &&
|
||||
txn_options.large_txn_commit_optimize_threshold != 1)) {
|
||||
txn_options.large_txn_commit_optimize_byte_threshold = 1;
|
||||
}
|
||||
if (commit_bypass_memtable) {
|
||||
*commit_bypass_memtable = txn_options.commit_bypass_memtable;
|
||||
}
|
||||
@@ -3405,7 +3413,44 @@ void StressTest::Open(SharedState* shared, bool reopen) {
|
||||
InitializeOptionsFromFlags(cache_, filter_policy_, options_);
|
||||
}
|
||||
InitializeOptionsGeneral(cache_, filter_policy_, sqfc_factory_, options_);
|
||||
|
||||
{
|
||||
// We must register any compression managers with a custom
|
||||
// CompatibilityName() so that if it was used in a past invocation but not
|
||||
// the current invocation, we can still read the SST files requiring it.
|
||||
static std::once_flag loaded;
|
||||
std::call_once(loaded, [&]() {
|
||||
TEST_AllowUnsupportedFormatVersion() = true;
|
||||
auto& library = *ObjectLibrary::Default();
|
||||
library.AddFactory<CompressionManager>(
|
||||
DbStressCustomCompressionManager().CompatibilityName(),
|
||||
[](const std::string& /*uri*/,
|
||||
std::unique_ptr<CompressionManager>* guard,
|
||||
std::string* /*errmsg*/) {
|
||||
*guard = std::make_unique<DbStressCustomCompressionManager>();
|
||||
return guard->get();
|
||||
});
|
||||
});
|
||||
}
|
||||
if (!strcasecmp(FLAGS_compression_manager.c_str(), "custom")) {
|
||||
options_.compression_manager =
|
||||
std::make_shared<DbStressCustomCompressionManager>();
|
||||
} else if (!strcasecmp(FLAGS_compression_manager.c_str(), "mixed")) {
|
||||
options_.compression_manager =
|
||||
std::make_shared<RoundRobinManager>(GetBuiltinV2CompressionManager());
|
||||
} else if (!strcasecmp(FLAGS_compression_manager.c_str(), "randommixed")) {
|
||||
options_.compression_manager =
|
||||
std::make_shared<RandomMixedCompressionManager>(
|
||||
GetBuiltinV2CompressionManager());
|
||||
} else if (!strcasecmp(FLAGS_compression_manager.c_str(), "autoskip")) {
|
||||
options_.compression_manager =
|
||||
CreateAutoSkipCompressionManager(GetBuiltinV2CompressionManager());
|
||||
} else if (!strcasecmp(FLAGS_compression_manager.c_str(), "none")) {
|
||||
// Nothing to do using default compression manager
|
||||
} else {
|
||||
fprintf(stderr, "Unknown compression manager: %s\n",
|
||||
FLAGS_compression_manager.c_str());
|
||||
exit(1);
|
||||
}
|
||||
if (FLAGS_prefix_size == 0 && FLAGS_rep_factory == kHashSkipList) {
|
||||
fprintf(stderr,
|
||||
"prefeix_size cannot be zero if memtablerep == prefix_hash\n");
|
||||
@@ -4331,6 +4376,8 @@ void InitializeOptionsFromFlags(
|
||||
}
|
||||
|
||||
options.memtable_op_scan_flush_trigger = FLAGS_memtable_op_scan_flush_trigger;
|
||||
options.compaction_options_universal.reduce_file_locking =
|
||||
FLAGS_universal_reduce_file_locking;
|
||||
}
|
||||
|
||||
void InitializeOptionsGeneral(
|
||||
|
||||
Vendored
+4
-2
@@ -169,8 +169,9 @@ class PosixClock : public SystemClock {
|
||||
struct timespec ts;
|
||||
clock_gettime(CLOCK_THREAD_CPUTIME_ID, &ts);
|
||||
return (static_cast<uint64_t>(ts.tv_sec) * 1000000000 + ts.tv_nsec) / 1000;
|
||||
#endif
|
||||
#else
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
uint64_t CPUNanos() override {
|
||||
@@ -179,8 +180,9 @@ class PosixClock : public SystemClock {
|
||||
struct timespec ts;
|
||||
clock_gettime(CLOCK_THREAD_CPUTIME_ID, &ts);
|
||||
return static_cast<uint64_t>(ts.tv_sec) * 1000000000 + ts.tv_nsec;
|
||||
#endif
|
||||
#else
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
void SleepForMicroseconds(int micros) override { usleep(micros); }
|
||||
|
||||
Vendored
-1
@@ -868,7 +868,6 @@ class PosixFileSystem : public FileSystem {
|
||||
IOOptions opts;
|
||||
return CreateDirIfMissing(*result, opts, nullptr);
|
||||
}
|
||||
return IOStatus::OK();
|
||||
}
|
||||
|
||||
IOStatus GetFreeSpace(const std::string& fname, const IOOptions& /*opts*/,
|
||||
|
||||
@@ -162,7 +162,6 @@ class SstFileManagerImpl : public SstFileManager {
|
||||
void Close();
|
||||
|
||||
void SetStatisticsPtr(const std::shared_ptr<Statistics>& stats) override {
|
||||
stats_ = stats;
|
||||
delete_scheduler_.SetStatisticsPtr(stats);
|
||||
}
|
||||
|
||||
@@ -216,7 +215,6 @@ class SstFileManagerImpl : public SstFileManager {
|
||||
std::list<ErrorHandler*> error_handler_list_;
|
||||
// Pointer to ErrorHandler instance that is currently processing recovery
|
||||
ErrorHandler* cur_instance_;
|
||||
std::shared_ptr<Statistics> stats_;
|
||||
};
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
@@ -687,9 +687,9 @@ IOStatus WritableFileWriter::WriteBufferedWithChecksum(const IOOptions& opts,
|
||||
if (rate_limiter_ != nullptr && rate_limiter_priority_used != Env::IO_TOTAL) {
|
||||
while (data_size > 0) {
|
||||
size_t tmp_size;
|
||||
tmp_size = rate_limiter_->RequestToken(data_size, buf_.Alignment(),
|
||||
rate_limiter_priority_used, stats_,
|
||||
RateLimiter::OpType::kWrite);
|
||||
tmp_size =
|
||||
rate_limiter_->RequestToken(data_size, 0, rate_limiter_priority_used,
|
||||
stats_, RateLimiter::OpType::kWrite);
|
||||
data_size -= tmp_size;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,7 +91,8 @@ TableReader* NewTableReader(const std::string& sst_file_path,
|
||||
}
|
||||
if (s.ok()) {
|
||||
ImmutableOptions iopts(options, cf_ioptions);
|
||||
TableReaderOptions t_opt(iopts, /*prefix_extractor=*/nullptr, env_options,
|
||||
TableReaderOptions t_opt(iopts, /*prefix_extractor=*/nullptr,
|
||||
/*compression_manager=*/nullptr, env_options,
|
||||
cf_ioptions.internal_comparator,
|
||||
0 /* block_protection_bytes_per_key */);
|
||||
t_opt.largest_seqno = kMaxSequenceNumber;
|
||||
|
||||
@@ -0,0 +1,598 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
// 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).
|
||||
//
|
||||
// APIs for customizing compression in RocksDB.
|
||||
//
|
||||
// ***********************************************************************
|
||||
// EXPERIMENTAL - subject to change while under development
|
||||
// ***********************************************************************
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "rocksdb/cache.h"
|
||||
#include "rocksdb/compression_type.h"
|
||||
#include "rocksdb/data_structure.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
// TODO: alias/adapt for compression
|
||||
struct FilterBuildingContext;
|
||||
|
||||
// A Compressor represents a very specific but potentially adapting strategy for
|
||||
// compressing blocks, including the relevant algorithm(s), options, dictionary,
|
||||
// etc. as applicable--every input except the sequence of bytes to compress.
|
||||
// Compressor is generally thread-safe so can be shared by multiple threads. (It
|
||||
// could make sense to convert unique_ptr<Compressor> to
|
||||
// shared_ptr<Compressor>.) A Compressor for data files is expected to be used
|
||||
// for just one file, so that compression strategy can be explicitly
|
||||
// reconsidered for each new file. However, a Compressor for in-memory use could
|
||||
// live indefinitely.
|
||||
//
|
||||
// If a single thread is doing many compressions under the same strategy, it
|
||||
// should request a WorkingArea that will in some cases make repeated
|
||||
// compression in a single thread more efficient. Unlike the rest of Compressor,
|
||||
// each WorkingArea can only be used by one thread at a time. WorkingAreas can
|
||||
// have pre-allocated space and/or data structures, and/or thread-local
|
||||
// statistics that are later incorporated into shared statistics objects.
|
||||
//
|
||||
// The Compressor marks each block with a CompressionType to guide
|
||||
// decompression. However, the compression dictionary (or whether there is one
|
||||
// associated) is determined at Compressor creation time, though the process of
|
||||
// getting a Compressor with a dictionary starts with a Compressor without
|
||||
// dictionary (which will often be relevant alongside); see relevant functions.
|
||||
// If the Compressor wants to decide block-by-block whether to apply the
|
||||
// configured dictionary, that would need to be encoded in CompressionType or
|
||||
// the compressed output. (NOTE: this was historically NOT encoded in
|
||||
// CompressionType and instead implied by BlockType and the presence of a
|
||||
// dictionary block in the file. Some of the resulting awkwardness includes
|
||||
// a number of built-in CompressionTypes that ignore any dictionary block in
|
||||
// the file; therefore they cannot accommodate dictionary compression in the
|
||||
// future without a schema change / extension.)
|
||||
class Compressor {
|
||||
public:
|
||||
Compressor() = default;
|
||||
virtual ~Compressor() = default;
|
||||
|
||||
// Class name for logging / debugging purposes
|
||||
virtual const char* Name() const = 0;
|
||||
|
||||
// Potentially more elaborate identifier for logging / debugging purposes
|
||||
virtual std::string GetId() const {
|
||||
std::string id = Name();
|
||||
return id;
|
||||
}
|
||||
|
||||
// Returns the max total bytes of for all sampled blocks for creating the data
|
||||
// dictionary, or zero indicating dictionary compression should not be
|
||||
// used/configured. This will typically be called after
|
||||
// CompressionManager::GetCompressor() to see if samples should be accumulated
|
||||
// and passed to MaybeCloneSpecialized().
|
||||
virtual size_t GetMaxSampleSizeIfWantDict(CacheEntryRole block_type) const {
|
||||
// Default implementation: no dictionary
|
||||
(void)block_type;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Returns the serialized form of the data dictionary associated with this
|
||||
// Compressor. NOTE: empty dict is equivalent to no dict.
|
||||
virtual Slice GetSerializedDict() const { return Slice(); }
|
||||
|
||||
// If there's a dominant compression type returned by this compressor as
|
||||
// configured, return it. Otherwise, return kDisableCompressionOption.
|
||||
virtual CompressionType GetPreferredCompressionType() const {
|
||||
return CompressionType::kDisableCompressionOption;
|
||||
}
|
||||
|
||||
// Utility struct for providing sample data for the compression dictionary.
|
||||
// Potentially extensible by callers of Compressor (but not recommended)
|
||||
struct DictSampleArgs {
|
||||
// All the sample input blocks stored contiguously
|
||||
std::string sample_data;
|
||||
// The lengths of each of the sample blocks in `sample_data`
|
||||
std::vector<size_t> sample_lens;
|
||||
|
||||
bool empty() { return sample_data.empty(); }
|
||||
bool Verify() {
|
||||
size_t total_len = 0;
|
||||
for (auto len : sample_lens) {
|
||||
total_len += len;
|
||||
}
|
||||
return total_len == sample_data.size();
|
||||
}
|
||||
};
|
||||
|
||||
// Create potential variants of the same Compressor that might be
|
||||
// (a) optimized for a particular block type (does not affect correct
|
||||
// decompression), and/or
|
||||
// (b) configured to use a compression dictionary, based on the given
|
||||
// samples (decompression must provide the dictionary from
|
||||
// GetSerializedDict())
|
||||
// Return of nullptr indicates no specialization exists or was attempted
|
||||
// and the caller is best to use the current Compressor for the desired
|
||||
// scenario. Using CacheEntryRole:kMisc for block_type generally means
|
||||
// "unspecified", and both parameters are merely suggestions. The exact
|
||||
// dictionary associated with a returned compressor must be read from
|
||||
// GetSerializedDict().
|
||||
virtual std::unique_ptr<Compressor> MaybeCloneSpecialized(
|
||||
CacheEntryRole block_type, DictSampleArgs&& dict_samples) {
|
||||
// Default implementation: no specialization
|
||||
(void)block_type;
|
||||
(void)dict_samples;
|
||||
// Caller should have checked GetMaxSampleSizeIfWantDict before attempting
|
||||
// to provide dictionary samples
|
||||
assert(dict_samples.empty());
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// A WorkingArea is an optional structure (both for callers and
|
||||
// implementations) that can enable optimizing repeated compressions by
|
||||
// reusing working space or thread-local tracking of statistics or trends.
|
||||
// This enables use of ZSTD context, for example.
|
||||
//
|
||||
// EXTENSIBLE or reinterpret_cast-able by custom Compressor implementations
|
||||
struct WorkingArea {};
|
||||
|
||||
protected:
|
||||
// To allow for flexible re-use / reclaimation, we have explicit Get and
|
||||
// Release functions, and usually wrap in a special RAII smart pointer.
|
||||
// For example, a WorkingArea could be saved/recycled in thread-local or
|
||||
// core-local storage, or heap managed, etc., though an explicit WorkingArea
|
||||
// is only advised for repeated compression (by a single thread).
|
||||
virtual void ReleaseWorkingArea(WorkingArea*) {}
|
||||
|
||||
public:
|
||||
using ManagedWorkingArea =
|
||||
ManagedPtr<WorkingArea, Compressor, &Compressor::ReleaseWorkingArea>;
|
||||
|
||||
// See struct WorkingArea above
|
||||
virtual ManagedWorkingArea ObtainWorkingArea() {
|
||||
// Default implementation: no working area
|
||||
return {};
|
||||
}
|
||||
|
||||
// Compress `uncompressed_data` to `compressed_output`, which should be
|
||||
// passed in empty. Note that the compressed output will be decompressed
|
||||
// by the sequence Decompressor::ExtractUncompressedSize() followed by
|
||||
// Decompressor::DecompressBlock(), which must also be provided the same
|
||||
// CompressionType saved in `out_compression_type`. (In many configurations,
|
||||
// `compressed_output` will have a prefix storing the uncompressed_data size
|
||||
// before the compressed bytes returned by the underlying compression
|
||||
// algorithm. And the compression type is usually stored adjacent to the
|
||||
// compressed data, or in some cases assumed/asserted based on the particular
|
||||
// Compressor.)
|
||||
//
|
||||
// If return status is not OK, then some fatal condition has arisen. On OK
|
||||
// status, setting `*out_compression_type = kNoCompression` means compression
|
||||
// is declined and the caller should use the original uncompressed_data and
|
||||
// ignore any result in `compressed_output`. Otherwise, compression has
|
||||
// happened with results in `compressed_output` and `out_compression_type`,
|
||||
// which are allowed to vary from call to call.
|
||||
//
|
||||
// The working area is optional and used to optimize repeated compression by
|
||||
// a single thread. ManagedWorkingArea is provided rather than just
|
||||
// WorkingArea so that it can be used only if the `owner` matches expectation.
|
||||
// This could be useful for a Compressor wrapping more than one alternative
|
||||
// underlying Compressor.
|
||||
//
|
||||
// TODO: instead of string, consider a buffer only large enough for max
|
||||
// tolerable compressed size. Does that work for all existing algorithms?
|
||||
// * Looks like Snappy doesn't support that. :(
|
||||
// * Except perhaps using the Sink interface
|
||||
// * But looks like everything else should. :)
|
||||
// Could save CPU by eliminating extra zero-ing and giving up quicker when
|
||||
// ratio is insufficient.
|
||||
virtual Status CompressBlock(Slice uncompressed_data,
|
||||
std::string* compressed_output,
|
||||
CompressionType* out_compression_type,
|
||||
ManagedWorkingArea* working_area) = 0;
|
||||
|
||||
// TODO: something to populate table properties based on settings, after all
|
||||
// or as WorkingAreas released. Maybe also update stats, or that could be in
|
||||
// thread-specific WorkingArea.
|
||||
};
|
||||
|
||||
// A Decompressor usually has a wide capability to decompress all kinds of
|
||||
// compressed data in the scope of a CompressionManager (see that class below),
|
||||
// except
|
||||
// (a) it might be optimized for or limited to a particular compression type(s)
|
||||
// (see GetDecompressor* functions for in CompressionManager),
|
||||
// (b) distinct Decompressors are required to decompress with compression
|
||||
// dictionaries. (Decompressors are generally associated with empty/no
|
||||
// dictionary unless created with MaybeCloneForDict().)
|
||||
//
|
||||
// Similar to Compressor, Decompressor is generally thread safe except that each
|
||||
// WorkingArea can only be used by a single thread at a time.
|
||||
//
|
||||
// Decompressors known to be associated with no dictionary are typically
|
||||
// returned as shared_ptr, because they are broadly usable across threads.
|
||||
// Because compression dictionaries are externally managed (see
|
||||
// MaybeCloneForDict()), Decompressors associated with compression dictionaries
|
||||
// are typically returned as unique_ptr, so that they are more easily
|
||||
// guaranteed not to outlive their dictionaries (e.g. in block cache).
|
||||
// Decompressors associated with compression dictionaries might include a
|
||||
// processed or "digested" form of the raw dictionary for efficient repeated
|
||||
// compressions.
|
||||
//
|
||||
// NOTE: Splitting the interface between ExtractUncompressedSize and
|
||||
// DecompressBlock leaves to the caller details of (and flexibility in)
|
||||
// allocating buffers for decompressing into. For example, the data could be
|
||||
// decompressed into part of a single buffer allocated to hold a block's
|
||||
// uncompressed contents along with an in-memory object representation of the
|
||||
// block (to reduce fragmentation and other overheads of separate objects).
|
||||
class Decompressor {
|
||||
public:
|
||||
Decompressor() = default;
|
||||
virtual ~Decompressor() = default;
|
||||
|
||||
// A name for logging / debugging purposes
|
||||
virtual const char* Name() const = 0;
|
||||
|
||||
// A WorkingArea is an optional structure (both for callers and
|
||||
// implementations) that can enable optimizing repeated decompressions by
|
||||
// reusing working space or thread-local tracking of statistics. This enables
|
||||
// use of ZSTD context, for example.
|
||||
//
|
||||
// EXTENSIBLE or reinterpret_cast-able by custom Compressor implementations
|
||||
struct WorkingArea {};
|
||||
|
||||
// To allow for flexible re-use / reclaimation, we have explicit Obtain and
|
||||
// Release functions, which are typically wrapped in a special RAII smart
|
||||
// pointer. For example, a WorkingArea could be saved/recycled in thread-local
|
||||
// or core-local storage, or heap managed, etc., though an explicit
|
||||
// WorkingArea is only advised for repeated decompression (by a single
|
||||
// thread). ReleaseWorkingArea() in not intended to be called directly, but
|
||||
// used by ManagedWorkingArea.
|
||||
virtual void ReleaseWorkingArea(WorkingArea* wa) {
|
||||
// Default implementation: no working area
|
||||
(void)wa;
|
||||
assert(wa == nullptr);
|
||||
}
|
||||
|
||||
using ManagedWorkingArea =
|
||||
ManagedPtr<WorkingArea, Decompressor, &Decompressor::ReleaseWorkingArea>;
|
||||
|
||||
virtual ManagedWorkingArea ObtainWorkingArea(CompressionType /*preferred*/) {
|
||||
// Default implementation: no working area
|
||||
return {};
|
||||
}
|
||||
|
||||
// If this Decompressor is associated with a (de)compression dictionary
|
||||
// (created with MaybeCloneForDict()), this returns a pointer to those raw (or
|
||||
// "serialized") bytes, which are externally managed (see
|
||||
// MaybeCloneForDict()).
|
||||
// Default: empty slice => no dictionary
|
||||
virtual const Slice& GetSerializedDict() const;
|
||||
|
||||
// Create a variant of this Decompressor in `out` using the specified raw
|
||||
// ("serialized") dictionary. This step is required for decompressing data
|
||||
// compressed with the same dictionary. The new Decompressor references the
|
||||
// given Slice through its lifetime so the data it points to must be managed
|
||||
// by the caller along with (or beyond) the new Decompressor. If the
|
||||
// dictionary is processed into a form reusable by repeated compressions in
|
||||
// many threads, that happens within this call.
|
||||
//
|
||||
// Must return OK if and only if storing a result in `out`. Otherwise, could
|
||||
// return values like NotSupported - dictionary compression is not (yet)
|
||||
// supported for this kind of Decompressor. Corruption - dictionary is
|
||||
// malformed (though many implementations will accept any data as a
|
||||
// dictionary)
|
||||
virtual Status MaybeCloneForDict(const Slice& /*serialized_dict*/,
|
||||
std::unique_ptr<Decompressor>* /*out*/) {
|
||||
return Status::NotSupported(
|
||||
"Dictionary compression not (yet) supported by " + std::string(Name()));
|
||||
}
|
||||
|
||||
// Memory size of this object and others it owns. Does not include the
|
||||
// serialized dictionary (when used) which is externally managed.
|
||||
virtual size_t ApproximateOwnedMemoryUsage() const {
|
||||
// Default: negligible
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Potentially extensible by callers of Decompressor (but not recommended)
|
||||
struct Args {
|
||||
CompressionType compression_type = kNoCompression;
|
||||
Slice compressed_data;
|
||||
uint64_t uncompressed_size = 0;
|
||||
ManagedWorkingArea* working_area = nullptr;
|
||||
};
|
||||
|
||||
// For efficiency on the read path, RocksDB strongly prefers the uncompressed
|
||||
// data size to be encoded in the compressed data in an easily accessible way,
|
||||
// so that allocation of a potentially long-lived buffer can be ideally sized.
|
||||
// This function determines the uncompressed size and potentially modifies
|
||||
// `args.compressed_data` to strip off the size metadata, for providing both
|
||||
// to DecompressBlock along with an appropriate buffer based on that size.
|
||||
// Some implementations will leave `compressed_data` unmodified and let
|
||||
// DecompressBlock call a library function that processes a format that
|
||||
// includes size metadata (e.g. Snappy).
|
||||
//
|
||||
// Even for legacy cases without size metadata (e.g. some very old RocksDB
|
||||
// formats), an exact size is required and could require decompressing the
|
||||
// data (here and in DecompressBlock()).
|
||||
//
|
||||
// Return non-OK in case of corrupt data or some other unworkable limitation
|
||||
// or failure.
|
||||
//
|
||||
// The default implementation uses a standard format for prepending
|
||||
// uncompressed size to the compressed payload. (RocksDB
|
||||
// compress_format_version=2 except Snappy)
|
||||
virtual Status ExtractUncompressedSize(Args& args);
|
||||
|
||||
// Called to decompress a block of data after running ExtractUncompressedSize
|
||||
// on it. `args.compressed_data` is what ExtractUncompressedSize left there
|
||||
// after potentially stripping off the uncompressed size metadata. Returns OK
|
||||
// iff uncompressed data of size `uncompressed_size` is written to
|
||||
// `uncompressed_output`.
|
||||
virtual Status DecompressBlock(const Args& args,
|
||||
char* uncompressed_output) = 0;
|
||||
};
|
||||
|
||||
// A CompressionManager represents
|
||||
// * When/where/how to use different compressions
|
||||
// * A schema (or set of schemas) and implementation for mapping
|
||||
// <CompressionType, dictionary, compressed data>
|
||||
// to uncompressed data (or error), which can expand over time (error in fewer
|
||||
// cases) for a given CompatibilityName() but can never change that mapping
|
||||
// (because that would break backward compatibility, potential quiet
|
||||
// corruption)
|
||||
// TODO: consider adding optional streaming compression support (low priority)
|
||||
class CompressionManager
|
||||
: public std::enable_shared_from_this<CompressionManager>,
|
||||
public Customizable {
|
||||
public:
|
||||
CompressionManager() = default;
|
||||
virtual ~CompressionManager() = default;
|
||||
static const char* Type() { return "CompressionManager"; }
|
||||
|
||||
// *************** Creating various Compression Managers *************** //
|
||||
// A name for the schema family of this CompressionManager. In short, if
|
||||
// two CompressionManagers have functionally the same Decompressor(s), they
|
||||
// should have the same CompatibilityName(), so that a compatible
|
||||
// CompressionManager/Decompressor might be used if the original is
|
||||
// unavailable. (Name() can be useful in addition to CompatibilityName() for
|
||||
// understanding what compression strategy was used.) This name should be
|
||||
// limited to legal variable names in C++ (alphanumeric and underscores).
|
||||
virtual const char* CompatibilityName() const = 0;
|
||||
|
||||
// Default implementation checks the current compatibility name and returns
|
||||
// this CompressionManager (via `out`) if appropriate, and otherwise defers
|
||||
// to CreateFromString(). Failure should simply be a matter of "not found" in
|
||||
// which case nullptr is returned.
|
||||
virtual std::shared_ptr<CompressionManager> FindCompatibleCompressionManager(
|
||||
Slice compatibility_name);
|
||||
|
||||
// Create or find a CompressionManager from a string, including built-in
|
||||
// CompressionManager types.
|
||||
// TODO: ObjectLibrary stuff
|
||||
static Status CreateFromString(const ConfigOptions& config_options,
|
||||
const std::string& id,
|
||||
std::shared_ptr<CompressionManager>* result);
|
||||
|
||||
// Returns false iff a configuration that would pass the given compression
|
||||
// type to GetCompressor/GetCompressorForSST should be rejected (not
|
||||
// supported)
|
||||
virtual bool SupportsCompressionType(CompressionType type) const = 0;
|
||||
|
||||
// TODO: function to check compatibility with or sanitize CompressionOptions
|
||||
|
||||
// ************************* Compressor creation *********************** //
|
||||
// Returning nullptr means compression is entirely disabled for the file,
|
||||
// which is valid at the discretion of the CompressionManager. Returning
|
||||
// nullptr should normally be the result if preferred == kNoCompression.
|
||||
//
|
||||
// These functions must be thread-safe.
|
||||
|
||||
// Get a compressor for an SST file.
|
||||
// SUBJECT TO CHANGE
|
||||
// TODO: is it practical to get ColumnFamilyOptions plumbed into here?
|
||||
virtual std::unique_ptr<Compressor> GetCompressorForSST(
|
||||
const FilterBuildingContext&, const CompressionOptions& opts,
|
||||
CompressionType preferred) {
|
||||
return GetCompressor(opts, preferred);
|
||||
}
|
||||
|
||||
// Get a compressor for a generic/unspecified purpose (e.g. in-memory
|
||||
// compression).
|
||||
virtual std::unique_ptr<Compressor> GetCompressor(
|
||||
const CompressionOptions& opts, CompressionType type) = 0;
|
||||
|
||||
// **************************** Decompressors ************************** //
|
||||
// Get a decompressor that is compatible with any blocks compressed by
|
||||
// compressors returned by this CompressionManager (at least this code
|
||||
// revision and earlier). (NOTE: recommended to return a shared_ptr alias of
|
||||
// this shared_ptr to a field that is a Decompressor.)
|
||||
// Justification for not making CompressionManager inherit Decompressor: this
|
||||
// tends to run into the diamond inheritance problem in implementations and
|
||||
// potential overheads of virtual inheritance.
|
||||
virtual std::shared_ptr<Decompressor> GetDecompressor() = 0;
|
||||
|
||||
// Compatible with same as above, but potentially optimized for a certain
|
||||
// expected CompressionType
|
||||
virtual std::shared_ptr<Decompressor> GetDecompressorOptimizeFor(
|
||||
CompressionType /*optimize_for_type*/) {
|
||||
// Safe default implementation
|
||||
return GetDecompressor();
|
||||
}
|
||||
|
||||
// Get a decompressor that is allowed to have support only for the
|
||||
// CompressionTypes in the given start-to-end array (unique, sorted by
|
||||
// unsigned char)
|
||||
virtual std::shared_ptr<Decompressor> GetDecompressorForTypes(
|
||||
const CompressionType* /*types_begin*/,
|
||||
const CompressionType* /*types_end*/) {
|
||||
// Safe default implementation
|
||||
return GetDecompressor();
|
||||
}
|
||||
|
||||
// Get a decompressor that is allowed to have support only for the
|
||||
// CompressionTypes used by the given Compressor.
|
||||
virtual std::shared_ptr<Decompressor> GetDecompressorForCompressor(
|
||||
const Compressor& compressor) {
|
||||
// Reasonable default implementation
|
||||
return GetDecompressorOptimizeFor(compressor.GetPreferredCompressionType());
|
||||
}
|
||||
};
|
||||
|
||||
// ************************* Utility wrappers etc. *********************** //
|
||||
class CompressorWrapper : public Compressor {
|
||||
public:
|
||||
explicit CompressorWrapper(std::unique_ptr<Compressor> compressor)
|
||||
: wrapped_(std::move(compressor)) {}
|
||||
// No copies
|
||||
CompressorWrapper(const CompressorWrapper&) = delete;
|
||||
CompressorWrapper& operator=(const CompressorWrapper&) = delete;
|
||||
|
||||
size_t GetMaxSampleSizeIfWantDict(CacheEntryRole block_type) const override {
|
||||
return wrapped_->GetMaxSampleSizeIfWantDict(block_type);
|
||||
}
|
||||
|
||||
Slice GetSerializedDict() const override {
|
||||
return wrapped_->GetSerializedDict();
|
||||
}
|
||||
|
||||
CompressionType GetPreferredCompressionType() const override {
|
||||
return wrapped_->GetPreferredCompressionType();
|
||||
}
|
||||
|
||||
std::unique_ptr<Compressor> MaybeCloneSpecialized(
|
||||
CacheEntryRole block_type, DictSampleArgs&& dict_samples) override {
|
||||
return wrapped_->MaybeCloneSpecialized(block_type, std::move(dict_samples));
|
||||
}
|
||||
|
||||
ManagedWorkingArea ObtainWorkingArea() override {
|
||||
return wrapped_->ObtainWorkingArea();
|
||||
}
|
||||
|
||||
Status CompressBlock(Slice uncompressed_data, std::string* compressed_output,
|
||||
CompressionType* out_compression_type,
|
||||
ManagedWorkingArea* working_area) override {
|
||||
return wrapped_->CompressBlock(uncompressed_data, compressed_output,
|
||||
out_compression_type, working_area);
|
||||
}
|
||||
|
||||
protected:
|
||||
std::unique_ptr<Compressor> wrapped_;
|
||||
};
|
||||
|
||||
class DecompressorWrapper : public Decompressor {
|
||||
public:
|
||||
explicit DecompressorWrapper(std::shared_ptr<Decompressor> decompressor)
|
||||
: wrapped_(std::move(decompressor)) {}
|
||||
// No copies
|
||||
DecompressorWrapper(const DecompressorWrapper&) = delete;
|
||||
DecompressorWrapper& operator=(const DecompressorWrapper&) = delete;
|
||||
|
||||
const char* Name() const override { return wrapped_->Name(); }
|
||||
|
||||
void ReleaseWorkingArea(WorkingArea* wa) override {
|
||||
wrapped_->ReleaseWorkingArea(wa);
|
||||
}
|
||||
|
||||
ManagedWorkingArea ObtainWorkingArea(CompressionType preferred) override {
|
||||
return wrapped_->ObtainWorkingArea(preferred);
|
||||
}
|
||||
|
||||
const Slice& GetSerializedDict() const override {
|
||||
return wrapped_->GetSerializedDict();
|
||||
}
|
||||
|
||||
Status MaybeCloneForDict(const Slice& serialized_dict,
|
||||
std::unique_ptr<Decompressor>* out) override {
|
||||
// NOTE: derived class probably needs to override this to ensure a
|
||||
// derived wrapper around the new Decompressor
|
||||
return wrapped_->MaybeCloneForDict(serialized_dict, out);
|
||||
}
|
||||
|
||||
size_t ApproximateOwnedMemoryUsage() const override {
|
||||
return wrapped_->ApproximateOwnedMemoryUsage();
|
||||
}
|
||||
|
||||
Status ExtractUncompressedSize(Args& args) override {
|
||||
return wrapped_->ExtractUncompressedSize(args);
|
||||
}
|
||||
|
||||
Status DecompressBlock(const Args& args, char* uncompressed_output) override {
|
||||
return wrapped_->DecompressBlock(args, uncompressed_output);
|
||||
}
|
||||
|
||||
protected:
|
||||
std::shared_ptr<Decompressor> wrapped_;
|
||||
};
|
||||
|
||||
// TODO: CompressorBase, for custom compressions
|
||||
|
||||
class CompressionManagerWrapper : public CompressionManager {
|
||||
public:
|
||||
explicit CompressionManagerWrapper(
|
||||
std::shared_ptr<CompressionManager> wrapped)
|
||||
: wrapped_(std::move(wrapped)) {}
|
||||
|
||||
const char* CompatibilityName() const override {
|
||||
return wrapped_->CompatibilityName();
|
||||
}
|
||||
|
||||
std::shared_ptr<CompressionManager> FindCompatibleCompressionManager(
|
||||
Slice compatibility_name) override {
|
||||
return wrapped_->FindCompatibleCompressionManager(compatibility_name);
|
||||
}
|
||||
|
||||
bool SupportsCompressionType(CompressionType type) const override {
|
||||
return wrapped_->SupportsCompressionType(type);
|
||||
}
|
||||
|
||||
std::unique_ptr<Compressor> GetCompressorForSST(
|
||||
const FilterBuildingContext& context, const CompressionOptions& opts,
|
||||
CompressionType preferred) override {
|
||||
return wrapped_->GetCompressorForSST(context, opts, preferred);
|
||||
}
|
||||
|
||||
std::unique_ptr<Compressor> GetCompressor(const CompressionOptions& opts,
|
||||
CompressionType type) override {
|
||||
return wrapped_->GetCompressor(opts, type);
|
||||
}
|
||||
|
||||
std::shared_ptr<Decompressor> GetDecompressor() override {
|
||||
return wrapped_->GetDecompressor();
|
||||
}
|
||||
|
||||
std::shared_ptr<Decompressor> GetDecompressorOptimizeFor(
|
||||
CompressionType optimize_for_type) override {
|
||||
return wrapped_->GetDecompressorOptimizeFor(optimize_for_type);
|
||||
}
|
||||
|
||||
std::shared_ptr<Decompressor> GetDecompressorForTypes(
|
||||
const CompressionType* types_begin,
|
||||
const CompressionType* types_end) override {
|
||||
return wrapped_->GetDecompressorForTypes(types_begin, types_end);
|
||||
}
|
||||
|
||||
std::shared_ptr<Decompressor> GetDecompressorForCompressor(
|
||||
const Compressor& compressor) override {
|
||||
return wrapped_->GetDecompressorForCompressor(compressor);
|
||||
}
|
||||
|
||||
protected:
|
||||
std::shared_ptr<CompressionManager> wrapped_;
|
||||
};
|
||||
|
||||
// Compression manager that implements the second schema for RocksDB built-in
|
||||
// compression support. (The first schema is intentionally not provided here.)
|
||||
// *** CURRENT STATE ***
|
||||
// This is currently the latest schema for built-in compression, and the
|
||||
// compression manager used when compression_manager=nullptr.
|
||||
const std::shared_ptr<CompressionManager>& GetBuiltinV2CompressionManager();
|
||||
|
||||
// NOTE: No GetLatestBuiltinCompressionManager() is provided because that could
|
||||
// lead to unexpected schema changes for user CompressionManagers building on
|
||||
// the built-in schema, in the unlikely/rare case of a new built-in schema.
|
||||
|
||||
// Gets CompressionManager designed for the automated compression strategy.
|
||||
// This may include deciding to compress or not.
|
||||
// In future should be able to select compression algorithm based on the CPU
|
||||
// utilization and IO constraints.
|
||||
std::shared_ptr<CompressionManagerWrapper> CreateAutoSkipCompressionManager(
|
||||
std::shared_ptr<CompressionManager> wrapped);
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
@@ -863,7 +863,7 @@ struct AdvancedColumnFamilyOptions {
|
||||
//
|
||||
// Default: 0 (disable the feature)
|
||||
//
|
||||
// Not dynamically changeable, change it requires db restart.
|
||||
// Dynamically changeable through the SetOptions() API
|
||||
uint64_t preclude_last_level_data_seconds = 0;
|
||||
|
||||
// EXPERIMENTAL
|
||||
@@ -886,7 +886,7 @@ struct AdvancedColumnFamilyOptions {
|
||||
//
|
||||
// Default: 0 (disable the feature)
|
||||
//
|
||||
// Not dynamically changeable, change it requires db restart.
|
||||
// Dynamically changeable through the SetOptions() API
|
||||
uint64_t preserve_internal_time_seconds = 0;
|
||||
|
||||
// When set, large values (blobs) are written to separate blob files, and
|
||||
@@ -1127,6 +1127,25 @@ struct AdvancedColumnFamilyOptions {
|
||||
// Dynamically changeable through the SetOptions() API.
|
||||
uint32_t memtable_op_scan_flush_trigger = 0;
|
||||
|
||||
// Similar to `memtable_op_scan_flush_trigger`, but this option applies to
|
||||
// Next() calls between Seeks or until iterator destruction. If the average
|
||||
// of the number of invisible entries scanned from the active memtable, the
|
||||
// memtable will be marked for flush.
|
||||
// Note that to avoid the case where the window between Seeks is too small,
|
||||
// the option only takes effect if the total number of hidden entries scanned
|
||||
// within a window is at least `memtable_op_scan_flush_trigger`. So this
|
||||
// option is only effective when `memtable_op_scan_flush_trigger` is set.
|
||||
//
|
||||
// This option should be set to a lower value than
|
||||
// `memtable_op_scan_flush_trigger`. It covers the case where an iterator
|
||||
// scans through an expensive key range with many invisible entries from the
|
||||
// active memtable, but the number of invisible entries per operation does not
|
||||
// exceed `memtable_op_scan_flush_trigger`.
|
||||
//
|
||||
// Default: 0 (disabled)
|
||||
// Dynamically changeable through the SetOptions() API.
|
||||
uint32_t memtable_avg_op_scan_flush_trigger = 0;
|
||||
|
||||
// Create ColumnFamilyOptions with default values for all fields
|
||||
AdvancedColumnFamilyOptions();
|
||||
// Create ColumnFamilyOptions from Options
|
||||
|
||||
@@ -1507,6 +1507,12 @@ rocksdb_options_set_memtable_op_scan_flush_trigger(rocksdb_options_t*,
|
||||
extern ROCKSDB_LIBRARY_API uint32_t
|
||||
rocksdb_options_get_memtable_op_scan_flush_trigger(rocksdb_options_t*);
|
||||
|
||||
extern ROCKSDB_LIBRARY_API void
|
||||
rocksdb_options_set_memtable_avg_op_scan_flush_trigger(rocksdb_options_t*,
|
||||
uint32_t);
|
||||
extern ROCKSDB_LIBRARY_API uint32_t
|
||||
rocksdb_options_get_memtable_avg_op_scan_flush_trigger(rocksdb_options_t*);
|
||||
|
||||
enum {
|
||||
rocksdb_statistics_level_disable_all = 0,
|
||||
rocksdb_statistics_level_except_tickers =
|
||||
|
||||
@@ -18,16 +18,148 @@ namespace ROCKSDB_NAMESPACE {
|
||||
enum CompressionType : unsigned char {
|
||||
// NOTE: do not change the values of existing entries, as these are
|
||||
// part of the persistent format on disk.
|
||||
kNoCompression = 0x0,
|
||||
kSnappyCompression = 0x1,
|
||||
kZlibCompression = 0x2,
|
||||
kBZip2Compression = 0x3,
|
||||
kLZ4Compression = 0x4,
|
||||
kLZ4HCCompression = 0x5,
|
||||
kXpressCompression = 0x6,
|
||||
kZSTD = 0x7,
|
||||
kNoCompression = 0x00,
|
||||
kSnappyCompression = 0x01,
|
||||
kZlibCompression = 0x02,
|
||||
kBZip2Compression = 0x03,
|
||||
kLZ4Compression = 0x04,
|
||||
kLZ4HCCompression = 0x05,
|
||||
kXpressCompression = 0x06,
|
||||
kZSTD = 0x07,
|
||||
kLastBuiltinCompression = kZSTD,
|
||||
|
||||
// TODO: add enum values for user custom compression types
|
||||
// Reserved for future use: up to 0x7F
|
||||
|
||||
// For use by user custom CompressionManagers
|
||||
kCustomCompression80 = 0x80,
|
||||
kFirstCustomCompression = kCustomCompression80,
|
||||
kCustomCompression81 = 0x81,
|
||||
kCustomCompression82 = 0x82,
|
||||
kCustomCompression83 = 0x83,
|
||||
kCustomCompression84 = 0x84,
|
||||
kCustomCompression85 = 0x85,
|
||||
kCustomCompression86 = 0x86,
|
||||
kCustomCompression87 = 0x87,
|
||||
kCustomCompression88 = 0x88,
|
||||
kCustomCompression89 = 0x89,
|
||||
kCustomCompression8A = 0x8A,
|
||||
kCustomCompression8B = 0x8B,
|
||||
kCustomCompression8C = 0x8C,
|
||||
kCustomCompression8D = 0x8D,
|
||||
kCustomCompression8E = 0x8E,
|
||||
kCustomCompression8F = 0x8F,
|
||||
kCustomCompression90 = 0x90,
|
||||
kCustomCompression91 = 0x91,
|
||||
kCustomCompression92 = 0x92,
|
||||
kCustomCompression93 = 0x93,
|
||||
kCustomCompression94 = 0x94,
|
||||
kCustomCompression95 = 0x95,
|
||||
kCustomCompression96 = 0x96,
|
||||
kCustomCompression97 = 0x97,
|
||||
kCustomCompression98 = 0x98,
|
||||
kCustomCompression99 = 0x99,
|
||||
kCustomCompression9A = 0x9A,
|
||||
kCustomCompression9B = 0x9B,
|
||||
kCustomCompression9C = 0x9C,
|
||||
kCustomCompression9D = 0x9D,
|
||||
kCustomCompression9E = 0x9E,
|
||||
kCustomCompression9F = 0x9F,
|
||||
kCustomCompressionA0 = 0xA0,
|
||||
kCustomCompressionA1 = 0xA1,
|
||||
kCustomCompressionA2 = 0xA2,
|
||||
kCustomCompressionA3 = 0xA3,
|
||||
kCustomCompressionA4 = 0xA4,
|
||||
kCustomCompressionA5 = 0xA5,
|
||||
kCustomCompressionA6 = 0xA6,
|
||||
kCustomCompressionA7 = 0xA7,
|
||||
kCustomCompressionA8 = 0xA8,
|
||||
kCustomCompressionA9 = 0xA9,
|
||||
kCustomCompressionAA = 0xAA,
|
||||
kCustomCompressionAB = 0xAB,
|
||||
kCustomCompressionAC = 0xAC,
|
||||
kCustomCompressionAD = 0xAD,
|
||||
kCustomCompressionAE = 0xAE,
|
||||
kCustomCompressionAF = 0xAF,
|
||||
kCustomCompressionB0 = 0xB0,
|
||||
kCustomCompressionB1 = 0xB1,
|
||||
kCustomCompressionB2 = 0xB2,
|
||||
kCustomCompressionB3 = 0xB3,
|
||||
kCustomCompressionB4 = 0xB4,
|
||||
kCustomCompressionB5 = 0xB5,
|
||||
kCustomCompressionB6 = 0xB6,
|
||||
kCustomCompressionB7 = 0xB7,
|
||||
kCustomCompressionB8 = 0xB8,
|
||||
kCustomCompressionB9 = 0xB9,
|
||||
kCustomCompressionBA = 0xBA,
|
||||
kCustomCompressionBB = 0xBB,
|
||||
kCustomCompressionBC = 0xBC,
|
||||
kCustomCompressionBD = 0xBD,
|
||||
kCustomCompressionBE = 0xBE,
|
||||
kCustomCompressionBF = 0xBF,
|
||||
kCustomCompressionC0 = 0xC0,
|
||||
kCustomCompressionC1 = 0xC1,
|
||||
kCustomCompressionC2 = 0xC2,
|
||||
kCustomCompressionC3 = 0xC3,
|
||||
kCustomCompressionC4 = 0xC4,
|
||||
kCustomCompressionC5 = 0xC5,
|
||||
kCustomCompressionC6 = 0xC6,
|
||||
kCustomCompressionC7 = 0xC7,
|
||||
kCustomCompressionC8 = 0xC8,
|
||||
kCustomCompressionC9 = 0xC9,
|
||||
kCustomCompressionCA = 0xCA,
|
||||
kCustomCompressionCB = 0xCB,
|
||||
kCustomCompressionCC = 0xCC,
|
||||
kCustomCompressionCD = 0xCD,
|
||||
kCustomCompressionCE = 0xCE,
|
||||
kCustomCompressionCF = 0xCF,
|
||||
kCustomCompressionD0 = 0xD0,
|
||||
kCustomCompressionD1 = 0xD1,
|
||||
kCustomCompressionD2 = 0xD2,
|
||||
kCustomCompressionD3 = 0xD3,
|
||||
kCustomCompressionD4 = 0xD4,
|
||||
kCustomCompressionD5 = 0xD5,
|
||||
kCustomCompressionD6 = 0xD6,
|
||||
kCustomCompressionD7 = 0xD7,
|
||||
kCustomCompressionD8 = 0xD8,
|
||||
kCustomCompressionD9 = 0xD9,
|
||||
kCustomCompressionDA = 0xDA,
|
||||
kCustomCompressionDB = 0xDB,
|
||||
kCustomCompressionDC = 0xDC,
|
||||
kCustomCompressionDD = 0xDD,
|
||||
kCustomCompressionDE = 0xDE,
|
||||
kCustomCompressionDF = 0xDF,
|
||||
kCustomCompressionE0 = 0xE0,
|
||||
kCustomCompressionE1 = 0xE1,
|
||||
kCustomCompressionE2 = 0xE2,
|
||||
kCustomCompressionE3 = 0xE3,
|
||||
kCustomCompressionE4 = 0xE4,
|
||||
kCustomCompressionE5 = 0xE5,
|
||||
kCustomCompressionE6 = 0xE6,
|
||||
kCustomCompressionE7 = 0xE7,
|
||||
kCustomCompressionE8 = 0xE8,
|
||||
kCustomCompressionE9 = 0xE9,
|
||||
kCustomCompressionEA = 0xEA,
|
||||
kCustomCompressionEB = 0xEB,
|
||||
kCustomCompressionEC = 0xEC,
|
||||
kCustomCompressionED = 0xED,
|
||||
kCustomCompressionEE = 0xEE,
|
||||
kCustomCompressionEF = 0xEF,
|
||||
kCustomCompressionF0 = 0xF0,
|
||||
kCustomCompressionF1 = 0xF1,
|
||||
kCustomCompressionF2 = 0xF2,
|
||||
kCustomCompressionF3 = 0xF3,
|
||||
kCustomCompressionF4 = 0xF4,
|
||||
kCustomCompressionF5 = 0xF5,
|
||||
kCustomCompressionF6 = 0xF6,
|
||||
kCustomCompressionF7 = 0xF7,
|
||||
kCustomCompressionF8 = 0xF8,
|
||||
kCustomCompressionF9 = 0xF9,
|
||||
kCustomCompressionFA = 0xFA,
|
||||
kCustomCompressionFB = 0xFB,
|
||||
kCustomCompressionFC = 0xFC,
|
||||
kCustomCompressionFD = 0xFD,
|
||||
kCustomCompressionFE = 0xFE,
|
||||
kLastCustomCompression = kCustomCompressionFE,
|
||||
|
||||
// kDisableCompressionOption is used to disable some compression options.
|
||||
kDisableCompressionOption = 0xff,
|
||||
@@ -182,4 +314,7 @@ struct CompressionOptions {
|
||||
#endif
|
||||
};
|
||||
|
||||
// See advanced_compression.h
|
||||
class CompressionManager;
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
@@ -7,9 +7,9 @@
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
#include "rocksdb/rocksdb_namespace.h"
|
||||
|
||||
@@ -17,24 +17,33 @@ namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
namespace detail {
|
||||
int CountTrailingZeroBitsForSmallEnumSet(uint64_t);
|
||||
int BitsSetToOneForSmallEnumSet(uint64_t);
|
||||
} // namespace detail
|
||||
|
||||
// Represents a set of values of some enum type with a small number of
|
||||
// possible enumerators. For now, it supports enums where no enumerator
|
||||
// exceeds 63 when converted to int.
|
||||
// Represents a set of values of some enum type with a small number of possible
|
||||
// enumerators. Assumes that any combination of enumerators with values 0
|
||||
// through MAX_ENUMERATOR (inclusive) might be part of the set. NOTE: would like
|
||||
// to use std::bitset, but it doesn't support constexpr (in C++17) operations
|
||||
// and doesn't support efficient iteration over sparse "set to true" entries.
|
||||
template <typename ENUM_TYPE, ENUM_TYPE MAX_ENUMERATOR>
|
||||
class SmallEnumSet {
|
||||
private:
|
||||
using StateT = uint64_t;
|
||||
static constexpr int kStateBits = sizeof(StateT) * 8;
|
||||
static constexpr int kMaxMax = kStateBits - 1;
|
||||
static constexpr int kMaxValue = static_cast<int>(MAX_ENUMERATOR);
|
||||
static_assert(kMaxValue >= 0);
|
||||
static_assert(kMaxValue <= kMaxMax);
|
||||
static_assert(kMaxValue < 1024, "MAX_ENUMERATOR is suspiciously large");
|
||||
using PieceT = uint64_t;
|
||||
static constexpr int kPieceBits = 64;
|
||||
static constexpr int kPieceMask = 63;
|
||||
static constexpr int kPieceShift = 6;
|
||||
static constexpr int kPieceCount = kMaxValue / kPieceBits + 1;
|
||||
using StateT = std::array<PieceT, kPieceCount>;
|
||||
static constexpr int kStateBits = kPieceBits * kPieceCount;
|
||||
static_assert(kStateBits == sizeof(StateT) * 8);
|
||||
static_assert(kMaxValue <= kStateBits - 1);
|
||||
|
||||
public:
|
||||
// construct / create
|
||||
SmallEnumSet() : state_(0) {}
|
||||
// construct / create empty set
|
||||
SmallEnumSet() : state_{} {}
|
||||
|
||||
template <class... TRest>
|
||||
/*implicit*/ constexpr SmallEnumSet(const ENUM_TYPE e, TRest... rest) {
|
||||
@@ -44,8 +53,16 @@ class SmallEnumSet {
|
||||
// Return the set that includes all valid values, assuming the enum
|
||||
// is "dense" (includes all values converting to 0 through kMaxValue)
|
||||
static constexpr SmallEnumSet All() {
|
||||
StateT tmp = StateT{1} << kMaxValue;
|
||||
return SmallEnumSet(RawStateMarker(), tmp | (tmp - 1));
|
||||
StateT tmp;
|
||||
for (int i = 0; i < kPieceCount - 1; ++i) {
|
||||
tmp[i] = ~PieceT{0};
|
||||
}
|
||||
if constexpr (((kMaxValue + 1) & kPieceMask) != 0) {
|
||||
tmp[kPieceCount - 1] = (PieceT{1} << ((kMaxValue + 1) & kPieceMask)) - 1;
|
||||
} else {
|
||||
tmp[kPieceCount - 1] = ~PieceT{0};
|
||||
}
|
||||
return SmallEnumSet(RawStateMarker(), tmp);
|
||||
}
|
||||
|
||||
// equality
|
||||
@@ -60,11 +77,17 @@ class SmallEnumSet {
|
||||
bool Contains(const ENUM_TYPE e) const {
|
||||
int value = static_cast<int>(e);
|
||||
assert(value >= 0 && value <= kMaxValue);
|
||||
StateT tmp = 1;
|
||||
return state_ & (tmp << value);
|
||||
return GetPiece(value) & (PieceT{1} << (value & kPieceMask));
|
||||
}
|
||||
|
||||
bool empty() const { return state_ == 0; }
|
||||
bool empty() const {
|
||||
for (int i = 0; i < kPieceCount; ++i) {
|
||||
if (state_[i] != 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// iterator
|
||||
class const_iterator {
|
||||
@@ -92,7 +115,7 @@ class SmallEnumSet {
|
||||
if (pos_ < kMaxValue) {
|
||||
pos_ = set_->SkipUnset(pos_ + 1);
|
||||
} else {
|
||||
pos_ = kStateBits;
|
||||
pos_ = kMaxValue + 1;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
@@ -118,7 +141,15 @@ class SmallEnumSet {
|
||||
|
||||
const_iterator begin() const { return const_iterator(this, SkipUnset(0)); }
|
||||
|
||||
const_iterator end() const { return const_iterator(this, kStateBits); }
|
||||
const_iterator end() const { return const_iterator(this, kMaxValue + 1); }
|
||||
|
||||
size_t count() const {
|
||||
size_t rv = 0;
|
||||
for (int i = 0; i < kPieceCount; ++i) {
|
||||
rv += static_cast<size_t>(detail::BitsSetToOneForSmallEnumSet(state_[i]));
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
// mutable ops
|
||||
|
||||
@@ -127,9 +158,10 @@ class SmallEnumSet {
|
||||
bool Add(const ENUM_TYPE e) {
|
||||
int value = static_cast<int>(e);
|
||||
assert(value >= 0 && value <= kMaxValue);
|
||||
StateT old_state = state_;
|
||||
state_ |= (StateT{1} << value);
|
||||
return old_state != state_;
|
||||
PieceT& piece_ref = RefPiece(value);
|
||||
PieceT old_piece = piece_ref;
|
||||
piece_ref |= (PieceT{1} << (value & kPieceMask));
|
||||
return old_piece != piece_ref;
|
||||
}
|
||||
|
||||
// Modifies the set (if needed) not to include the given value. Returns true
|
||||
@@ -137,18 +169,20 @@ class SmallEnumSet {
|
||||
bool Remove(const ENUM_TYPE e) {
|
||||
int value = static_cast<int>(e);
|
||||
assert(value >= 0 && value <= kMaxValue);
|
||||
StateT old_state = state_;
|
||||
state_ &= ~(StateT{1} << value);
|
||||
return old_state != state_;
|
||||
PieceT& piece_ref = RefPiece(value);
|
||||
PieceT old_piece = piece_ref;
|
||||
piece_ref &= ~(PieceT{1} << (value & kPieceMask));
|
||||
return old_piece != piece_ref;
|
||||
}
|
||||
|
||||
// applicative ops
|
||||
|
||||
// Return a new set based on this one with the additional value(s) inserted
|
||||
constexpr SmallEnumSet With(const ENUM_TYPE e) const {
|
||||
int value = static_cast<int>(e);
|
||||
assert(value >= 0 && value <= kMaxValue);
|
||||
return SmallEnumSet(RawStateMarker(), state_ | (StateT{1} << value));
|
||||
assert(static_cast<int>(e) >= 0 && static_cast<int>(e) <= kMaxValue);
|
||||
SmallEnumSet rv(*this);
|
||||
rv.Add(e);
|
||||
return rv;
|
||||
}
|
||||
template <class... TRest>
|
||||
constexpr SmallEnumSet With(const ENUM_TYPE e1, const ENUM_TYPE e2,
|
||||
@@ -158,9 +192,10 @@ class SmallEnumSet {
|
||||
|
||||
// Return a new set based on this one excluding the given value(s)
|
||||
constexpr SmallEnumSet Without(const ENUM_TYPE e) const {
|
||||
int value = static_cast<int>(e);
|
||||
assert(value >= 0 && value <= kMaxValue);
|
||||
return SmallEnumSet(RawStateMarker(), state_ & ~(StateT{1} << value));
|
||||
assert(static_cast<int>(e) >= 0 && static_cast<int>(e) <= kMaxValue);
|
||||
SmallEnumSet rv(*this);
|
||||
rv.Remove(e);
|
||||
return rv;
|
||||
}
|
||||
template <class... TRest>
|
||||
constexpr SmallEnumSet Without(const ENUM_TYPE e1, const ENUM_TYPE e2,
|
||||
@@ -170,17 +205,80 @@ class SmallEnumSet {
|
||||
|
||||
private:
|
||||
int SkipUnset(int pos) const {
|
||||
StateT tmp = state_ >> pos;
|
||||
if (tmp == 0) {
|
||||
return kStateBits;
|
||||
} else {
|
||||
return pos + detail::CountTrailingZeroBitsForSmallEnumSet(tmp);
|
||||
while (pos <= kMaxValue) {
|
||||
PieceT remainder = GetPiece(pos) >> (pos & kPieceMask);
|
||||
if (remainder != 0) {
|
||||
return pos + detail::CountTrailingZeroBitsForSmallEnumSet(remainder);
|
||||
}
|
||||
pos = (pos + kPieceBits) & ~kPieceMask;
|
||||
}
|
||||
return kMaxValue + 1;
|
||||
}
|
||||
struct RawStateMarker {};
|
||||
explicit SmallEnumSet(RawStateMarker, StateT state) : state_(state) {}
|
||||
PieceT GetPiece(int pos) const {
|
||||
if constexpr (kPieceCount == 1) {
|
||||
return state_[0];
|
||||
} else {
|
||||
return state_[pos >> kPieceShift];
|
||||
}
|
||||
}
|
||||
PieceT& RefPiece(int pos) {
|
||||
if constexpr (kPieceCount == 1) {
|
||||
return state_[0];
|
||||
} else {
|
||||
return state_[pos >> kPieceShift];
|
||||
}
|
||||
}
|
||||
|
||||
StateT state_;
|
||||
};
|
||||
|
||||
// A smart pointer that tracks an object and an owner, using a statically
|
||||
// determined function on those to reclaim the object, if both object and owner
|
||||
// are non-null
|
||||
template <typename T, class Owner, auto Fn>
|
||||
class ManagedPtr {
|
||||
public:
|
||||
ManagedPtr() = default;
|
||||
ManagedPtr(T* ptr, Owner* owner) : ptr_(ptr), owner_(owner) {}
|
||||
~ManagedPtr() {
|
||||
if (ptr_ && owner_) {
|
||||
if constexpr (std::is_member_function_pointer_v<decltype(Fn)>) {
|
||||
(owner_->*Fn)(ptr_);
|
||||
} else {
|
||||
Fn(owner_, ptr_);
|
||||
}
|
||||
}
|
||||
}
|
||||
// No copies
|
||||
ManagedPtr(const ManagedPtr&) = delete;
|
||||
ManagedPtr& operator=(const ManagedPtr&) = delete;
|
||||
// Moves
|
||||
ManagedPtr(ManagedPtr&& other) noexcept {
|
||||
ptr_ = other.ptr_;
|
||||
owner_ = other.owner_;
|
||||
other.ptr_ = nullptr;
|
||||
other.owner_ = nullptr;
|
||||
}
|
||||
ManagedPtr& operator=(ManagedPtr&& other) noexcept {
|
||||
ptr_ = other.ptr_;
|
||||
owner_ = other.owner_;
|
||||
other.ptr_ = nullptr;
|
||||
other.owner_ = nullptr;
|
||||
return *this;
|
||||
}
|
||||
|
||||
T* get() const { return ptr_; }
|
||||
T* operator->() const { return ptr_; }
|
||||
T& operator*() const { return *ptr_; }
|
||||
operator bool() const { return ptr_ != nullptr; }
|
||||
|
||||
Owner* owner() const { return owner_; }
|
||||
|
||||
private:
|
||||
T* ptr_ = nullptr;
|
||||
Owner* owner_ = nullptr;
|
||||
};
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
@@ -269,7 +269,7 @@ class ExternalTableFactory : public Customizable {
|
||||
|
||||
// Allocate a TableFactory that wraps around an ExternalTableFactory. Use this
|
||||
// to allocate and set in ColumnFamilyOptions::table_factory.
|
||||
std::shared_ptr<TableFactory> NewExternalTableFactory(
|
||||
std::unique_ptr<TableFactory> NewExternalTableFactory(
|
||||
std::shared_ptr<ExternalTableFactory> inner_factory);
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
@@ -80,7 +80,8 @@ class FileChecksumGenFactory : public Customizable {
|
||||
const ConfigOptions& options, const std::string& value,
|
||||
std::shared_ptr<FileChecksumGenFactory>* result);
|
||||
|
||||
// Create a new FileChecksumGenerator.
|
||||
// Create a new FileChecksumGenerator. Recommended to return nullptr if the
|
||||
// requested function name is not recognized.
|
||||
virtual std::unique_ptr<FileChecksumGenerator> CreateFileChecksumGenerator(
|
||||
const FileChecksumGenContext& context) = 0;
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <any>
|
||||
#include <chrono>
|
||||
#include <cstdarg>
|
||||
#include <functional>
|
||||
@@ -250,16 +251,44 @@ struct IODebugContext {
|
||||
};
|
||||
uint64_t trace_data = 0;
|
||||
|
||||
// Arbitrary structure containing cost information about the IO request
|
||||
std::any cost_info;
|
||||
|
||||
IODebugContext() {}
|
||||
|
||||
// Copy constructor
|
||||
IODebugContext(const IODebugContext& other)
|
||||
: file_path(other.file_path),
|
||||
counters(other.counters),
|
||||
msg(other.msg),
|
||||
trace_data(other.trace_data),
|
||||
cost_info(other.cost_info),
|
||||
_request_id(other.request_id ? *other.request_id : "") {
|
||||
request_id = other.request_id ? &_request_id : nullptr;
|
||||
}
|
||||
|
||||
// Copy assignment operator
|
||||
IODebugContext& operator=(const IODebugContext& other) {
|
||||
if (this != &other) {
|
||||
file_path = other.file_path;
|
||||
counters = other.counters;
|
||||
msg = other.msg;
|
||||
trace_data = other.trace_data;
|
||||
cost_info = other.cost_info;
|
||||
_request_id = other.request_id ? *other.request_id : "";
|
||||
request_id = other.request_id ? &_request_id : nullptr;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
void AddCounter(std::string& name, uint64_t value) {
|
||||
counters.emplace(name, value);
|
||||
}
|
||||
|
||||
// Called by underlying file system to set request_id and log request_id in
|
||||
// IOTracing.
|
||||
void SetRequestId(const std::string* _request_id) {
|
||||
request_id = _request_id;
|
||||
void SetRequestId(const std::string* updated_request_id) {
|
||||
request_id = updated_request_id;
|
||||
trace_data |= (1 << TraceData::kRequestID);
|
||||
}
|
||||
|
||||
@@ -272,6 +301,12 @@ struct IODebugContext {
|
||||
ss << msg;
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
private:
|
||||
// Private member that allows for safe copying of IODebugContext without any
|
||||
// memory ownership issues. After copying, request_id can point directly to
|
||||
// this field.
|
||||
std::string _request_id;
|
||||
};
|
||||
|
||||
// A function pointer type for custom destruction of void pointer passed to
|
||||
|
||||
@@ -162,6 +162,12 @@ class MemTableRep {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Only used after concurrent memtable inserts.
|
||||
// This function will be called by each writer after all writes are done
|
||||
// through InsertConcurrently().
|
||||
// This is used by VectorRep to do batched writes for concurrent inserts.
|
||||
virtual void BatchPostProcess() {}
|
||||
|
||||
// Returns true iff an entry that compares equal to key is in the collection.
|
||||
virtual bool Contains(const char* key) const = 0;
|
||||
|
||||
@@ -397,6 +403,11 @@ class SkipListFactory : public MemTableRepFactory {
|
||||
// the vector is sorted. This is useful for workloads where iteration is very
|
||||
// rare and writes are generally not issued after reads begin.
|
||||
//
|
||||
// Concurrent inserts are supported by buffering writes in thread-local vectors
|
||||
// for each write batch. To optimize performance for concurrent inserts, it is
|
||||
// recommended to perform batched writes, and enable unordered_write (refer to
|
||||
// the option comment for its impact on read consistency).
|
||||
//
|
||||
// Parameters:
|
||||
// count: Passed to the constructor of the underlying std::vector of each
|
||||
// VectorRep. On initialization, the underlying array will be at least count
|
||||
@@ -418,6 +429,8 @@ class VectorRepFactory : public MemTableRepFactory {
|
||||
MemTableRep* CreateMemTableRep(const MemTableRep::KeyComparator&, Allocator*,
|
||||
const SliceTransform*,
|
||||
Logger* logger) override;
|
||||
|
||||
bool IsInsertConcurrentlySupported() const override { return true; }
|
||||
};
|
||||
|
||||
// This class contains a fixed array of buckets, each
|
||||
|
||||
@@ -233,6 +233,14 @@ struct ColumnFamilyOptions : public AdvancedColumnFamilyOptions {
|
||||
// different options for compression algorithms
|
||||
CompressionOptions compression_opts;
|
||||
|
||||
// EXPERIMENTAL
|
||||
// Customized compression through a callback interface. When non-nullptr,
|
||||
// supersedes the above compression options, except that the above options are
|
||||
// still processed as they historically would be and passed to
|
||||
// CompressionManager::GetCompressorForSST as hints or suggestions. See
|
||||
// advanced_compression.h
|
||||
std::shared_ptr<CompressionManager> compression_manager;
|
||||
|
||||
// Number of files to trigger level-0 compaction. A value <0 means that
|
||||
// level-0 compaction will not be triggered by number of files at all.
|
||||
//
|
||||
@@ -2214,10 +2222,20 @@ struct CompactionOptions {
|
||||
// If > 0, it will replace the option in the DBOptions for this compaction.
|
||||
uint32_t max_subcompactions;
|
||||
|
||||
// Allows cancellation of an in-progress manual compaction.
|
||||
//
|
||||
// Cancellation can be delayed waiting on automatic compactions when used
|
||||
// together with `exclusive_manual_compaction == true`.
|
||||
std::atomic<bool>* canceled;
|
||||
// NOTE: Calling DisableManualCompaction() will not override the
|
||||
// canceled variable in CompactionOptions, as it does for CompactRangeOptions
|
||||
// - this is because ManualCompactionState is not used
|
||||
|
||||
CompactionOptions()
|
||||
: compression(kDisableCompressionOption),
|
||||
output_file_size_limit(std::numeric_limits<uint64_t>::max()),
|
||||
max_subcompactions(0) {}
|
||||
max_subcompactions(0),
|
||||
canceled(nullptr) {}
|
||||
};
|
||||
|
||||
// For level based compaction, we can configure if we want to skip/force
|
||||
@@ -2284,7 +2302,7 @@ struct CompactRangeOptions {
|
||||
// Cancellation can be delayed waiting on automatic compactions when used
|
||||
// together with `exclusive_manual_compaction == true`.
|
||||
std::atomic<bool>* canceled = nullptr;
|
||||
// NOTE: Calling DisableManualCompaction() overwrites the uer-provided
|
||||
// NOTE: Calling DisableManualCompaction() overwrites the user-provided
|
||||
// canceled variable in CompactRangeOptions.
|
||||
// Typically, when CompactRange is being called in one thread (t1) with
|
||||
// canceled = false, and DisableManualCompaction is being called in the
|
||||
|
||||
@@ -561,6 +561,10 @@ struct BlockBasedTableOptions {
|
||||
// misplaced within or between files is as likely to fail checksum
|
||||
// verification as random corruption. Also checksum-protects SST footer.
|
||||
// Can be read by RocksDB versions >= 8.6.0.
|
||||
// 7 -- Support for custom compression algorithms with a CompressionManager
|
||||
// using a non-built-in CompatibilityName(). See `compression_manager` in
|
||||
// ColumnFamilyOptions. Also changes the format of TableProperties field
|
||||
// `compression_name`. Can be read by RocksDB versions >= 10.4.0.
|
||||
//
|
||||
// Using the default setting of format_version is strongly recommended, so
|
||||
// that available enhancements are adopted eventually and automatically. The
|
||||
|
||||
@@ -109,6 +109,10 @@ class TablePropertiesCollector {
|
||||
// table.
|
||||
// @params key the user key that is inserted into the table.
|
||||
// @params value the value that is inserted into the table.
|
||||
// @params file_size the current file size. For BlockBasedTable, this
|
||||
// includes all the data blocks written so far, upto but not including
|
||||
// the current block being built. With parallel compression, data
|
||||
// blocks are written async so it depends on the compression progress.
|
||||
virtual Status AddUserKey(const Slice& key, const Slice& value,
|
||||
EntryType /*type*/, SequenceNumber /*seq*/,
|
||||
uint64_t /*file_size*/) {
|
||||
@@ -143,7 +147,7 @@ class TablePropertiesCollector {
|
||||
// The name of the properties collector can be used for debugging purpose.
|
||||
virtual const char* Name() const = 0;
|
||||
|
||||
// EXPERIMENTAL Return whether the output file should be further compacted
|
||||
// Return whether the output file should be further compacted
|
||||
virtual bool NeedCompact() const { return false; }
|
||||
|
||||
// For internal use only.
|
||||
@@ -344,7 +348,20 @@ struct TableProperties {
|
||||
// {collector_name[1]},{collector_name[2]},{collector_name[3]} ..
|
||||
std::string property_collectors_names;
|
||||
|
||||
// The compression algo used to compress the SST files.
|
||||
// Identifies the compression algorithm or schema used in the file.
|
||||
// Specifically:
|
||||
// * For format_version < 7, it is one of several names for built-in
|
||||
// compression types. Because of how some previous versions of RocksDB
|
||||
// behave, this must be set to "ZSTD" if any blocks are compressed
|
||||
// with zstd and must NOT be set to "NoCompression" if any blocks are
|
||||
// compressed.
|
||||
// * For format_version >= 7, the format is
|
||||
// <compatibility_name>;<hex-coded compression types>;<future use>
|
||||
// where <compatibility_name> is the CompatibilityName() of the
|
||||
// CompressionManager used for the file, or empty if compression was
|
||||
// disabled; <hex-coded compression types> represents a sorted set of
|
||||
// CompressionType values used in the file other than kNoCompression, each
|
||||
// as 2-digit hex, e.g. 04 for LZ$, 07 for ZSTD, etc.
|
||||
std::string compression_name;
|
||||
|
||||
// Compression options used to compress the SST files.
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
@@ -111,6 +111,26 @@ class CompactionOptionsUniversal {
|
||||
// Default: false
|
||||
bool incremental;
|
||||
|
||||
// EXPERIMENTAL
|
||||
//
|
||||
// If true, auto universal compaction picking will adjust to minimize locking
|
||||
// of input files when bottom priority compactions are waiting to run. This
|
||||
// can increase the likelihood of existing L0s being selected for compaction,
|
||||
// thereby improving write stall and reducing read regression. It may increase
|
||||
// the overrall write amplification and compaction load on low priority
|
||||
// threads.
|
||||
//
|
||||
// Default: false (disabled)
|
||||
//
|
||||
// This options does not apply to manual compactions.
|
||||
//
|
||||
// This option is temporary in case turning on this feature causes problems
|
||||
// and users need to undo it quickly. This option is planned for removal in
|
||||
// the near future with default value set to true.
|
||||
//
|
||||
// Dynamically changeable through the SetOptions() API.
|
||||
bool reduce_file_locking;
|
||||
|
||||
// Default set of parameters
|
||||
CompactionOptionsUniversal()
|
||||
: size_ratio(1),
|
||||
@@ -121,7 +141,8 @@ class CompactionOptionsUniversal {
|
||||
max_read_amp(-1),
|
||||
stop_style(kCompactionStopStyleTotalSize),
|
||||
allow_trivial_move(false),
|
||||
incremental(false) {}
|
||||
incremental(false),
|
||||
reduce_file_locking(false) {}
|
||||
|
||||
#if __cplusplus >= 202002L
|
||||
bool operator==(const CompactionOptionsUniversal& rhs) const = default;
|
||||
|
||||
@@ -23,15 +23,20 @@ class CompactOnDeletionCollectorFactory
|
||||
// A factory of a table property collector that marks a SST
|
||||
// file as need-compaction when it observe at least "D" deletion
|
||||
// entries in any "N" consecutive entries, or the ratio of tombstone
|
||||
// entries >= deletion_ratio.
|
||||
// entries >= deletion_ratio for the entire file.
|
||||
//
|
||||
// @param sliding_window_size "N"
|
||||
// @param deletion_trigger "D"
|
||||
// @param deletion_ratio, if <= 0 or > 1, disable triggering compaction
|
||||
// based on deletion ratio.
|
||||
// @param min_file_size, a file needs to be at least this size to be marked
|
||||
// for compaction. See comments above
|
||||
// TablePropertiesCollector::AddUserKey() for limitations/inaccuracies on
|
||||
// the file size.
|
||||
CompactOnDeletionCollectorFactory(size_t sliding_window_size,
|
||||
size_t deletion_trigger,
|
||||
double deletion_ratio);
|
||||
double deletion_ratio,
|
||||
uint64_t min_file_size = 0);
|
||||
|
||||
~CompactOnDeletionCollectorFactory() override {}
|
||||
|
||||
@@ -59,6 +64,12 @@ class CompactOnDeletionCollectorFactory
|
||||
}
|
||||
|
||||
double GetDeletionRatio() const { return deletion_ratio_.load(); }
|
||||
|
||||
uint64_t GetMinFileSize() const { return min_file_size_.load(); }
|
||||
void SetMinFileSize(uint64_t min_file_size) {
|
||||
min_file_size_.store(min_file_size);
|
||||
}
|
||||
|
||||
static const char* kClassName() { return "CompactOnDeletionCollector"; }
|
||||
const char* Name() const override { return kClassName(); }
|
||||
|
||||
@@ -68,6 +79,7 @@ class CompactOnDeletionCollectorFactory
|
||||
std::atomic<size_t> sliding_window_size_;
|
||||
std::atomic<size_t> deletion_trigger_;
|
||||
std::atomic<double> deletion_ratio_;
|
||||
std::atomic<uint64_t> min_file_size_;
|
||||
};
|
||||
|
||||
// Creates a factory of a table property collector that marks a SST
|
||||
@@ -85,7 +97,8 @@ class CompactOnDeletionCollectorFactory
|
||||
std::shared_ptr<CompactOnDeletionCollectorFactory>
|
||||
NewCompactOnDeletionCollectorFactory(size_t sliding_window_size,
|
||||
size_t deletion_trigger,
|
||||
double deletion_ratio = 0);
|
||||
double deletion_ratio = 0,
|
||||
uint64_t min_file_size = 0);
|
||||
|
||||
// A factory of a table property collector that marks a SST file as
|
||||
// need-compaction when for the tiering use case, it observes, among all the
|
||||
|
||||
@@ -254,7 +254,7 @@ struct TransactionDBOptions {
|
||||
// for more details.
|
||||
std::vector<std::shared_ptr<SecondaryIndex>> secondary_indices;
|
||||
|
||||
// Deprecated, this option may be removed in the future.
|
||||
// Deprecated, this option has no effect and may be removed in the future.
|
||||
// Use TransactionOptions::large_txn_commit_optimize_threshold instead.
|
||||
//
|
||||
// This option is only valid for write committed. If the number of updates in
|
||||
@@ -366,6 +366,22 @@ struct TransactionOptions {
|
||||
// DeleteRange, SingleDelete.
|
||||
bool write_batch_track_timestamp_size = false;
|
||||
|
||||
// The following three options enable optimizations for large transaction
|
||||
// commit to bypass memtable write.
|
||||
// - If any transaction's commit should bybass memtable write,
|
||||
// set commit_bypass_memtable to true.
|
||||
// - If only bypass memtable write for transactions with >= n operations,
|
||||
// set commit_bypass_memtable to false,
|
||||
// large_txn_commit_optimize_threshold to n, and
|
||||
// large_txn_commit_optimize_byte_threshold to 0.
|
||||
// Similarly for only optimize when a transaction's write batch size is >= n.
|
||||
// - If bypass memtable write for transactions with >= n operations or >= x
|
||||
// bytes,
|
||||
// set commit_bypass_memtable to false,
|
||||
// large_txn_commit_optimize_threshold to n, and
|
||||
// large_txn_commit_optimize_byte_threshold to x.
|
||||
//
|
||||
//
|
||||
// EXPERIMENTAL, SUBJECT TO CHANGE
|
||||
// Only supports write-committed policy. If set to true, the transaction will
|
||||
// skip memtable write and ingest into the DB directly during Commit(). This
|
||||
@@ -394,8 +410,17 @@ struct TransactionOptions {
|
||||
// When the number of updates in a transaction is at least this threshold,
|
||||
// we will enable optimizations for commiting a large transaction. See
|
||||
// comment for `commit_bypass_memtable` for more optimization detail.
|
||||
uint32_t large_txn_commit_optimize_threshold =
|
||||
std::numeric_limits<uint32_t>::max();
|
||||
//
|
||||
// Default: 0 (disabled).
|
||||
uint32_t large_txn_commit_optimize_threshold = 0;
|
||||
|
||||
// EXPERIMENTAL, SUBJECT TO CHANGE
|
||||
// When the size of a transaction's write batch is at least this threshold,
|
||||
// we will enable optimizations for commiting a large transaction. See
|
||||
// comment for `commit_bypass_memtable` for more optimization detail.
|
||||
//
|
||||
// Default: 0 (disabled).
|
||||
uint64_t large_txn_commit_optimize_byte_threshold = 0;
|
||||
};
|
||||
|
||||
// The per-write optimizations that do not involve transactions. TransactionDB
|
||||
|
||||
@@ -12,8 +12,8 @@
|
||||
// NOTE: in 'main' development branch, this should be the *next*
|
||||
// minor or major version number planned for release.
|
||||
#define ROCKSDB_MAJOR 10
|
||||
#define ROCKSDB_MINOR 3
|
||||
#define ROCKSDB_PATCH 0
|
||||
#define ROCKSDB_MINOR 4
|
||||
#define ROCKSDB_PATCH 2
|
||||
|
||||
// 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
|
||||
|
||||
@@ -30,6 +30,14 @@ public class NativeLibraryLoader {
|
||||
private static final String tempFilePrefix = "librocksdbjni";
|
||||
private static final String tempFileSuffix = Environment.getJniLibraryExtension();
|
||||
|
||||
/**
|
||||
* If you set the System Property ROCKS_JAVA_DEBUG_NLL can be to true
|
||||
* messages about attempts to load the native library will be printed
|
||||
* to std out.
|
||||
*/
|
||||
private static boolean DEBUG_LOADING =
|
||||
"true".equals(System.getProperty("ROCKS_JAVA_DEBUG_NLL", "false"));
|
||||
|
||||
/**
|
||||
* Get a reference to the NativeLibraryLoader
|
||||
*
|
||||
@@ -55,7 +63,7 @@ public class NativeLibraryLoader {
|
||||
*
|
||||
* @throws java.io.IOException if a filesystem operation fails.
|
||||
*/
|
||||
@SuppressWarnings("PMD.EmptyCatchBlock")
|
||||
@SuppressWarnings({"PMD.EmptyCatchBlock", "PMD.SystemPrintln"})
|
||||
public synchronized void loadLibrary(final String tmpDir) throws IOException {
|
||||
try {
|
||||
// try dynamic library
|
||||
@@ -63,6 +71,9 @@ public class NativeLibraryLoader {
|
||||
return;
|
||||
} catch (final UnsatisfiedLinkError ule) {
|
||||
// ignore - try from static library
|
||||
if (DEBUG_LOADING) {
|
||||
System.out.println("Unable to load shared dynamic library: " + sharedLibraryName);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -71,6 +82,9 @@ public class NativeLibraryLoader {
|
||||
return;
|
||||
} catch (final UnsatisfiedLinkError ule) {
|
||||
// ignore - then try static library fallback or from jar
|
||||
if (DEBUG_LOADING) {
|
||||
System.out.println("Unable to load shared static library: " + jniLibraryName);
|
||||
}
|
||||
}
|
||||
|
||||
if (fallbackJniLibraryName != null) {
|
||||
@@ -80,6 +94,10 @@ public class NativeLibraryLoader {
|
||||
return;
|
||||
} catch (final UnsatisfiedLinkError ule) {
|
||||
// ignore - then try from jar
|
||||
if (DEBUG_LOADING) {
|
||||
System.out.println(
|
||||
"Unable to load shared static fallback library: " + fallbackJniLibraryName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,18 +155,23 @@ public class NativeLibraryLoader {
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings({"PMD.UseProperClassLoader", "PMD.UseTryWithResources"})
|
||||
@SuppressWarnings({"PMD.UseProperClassLoader", "PMD.UseTryWithResources", "PMD.SystemPrintln"})
|
||||
File loadLibraryFromJarToTemp(final String tmpDir) throws IOException {
|
||||
try (InputStream is = getClass().getClassLoader().getResourceAsStream(jniLibraryFileName)) {
|
||||
if (is != null) {
|
||||
final File temp = createTemp(tmpDir, jniLibraryFileName);
|
||||
Files.copy(is, temp.toPath(), StandardCopyOption.REPLACE_EXISTING);
|
||||
return temp;
|
||||
} else {
|
||||
if (DEBUG_LOADING) {
|
||||
System.out.println("Unable to find: " + jniLibraryFileName + " on the classpath");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (fallbackJniLibraryFileName == null) {
|
||||
throw new RuntimeException(fallbackJniLibraryFileName + " was not found inside JAR.");
|
||||
throw new RuntimeException(
|
||||
jniLibraryFileName + " was not found inside JAR, and there is no fallback.");
|
||||
}
|
||||
|
||||
try (InputStream is =
|
||||
@@ -157,10 +180,16 @@ public class NativeLibraryLoader {
|
||||
final File temp = createTemp(tmpDir, fallbackJniLibraryFileName);
|
||||
Files.copy(is, temp.toPath(), StandardCopyOption.REPLACE_EXISTING);
|
||||
return temp;
|
||||
} else {
|
||||
if (DEBUG_LOADING) {
|
||||
System.out.println(
|
||||
"Unable to find fallback: " + fallbackJniLibraryFileName + " on the classpath");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw new RuntimeException(jniLibraryFileName + " was not found inside JAR.");
|
||||
throw new RuntimeException("Neither " + jniLibraryFileName + " or " + fallbackJniLibraryFileName
|
||||
+ " were found inside the JAR, and there is no fallback.");
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -84,13 +84,7 @@ public class RocksDB extends RocksObject {
|
||||
return;
|
||||
}
|
||||
|
||||
while (libraryLoaded.get() == LibraryState.LOADING) {
|
||||
try {
|
||||
Thread.sleep(10);
|
||||
} catch(final InterruptedException e) {
|
||||
//ignore
|
||||
}
|
||||
}
|
||||
waitForLibraryToBeLoaded();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -146,12 +140,28 @@ public class RocksDB extends RocksObject {
|
||||
return;
|
||||
}
|
||||
|
||||
while (libraryLoaded.get() == LibraryState.LOADING) {
|
||||
try {
|
||||
Thread.sleep(10);
|
||||
} catch(final InterruptedException e) {
|
||||
//ignore
|
||||
waitForLibraryToBeLoaded();
|
||||
}
|
||||
|
||||
private static void waitForLibraryToBeLoaded() {
|
||||
final long wait = 10; // Time to wait before re-checking if another thread loaded the library
|
||||
final long timeout =
|
||||
10 * 1000; // Maximum time to wait for another thread to load the library (10 seconds)
|
||||
long waited = 0;
|
||||
try {
|
||||
while (libraryLoaded.get() == LibraryState.LOADING) {
|
||||
Thread.sleep(wait);
|
||||
waited += wait;
|
||||
|
||||
if (waited >= timeout) {
|
||||
throw new RuntimeException(
|
||||
"Exceeded timeout whilst trying to load the RocksDB shared library");
|
||||
}
|
||||
}
|
||||
} catch (final InterruptedException e) {
|
||||
// restore interrupted status
|
||||
Thread.currentThread().interrupt();
|
||||
throw new RuntimeException("Interrupted whilst trying to load the RocksDB shared library", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+69
-8
@@ -30,6 +30,8 @@ class VectorRep : public MemTableRep {
|
||||
// collection.
|
||||
void Insert(KeyHandle handle) override;
|
||||
|
||||
void InsertConcurrently(KeyHandle handle) override;
|
||||
|
||||
// Returns true iff an entry that compares equal to key is in the collection.
|
||||
bool Contains(const char* key) const override;
|
||||
|
||||
@@ -40,6 +42,8 @@ class VectorRep : public MemTableRep {
|
||||
void Get(const LookupKey& k, void* callback_args,
|
||||
bool (*callback_func)(void* arg, const char* entry)) override;
|
||||
|
||||
void BatchPostProcess() override;
|
||||
|
||||
~VectorRep() override = default;
|
||||
|
||||
class Iterator : public MemTableRep::Iterator {
|
||||
@@ -79,6 +83,10 @@ class VectorRep : public MemTableRep {
|
||||
// Advance to the first entry with a key >= target
|
||||
void Seek(const Slice& user_key, const char* memtable_key) override;
|
||||
|
||||
// Seek and do some memory validation
|
||||
Status SeekAndValidate(const Slice& internal_key, const char* memtable_key,
|
||||
bool allow_data_in_errors) override;
|
||||
|
||||
// Advance to the first entry with a key <= target
|
||||
void SeekForPrev(const Slice& user_key, const char* memtable_key) override;
|
||||
|
||||
@@ -96,19 +104,40 @@ class VectorRep : public MemTableRep {
|
||||
|
||||
private:
|
||||
friend class Iterator;
|
||||
ALIGN_AS(CACHE_LINE_SIZE) RelaxedAtomic<size_t> bucket_size_;
|
||||
using Bucket = std::vector<const char*>;
|
||||
std::shared_ptr<Bucket> bucket_;
|
||||
mutable port::RWMutex rwlock_;
|
||||
bool immutable_;
|
||||
bool sorted_;
|
||||
const KeyComparator& compare_;
|
||||
// Thread-local vector to buffer concurrent writes.
|
||||
using TlBucket = std::vector<const char*>;
|
||||
ThreadLocalPtr tl_writes_;
|
||||
|
||||
static void DeleteTlBucket(void* ptr) {
|
||||
auto* v = static_cast<TlBucket*>(ptr);
|
||||
delete v;
|
||||
}
|
||||
};
|
||||
|
||||
void VectorRep::Insert(KeyHandle handle) {
|
||||
auto* key = static_cast<char*>(handle);
|
||||
WriteLock l(&rwlock_);
|
||||
assert(!immutable_);
|
||||
bucket_->push_back(key);
|
||||
{
|
||||
WriteLock l(&rwlock_);
|
||||
assert(!immutable_);
|
||||
bucket_->push_back(key);
|
||||
}
|
||||
bucket_size_.FetchAddRelaxed(1);
|
||||
}
|
||||
|
||||
void VectorRep::InsertConcurrently(KeyHandle handle) {
|
||||
auto* v = static_cast<TlBucket*>(tl_writes_.Get());
|
||||
if (!v) {
|
||||
v = new TlBucket();
|
||||
tl_writes_.Reset(v);
|
||||
}
|
||||
v->push_back(static_cast<char*>(handle));
|
||||
}
|
||||
|
||||
// Returns true iff an entry that compares equal to key is in the collection.
|
||||
@@ -123,19 +152,35 @@ void VectorRep::MarkReadOnly() {
|
||||
}
|
||||
|
||||
size_t VectorRep::ApproximateMemoryUsage() {
|
||||
return sizeof(bucket_) + sizeof(*bucket_) +
|
||||
bucket_->size() *
|
||||
sizeof(
|
||||
std::remove_reference<decltype(*bucket_)>::type::value_type);
|
||||
return bucket_size_.LoadRelaxed() *
|
||||
sizeof(std::remove_reference<decltype(*bucket_)>::type::value_type);
|
||||
}
|
||||
|
||||
void VectorRep::BatchPostProcess() {
|
||||
auto* v = static_cast<TlBucket*>(tl_writes_.Get());
|
||||
if (v) {
|
||||
{
|
||||
WriteLock l(&rwlock_);
|
||||
assert(!immutable_);
|
||||
for (auto& key : *v) {
|
||||
bucket_->push_back(key);
|
||||
}
|
||||
}
|
||||
bucket_size_.FetchAddRelaxed(v->size());
|
||||
delete v;
|
||||
tl_writes_.Reset(nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
VectorRep::VectorRep(const KeyComparator& compare, Allocator* allocator,
|
||||
size_t count)
|
||||
: MemTableRep(allocator),
|
||||
bucket_size_(0),
|
||||
bucket_(new Bucket()),
|
||||
immutable_(false),
|
||||
sorted_(false),
|
||||
compare_(compare) {
|
||||
compare_(compare),
|
||||
tl_writes_(DeleteTlBucket) {
|
||||
bucket_.get()->reserve(count);
|
||||
}
|
||||
|
||||
@@ -221,6 +266,22 @@ void VectorRep::Iterator::Seek(const Slice& user_key,
|
||||
.first;
|
||||
}
|
||||
|
||||
Status VectorRep::Iterator::SeekAndValidate(const Slice& /* internal_key */,
|
||||
const char* /* memtable_key */,
|
||||
bool /* allow_data_in_errors */) {
|
||||
if (vrep_) {
|
||||
WriteLock l(&vrep_->rwlock_);
|
||||
if (bucket_->begin() == bucket_->end()) {
|
||||
// Memtable is empty
|
||||
return Status::OK();
|
||||
} else {
|
||||
return Status::NotSupported("SeekAndValidate() not implemented");
|
||||
}
|
||||
} else {
|
||||
return Status::NotSupported("SeekAndValidate() not implemented");
|
||||
}
|
||||
}
|
||||
|
||||
// Advance to the first entry with a key <= target
|
||||
void VectorRep::Iterator::SeekForPrev(const Slice& /*user_key*/,
|
||||
const char* /*memtable_key*/) {
|
||||
|
||||
+17
-1
@@ -349,6 +349,10 @@ static std::unordered_map<std::string, OptionTypeInfo>
|
||||
OptionTypeFlags::kMutable}},
|
||||
{"allow_trivial_move",
|
||||
{offsetof(class CompactionOptionsUniversal, allow_trivial_move),
|
||||
OptionType::kBoolean, OptionVerificationType::kNormal,
|
||||
OptionTypeFlags::kMutable}},
|
||||
{"reduce_file_locking",
|
||||
{offsetof(class CompactionOptionsUniversal, reduce_file_locking),
|
||||
OptionType::kBoolean, OptionVerificationType::kNormal,
|
||||
OptionTypeFlags::kMutable}}};
|
||||
|
||||
@@ -698,6 +702,11 @@ static std::unordered_map<std::string, OptionTypeInfo>
|
||||
name, value, addr);
|
||||
}
|
||||
})},
|
||||
{"compression_manager",
|
||||
OptionTypeInfo::AsCustomSharedPtr<CompressionManager>(
|
||||
offsetof(struct MutableCFOptions, compression_manager),
|
||||
OptionVerificationType::kByNameAllowNull,
|
||||
(OptionTypeFlags::kMutable | OptionTypeFlags::kAllowNull))},
|
||||
// End special case properties
|
||||
{"memtable_max_range_deletions",
|
||||
{offsetof(struct MutableCFOptions, memtable_max_range_deletions),
|
||||
@@ -707,6 +716,10 @@ static std::unordered_map<std::string, OptionTypeInfo>
|
||||
{offsetof(struct MutableCFOptions, memtable_op_scan_flush_trigger),
|
||||
OptionType::kUInt32T, OptionVerificationType::kNormal,
|
||||
OptionTypeFlags::kMutable}},
|
||||
{"memtable_avg_op_scan_flush_trigger",
|
||||
{offsetof(struct MutableCFOptions, memtable_avg_op_scan_flush_trigger),
|
||||
OptionType::kUInt32T, OptionVerificationType::kNormal,
|
||||
OptionTypeFlags::kMutable}},
|
||||
};
|
||||
|
||||
static std::unordered_map<std::string, OptionTypeInfo>
|
||||
@@ -1190,7 +1203,8 @@ void MutableCFOptions::Dump(Logger* log) const {
|
||||
uncache_aggressiveness);
|
||||
ROCKS_LOG_INFO(log, " memtable_op_scan_flush_trigger: %" PRIu32,
|
||||
memtable_op_scan_flush_trigger);
|
||||
|
||||
ROCKS_LOG_INFO(log, " memtable_avg_op_scan_flush_trigger: %" PRIu32,
|
||||
memtable_avg_op_scan_flush_trigger);
|
||||
// Universal Compaction Options
|
||||
ROCKS_LOG_INFO(log, "compaction_options_universal.size_ratio : %d",
|
||||
compaction_options_universal.size_ratio);
|
||||
@@ -1213,6 +1227,8 @@ void MutableCFOptions::Dump(Logger* log) const {
|
||||
static_cast<int>(compaction_options_universal.allow_trivial_move));
|
||||
ROCKS_LOG_INFO(log, "compaction_options_universal.incremental : %d",
|
||||
static_cast<int>(compaction_options_universal.incremental));
|
||||
ROCKS_LOG_INFO(log, "compaction_options_universal.reduce_file_locking : %d",
|
||||
compaction_options_universal.reduce_file_locking);
|
||||
|
||||
// FIFO Compaction Options
|
||||
ROCKS_LOG_INFO(log, "compaction_options_fifo.max_table_files_size : %" PRIu64,
|
||||
|
||||
@@ -161,6 +161,7 @@ struct MutableCFOptions {
|
||||
bottommost_compression(options.bottommost_compression),
|
||||
compression_opts(options.compression_opts),
|
||||
bottommost_compression_opts(options.bottommost_compression_opts),
|
||||
compression_manager(options.compression_manager),
|
||||
last_level_temperature(options.last_level_temperature),
|
||||
default_write_temperature(options.default_write_temperature),
|
||||
memtable_protection_bytes_per_key(
|
||||
@@ -174,7 +175,9 @@ struct MutableCFOptions {
|
||||
bottommost_file_compaction_delay(
|
||||
options.bottommost_file_compaction_delay),
|
||||
uncache_aggressiveness(options.uncache_aggressiveness),
|
||||
memtable_op_scan_flush_trigger(options.memtable_op_scan_flush_trigger) {
|
||||
memtable_op_scan_flush_trigger(options.memtable_op_scan_flush_trigger),
|
||||
memtable_avg_op_scan_flush_trigger(
|
||||
options.memtable_avg_op_scan_flush_trigger) {
|
||||
RefreshDerivedOptions(options.num_levels, options.compaction_style);
|
||||
}
|
||||
|
||||
@@ -230,7 +233,8 @@ struct MutableCFOptions {
|
||||
memtable_max_range_deletions(0),
|
||||
bottommost_file_compaction_delay(0),
|
||||
uncache_aggressiveness(0),
|
||||
memtable_op_scan_flush_trigger(0) {}
|
||||
memtable_op_scan_flush_trigger(0),
|
||||
memtable_avg_op_scan_flush_trigger(0) {}
|
||||
|
||||
explicit MutableCFOptions(const Options& options);
|
||||
|
||||
@@ -327,6 +331,7 @@ struct MutableCFOptions {
|
||||
CompressionType bottommost_compression;
|
||||
CompressionOptions compression_opts;
|
||||
CompressionOptions bottommost_compression_opts;
|
||||
std::shared_ptr<CompressionManager> compression_manager;
|
||||
Temperature last_level_temperature;
|
||||
Temperature default_write_temperature;
|
||||
uint32_t memtable_protection_bytes_per_key;
|
||||
@@ -339,6 +344,7 @@ struct MutableCFOptions {
|
||||
uint32_t bottommost_file_compaction_delay;
|
||||
uint32_t uncache_aggressiveness;
|
||||
uint32_t memtable_op_scan_flush_trigger;
|
||||
uint32_t memtable_avg_op_scan_flush_trigger;
|
||||
|
||||
// Derived options
|
||||
// Per-level target file size.
|
||||
|
||||
@@ -272,7 +272,8 @@ Status ConfigurableHelper::ConfigureOptions(
|
||||
if (config_options.ignore_unknown_options) {
|
||||
s = Status::OK();
|
||||
} else if (s.ok() && unused == nullptr && !remaining.empty()) {
|
||||
s = Status::NotFound("Could not find option: ", remaining.begin()->first);
|
||||
s = Status::NotFound("Extra option not recognized",
|
||||
remaining.begin()->first);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
@@ -369,7 +370,7 @@ Status ConfigurableHelper::ConfigureSingleOption(
|
||||
const auto opt_info =
|
||||
FindOption(configurable, opt_name, &elem_name, &opt_ptr);
|
||||
if (opt_info == nullptr) {
|
||||
return Status::NotFound("Could not find option: ", name);
|
||||
return Status::NotFound("Could not find option", name);
|
||||
} else {
|
||||
return ConfigureOption(config_options, configurable, *opt_info, opt_name,
|
||||
elem_name, value, opt_ptr);
|
||||
@@ -465,7 +466,7 @@ Status ConfigurableHelper::ConfigureOption(
|
||||
return configurable.ParseOption(config_options, opt_info, name, value,
|
||||
opt_ptr);
|
||||
} else {
|
||||
return Status::NotFound("Could not find option: ", name);
|
||||
return Status::NotFound("Unknown how to configure option", name);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+12
-4
@@ -112,7 +112,9 @@ AdvancedColumnFamilyOptions::AdvancedColumnFamilyOptions(const Options& options)
|
||||
blob_cache(options.blob_cache),
|
||||
prepopulate_blob_cache(options.prepopulate_blob_cache),
|
||||
persist_user_defined_timestamps(options.persist_user_defined_timestamps),
|
||||
memtable_op_scan_flush_trigger(options.memtable_op_scan_flush_trigger) {
|
||||
memtable_op_scan_flush_trigger(options.memtable_op_scan_flush_trigger),
|
||||
memtable_avg_op_scan_flush_trigger(
|
||||
options.memtable_avg_op_scan_flush_trigger) {
|
||||
assert(memtable_factory.get() != nullptr);
|
||||
if (max_bytes_for_level_multiplier_additional.size() <
|
||||
static_cast<unsigned int>(num_levels)) {
|
||||
@@ -284,9 +286,12 @@ void ColumnFamilyOptions::Dump(Logger* log) const {
|
||||
ROCKS_LOG_HEADER(log,
|
||||
" Options.max_sequential_skip_in_iterations: %" PRIu64,
|
||||
max_sequential_skip_in_iterations);
|
||||
ROCKS_LOG_HEADER(
|
||||
log, " Options.memtable_op_scan_flush_trigger: %" PRIu32,
|
||||
memtable_op_scan_flush_trigger);
|
||||
ROCKS_LOG_HEADER(log,
|
||||
" Options.memtable_op_scan_flush_trigger: %" PRIu32,
|
||||
memtable_op_scan_flush_trigger);
|
||||
ROCKS_LOG_HEADER(log,
|
||||
" Options.memtable_avg_op_scan_flush_trigger: %" PRIu32,
|
||||
memtable_avg_op_scan_flush_trigger);
|
||||
ROCKS_LOG_HEADER(log,
|
||||
" Options.max_compaction_bytes: %" PRIu64,
|
||||
max_compaction_bytes);
|
||||
@@ -353,6 +358,9 @@ void ColumnFamilyOptions::Dump(Logger* log) const {
|
||||
str_compaction_stop_style.c_str());
|
||||
ROCKS_LOG_HEADER(log, "Options.compaction_options_universal.max_read_amp: %d",
|
||||
compaction_options_universal.max_read_amp);
|
||||
ROCKS_LOG_HEADER(
|
||||
log, "Options.compaction_options_universal.reduce_file_locking: %d",
|
||||
compaction_options_universal.reduce_file_locking);
|
||||
ROCKS_LOG_HEADER(
|
||||
log, "Options.compaction_options_fifo.max_table_files_size: %" PRIu64,
|
||||
compaction_options_fifo.max_table_files_size);
|
||||
|
||||
+131
-2
@@ -293,6 +293,7 @@ void UpdateColumnFamilyOptions(const MutableCFOptions& moptions,
|
||||
cf_opts->compression_opts = moptions.compression_opts;
|
||||
cf_opts->bottommost_compression = moptions.bottommost_compression;
|
||||
cf_opts->bottommost_compression_opts = moptions.bottommost_compression_opts;
|
||||
cf_opts->compression_manager = moptions.compression_manager;
|
||||
cf_opts->sample_for_compression = moptions.sample_for_compression;
|
||||
cf_opts->compression_per_level = moptions.compression_per_level;
|
||||
cf_opts->last_level_temperature = moptions.last_level_temperature;
|
||||
@@ -301,6 +302,8 @@ void UpdateColumnFamilyOptions(const MutableCFOptions& moptions,
|
||||
cf_opts->uncache_aggressiveness = moptions.uncache_aggressiveness;
|
||||
cf_opts->memtable_op_scan_flush_trigger =
|
||||
moptions.memtable_op_scan_flush_trigger;
|
||||
cf_opts->memtable_avg_op_scan_flush_trigger =
|
||||
moptions.memtable_avg_op_scan_flush_trigger;
|
||||
}
|
||||
|
||||
void UpdateColumnFamilyOptions(const ImmutableCFOptions& ioptions,
|
||||
@@ -383,6 +386,133 @@ std::unordered_map<std::string, CompressionType>
|
||||
{"kLZ4HCCompression", kLZ4HCCompression},
|
||||
{"kXpressCompression", kXpressCompression},
|
||||
{"kZSTD", kZSTD},
|
||||
{"kCustomCompression80", kCustomCompression80},
|
||||
{"kCustomCompression81", kCustomCompression81},
|
||||
{"kCustomCompression82", kCustomCompression82},
|
||||
{"kCustomCompression83", kCustomCompression83},
|
||||
{"kCustomCompression84", kCustomCompression84},
|
||||
{"kCustomCompression85", kCustomCompression85},
|
||||
{"kCustomCompression86", kCustomCompression86},
|
||||
{"kCustomCompression87", kCustomCompression87},
|
||||
{"kCustomCompression88", kCustomCompression88},
|
||||
{"kCustomCompression89", kCustomCompression89},
|
||||
{"kCustomCompression8A", kCustomCompression8A},
|
||||
{"kCustomCompression8B", kCustomCompression8B},
|
||||
{"kCustomCompression8C", kCustomCompression8C},
|
||||
{"kCustomCompression8D", kCustomCompression8D},
|
||||
{"kCustomCompression8E", kCustomCompression8E},
|
||||
{"kCustomCompression8F", kCustomCompression8F},
|
||||
{"kCustomCompression90", kCustomCompression90},
|
||||
{"kCustomCompression91", kCustomCompression91},
|
||||
{"kCustomCompression92", kCustomCompression92},
|
||||
{"kCustomCompression93", kCustomCompression93},
|
||||
{"kCustomCompression94", kCustomCompression94},
|
||||
{"kCustomCompression95", kCustomCompression95},
|
||||
{"kCustomCompression96", kCustomCompression96},
|
||||
{"kCustomCompression97", kCustomCompression97},
|
||||
{"kCustomCompression98", kCustomCompression98},
|
||||
{"kCustomCompression99", kCustomCompression99},
|
||||
{"kCustomCompression9A", kCustomCompression9A},
|
||||
{"kCustomCompression9B", kCustomCompression9B},
|
||||
{"kCustomCompression9C", kCustomCompression9C},
|
||||
{"kCustomCompression9D", kCustomCompression9D},
|
||||
{"kCustomCompression9E", kCustomCompression9E},
|
||||
{"kCustomCompression9F", kCustomCompression9F},
|
||||
{"kCustomCompressionA0", kCustomCompressionA0},
|
||||
{"kCustomCompressionA1", kCustomCompressionA1},
|
||||
{"kCustomCompressionA2", kCustomCompressionA2},
|
||||
{"kCustomCompressionA3", kCustomCompressionA3},
|
||||
{"kCustomCompressionA4", kCustomCompressionA4},
|
||||
{"kCustomCompressionA5", kCustomCompressionA5},
|
||||
{"kCustomCompressionA6", kCustomCompressionA6},
|
||||
{"kCustomCompressionA7", kCustomCompressionA7},
|
||||
{"kCustomCompressionA8", kCustomCompressionA8},
|
||||
{"kCustomCompressionA9", kCustomCompressionA9},
|
||||
{"kCustomCompressionAA", kCustomCompressionAA},
|
||||
{"kCustomCompressionAB", kCustomCompressionAB},
|
||||
{"kCustomCompressionAC", kCustomCompressionAC},
|
||||
{"kCustomCompressionAD", kCustomCompressionAD},
|
||||
{"kCustomCompressionAE", kCustomCompressionAE},
|
||||
{"kCustomCompressionAF", kCustomCompressionAF},
|
||||
{"kCustomCompressionB0", kCustomCompressionB0},
|
||||
{"kCustomCompressionB1", kCustomCompressionB1},
|
||||
{"kCustomCompressionB2", kCustomCompressionB2},
|
||||
{"kCustomCompressionB3", kCustomCompressionB3},
|
||||
{"kCustomCompressionB4", kCustomCompressionB4},
|
||||
{"kCustomCompressionB5", kCustomCompressionB5},
|
||||
{"kCustomCompressionB6", kCustomCompressionB6},
|
||||
{"kCustomCompressionB7", kCustomCompressionB7},
|
||||
{"kCustomCompressionB8", kCustomCompressionB8},
|
||||
{"kCustomCompressionB9", kCustomCompressionB9},
|
||||
{"kCustomCompressionBA", kCustomCompressionBA},
|
||||
{"kCustomCompressionBB", kCustomCompressionBB},
|
||||
{"kCustomCompressionBC", kCustomCompressionBC},
|
||||
{"kCustomCompressionBD", kCustomCompressionBD},
|
||||
{"kCustomCompressionBE", kCustomCompressionBE},
|
||||
{"kCustomCompressionBF", kCustomCompressionBF},
|
||||
{"kCustomCompressionC0", kCustomCompressionC0},
|
||||
{"kCustomCompressionC1", kCustomCompressionC1},
|
||||
{"kCustomCompressionC2", kCustomCompressionC2},
|
||||
{"kCustomCompressionC3", kCustomCompressionC3},
|
||||
{"kCustomCompressionC4", kCustomCompressionC4},
|
||||
{"kCustomCompressionC5", kCustomCompressionC5},
|
||||
{"kCustomCompressionC6", kCustomCompressionC6},
|
||||
{"kCustomCompressionC7", kCustomCompressionC7},
|
||||
{"kCustomCompressionC8", kCustomCompressionC8},
|
||||
{"kCustomCompressionC9", kCustomCompressionC9},
|
||||
{"kCustomCompressionCA", kCustomCompressionCA},
|
||||
{"kCustomCompressionCB", kCustomCompressionCB},
|
||||
{"kCustomCompressionCC", kCustomCompressionCC},
|
||||
{"kCustomCompressionCD", kCustomCompressionCD},
|
||||
{"kCustomCompressionCE", kCustomCompressionCE},
|
||||
{"kCustomCompressionCF", kCustomCompressionCF},
|
||||
{"kCustomCompressionD0", kCustomCompressionD0},
|
||||
{"kCustomCompressionD1", kCustomCompressionD1},
|
||||
{"kCustomCompressionD2", kCustomCompressionD2},
|
||||
{"kCustomCompressionD3", kCustomCompressionD3},
|
||||
{"kCustomCompressionD4", kCustomCompressionD4},
|
||||
{"kCustomCompressionD5", kCustomCompressionD5},
|
||||
{"kCustomCompressionD6", kCustomCompressionD6},
|
||||
{"kCustomCompressionD7", kCustomCompressionD7},
|
||||
{"kCustomCompressionD8", kCustomCompressionD8},
|
||||
{"kCustomCompressionD9", kCustomCompressionD9},
|
||||
{"kCustomCompressionDA", kCustomCompressionDA},
|
||||
{"kCustomCompressionDB", kCustomCompressionDB},
|
||||
{"kCustomCompressionDC", kCustomCompressionDC},
|
||||
{"kCustomCompressionDD", kCustomCompressionDD},
|
||||
{"kCustomCompressionDE", kCustomCompressionDE},
|
||||
{"kCustomCompressionDF", kCustomCompressionDF},
|
||||
{"kCustomCompressionE0", kCustomCompressionE0},
|
||||
{"kCustomCompressionE1", kCustomCompressionE1},
|
||||
{"kCustomCompressionE2", kCustomCompressionE2},
|
||||
{"kCustomCompressionE3", kCustomCompressionE3},
|
||||
{"kCustomCompressionE4", kCustomCompressionE4},
|
||||
{"kCustomCompressionE5", kCustomCompressionE5},
|
||||
{"kCustomCompressionE6", kCustomCompressionE6},
|
||||
{"kCustomCompressionE7", kCustomCompressionE7},
|
||||
{"kCustomCompressionE8", kCustomCompressionE8},
|
||||
{"kCustomCompressionE9", kCustomCompressionE9},
|
||||
{"kCustomCompressionEA", kCustomCompressionEA},
|
||||
{"kCustomCompressionEB", kCustomCompressionEB},
|
||||
{"kCustomCompressionEC", kCustomCompressionEC},
|
||||
{"kCustomCompressionED", kCustomCompressionED},
|
||||
{"kCustomCompressionEE", kCustomCompressionEE},
|
||||
{"kCustomCompressionEF", kCustomCompressionEF},
|
||||
{"kCustomCompressionF0", kCustomCompressionF0},
|
||||
{"kCustomCompressionF1", kCustomCompressionF1},
|
||||
{"kCustomCompressionF2", kCustomCompressionF2},
|
||||
{"kCustomCompressionF3", kCustomCompressionF3},
|
||||
{"kCustomCompressionF4", kCustomCompressionF4},
|
||||
{"kCustomCompressionF5", kCustomCompressionF5},
|
||||
{"kCustomCompressionF6", kCustomCompressionF6},
|
||||
{"kCustomCompressionF7", kCustomCompressionF7},
|
||||
{"kCustomCompressionF8", kCustomCompressionF8},
|
||||
{"kCustomCompressionF9", kCustomCompressionF9},
|
||||
{"kCustomCompressionFA", kCustomCompressionFA},
|
||||
{"kCustomCompressionFB", kCustomCompressionFB},
|
||||
{"kCustomCompressionFC", kCustomCompressionFC},
|
||||
{"kCustomCompressionFD", kCustomCompressionFD},
|
||||
{"kCustomCompressionFE", kCustomCompressionFE},
|
||||
{"kDisableCompressionOption", kDisableCompressionOption}};
|
||||
|
||||
const std::vector<CompressionType>& GetSupportedCompressions() {
|
||||
@@ -565,7 +695,6 @@ bool SerializeSingleOptionHelper(const void* opt_address,
|
||||
return SerializeEnum<CompressionType>(
|
||||
compression_type_string_map,
|
||||
*(static_cast<const CompressionType*>(opt_address)), value);
|
||||
break;
|
||||
case OptionType::kChecksumType:
|
||||
return SerializeEnum<ChecksumType>(
|
||||
checksum_type_string_map,
|
||||
@@ -908,7 +1037,7 @@ Status OptionTypeInfo::Parse(const ConfigOptions& config_options,
|
||||
: value;
|
||||
|
||||
if (opt_ptr == nullptr) {
|
||||
return Status::NotFound("Could not find option", opt_name);
|
||||
return Status::NotFound("Nullptr option", opt_name);
|
||||
} else if (parse_func_ != nullptr) {
|
||||
ConfigOptions copy = config_options;
|
||||
copy.invoke_prepare_options = false;
|
||||
|
||||
@@ -530,6 +530,8 @@ TEST_F(OptionsSettableTest, ColumnFamilyOptionsAllFieldsSettable) {
|
||||
sizeof(const CompactionFilter*)},
|
||||
{offsetof(struct ColumnFamilyOptions, compaction_filter_factory),
|
||||
sizeof(std::shared_ptr<CompactionFilterFactory>)},
|
||||
{offsetof(struct ColumnFamilyOptions, compression_manager),
|
||||
sizeof(std::shared_ptr<CompressionManager>)},
|
||||
{offsetof(struct ColumnFamilyOptions, prefix_extractor),
|
||||
sizeof(std::shared_ptr<const SliceTransform>)},
|
||||
{offsetof(struct ColumnFamilyOptions, snap_refresh_nanos),
|
||||
@@ -619,6 +621,7 @@ TEST_F(OptionsSettableTest, ColumnFamilyOptionsAllFieldsSettable) {
|
||||
"strategy=7;max_dict_bytes=8;level=9;window_bits=10;max_compressed_bytes_"
|
||||
"per_kb=876;checksum=true};"
|
||||
"bottommost_compression=kDisableCompressionOption;"
|
||||
"compression_manager=BuiltinV2;"
|
||||
"level0_stop_writes_trigger=33;"
|
||||
"num_levels=99;"
|
||||
"level0_slowdown_writes_trigger=22;"
|
||||
@@ -675,7 +678,8 @@ TEST_F(OptionsSettableTest, ColumnFamilyOptionsAllFieldsSettable) {
|
||||
"bottommost_file_compaction_delay=7200;"
|
||||
"uncache_aggressiveness=1234;"
|
||||
"paranoid_memory_checks=1;"
|
||||
"memtable_op_scan_flush_trigger=123;",
|
||||
"memtable_op_scan_flush_trigger=123;"
|
||||
"memtable_avg_op_scan_flush_trigger=12;",
|
||||
new_options));
|
||||
|
||||
ASSERT_NE(new_options->blob_cache.get(), nullptr);
|
||||
@@ -699,6 +703,11 @@ TEST_F(OptionsSettableTest, ColumnFamilyOptionsAllFieldsSettable) {
|
||||
new_options->compaction_options_fifo.file_temperature_age_thresholds[0]
|
||||
.age,
|
||||
12345);
|
||||
// TODO: try to enhance ObjectLibrary to support singletons
|
||||
// ASSERT_EQ(new_options->compression_manager,
|
||||
// GetBuiltinV2CompressionManager());
|
||||
ASSERT_STREQ(new_options->compression_manager->Name(),
|
||||
GetBuiltinV2CompressionManager()->Name());
|
||||
|
||||
ColumnFamilyOptions rnd_filled_options = *new_options;
|
||||
|
||||
@@ -718,6 +727,8 @@ TEST_F(OptionsSettableTest, ColumnFamilyOptionsAllFieldsSettable) {
|
||||
sizeof(std::vector<int>)},
|
||||
{offsetof(struct MutableCFOptions, compaction_options_fifo),
|
||||
sizeof(struct CompactionOptionsFIFO)},
|
||||
{offsetof(struct MutableCFOptions, compression_manager),
|
||||
sizeof(std::shared_ptr<CompressionManager>)},
|
||||
{offsetof(struct MutableCFOptions, compression_per_level),
|
||||
sizeof(std::vector<CompressionType>)},
|
||||
{offsetof(struct MutableCFOptions, max_file_size),
|
||||
|
||||
@@ -2031,7 +2031,7 @@ TEST_F(OptionsTest, GetStringFromCompressionType) {
|
||||
ASSERT_EQ(res, "kZlibCompression");
|
||||
|
||||
ASSERT_NOK(
|
||||
GetStringFromCompressionType(&res, static_cast<CompressionType>(-10)));
|
||||
GetStringFromCompressionType(&res, static_cast<CompressionType>(0x7F)));
|
||||
}
|
||||
|
||||
TEST_F(OptionsTest, OnlyMutableDBOptions) {
|
||||
|
||||
+2
-1
@@ -220,8 +220,9 @@ int GetMaxOpenFiles() {
|
||||
return std::numeric_limits<int>::max();
|
||||
}
|
||||
return static_cast<int>(no_files_limit.rlim_cur);
|
||||
#endif
|
||||
#else
|
||||
return -1;
|
||||
#endif
|
||||
}
|
||||
|
||||
void* cacheline_aligned_alloc(size_t size) {
|
||||
|
||||
Loaded 100 of 157 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user