mirror of
https://github.com/facebook/rocksdb.git
synced 2026-07-08 07:05:23 +08:00
Compare commits
176 Commits
ajkr-patch-2
...
v8.3.2
| Author | SHA1 | Date | |
|---|---|---|---|
| 3f7c92b975 | |||
| e978dccd7a | |||
| 32c6de7bc3 | |||
| b36f691e58 | |||
| 0f6d363db9 | |||
| 031714aa83 | |||
| 39f5846ec7 | |||
| 4067acabca | |||
| 8d8eb0e77e | |||
| 7263f51d50 | |||
| 50046869a4 | |||
| e110d713e0 | |||
| 586d78b31e | |||
| 206fdea3d9 | |||
| 5fc57eec2b | |||
| fb636f2498 | |||
| 2084cdf237 | |||
| 113f3250f1 | |||
| 47235dda9e | |||
| 8827cd0618 | |||
| 7531cbda91 | |||
| 459969e993 | |||
| f4a02f2c52 | |||
| 6ba4717f35 | |||
| 8f763bdeab | |||
| e1d1c50317 | |||
| 736b3c4909 | |||
| a5909f8864 | |||
| a11f1e12ca | |||
| c81d58016b | |||
| 50b33ebb1b | |||
| a475e9f746 | |||
| 03a892a9fb | |||
| 41a7fbf758 | |||
| e0e318f370 | |||
| 76a40286b0 | |||
| 925d8252e5 | |||
| d3ed796855 | |||
| 62fc15f009 | |||
| 40d69b59ad | |||
| 3622cfa34a | |||
| 46dbcfd799 | |||
| a2c1f57358 | |||
| fb63d9b4ee | |||
| d79be3dca2 | |||
| adc9001f20 | |||
| 151242ce46 | |||
| 0a774a102f | |||
| 6cac4c79d4 | |||
| 43e9a60bb2 | |||
| bd80433c73 | |||
| 9b698cda51 | |||
| f3818948e8 | |||
| b8555ba470 | |||
| 226ee25d30 | |||
| 4d72f48e57 | |||
| ba16e8eee7 | |||
| 9500d90d1b | |||
| 6b67b561bc | |||
| 647cd73674 | |||
| 760b773f58 | |||
| d5a9c0c937 | |||
| c8552d8c63 | |||
| d30bb3d14a | |||
| b2c4bc5f73 | |||
| 140dd93b57 | |||
| 64cead919f | |||
| f9db0c6e9c | |||
| f631138e1c | |||
| 0578d9f951 | |||
| b3c43a5b99 | |||
| 0623c5b903 | |||
| 7f5b9f40cb | |||
| b45738622a | |||
| 03ccb1cd42 | |||
| 3c17930ede | |||
| e5a560ec98 | |||
| b4d78189b3 | |||
| 891ced8b15 | |||
| 39c29372bf | |||
| 0efd7b4ba1 | |||
| c14eb134ed | |||
| 601320164b | |||
| 40c2ec6d08 | |||
| 9f8cdc8ad6 | |||
| 57abdea389 | |||
| 8f6c2a2cc0 | |||
| b92bc04ab0 | |||
| cea81cad66 | |||
| 8c445407b7 | |||
| 87de4fee6b | |||
| cb58477185 | |||
| 204fcff751 | |||
| eac6b6d0cd | |||
| a72d55c99d | |||
| 291300ece8 | |||
| ccaa3225b0 | |||
| 664dabda8f | |||
| bab5f9a6f2 | |||
| 601efe3cf2 | |||
| 11cb6af6e5 | |||
| 2a23bee963 | |||
| 49881921cd | |||
| 648e972f30 | |||
| 9aa3b6f9ae | |||
| fbd603d04a | |||
| 969d4e1dd2 | |||
| 7a07afe82e | |||
| daeec505a4 | |||
| 1de697628e | |||
| 1d52438504 | |||
| 6c65bf1743 | |||
| e01073252b | |||
| 13357de0c2 | |||
| a1a3b23346 | |||
| 50e9b3f9c7 | |||
| e168c1b1a4 | |||
| ddde1e6af8 | |||
| d053926fa2 | |||
| 95d67f3646 | |||
| 8dfcfd4e90 | |||
| cf09917c18 | |||
| b7e73501d8 | |||
| 3c9eed688e | |||
| af7872ffd1 | |||
| f007b8fdea | |||
| 229297d1b8 | |||
| 9fa9becf53 | |||
| 286080456c | |||
| 476b01579c | |||
| 1b48ecc2c6 | |||
| ea85148b78 | |||
| ebfca2cf00 | |||
| 83bc03a99a | |||
| cfe50f7e77 | |||
| 142b18d00b | |||
| fcd816d534 | |||
| d47126875b | |||
| b6640c3117 | |||
| 25e1365227 | |||
| 6aef1a05d6 | |||
| 68e4581c67 | |||
| 64a1f7670f | |||
| 88056ea6cb | |||
| ab22e79824 | |||
| 94ec433833 | |||
| 9794acb597 | |||
| 6d5e8604f1 | |||
| 1969815fa8 | |||
| c19672c187 | |||
| 42d6652ba2 | |||
| a72f591825 | |||
| ab2157fa6f | |||
| 3cacd4b4ec | |||
| b7747bbc9f | |||
| 34bb3ddc43 | |||
| ee5305fabb | |||
| 77b61abc7b | |||
| 876d281592 | |||
| 6650ca244e | |||
| b5827c806c | |||
| 68fa90ca43 | |||
| 54d72085b5 | |||
| 9b66331388 | |||
| 92e8874654 | |||
| 4a51900700 | |||
| 27cf09172c | |||
| cf756ed916 | |||
| e17f31057b | |||
| 0cf1008fe3 | |||
| 390cc0b156 | |||
| 63da9cfa26 | |||
| 701a19cc83 | |||
| fec5c8deb8 | |||
| 6781009ee8 | |||
| 6af16ac7c1 |
+32
-15
@@ -152,15 +152,15 @@ commands:
|
||||
steps:
|
||||
- run:
|
||||
name: "Test low-variance benchmarks"
|
||||
command: ./tools/benchmark_ci.py --db_dir /tmp/rocksdb-benchmark-datadir --output_dir /tmp/benchmark-results --num_keys 10000000
|
||||
command: ./tools/benchmark_ci.py --db_dir /tmp/rocksdb-benchmark-datadir --output_dir /tmp/benchmark-results --num_keys 20000000
|
||||
environment:
|
||||
LD_LIBRARY_PATH: /usr/local/lib
|
||||
# How long to run parts of the test(s)
|
||||
DURATION_RO: 400
|
||||
DURATION_RW: 700
|
||||
DURATION_RO: 300
|
||||
DURATION_RW: 500
|
||||
# Keep threads within physical capacity of server (much lower than default)
|
||||
NUM_THREADS: 1
|
||||
MAX_BACKGROUND_JOBS: 3
|
||||
MAX_BACKGROUND_JOBS: 4
|
||||
# Don't run a couple of "optional" initial tests
|
||||
CI_TESTS_ONLY: "true"
|
||||
# Reduce configured size of levels to ensure more levels in the leveled compaction LSM tree
|
||||
@@ -170,7 +170,11 @@ commands:
|
||||
# The benchmark host has 32GB memory
|
||||
# The following values are tailored to work with that
|
||||
# Note, tests may not exercise the targeted issues if the memory is increased on new test hosts.
|
||||
|
||||
COMPRESSION_TYPE: "none"
|
||||
CACHE_INDEX_AND_FILTER_BLOCKS: 1
|
||||
MIN_LEVEL_TO_COMPRESS: 3
|
||||
CACHE_SIZE_MB: 10240
|
||||
MB_WRITE_PER_SEC: 2
|
||||
|
||||
post-benchmarks:
|
||||
steps:
|
||||
@@ -269,12 +273,12 @@ jobs:
|
||||
./sst_dump --help | grep -E -q 'Supported compression types: kNoCompression$' # Verify no compiled in compression
|
||||
- post-steps
|
||||
|
||||
build-linux-shared_lib-alt_namespace-status_checked:
|
||||
build-linux-static_lib-alt_namespace-status_checked:
|
||||
executor: linux-docker
|
||||
resource_class: 2xlarge
|
||||
steps:
|
||||
- pre-steps
|
||||
- run: ASSERT_STATUS_CHECKED=1 TEST_UINT128_COMPAT=1 ROCKSDB_MODIFY_NPHASH=1 LIB_MODE=shared OPT="-DROCKSDB_NAMESPACE=alternative_rocksdb_ns" make V=1 -j32 check
|
||||
- run: ASSERT_STATUS_CHECKED=1 TEST_UINT128_COMPAT=1 ROCKSDB_MODIFY_NPHASH=1 LIB_MODE=static OPT="-DROCKSDB_NAMESPACE=alternative_rocksdb_ns" make V=1 -j24 check
|
||||
- post-steps
|
||||
|
||||
build-linux-release:
|
||||
@@ -282,11 +286,21 @@ jobs:
|
||||
resource_class: 2xlarge
|
||||
steps:
|
||||
- checkout # check out the code in the project directory
|
||||
- run: make V=1 -j32 LIB_MODE=shared release
|
||||
- run: ls librocksdb.so # ensure shared lib built
|
||||
- run: ./db_stress --version # ensure with gflags
|
||||
- run: make clean
|
||||
- run: make V=1 -j32 release
|
||||
- run: ls librocksdb.a # ensure static lib built
|
||||
- run: ./db_stress --version # ensure with gflags
|
||||
- run: make clean
|
||||
- run: apt-get remove -y libgflags-dev
|
||||
- run: make V=1 -j32 LIB_MODE=shared release
|
||||
- run: ls librocksdb.so # ensure shared lib built
|
||||
- run: if ./db_stress --version; then false; else true; fi # ensure without gflags
|
||||
- run: make clean
|
||||
- run: make V=1 -j32 release
|
||||
- run: ls librocksdb.a # ensure static lib built
|
||||
- run: if ./db_stress --version; then false; else true; fi # ensure without gflags
|
||||
- post-steps
|
||||
|
||||
@@ -406,7 +420,10 @@ jobs:
|
||||
steps:
|
||||
- checkout # check out the code in the project directory
|
||||
- run: apt-get update -y && apt-get install -y libgflags-dev
|
||||
- run: make V=1 -j8 unity_test
|
||||
- run:
|
||||
name: "Unity build"
|
||||
command: make V=1 -j8 unity_test
|
||||
no_output_timeout: 20m
|
||||
- run: make V=1 -j8 -k check-headers # could be moved to a different build
|
||||
- post-steps
|
||||
|
||||
@@ -417,7 +434,7 @@ jobs:
|
||||
- pre-steps
|
||||
- setup-folly
|
||||
- build-folly
|
||||
- run: USE_FOLLY=1 CC=gcc-7 CXX=g++-7 V=1 make -j32 check
|
||||
- run: USE_FOLLY=1 LIB_MODE=static CC=gcc-7 CXX=g++-7 V=1 make -j32 check # TODO: LIB_MODE only to work around unresolved linker failures
|
||||
- post-steps
|
||||
|
||||
build-linux-gcc-7-with-folly-lite-no-test:
|
||||
@@ -463,7 +480,7 @@ jobs:
|
||||
resource_class: 2xlarge
|
||||
steps:
|
||||
- pre-steps
|
||||
- run: CC=gcc-11 CXX=g++-11 V=1 make -j32 all microbench
|
||||
- run: LIB_MODE=static CC=gcc-11 CXX=g++-11 V=1 make -j32 all microbench # TODO: LIB_MODE only to work around unresolved linker failures
|
||||
- post-steps
|
||||
|
||||
build-linux-clang-13-no_test_run:
|
||||
@@ -482,7 +499,7 @@ jobs:
|
||||
- pre-steps
|
||||
- setup-folly
|
||||
- build-folly
|
||||
- run: CC=clang-13 CXX=clang++-13 USE_CLANG=1 USE_FOLLY=1 COMPILE_WITH_UBSAN=1 COMPILE_WITH_ASAN=1 make -j32 check
|
||||
- run: CC=clang-13 CXX=clang++-13 LIB_MODE=static USE_CLANG=1 USE_FOLLY=1 COMPILE_WITH_UBSAN=1 COMPILE_WITH_ASAN=1 make -j32 check # TODO: LIB_MODE only to work around unresolved linker failures
|
||||
- post-steps
|
||||
|
||||
# This job is only to make sure the microbench tests are able to run, the benchmark result is not meaningful as the CI host is changing.
|
||||
@@ -499,7 +516,7 @@ jobs:
|
||||
resource_class: large
|
||||
steps:
|
||||
- pre-steps
|
||||
- run: ulimit -S -n `ulimit -H -n` && make V=1 -j8 CRASH_TEST_EXT_ARGS='--duration=960 --max_key=2500000' blackbox_crash_test_with_atomic_flush
|
||||
- run: ulimit -S -n `ulimit -H -n` && make V=1 -j8 CRASH_TEST_EXT_ARGS='--duration=960 --max_key=2500000 --use_io_uring=0' blackbox_crash_test_with_atomic_flush
|
||||
- post-steps
|
||||
|
||||
build-linux-crashtest-tiered-storage-bb:
|
||||
@@ -509,7 +526,7 @@ jobs:
|
||||
- pre-steps
|
||||
- run:
|
||||
name: "run crashtest"
|
||||
command: ulimit -S -n `ulimit -H -n` && make V=1 -j32 CRASH_TEST_EXT_ARGS=--duration=10800 blackbox_crash_test_with_tiered_storage
|
||||
command: ulimit -S -n `ulimit -H -n` && make V=1 -j32 CRASH_TEST_EXT_ARGS='--duration=10800 --use_io_uring=0' blackbox_crash_test_with_tiered_storage
|
||||
no_output_timeout: 100m
|
||||
- post-steps
|
||||
|
||||
@@ -520,7 +537,7 @@ jobs:
|
||||
- pre-steps
|
||||
- run:
|
||||
name: "run crashtest"
|
||||
command: ulimit -S -n `ulimit -H -n` && make V=1 -j32 CRASH_TEST_EXT_ARGS=--duration=10800 whitebox_crash_test_with_tiered_storage
|
||||
command: ulimit -S -n `ulimit -H -n` && make V=1 -j32 CRASH_TEST_EXT_ARGS='--duration=10800 --use_io_uring=0' whitebox_crash_test_with_tiered_storage
|
||||
no_output_timeout: 100m
|
||||
- post-steps
|
||||
|
||||
@@ -805,7 +822,7 @@ workflows:
|
||||
- build-linux-clang10-asan
|
||||
- build-linux-clang10-ubsan
|
||||
- build-linux-clang10-mini-tsan
|
||||
- build-linux-shared_lib-alt_namespace-status_checked
|
||||
- build-linux-static_lib-alt_namespace-status_checked
|
||||
jobs-linux-no-test-run:
|
||||
jobs:
|
||||
- build-linux-release
|
||||
|
||||
@@ -95,3 +95,4 @@ fuzz/crash-*
|
||||
|
||||
cmake-build-*
|
||||
third-party/folly/
|
||||
.cache
|
||||
+21
-52
@@ -253,33 +253,10 @@ if(CMAKE_SYSTEM_PROCESSOR MATCHES "loongarch64")
|
||||
endif(HAS_LOONGARCH64)
|
||||
endif(CMAKE_SYSTEM_PROCESSOR MATCHES "loongarch64")
|
||||
|
||||
option(PORTABLE "build a portable binary" OFF)
|
||||
option(FORCE_SSE42 "force building with SSE4.2, even when PORTABLE=ON" OFF)
|
||||
option(FORCE_AVX "force building with AVX, even when PORTABLE=ON" OFF)
|
||||
option(FORCE_AVX2 "force building with AVX2, even when PORTABLE=ON" OFF)
|
||||
if(PORTABLE)
|
||||
add_definitions(-DROCKSDB_PORTABLE)
|
||||
|
||||
# MSVC does not need a separate compiler flag to enable SSE4.2; if nmmintrin.h
|
||||
# is available, it is available by default.
|
||||
if(FORCE_SSE42 AND NOT MSVC)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -msse4.2 -mpclmul")
|
||||
endif()
|
||||
if(MSVC)
|
||||
if(FORCE_AVX)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /arch:AVX")
|
||||
endif()
|
||||
# MSVC automatically enables BMI / lzcnt with AVX2.
|
||||
if(FORCE_AVX2)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /arch:AVX2")
|
||||
endif()
|
||||
else()
|
||||
if(FORCE_AVX)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mavx")
|
||||
endif()
|
||||
if(FORCE_AVX2)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mavx2 -mbmi -mlzcnt")
|
||||
endif()
|
||||
set(PORTABLE 0 CACHE STRING "Minimum CPU arch to support, or 0 = current CPU, 1 = baseline CPU")
|
||||
if(PORTABLE STREQUAL 1)
|
||||
# Usually nothing to do; compiler default is typically the most general
|
||||
if(NOT MSVC)
|
||||
if(CMAKE_SYSTEM_PROCESSOR MATCHES "^s390x")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=z196")
|
||||
endif()
|
||||
@@ -287,10 +264,21 @@ if(PORTABLE)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=loongarch64")
|
||||
endif()
|
||||
endif()
|
||||
elseif(PORTABLE MATCHES [^0]+)
|
||||
# Name of a CPU arch spec or feature set to require
|
||||
if(MSVC)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /arch:${PORTABLE}")
|
||||
else()
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=${PORTABLE}")
|
||||
endif()
|
||||
else()
|
||||
if(MSVC)
|
||||
# NOTE: No auto-detection of current CPU, but instead assume some useful
|
||||
# level of optimization is supported
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /arch:AVX2")
|
||||
else()
|
||||
# Require instruction set from current CPU (with some legacy or opt-out
|
||||
# exceptions)
|
||||
if(CMAKE_SYSTEM_PROCESSOR MATCHES "^s390x" AND NOT HAS_S390X_MARCH_NATIVE)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=z196")
|
||||
elseif(NOT CMAKE_SYSTEM_PROCESSOR MATCHES "^(powerpc|ppc)64" AND NOT HAS_ARMV8_CRC)
|
||||
@@ -305,25 +293,6 @@ if(NOT MSVC)
|
||||
set(CMAKE_REQUIRED_FLAGS "-msse4.2 -mpclmul")
|
||||
endif()
|
||||
|
||||
CHECK_CXX_SOURCE_COMPILES("
|
||||
#include <cstdint>
|
||||
#include <nmmintrin.h>
|
||||
#include <wmmintrin.h>
|
||||
int main() {
|
||||
volatile uint32_t x = _mm_crc32_u32(0, 0);
|
||||
const auto a = _mm_set_epi64x(0, 0);
|
||||
const auto b = _mm_set_epi64x(0, 0);
|
||||
const auto c = _mm_clmulepi64_si128(a, b, 0x00);
|
||||
auto d = _mm_cvtsi128_si64(c);
|
||||
}
|
||||
" HAVE_SSE42)
|
||||
if(HAVE_SSE42)
|
||||
add_definitions(-DHAVE_SSE42)
|
||||
add_definitions(-DHAVE_PCLMUL)
|
||||
elseif(FORCE_SSE42)
|
||||
message(FATAL_ERROR "FORCE_SSE42=ON but unable to compile with SSE4.2 enabled")
|
||||
endif()
|
||||
|
||||
# Check if -latomic is required or not
|
||||
if (NOT MSVC)
|
||||
set(CMAKE_REQUIRED_FLAGS "--std=c++17")
|
||||
@@ -661,6 +630,7 @@ set(SOURCES
|
||||
cache/compressed_secondary_cache.cc
|
||||
cache/lru_cache.cc
|
||||
cache/secondary_cache.cc
|
||||
cache/secondary_cache_adapter.cc
|
||||
cache/sharded_cache.cc
|
||||
db/arena_wrapped_db_iter.cc
|
||||
db/blob/blob_contents.cc
|
||||
@@ -747,6 +717,7 @@ set(SOURCES
|
||||
db/write_batch.cc
|
||||
db/write_batch_base.cc
|
||||
db/write_controller.cc
|
||||
db/write_stall_stats.cc
|
||||
db/write_thread.cc
|
||||
env/composite_env.cc
|
||||
env/env.cc
|
||||
@@ -838,6 +809,7 @@ set(SOURCES
|
||||
table/get_context.cc
|
||||
table/iterator.cc
|
||||
table/merging_iterator.cc
|
||||
table/compaction_merging_iterator.cc
|
||||
table/meta_blocks.cc
|
||||
table/persistent_cache_helper.cc
|
||||
table/plain/plain_table_bloom.cc
|
||||
@@ -879,6 +851,7 @@ set(SOURCES
|
||||
util/compression_context_cache.cc
|
||||
util/concurrent_task_limiter_impl.cc
|
||||
util/crc32c.cc
|
||||
util/data_structure.cc
|
||||
util/dynamic_bloom.cc
|
||||
util/hash.cc
|
||||
util/murmurhash.cc
|
||||
@@ -1006,12 +979,6 @@ if ( ROCKSDB_PLUGINS )
|
||||
endforeach()
|
||||
endif()
|
||||
|
||||
if(HAVE_SSE42 AND NOT MSVC)
|
||||
set_source_files_properties(
|
||||
util/crc32c.cc
|
||||
PROPERTIES COMPILE_FLAGS "-msse4.2 -mpclmul")
|
||||
endif()
|
||||
|
||||
if(CMAKE_SYSTEM_PROCESSOR MATCHES "^(powerpc|ppc)64")
|
||||
list(APPEND SOURCES
|
||||
util/crc32c_ppc.c
|
||||
@@ -1277,6 +1244,7 @@ if(WITH_TESTS OR WITH_BENCHMARK_TOOLS)
|
||||
add_subdirectory(third-party/gtest-1.8.1/fused-src/gtest)
|
||||
add_library(testharness STATIC
|
||||
test_util/mock_time_env.cc
|
||||
test_util/secondary_cache_test_util.cc
|
||||
test_util/testharness.cc)
|
||||
target_link_libraries(testharness gtest)
|
||||
endif()
|
||||
@@ -1322,6 +1290,7 @@ if(WITH_TESTS)
|
||||
db/db_bloom_filter_test.cc
|
||||
db/db_compaction_filter_test.cc
|
||||
db/db_compaction_test.cc
|
||||
db/db_clip_test.cc
|
||||
db/db_dynamic_level_test.cc
|
||||
db/db_encryption_test.cc
|
||||
db/db_flush_test.cc
|
||||
|
||||
+94
-1
@@ -1,8 +1,85 @@
|
||||
# Rocksdb Change Log
|
||||
## Unreleased
|
||||
## 8.3.2 (06/14/2023)
|
||||
### Bug Fixes
|
||||
* Reduced cases of illegally using Env::Default() during static destruction by never destroying the internal PosixEnv itself (except for builds checking for memory leaks). (#11538)
|
||||
|
||||
## 8.3.1 (06/07/2023)
|
||||
### Performance Improvements
|
||||
* Fixed higher read QPS during DB::Open() reading files created prior to #11406, especially when reading many small file (size < 52 MB) during DB::Open() and partitioned filter or index is used.
|
||||
|
||||
## 8.3.0 (05/19/2023)
|
||||
### New Features
|
||||
* Introduced a new option `block_protection_bytes_per_key`, which can be used to enable per key-value integrity protection for in-memory blocks in block cache (#11287).
|
||||
* Added `JemallocAllocatorOptions::num_arenas`. Setting `num_arenas > 1` may mitigate mutex contention in the allocator, particularly in scenarios where block allocations commonly bypass jemalloc tcache.
|
||||
* Improve the operational safety of publishing a DB or SST files to many hosts by using different block cache hash seeds on different hosts. The exact behavior is controlled by new option `ShardedCacheOptions::hash_seed`, which also documents the solved problem in more detail.
|
||||
* Introduced a new option `CompactionOptionsFIFO::file_temperature_age_thresholds` that allows FIFO compaction to compact files to different temperatures based on key age (#11428).
|
||||
* Added a new ticker stat to count how many times RocksDB detected a corruption while verifying a block checksum: `BLOCK_CHECKSUM_MISMATCH_COUNT`.
|
||||
* New statistics `rocksdb.file.read.db.open.micros` that measures read time of block-based SST tables or blob files during db open.
|
||||
* New statistics tickers for various iterator seek behaviors and relevant filtering, as \*`_LEVEL_SEEK_`\*. (#11460)
|
||||
|
||||
### Public API Changes
|
||||
* EXPERIMENTAL: Add new API `DB::ClipColumnFamily` to clip the key in CF to a certain range. It will physically deletes all keys outside the range including tombstones.
|
||||
* Add `MakeSharedCache()` construction functions to various cache Options objects, and deprecated the `NewWhateverCache()` functions with long parameter lists.
|
||||
* Changed the meaning of various Bloom filter stats (prefix vs. whole key), with iterator-related filtering only being tracked in the new \*`_LEVEL_SEEK_`\*. stats. (#11460)
|
||||
|
||||
### Behavior changes
|
||||
* For x86, CPU features are no longer detected at runtime nor in build scripts, but in source code using common preprocessor defines. This will likely unlock some small performance improvements on some newer hardware, but could hurt performance of the kCRC32c checksum, which is no longer the default, on some "portable" builds. See PR #11419 for details.
|
||||
|
||||
### Bug Fixes
|
||||
* Delete an empty WAL file on DB open if the log number is less than the min log number to keep
|
||||
* Delete temp OPTIONS file on DB open if there is a failure to write it out or rename it
|
||||
|
||||
### Performance Improvements
|
||||
* Improved the I/O efficiency of prefetching SST metadata by recording more information in the DB manifest. Opening files written with previous versions will still rely on heuristics for how much to prefetch (#11406).
|
||||
|
||||
## 8.2.0 (04/24/2023)
|
||||
### Public API Changes
|
||||
* `SstFileWriter::DeleteRange()` now returns `Status::InvalidArgument` if the range's end key comes before its start key according to the user comparator. Previously the behavior was undefined.
|
||||
* Add `multi_get_for_update` to C API.
|
||||
* Remove unnecessary constructor for CompressionOptions.
|
||||
|
||||
### Behavior changes
|
||||
* Changed default block cache size from an 8MB to 32MB LRUCache, which increases the default number of cache shards from 16 to 64. This change is intended to minimize cache mutex contention under stress conditions. See https://github.com/facebook/rocksdb/wiki/Block-Cache for more information.
|
||||
* For level compaction with `level_compaction_dynamic_level_bytes=true`, RocksDB now trivially moves levels down to fill LSM starting from bottommost level during DB open. See more in comments for option `level_compaction_dynamic_level_bytes` (#11321).
|
||||
* User-provided `ReadOptions` take effect for more reads of non-`CacheEntryRole::kDataBlock` blocks.
|
||||
* For level compaction with `level_compaction_dynamic_level_bytes=true`, RocksDB now drains unnecessary levels through background compaction automatically (#11340). This together with #11321 makes it automatic to migrate other compaction settings to level compaction with `level_compaction_dynamic_level_bytes=true`. In addition, a live DB that becomes smaller will now have unnecessary levels drained which can help to reduce read and space amp.
|
||||
* If `CompactRange()` is called with `CompactRangeOptions::bottommost_level_compaction=kForce*` to compact from L0 to L1, RocksDB now will try to do trivial move from L0 to L1 and then do an intra L1 compaction, instead of a L0 to L1 compaction with trivial move disabled (#11375)).
|
||||
|
||||
### Bug Fixes
|
||||
* In the DB::VerifyFileChecksums API, ensure that file system reads of SST files are equal to the readahead_size in ReadOptions, if specified. Previously, each read was 2x the readahead_size.
|
||||
* In block cache tracing, fixed some cases of bad hit/miss information (and more) with MultiGet.
|
||||
|
||||
### New Features
|
||||
* Add experimental `PerfContext` counters `iter_{next|prev|seek}_count` for db iterator, each counting the times of corresponding API being called.
|
||||
* Allow runtime changes to whether `WriteBufferManager` allows stall or not by calling `SetAllowStall()`
|
||||
* Added statistics tickers BYTES_COMPRESSED_FROM, BYTES_COMPRESSED_TO, BYTES_COMPRESSION_BYPASSED, BYTES_COMPRESSION_REJECTED, NUMBER_BLOCK_COMPRESSION_BYPASSED, and NUMBER_BLOCK_COMPRESSION_REJECTED. Disabled/deprecated histograms BYTES_COMPRESSED and BYTES_DECOMPRESSED, and ticker NUMBER_BLOCK_NOT_COMPRESSED. The new tickers offer more inight into compression ratios, rejected vs. disabled compression, etc. (#11388)
|
||||
* New statistics `rocksdb.file.read.{flush|compaction}.micros` that measure read time of block-based SST tables or blob files during flush or compaction.
|
||||
|
||||
## 8.1.0 (03/18/2023)
|
||||
### Behavior changes
|
||||
* Compaction output file cutting logic now considers range tombstone start keys. For example, SST partitioner now may receive ParitionRequest for range tombstone start keys.
|
||||
* If the async_io ReadOption is specified for MultiGet or NewIterator on a platform that doesn't support IO uring, the option is ignored and synchronous IO is used.
|
||||
|
||||
### Bug Fixes
|
||||
* Fixed an issue for backward iteration when user defined timestamp is enabled in combination with BlobDB.
|
||||
* Fixed a couple of cases where a Merge operand encountered during iteration wasn't reflected in the `internal_merge_count` PerfContext counter.
|
||||
* Fixed a bug in CreateColumnFamilyWithImport()/ExportColumnFamily() which did not support range tombstones (#11252).
|
||||
* Fixed a bug where an excluded column family from an atomic flush contains unflushed data that should've been included in this atomic flush (i.e, data of seqno less than the max seqno of this atomic flush), leading to potential data loss in this excluded column family when `WriteOptions::disableWAL == true` (#11148).
|
||||
|
||||
### New Features
|
||||
* Add statistics rocksdb.secondary.cache.filter.hits, rocksdb.secondary.cache.index.hits, and rocksdb.secondary.cache.filter.hits
|
||||
* Added a new PerfContext counter `internal_merge_point_lookup_count` which tracks the number of Merge operands applied while serving point lookup queries.
|
||||
* Add new statistics rocksdb.table.open.prefetch.tail.read.bytes, rocksdb.table.open.prefetch.tail.{miss|hit}
|
||||
* Add support for SecondaryCache with HyperClockCache (`HyperClockCacheOptions` inherits `secondary_cache` option from `ShardedCacheOptions`)
|
||||
* Add new db properties `rocksdb.cf-write-stall-stats`, `rocksdb.db-write-stall-stats`and APIs to examine them in a structured way. In particular, users of `GetMapProperty()` with property `kCFWriteStallStats`/`kDBWriteStallStats` can now use the functions in `WriteStallStatsMapKeys` to find stats in the map.
|
||||
|
||||
### Public API Changes
|
||||
* Changed various functions and features in `Cache` that are mostly relevant to custom implementations or wrappers. Especially, asychronous lookup functionality is moved from `Lookup()` to a new `StartAsyncLookup()` function.
|
||||
|
||||
## 8.0.0 (02/19/2023)
|
||||
### Behavior changes
|
||||
* `ReadOptions::verify_checksums=false` disables checksum verification for more reads of non-`CacheEntryRole::kDataBlock` blocks.
|
||||
* In case of scan with async_io enabled, if posix doesn't support IOUring, Status::NotSupported error will be returned to the users. Initially that error was swallowed and reads were switched to synchronous reads.
|
||||
|
||||
### Bug Fixes
|
||||
* Fixed a data race on `ColumnFamilyData::flush_reason` caused by concurrent flushes.
|
||||
@@ -10,6 +87,9 @@
|
||||
* Fixed some atypical behaviors for `LockWAL()` such as allowing concurrent/recursive use and not expecting `UnlockWAL()` after non-OK result. See API comments.
|
||||
* Fixed a feature interaction bug where for blobs `GetEntity` would expose the blob reference instead of the blob value.
|
||||
* Fixed `DisableManualCompaction()` and `CompactRangeOptions::canceled` to cancel compactions even when they are waiting on conflicting compactions to finish
|
||||
* Fixed a bug in which a successful `GetMergeOperands()` could transiently return `Status::MergeInProgress()`
|
||||
* Return the correct error (Status::NotSupported()) to MultiGet caller when ReadOptions::async_io flag is true and IO uring is not enabled. Previously, Status::Corruption() was being returned when the actual failure was lack of async IO support.
|
||||
* Fixed a bug in DB open/recovery from a compressed WAL that was caused due to incorrect handling of certain record fragments with the same offset within a WAL block.
|
||||
|
||||
### Feature Removal
|
||||
* Remove RocksDB Lite.
|
||||
@@ -17,9 +97,22 @@
|
||||
* Remove deprecated Env::LoadEnv(). Use Env::CreateFromString() instead.
|
||||
* Remove deprecated FileSystem::Load(). Use FileSystem::CreateFromString() instead.
|
||||
* Removed the deprecated version of these utility functions and the corresponding Java bindings: `LoadOptionsFromFile`, `LoadLatestOptions`, `CheckOptionsCompatibility`.
|
||||
* Remove the FactoryFunc from the LoadObject method from the Customizable helper methods.
|
||||
|
||||
### Public API Changes
|
||||
* Moved rarely-needed Cache class definition to new advanced_cache.h, and added a CacheWrapper class to advanced_cache.h. Minor changes to SimCache API definitions.
|
||||
* Completely removed the following deprecated/obsolete statistics: the tickers `BLOCK_CACHE_INDEX_BYTES_EVICT`, `BLOCK_CACHE_FILTER_BYTES_EVICT`, `BLOOM_FILTER_MICROS`, `NO_FILE_CLOSES`, `STALL_L0_SLOWDOWN_MICROS`, `STALL_MEMTABLE_COMPACTION_MICROS`, `STALL_L0_NUM_FILES_MICROS`, `RATE_LIMIT_DELAY_MILLIS`, `NO_ITERATORS`, `NUMBER_FILTERED_DELETES`, `WRITE_TIMEDOUT`, `BLOB_DB_GC_NUM_KEYS_OVERWRITTEN`, `BLOB_DB_GC_NUM_KEYS_EXPIRED`, `BLOB_DB_GC_BYTES_OVERWRITTEN`, `BLOB_DB_GC_BYTES_EXPIRED`, `BLOCK_CACHE_COMPRESSION_DICT_BYTES_EVICT` as well as the histograms `STALL_L0_SLOWDOWN_COUNT`, `STALL_MEMTABLE_COMPACTION_COUNT`, `STALL_L0_NUM_FILES_COUNT`, `HARD_RATE_LIMIT_DELAY_COUNT`, `SOFT_RATE_LIMIT_DELAY_COUNT`, `BLOB_DB_GC_MICROS`, and `NUM_DATA_BLOCKS_READ_PER_LEVEL`. Note that as a result, the C++ enum values of the still supported statistics have changed. Developers are advised to not rely on the actual numeric values.
|
||||
* Deprecated IngestExternalFileOptions::write_global_seqno and change default to false. This option only needs to be set to true to generate a DB compatible with RocksDB versions before 5.16.0.
|
||||
* Remove deprecated APIs `GetColumnFamilyOptionsFrom{Map|String}(const ColumnFamilyOptions&, ..)`, `GetDBOptionsFrom{Map|String}(const DBOptions&, ..)`, `GetBlockBasedTableOptionsFrom{Map|String}(const BlockBasedTableOptions& table_options, ..)` and ` GetPlainTableOptionsFrom{Map|String}(const PlainTableOptions& table_options,..)`.
|
||||
* Added a subcode of `Status::Corruption`, `Status::SubCode::kMergeOperatorFailed`, for users to identify corruption failures originating in the merge operator, as opposed to RocksDB's internally identified data corruptions
|
||||
|
||||
### Build Changes
|
||||
* The `make` build now builds a shared library by default instead of a static library. Use `LIB_MODE=static` to override.
|
||||
|
||||
### New Features
|
||||
* Compaction filters are now supported for wide-column entities by means of the `FilterV3` API. See the comment of the API for more details.
|
||||
* Added `do_not_compress_roles` to `CompressedSecondaryCacheOptions` to disable compression on certain kinds of block. Filter blocks are now not compressed by CompressedSecondaryCache by default.
|
||||
* Added a new `MultiGetEntity` API that enables batched wide-column point lookups. See the API comments for more details.
|
||||
|
||||
## 7.10.0 (01/23/2023)
|
||||
### Behavior changes
|
||||
|
||||
+14
-6
@@ -20,12 +20,15 @@ There are few options when compiling RocksDB:
|
||||
depend on gflags. You will need to have gflags installed to run `make all`. This will compile RocksDB in debug mode. Don't
|
||||
use binaries compiled by `make all` in production.
|
||||
|
||||
* By default the binary we produce is optimized for the platform you're compiling on
|
||||
(`-march=native` or the equivalent). SSE4.2 will thus be enabled automatically if your
|
||||
CPU supports it. To print a warning if your CPU does not support SSE4.2, build with
|
||||
`USE_SSE=1 make static_lib` or, if using CMake, `cmake -DFORCE_SSE42=ON`. If you want
|
||||
to build a portable binary, add `PORTABLE=1` before your make commands, like this:
|
||||
`PORTABLE=1 make static_lib`.
|
||||
* By default the binary we produce is optimized for the CPU you're compiling on
|
||||
(`-march=native` or the equivalent). To build a binary compatible with the most
|
||||
general architecture supported by your CPU and compiler, set `PORTABLE=1` for
|
||||
the build, but performance will suffer as many operations benefit from newer
|
||||
and wider instructions. In addition to `PORTABLE=0` (default) and `PORTABLE=1`,
|
||||
it can be set to an architecture name recognized by your compiler. For example,
|
||||
on 64-bit x86, a reasonable compromise is `PORTABLE=haswell` which supports
|
||||
many or most of the available optimizations while still being compatible with
|
||||
most processors made since roughly 2013.
|
||||
|
||||
## Dependencies
|
||||
|
||||
@@ -48,6 +51,11 @@ to build a portable binary, add `PORTABLE=1` before your make commands, like thi
|
||||
* If you wish to build the RocksJava static target, then cmake is required for building Snappy.
|
||||
|
||||
* If you wish to run microbench (e.g, `make microbench`, `make ribbon_bench` or `cmake -DWITH_BENCHMARK=1`), Google benchmark >= 1.6.0 is needed.
|
||||
* You can do the following to install Google benchmark. These commands are copied from `./build_tools/ubuntu20_image/Dockerfile`:
|
||||
|
||||
`$ git clone --depth 1 --branch v1.7.0 https://github.com/google/benchmark.git ~/benchmark`
|
||||
|
||||
`$ cd ~/benchmark && mkdir build && cd build && cmake .. -GNinja -DCMAKE_BUILD_TYPE=Release -DBENCHMARK_ENABLE_GTEST_TESTS=0 && ninja && ninja install`
|
||||
|
||||
## Supported platforms
|
||||
|
||||
|
||||
@@ -44,13 +44,6 @@ quoted_perl_command = $(subst ','\'',$(perl_command))
|
||||
# Set the default DEBUG_LEVEL to 1
|
||||
DEBUG_LEVEL?=1
|
||||
|
||||
# LIB_MODE says whether or not to use/build "shared" or "static" libraries.
|
||||
# Mode "static" means to link against static libraries (.a)
|
||||
# Mode "shared" means to link against shared libraries (.so, .sl, .dylib, etc)
|
||||
#
|
||||
# Set the default LIB_MODE to static
|
||||
LIB_MODE?=static
|
||||
|
||||
# OBJ_DIR is where the object files reside. Default to the current directory
|
||||
OBJ_DIR?=.
|
||||
|
||||
@@ -81,7 +74,19 @@ else ifneq ($(filter jtest rocksdbjava%, $(MAKECMDGOALS)),)
|
||||
endif
|
||||
endif
|
||||
|
||||
$(info $$DEBUG_LEVEL is ${DEBUG_LEVEL})
|
||||
# LIB_MODE says whether or not to use/build "shared" or "static" libraries.
|
||||
# Mode "static" means to link against static libraries (.a)
|
||||
# Mode "shared" means to link against shared libraries (.so, .sl, .dylib, etc)
|
||||
#
|
||||
ifeq ($(DEBUG_LEVEL), 0)
|
||||
# For optimized, set the default LIB_MODE to static for code size/efficiency
|
||||
LIB_MODE?=static
|
||||
else
|
||||
# For debug, set the default LIB_MODE to shared for efficient `make check` etc.
|
||||
LIB_MODE?=shared
|
||||
endif
|
||||
|
||||
$(info $$DEBUG_LEVEL is $(DEBUG_LEVEL), $$LIB_MODE is $(LIB_MODE))
|
||||
|
||||
# Figure out optimize level.
|
||||
ifneq ($(DEBUG_LEVEL), 2)
|
||||
@@ -332,8 +337,8 @@ ifneq ($(MACHINE), arm64)
|
||||
# linking with jemalloc (as it won't be arm64-compatible) and remove some other options
|
||||
# set during platform detection
|
||||
DISABLE_JEMALLOC=1
|
||||
PLATFORM_CFLAGS := $(filter-out -march=native -DHAVE_SSE42 -DHAVE_AVX2, $(PLATFORM_CFLAGS))
|
||||
PLATFORM_CXXFLAGS := $(filter-out -march=native -DHAVE_SSE42 -DHAVE_AVX2, $(PLATFORM_CXXFLAGS))
|
||||
PLATFORM_CCFLAGS := $(filter-out -march=native, $(PLATFORM_CCFLAGS))
|
||||
PLATFORM_CXXFLAGS := $(filter-out -march=native, $(PLATFORM_CXXFLAGS))
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
@@ -534,7 +539,7 @@ endif
|
||||
|
||||
ifdef USE_CLANG
|
||||
# Used by some teams in Facebook
|
||||
WARNING_FLAGS += -Wshift-sign-overflow
|
||||
WARNING_FLAGS += -Wshift-sign-overflow -Wambiguous-reversed-operator
|
||||
endif
|
||||
|
||||
ifeq ($(PLATFORM), OS_OPENBSD)
|
||||
@@ -1217,9 +1222,9 @@ clean: clean-ext-libraries-all clean-rocks clean-rocksjava
|
||||
clean-not-downloaded: clean-ext-libraries-bin clean-rocks clean-not-downloaded-rocksjava
|
||||
|
||||
clean-rocks:
|
||||
echo shared=$(ALL_SHARED_LIBS)
|
||||
echo static=$(ALL_STATIC_LIBS)
|
||||
rm -f $(BENCHMARKS) $(TOOLS) $(TESTS) $(PARALLEL_TEST) $(ALL_STATIC_LIBS) $(ALL_SHARED_LIBS) $(MICROBENCHS)
|
||||
# Not practical to exactly match all versions/variants in naming (e.g. debug or not)
|
||||
rm -f ${LIBNAME}*.so* ${LIBNAME}*.a
|
||||
rm -f $(BENCHMARKS) $(TOOLS) $(TESTS) $(PARALLEL_TEST) $(MICROBENCHS)
|
||||
rm -rf $(CLEAN_FILES) ios-x86 ios-arm scan_build_report
|
||||
$(FIND) . -name "*.[oda]" -exec rm -f {} \;
|
||||
$(FIND) . -type f \( -name "*.gcda" -o -name "*.gcno" \) -exec rm -f {} \;
|
||||
@@ -1475,6 +1480,9 @@ db_compaction_filter_test: $(OBJ_DIR)/db/db_compaction_filter_test.o $(TEST_LIBR
|
||||
db_compaction_test: $(OBJ_DIR)/db/db_compaction_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
db_clip_test: $(OBJ_DIR)/db/db_clip_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
db_dynamic_level_test: $(OBJ_DIR)/db/db_dynamic_level_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
@@ -2430,6 +2438,8 @@ checkout_folly:
|
||||
@# 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
|
||||
|
||||
CXX_M_FLAGS = $(filter -m%, $(CXXFLAGS))
|
||||
|
||||
build_folly:
|
||||
FOLLY_INST_PATH=`cd third-party/folly; $(PYTHON) build/fbcode_builder/getdeps.py show-inst-dir`; \
|
||||
if [ "$$FOLLY_INST_PATH" ]; then \
|
||||
@@ -2440,8 +2450,8 @@ build_folly:
|
||||
fi
|
||||
# Restore the original version of Invoke.h with boost dependency
|
||||
cd third-party/folly && ${GIT_COMMAND} checkout folly/functional/Invoke.h
|
||||
cd third-party/folly && MAYBE_AVX2=`echo $(CXXFLAGS) | grep -o -- -DHAVE_AVX2 | sed 's/-DHAVE_AVX2/-mavx2/g' || true` && \
|
||||
CXXFLAGS=" $$MAYBE_AVX2 -DHAVE_CXX11_ATOMIC " $(PYTHON) build/fbcode_builder/getdeps.py build --no-tests
|
||||
cd third-party/folly && \
|
||||
CXXFLAGS=" $(CXX_M_FLAGS) -DHAVE_CXX11_ATOMIC " $(PYTHON) build/fbcode_builder/getdeps.py build --no-tests
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Build size testing
|
||||
|
||||
@@ -5,3 +5,4 @@ This is the list of all known third-party plugins for RocksDB. If something is m
|
||||
* [ZenFS](https://github.com/westerndigitalcorporation/zenfs): a file system for zoned block devices
|
||||
* [RADOS](https://github.com/riversand963/rocksdb-rados-env): an Env used for interacting with RADOS. Migrated from RocksDB main repo.
|
||||
* [PMEM](https://github.com/pmem/pmem-rocksdb-plugin): a collection of plugins to enable Persistent Memory on RocksDB.
|
||||
* [IPPCP](https://github.com/intel/ippcp-plugin-rocksdb): a plugin to enable encryption on RocksDB based on Intel optimized open source IPP-Crypto library.
|
||||
@@ -1,8 +1,6 @@
|
||||
## RocksDB: A Persistent Key-Value Store for Flash and RAM Storage
|
||||
|
||||
[](https://circleci.com/gh/facebook/rocksdb)
|
||||
[](https://ci.appveyor.com/project/Facebook/rocksdb/branch/main)
|
||||
[](http://140-211-168-68-openstack.osuosl.org:8080/job/rocksdb)
|
||||
|
||||
RocksDB is developed and maintained by Facebook Database Engineering Team.
|
||||
It is built on earlier work on [LevelDB](https://github.com/google/leveldb) by Sanjay Ghemawat (sanjay@google.com)
|
||||
|
||||
@@ -19,6 +19,7 @@ cpp_library_wrapper(name="rocksdb_lib", srcs=[
|
||||
"cache/compressed_secondary_cache.cc",
|
||||
"cache/lru_cache.cc",
|
||||
"cache/secondary_cache.cc",
|
||||
"cache/secondary_cache_adapter.cc",
|
||||
"cache/sharded_cache.cc",
|
||||
"db/arena_wrapped_db_iter.cc",
|
||||
"db/blob/blob_contents.cc",
|
||||
@@ -105,6 +106,7 @@ cpp_library_wrapper(name="rocksdb_lib", srcs=[
|
||||
"db/write_batch.cc",
|
||||
"db/write_batch_base.cc",
|
||||
"db/write_controller.cc",
|
||||
"db/write_stall_stats.cc",
|
||||
"db/write_thread.cc",
|
||||
"env/composite_env.cc",
|
||||
"env/env.cc",
|
||||
@@ -200,6 +202,7 @@ cpp_library_wrapper(name="rocksdb_lib", srcs=[
|
||||
"table/block_based/reader_common.cc",
|
||||
"table/block_based/uncompression_dict_reader.cc",
|
||||
"table/block_fetcher.cc",
|
||||
"table/compaction_merging_iterator.cc",
|
||||
"table/cuckoo/cuckoo_table_builder.cc",
|
||||
"table/cuckoo/cuckoo_table_factory.cc",
|
||||
"table/cuckoo/cuckoo_table_reader.cc",
|
||||
@@ -247,6 +250,7 @@ cpp_library_wrapper(name="rocksdb_lib", srcs=[
|
||||
"util/concurrent_task_limiter_impl.cc",
|
||||
"util/crc32c.cc",
|
||||
"util/crc32c_arm64.cc",
|
||||
"util/data_structure.cc",
|
||||
"util/dynamic_bloom.cc",
|
||||
"util/file_checksum_helper.cc",
|
||||
"util/hash.cc",
|
||||
@@ -350,353 +354,14 @@ cpp_library_wrapper(name="rocksdb_lib", srcs=[
|
||||
"//folly/synchronization:distributed_mutex",
|
||||
], headers=None, link_whole=False, extra_test_libs=False)
|
||||
|
||||
cpp_library_wrapper(name="rocksdb_whole_archive_lib", srcs=[
|
||||
"cache/cache.cc",
|
||||
"cache/cache_entry_roles.cc",
|
||||
"cache/cache_helpers.cc",
|
||||
"cache/cache_key.cc",
|
||||
"cache/cache_reservation_manager.cc",
|
||||
"cache/charged_cache.cc",
|
||||
"cache/clock_cache.cc",
|
||||
"cache/compressed_secondary_cache.cc",
|
||||
"cache/lru_cache.cc",
|
||||
"cache/secondary_cache.cc",
|
||||
"cache/sharded_cache.cc",
|
||||
"db/arena_wrapped_db_iter.cc",
|
||||
"db/blob/blob_contents.cc",
|
||||
"db/blob/blob_fetcher.cc",
|
||||
"db/blob/blob_file_addition.cc",
|
||||
"db/blob/blob_file_builder.cc",
|
||||
"db/blob/blob_file_cache.cc",
|
||||
"db/blob/blob_file_garbage.cc",
|
||||
"db/blob/blob_file_meta.cc",
|
||||
"db/blob/blob_file_reader.cc",
|
||||
"db/blob/blob_garbage_meter.cc",
|
||||
"db/blob/blob_log_format.cc",
|
||||
"db/blob/blob_log_sequential_reader.cc",
|
||||
"db/blob/blob_log_writer.cc",
|
||||
"db/blob/blob_source.cc",
|
||||
"db/blob/prefetch_buffer_collection.cc",
|
||||
"db/builder.cc",
|
||||
"db/c.cc",
|
||||
"db/column_family.cc",
|
||||
"db/compaction/compaction.cc",
|
||||
"db/compaction/compaction_iterator.cc",
|
||||
"db/compaction/compaction_job.cc",
|
||||
"db/compaction/compaction_outputs.cc",
|
||||
"db/compaction/compaction_picker.cc",
|
||||
"db/compaction/compaction_picker_fifo.cc",
|
||||
"db/compaction/compaction_picker_level.cc",
|
||||
"db/compaction/compaction_picker_universal.cc",
|
||||
"db/compaction/compaction_service_job.cc",
|
||||
"db/compaction/compaction_state.cc",
|
||||
"db/compaction/sst_partitioner.cc",
|
||||
"db/compaction/subcompaction_state.cc",
|
||||
"db/convenience.cc",
|
||||
"db/db_filesnapshot.cc",
|
||||
"db/db_impl/compacted_db_impl.cc",
|
||||
"db/db_impl/db_impl.cc",
|
||||
"db/db_impl/db_impl_compaction_flush.cc",
|
||||
"db/db_impl/db_impl_debug.cc",
|
||||
"db/db_impl/db_impl_experimental.cc",
|
||||
"db/db_impl/db_impl_files.cc",
|
||||
"db/db_impl/db_impl_open.cc",
|
||||
"db/db_impl/db_impl_readonly.cc",
|
||||
"db/db_impl/db_impl_secondary.cc",
|
||||
"db/db_impl/db_impl_write.cc",
|
||||
"db/db_info_dumper.cc",
|
||||
"db/db_iter.cc",
|
||||
"db/dbformat.cc",
|
||||
"db/error_handler.cc",
|
||||
"db/event_helpers.cc",
|
||||
"db/experimental.cc",
|
||||
"db/external_sst_file_ingestion_job.cc",
|
||||
"db/file_indexer.cc",
|
||||
"db/flush_job.cc",
|
||||
"db/flush_scheduler.cc",
|
||||
"db/forward_iterator.cc",
|
||||
"db/import_column_family_job.cc",
|
||||
"db/internal_stats.cc",
|
||||
"db/log_reader.cc",
|
||||
"db/log_writer.cc",
|
||||
"db/logs_with_prep_tracker.cc",
|
||||
"db/malloc_stats.cc",
|
||||
"db/memtable.cc",
|
||||
"db/memtable_list.cc",
|
||||
"db/merge_helper.cc",
|
||||
"db/merge_operator.cc",
|
||||
"db/output_validator.cc",
|
||||
"db/periodic_task_scheduler.cc",
|
||||
"db/range_del_aggregator.cc",
|
||||
"db/range_tombstone_fragmenter.cc",
|
||||
"db/repair.cc",
|
||||
"db/seqno_to_time_mapping.cc",
|
||||
"db/snapshot_impl.cc",
|
||||
"db/table_cache.cc",
|
||||
"db/table_properties_collector.cc",
|
||||
"db/transaction_log_impl.cc",
|
||||
"db/trim_history_scheduler.cc",
|
||||
"db/version_builder.cc",
|
||||
"db/version_edit.cc",
|
||||
"db/version_edit_handler.cc",
|
||||
"db/version_set.cc",
|
||||
"db/wal_edit.cc",
|
||||
"db/wal_manager.cc",
|
||||
"db/wide/wide_column_serialization.cc",
|
||||
"db/wide/wide_columns.cc",
|
||||
"db/write_batch.cc",
|
||||
"db/write_batch_base.cc",
|
||||
"db/write_controller.cc",
|
||||
"db/write_thread.cc",
|
||||
"env/composite_env.cc",
|
||||
"env/env.cc",
|
||||
"env/env_chroot.cc",
|
||||
"env/env_encryption.cc",
|
||||
"env/env_posix.cc",
|
||||
"env/file_system.cc",
|
||||
"env/file_system_tracer.cc",
|
||||
"env/fs_posix.cc",
|
||||
"env/fs_remap.cc",
|
||||
"env/io_posix.cc",
|
||||
"env/mock_env.cc",
|
||||
"env/unique_id_gen.cc",
|
||||
"file/delete_scheduler.cc",
|
||||
"file/file_prefetch_buffer.cc",
|
||||
"file/file_util.cc",
|
||||
"file/filename.cc",
|
||||
"file/line_file_reader.cc",
|
||||
"file/random_access_file_reader.cc",
|
||||
"file/read_write_util.cc",
|
||||
"file/readahead_raf.cc",
|
||||
"file/sequence_file_reader.cc",
|
||||
"file/sst_file_manager_impl.cc",
|
||||
"file/writable_file_writer.cc",
|
||||
"logging/auto_roll_logger.cc",
|
||||
"logging/event_logger.cc",
|
||||
"logging/log_buffer.cc",
|
||||
"memory/arena.cc",
|
||||
"memory/concurrent_arena.cc",
|
||||
"memory/jemalloc_nodump_allocator.cc",
|
||||
"memory/memkind_kmem_allocator.cc",
|
||||
"memory/memory_allocator.cc",
|
||||
"memtable/alloc_tracker.cc",
|
||||
"memtable/hash_linklist_rep.cc",
|
||||
"memtable/hash_skiplist_rep.cc",
|
||||
"memtable/skiplistrep.cc",
|
||||
"memtable/vectorrep.cc",
|
||||
"memtable/write_buffer_manager.cc",
|
||||
"monitoring/histogram.cc",
|
||||
"monitoring/histogram_windowing.cc",
|
||||
"monitoring/in_memory_stats_history.cc",
|
||||
"monitoring/instrumented_mutex.cc",
|
||||
"monitoring/iostats_context.cc",
|
||||
"monitoring/perf_context.cc",
|
||||
"monitoring/perf_level.cc",
|
||||
"monitoring/persistent_stats_history.cc",
|
||||
"monitoring/statistics.cc",
|
||||
"monitoring/thread_status_impl.cc",
|
||||
"monitoring/thread_status_updater.cc",
|
||||
"monitoring/thread_status_updater_debug.cc",
|
||||
"monitoring/thread_status_util.cc",
|
||||
"monitoring/thread_status_util_debug.cc",
|
||||
"options/cf_options.cc",
|
||||
"options/configurable.cc",
|
||||
"options/customizable.cc",
|
||||
"options/db_options.cc",
|
||||
"options/options.cc",
|
||||
"options/options_helper.cc",
|
||||
"options/options_parser.cc",
|
||||
"port/mmap.cc",
|
||||
"port/port_posix.cc",
|
||||
"port/stack_trace.cc",
|
||||
"port/win/env_default.cc",
|
||||
"port/win/env_win.cc",
|
||||
"port/win/io_win.cc",
|
||||
"port/win/port_win.cc",
|
||||
"port/win/win_logger.cc",
|
||||
"port/win/win_thread.cc",
|
||||
"table/adaptive/adaptive_table_factory.cc",
|
||||
"table/block_based/binary_search_index_reader.cc",
|
||||
"table/block_based/block.cc",
|
||||
"table/block_based/block_based_table_builder.cc",
|
||||
"table/block_based/block_based_table_factory.cc",
|
||||
"table/block_based/block_based_table_iterator.cc",
|
||||
"table/block_based/block_based_table_reader.cc",
|
||||
"table/block_based/block_builder.cc",
|
||||
"table/block_based/block_cache.cc",
|
||||
"table/block_based/block_prefetcher.cc",
|
||||
"table/block_based/block_prefix_index.cc",
|
||||
"table/block_based/data_block_footer.cc",
|
||||
"table/block_based/data_block_hash_index.cc",
|
||||
"table/block_based/filter_block_reader_common.cc",
|
||||
"table/block_based/filter_policy.cc",
|
||||
"table/block_based/flush_block_policy.cc",
|
||||
"table/block_based/full_filter_block.cc",
|
||||
"table/block_based/hash_index_reader.cc",
|
||||
"table/block_based/index_builder.cc",
|
||||
"table/block_based/index_reader_common.cc",
|
||||
"table/block_based/parsed_full_filter_block.cc",
|
||||
"table/block_based/partitioned_filter_block.cc",
|
||||
"table/block_based/partitioned_index_iterator.cc",
|
||||
"table/block_based/partitioned_index_reader.cc",
|
||||
"table/block_based/reader_common.cc",
|
||||
"table/block_based/uncompression_dict_reader.cc",
|
||||
"table/block_fetcher.cc",
|
||||
"table/cuckoo/cuckoo_table_builder.cc",
|
||||
"table/cuckoo/cuckoo_table_factory.cc",
|
||||
"table/cuckoo/cuckoo_table_reader.cc",
|
||||
"table/format.cc",
|
||||
"table/get_context.cc",
|
||||
"table/iterator.cc",
|
||||
"table/merging_iterator.cc",
|
||||
"table/meta_blocks.cc",
|
||||
"table/persistent_cache_helper.cc",
|
||||
"table/plain/plain_table_bloom.cc",
|
||||
"table/plain/plain_table_builder.cc",
|
||||
"table/plain/plain_table_factory.cc",
|
||||
"table/plain/plain_table_index.cc",
|
||||
"table/plain/plain_table_key_coding.cc",
|
||||
"table/plain/plain_table_reader.cc",
|
||||
"table/sst_file_dumper.cc",
|
||||
"table/sst_file_reader.cc",
|
||||
"table/sst_file_writer.cc",
|
||||
"table/table_factory.cc",
|
||||
"table/table_properties.cc",
|
||||
"table/two_level_iterator.cc",
|
||||
"table/unique_id.cc",
|
||||
"test_util/sync_point.cc",
|
||||
"test_util/sync_point_impl.cc",
|
||||
"test_util/transaction_test_util.cc",
|
||||
"tools/dump/db_dump_tool.cc",
|
||||
"tools/io_tracer_parser_tool.cc",
|
||||
"tools/ldb_cmd.cc",
|
||||
"tools/ldb_tool.cc",
|
||||
"tools/sst_dump_tool.cc",
|
||||
"trace_replay/block_cache_tracer.cc",
|
||||
"trace_replay/io_tracer.cc",
|
||||
"trace_replay/trace_record.cc",
|
||||
"trace_replay/trace_record_handler.cc",
|
||||
"trace_replay/trace_record_result.cc",
|
||||
"trace_replay/trace_replay.cc",
|
||||
"util/async_file_reader.cc",
|
||||
"util/build_version.cc",
|
||||
"util/cleanable.cc",
|
||||
"util/coding.cc",
|
||||
"util/compaction_job_stats_impl.cc",
|
||||
"util/comparator.cc",
|
||||
"util/compression.cc",
|
||||
"util/compression_context_cache.cc",
|
||||
"util/concurrent_task_limiter_impl.cc",
|
||||
"util/crc32c.cc",
|
||||
"util/crc32c_arm64.cc",
|
||||
"util/dynamic_bloom.cc",
|
||||
"util/file_checksum_helper.cc",
|
||||
"util/hash.cc",
|
||||
"util/murmurhash.cc",
|
||||
"util/random.cc",
|
||||
"util/rate_limiter.cc",
|
||||
"util/ribbon_config.cc",
|
||||
"util/slice.cc",
|
||||
"util/status.cc",
|
||||
"util/stderr_logger.cc",
|
||||
"util/string_util.cc",
|
||||
"util/thread_local.cc",
|
||||
"util/threadpool_imp.cc",
|
||||
"util/xxhash.cc",
|
||||
"utilities/agg_merge/agg_merge.cc",
|
||||
"utilities/backup/backup_engine.cc",
|
||||
"utilities/blob_db/blob_compaction_filter.cc",
|
||||
"utilities/blob_db/blob_db.cc",
|
||||
"utilities/blob_db/blob_db_impl.cc",
|
||||
"utilities/blob_db/blob_db_impl_filesnapshot.cc",
|
||||
"utilities/blob_db/blob_dump_tool.cc",
|
||||
"utilities/blob_db/blob_file.cc",
|
||||
"utilities/cache_dump_load.cc",
|
||||
"utilities/cache_dump_load_impl.cc",
|
||||
"utilities/cassandra/cassandra_compaction_filter.cc",
|
||||
"utilities/cassandra/format.cc",
|
||||
"utilities/cassandra/merge_operator.cc",
|
||||
"utilities/checkpoint/checkpoint_impl.cc",
|
||||
"utilities/compaction_filters.cc",
|
||||
"utilities/compaction_filters/remove_emptyvalue_compactionfilter.cc",
|
||||
"utilities/convenience/info_log_finder.cc",
|
||||
"utilities/counted_fs.cc",
|
||||
"utilities/debug.cc",
|
||||
"utilities/env_mirror.cc",
|
||||
"utilities/env_timed.cc",
|
||||
"utilities/fault_injection_env.cc",
|
||||
"utilities/fault_injection_fs.cc",
|
||||
"utilities/fault_injection_secondary_cache.cc",
|
||||
"utilities/leveldb_options/leveldb_options.cc",
|
||||
"utilities/memory/memory_util.cc",
|
||||
"utilities/merge_operators.cc",
|
||||
"utilities/merge_operators/bytesxor.cc",
|
||||
"utilities/merge_operators/max.cc",
|
||||
"utilities/merge_operators/put.cc",
|
||||
"utilities/merge_operators/sortlist.cc",
|
||||
"utilities/merge_operators/string_append/stringappend.cc",
|
||||
"utilities/merge_operators/string_append/stringappend2.cc",
|
||||
"utilities/merge_operators/uint64add.cc",
|
||||
"utilities/object_registry.cc",
|
||||
"utilities/option_change_migration/option_change_migration.cc",
|
||||
"utilities/options/options_util.cc",
|
||||
"utilities/persistent_cache/block_cache_tier.cc",
|
||||
"utilities/persistent_cache/block_cache_tier_file.cc",
|
||||
"utilities/persistent_cache/block_cache_tier_metadata.cc",
|
||||
"utilities/persistent_cache/persistent_cache_tier.cc",
|
||||
"utilities/persistent_cache/volatile_tier_impl.cc",
|
||||
"utilities/simulator_cache/cache_simulator.cc",
|
||||
"utilities/simulator_cache/sim_cache.cc",
|
||||
"utilities/table_properties_collectors/compact_on_deletion_collector.cc",
|
||||
"utilities/trace/file_trace_reader_writer.cc",
|
||||
"utilities/trace/replayer_impl.cc",
|
||||
"utilities/transactions/lock/lock_manager.cc",
|
||||
"utilities/transactions/lock/point/point_lock_manager.cc",
|
||||
"utilities/transactions/lock/point/point_lock_tracker.cc",
|
||||
"utilities/transactions/lock/range/range_tree/lib/locktree/concurrent_tree.cc",
|
||||
"utilities/transactions/lock/range/range_tree/lib/locktree/keyrange.cc",
|
||||
"utilities/transactions/lock/range/range_tree/lib/locktree/lock_request.cc",
|
||||
"utilities/transactions/lock/range/range_tree/lib/locktree/locktree.cc",
|
||||
"utilities/transactions/lock/range/range_tree/lib/locktree/manager.cc",
|
||||
"utilities/transactions/lock/range/range_tree/lib/locktree/range_buffer.cc",
|
||||
"utilities/transactions/lock/range/range_tree/lib/locktree/treenode.cc",
|
||||
"utilities/transactions/lock/range/range_tree/lib/locktree/txnid_set.cc",
|
||||
"utilities/transactions/lock/range/range_tree/lib/locktree/wfg.cc",
|
||||
"utilities/transactions/lock/range/range_tree/lib/standalone_port.cc",
|
||||
"utilities/transactions/lock/range/range_tree/lib/util/dbt.cc",
|
||||
"utilities/transactions/lock/range/range_tree/lib/util/memarena.cc",
|
||||
"utilities/transactions/lock/range/range_tree/range_tree_lock_manager.cc",
|
||||
"utilities/transactions/lock/range/range_tree/range_tree_lock_tracker.cc",
|
||||
"utilities/transactions/optimistic_transaction.cc",
|
||||
"utilities/transactions/optimistic_transaction_db_impl.cc",
|
||||
"utilities/transactions/pessimistic_transaction.cc",
|
||||
"utilities/transactions/pessimistic_transaction_db.cc",
|
||||
"utilities/transactions/snapshot_checker.cc",
|
||||
"utilities/transactions/transaction_base.cc",
|
||||
"utilities/transactions/transaction_db_mutex_impl.cc",
|
||||
"utilities/transactions/transaction_util.cc",
|
||||
"utilities/transactions/write_prepared_txn.cc",
|
||||
"utilities/transactions/write_prepared_txn_db.cc",
|
||||
"utilities/transactions/write_unprepared_txn.cc",
|
||||
"utilities/transactions/write_unprepared_txn_db.cc",
|
||||
"utilities/ttl/db_ttl_impl.cc",
|
||||
"utilities/wal_filter.cc",
|
||||
"utilities/write_batch_with_index/write_batch_with_index.cc",
|
||||
"utilities/write_batch_with_index/write_batch_with_index_internal.cc",
|
||||
], deps=[
|
||||
"//folly/container:f14_hash",
|
||||
"//folly/experimental/coro:blocking_wait",
|
||||
"//folly/experimental/coro:collect",
|
||||
"//folly/experimental/coro:coroutine",
|
||||
"//folly/experimental/coro:task",
|
||||
"//folly/synchronization:distributed_mutex",
|
||||
], headers=None, link_whole=True, extra_test_libs=False)
|
||||
cpp_library_wrapper(name="rocksdb_whole_archive_lib", srcs=[], deps=[":rocksdb_lib"], headers=None, link_whole=True, extra_test_libs=False)
|
||||
|
||||
cpp_library_wrapper(name="rocksdb_test_lib", srcs=[
|
||||
"db/db_test_util.cc",
|
||||
"db/db_with_timestamp_test_util.cc",
|
||||
"table/mock_table.cc",
|
||||
"test_util/mock_time_env.cc",
|
||||
"test_util/secondary_cache_test_util.cc",
|
||||
"test_util/testharness.cc",
|
||||
"test_util/testutil.cc",
|
||||
"tools/block_cache_analyzer/block_cache_trace_analyzer.cc",
|
||||
@@ -727,6 +392,7 @@ rocks_cpp_library_wrapper(name="rocksdb_stress_lib", srcs=[
|
||||
"db_stress_tool/db_stress_test_base.cc",
|
||||
"db_stress_tool/db_stress_tool.cc",
|
||||
"db_stress_tool/expected_state.cc",
|
||||
"db_stress_tool/expected_value.cc",
|
||||
"db_stress_tool/multi_ops_txns_stress.cc",
|
||||
"db_stress_tool/no_batched_ops_stress.cc",
|
||||
"test_util/testutil.cc",
|
||||
@@ -5084,6 +4750,12 @@ cpp_unittest_wrapper(name="db_bloom_filter_test",
|
||||
extra_compiler_flags=[])
|
||||
|
||||
|
||||
cpp_unittest_wrapper(name="db_clip_test",
|
||||
srcs=["db/db_clip_test.cc"],
|
||||
deps=[":rocksdb_test_lib"],
|
||||
extra_compiler_flags=[])
|
||||
|
||||
|
||||
cpp_unittest_wrapper(name="db_compaction_filter_test",
|
||||
srcs=["db/db_compaction_filter_test.cc"],
|
||||
deps=[":rocksdb_test_lib"],
|
||||
|
||||
@@ -15,6 +15,28 @@ At Facebook, we use RocksDB as storage engines in multiple data management servi
|
||||
|
||||
[2] https://code.facebook.com/posts/357056558062811/logdevice-a-distributed-data-store-for-logs/
|
||||
|
||||
## Bilibili
|
||||
[Bilibili](bilibili.com) [uses](https://www.alluxio.io/blog/when-ai-meets-alluxio-at-bilibili-building-an-efficient-ai-platform-for-data-preprocessing-and-model-training/) Alluxio to speed up its ML training workloads, and Alluxio uses RocksDB to store its filesystem metadata, so Bilibili uses RocksDB.
|
||||
|
||||
Bilibili's [real-time platform](https://www.alibabacloud.com/blog/architecture-and-practices-of-bilibilis-real-time-platform_596676) uses Flink, and uses RocksDB as Flink's state store.
|
||||
|
||||
## TikTok
|
||||
TikTok, or its parent company ByteDance, uses RocksDB as the storage engine for some storage systems, such as its distributed graph database [ByteGraph](https://vldb.org/pvldb/vol15/p3306-li.pdf).
|
||||
|
||||
Also, TikTok uses [Alluxio](alluxio.io) to [speed up Presto queries](https://www.alluxio.io/resources/videos/improving-presto-performance-with-alluxio-at-tiktok/), and Alluxio stores the files' metadata in RocksDB.
|
||||
|
||||
## FoundationDB
|
||||
[FoundationDB](https://www.foundationdb.org/) [uses](https://github.com/apple/foundationdb/blob/377f1f692da6ab2fe5bdac57035651db3e5fb66d/fdbserver/KeyValueStoreRocksDB.actor.cpp) RocksDB to implement a [key-value store interface](https://github.com/apple/foundationdb/blob/377f1f692da6ab2fe5bdac57035651db3e5fb66d/fdbserver/KeyValueStoreRocksDB.actor.cpp#L1127) in its server backend.
|
||||
|
||||
## Apple
|
||||
Apple [uses](https://opensource.apple.com/projects/foundationdb/) FoundationDB, so it also uses RocksDB.
|
||||
|
||||
## Snowflake
|
||||
Snowflake [uses](https://www.snowflake.com/blog/how-foundationdb-powers-snowflake-metadata-forward/) FoundationDB, so it also uses RocksDB.
|
||||
|
||||
## Microsoft
|
||||
The Bing search engine from Microsoft uses RocksDB as the storage engine for its web data platform: https://blogs.bing.com/Engineering-Blog/october-2021/RocksDB-in-Microsoft-Bing
|
||||
|
||||
## LinkedIn
|
||||
Two different use cases at Linkedin are using RocksDB as a storage engine:
|
||||
|
||||
@@ -26,6 +48,9 @@ Learn more about those use cases in a Tech Talk by Ankit Gupta and Naveen Somasu
|
||||
## Yahoo
|
||||
Yahoo is using RocksDB as a storage engine for their biggest distributed data store Sherpa. Learn more about it here: http://yahooeng.tumblr.com/post/120730204806/sherpa-scales-new-heights
|
||||
|
||||
## Tencent
|
||||
[PaxosStore](https://github.com/Tencent/paxosstore) is a distributed database supporting WeChat. It uses RocksDB as its storage engine.
|
||||
|
||||
## Baidu
|
||||
[Apache Doris](http://doris.apache.org/master/en/) is a MPP analytical database engine released by Baidu. It [uses RocksDB](http://doris.apache.org/master/en/administrator-guide/operation/tablet-meta-tool.html) to manage its tablet's metadata.
|
||||
|
||||
@@ -79,9 +104,18 @@ quasardb uses a heavily tuned RocksDB as its persistence layer.
|
||||
## TiKV
|
||||
[TiKV](https://github.com/pingcap/tikv) is a GEO-replicated, high-performance, distributed, transactional key-value database. TiKV is powered by Rust and Raft. TiKV uses RocksDB as its persistence layer.
|
||||
|
||||
## TiDB
|
||||
[TiDB](https://github.com/pingcap/tidb) uses the TiKV distributed key-value database, so it uses RocksDB.
|
||||
|
||||
## PingCAP
|
||||
[PingCAP](https://www.pingcap.com/) is the company behind TiDB, its cloud database service uses RocksDB.
|
||||
|
||||
## Apache Spark
|
||||
[Spark Structured Streaming](https://docs.databricks.com/structured-streaming/rocksdb-state-store.html) uses RocksDB as the local state store.
|
||||
|
||||
## Databricks
|
||||
[Databricks](https://www.databricks.com/) [replaces AWS RDS with TiDB](https://www.pingcap.com/case-study/how-databricks-tackles-the-scalability-limit-with-a-mysql-alternative/) for scalability, so it uses RocksDB.
|
||||
|
||||
## Apache Flink
|
||||
[Apache Flink](https://flink.apache.org/news/2016/03/08/release-1.0.0.html) uses RocksDB to store state locally on a machine.
|
||||
|
||||
|
||||
@@ -154,16 +154,9 @@ def generate_targets(repo_path, deps_map):
|
||||
# rocksdb_whole_archive_lib
|
||||
TARGETS.add_library(
|
||||
"rocksdb_whole_archive_lib",
|
||||
src_mk["LIB_SOURCES"] +
|
||||
# always add range_tree, it's only excluded on ppc64, which we don't use internally
|
||||
src_mk["RANGE_TREE_SOURCES"] + src_mk["TOOL_LIB_SOURCES"],
|
||||
[],
|
||||
deps=[
|
||||
"//folly/container:f14_hash",
|
||||
"//folly/experimental/coro:blocking_wait",
|
||||
"//folly/experimental/coro:collect",
|
||||
"//folly/experimental/coro:coroutine",
|
||||
"//folly/experimental/coro:task",
|
||||
"//folly/synchronization:distributed_mutex",
|
||||
":rocksdb_lib",
|
||||
],
|
||||
headers=None,
|
||||
extra_external_deps="",
|
||||
|
||||
@@ -63,13 +63,7 @@ if [ -z "$ROCKSDB_NO_FBCODE" -a -d /mnt/gvfs/third-party ]; then
|
||||
if [ "$LIB_MODE" == "shared" ]; then
|
||||
PIC_BUILD=1
|
||||
fi
|
||||
if [ -n "$ROCKSDB_FBCODE_BUILD_WITH_PLATFORM010" ]; then
|
||||
source "$PWD/build_tools/fbcode_config_platform010.sh"
|
||||
elif [ -n "$ROCKSDB_FBCODE_BUILD_WITH_PLATFORM009" ]; then
|
||||
source "$PWD/build_tools/fbcode_config_platform009.sh"
|
||||
else
|
||||
source "$PWD/build_tools/fbcode_config_platform009.sh"
|
||||
fi
|
||||
source "$PWD/build_tools/fbcode_config_platform010.sh"
|
||||
fi
|
||||
|
||||
# Delete existing output, if it exists
|
||||
@@ -627,7 +621,7 @@ EOF
|
||||
fi
|
||||
fi
|
||||
|
||||
if test "0$PORTABLE" -eq 0; then
|
||||
if [ "$PORTABLE" == "" ] || [ "$PORTABLE" == 0 ]; then
|
||||
if test -n "`echo $TARGET_ARCHITECTURE | grep ^ppc64`"; then
|
||||
# Tune for this POWER processor, treating '+' models as base models
|
||||
POWER=`LD_SHOW_AUXV=1 /bin/true | grep AT_PLATFORM | grep -E -o power[0-9]+`
|
||||
@@ -650,27 +644,26 @@ if test "0$PORTABLE" -eq 0; then
|
||||
COMMON_FLAGS="$COMMON_FLAGS -march=${RISC_ISA}"
|
||||
elif [ "$TARGET_OS" == "IOS" ]; then
|
||||
COMMON_FLAGS="$COMMON_FLAGS"
|
||||
elif [ "$TARGET_OS" == "AIX" ] || [ "$TARGET_OS" == "SunOS" ]; then
|
||||
# TODO: Not sure why we don't use -march=native on these OSes
|
||||
if test "$USE_SSE"; then
|
||||
TRY_SSE_ETC="1"
|
||||
fi
|
||||
else
|
||||
COMMON_FLAGS="$COMMON_FLAGS -march=native "
|
||||
fi
|
||||
else
|
||||
# PORTABLE=1
|
||||
if test "$USE_SSE"; then
|
||||
TRY_SSE_ETC="1"
|
||||
fi
|
||||
|
||||
if test -n "`echo $TARGET_ARCHITECTURE | grep ^s390x`"; then
|
||||
COMMON_FLAGS="$COMMON_FLAGS -march=z196 "
|
||||
fi
|
||||
|
||||
if test -n "`echo $TARGET_ARCHITECTURE | grep ^riscv64`"; then
|
||||
RISC_ISA=$(cat /proc/cpuinfo | grep isa | head -1 | cut --delimiter=: -f 2 | cut -b 2-)
|
||||
COMMON_FLAGS="$COMMON_FLAGS -march=${RISC_ISA}"
|
||||
# PORTABLE specified
|
||||
if [ "$PORTABLE" == 1 ]; then
|
||||
if test -n "`echo $TARGET_ARCHITECTURE | grep ^s390x`"; then
|
||||
COMMON_FLAGS="$COMMON_FLAGS -march=z196 "
|
||||
elif test -n "`echo $TARGET_ARCHITECTURE | grep ^riscv64`"; then
|
||||
RISC_ISA=$(cat /proc/cpuinfo | grep isa | head -1 | cut --delimiter=: -f 2 | cut -b 2-)
|
||||
COMMON_FLAGS="$COMMON_FLAGS -march=${RISC_ISA}"
|
||||
elif test "$USE_SSE"; then
|
||||
# USE_SSE is DEPRECATED
|
||||
# This is a rough approximation of the old USE_SSE behavior
|
||||
COMMON_FLAGS="$COMMON_FLAGS -march=haswell"
|
||||
fi
|
||||
# Other than those cases, not setting -march= here.
|
||||
else
|
||||
# Assume PORTABLE is a minimum assumed cpu type, e.g. PORTABLE=haswell
|
||||
COMMON_FLAGS="$COMMON_FLAGS -march=${PORTABLE}"
|
||||
fi
|
||||
|
||||
if [[ "${PLATFORM}" == "OS_MACOSX" ]]; then
|
||||
@@ -704,101 +697,6 @@ EOF
|
||||
fi
|
||||
fi
|
||||
|
||||
if test "$TRY_SSE_ETC"; then
|
||||
# The USE_SSE flag now means "attempt to compile with widely-available
|
||||
# Intel architecture extensions utilized by specific optimizations in the
|
||||
# source code." It's a qualifier on PORTABLE=1 that means "mostly portable."
|
||||
# It doesn't even really check that your current CPU is compatible.
|
||||
#
|
||||
# SSE4.2 available since nehalem, ca. 2008-2010
|
||||
# Includes POPCNT for BitsSetToOne, BitParity
|
||||
TRY_SSE42="-msse4.2"
|
||||
# PCLMUL available since westmere, ca. 2010-2011
|
||||
TRY_PCLMUL="-mpclmul"
|
||||
# AVX2 available since haswell, ca. 2013-2015
|
||||
TRY_AVX2="-mavx2"
|
||||
# BMI available since haswell, ca. 2013-2015
|
||||
# Primarily for TZCNT for CountTrailingZeroBits
|
||||
TRY_BMI="-mbmi"
|
||||
# LZCNT available since haswell, ca. 2013-2015
|
||||
# For FloorLog2
|
||||
TRY_LZCNT="-mlzcnt"
|
||||
fi
|
||||
|
||||
$CXX $PLATFORM_CXXFLAGS $COMMON_FLAGS $TRY_SSE42 -x c++ - -o test.o 2>/dev/null <<EOF
|
||||
#include <cstdint>
|
||||
#include <nmmintrin.h>
|
||||
int main() {
|
||||
volatile uint32_t x = _mm_crc32_u32(0, 0);
|
||||
(void)x;
|
||||
}
|
||||
EOF
|
||||
if [ "$?" = 0 ]; then
|
||||
COMMON_FLAGS="$COMMON_FLAGS $TRY_SSE42 -DHAVE_SSE42"
|
||||
elif test "$USE_SSE"; then
|
||||
echo "warning: USE_SSE specified but compiler could not use SSE intrinsics, disabling" >&2
|
||||
fi
|
||||
|
||||
$CXX $PLATFORM_CXXFLAGS $COMMON_FLAGS $TRY_PCLMUL -x c++ - -o test.o 2>/dev/null <<EOF
|
||||
#include <cstdint>
|
||||
#include <wmmintrin.h>
|
||||
int main() {
|
||||
const auto a = _mm_set_epi64x(0, 0);
|
||||
const auto b = _mm_set_epi64x(0, 0);
|
||||
const auto c = _mm_clmulepi64_si128(a, b, 0x00);
|
||||
auto d = _mm_cvtsi128_si64(c);
|
||||
(void)d;
|
||||
}
|
||||
EOF
|
||||
if [ "$?" = 0 ]; then
|
||||
COMMON_FLAGS="$COMMON_FLAGS $TRY_PCLMUL -DHAVE_PCLMUL"
|
||||
elif test "$USE_SSE"; then
|
||||
echo "warning: USE_SSE specified but compiler could not use PCLMUL intrinsics, disabling" >&2
|
||||
fi
|
||||
|
||||
$CXX $PLATFORM_CXXFLAGS $COMMON_FLAGS $TRY_AVX2 -x c++ - -o test.o 2>/dev/null <<EOF
|
||||
#include <cstdint>
|
||||
#include <immintrin.h>
|
||||
int main() {
|
||||
const auto a = _mm256_setr_epi32(0, 1, 2, 3, 4, 7, 6, 5);
|
||||
const auto b = _mm256_permutevar8x32_epi32(a, a);
|
||||
(void)b;
|
||||
}
|
||||
EOF
|
||||
if [ "$?" = 0 ]; then
|
||||
COMMON_FLAGS="$COMMON_FLAGS $TRY_AVX2 -DHAVE_AVX2"
|
||||
elif test "$USE_SSE"; then
|
||||
echo "warning: USE_SSE specified but compiler could not use AVX2 intrinsics, disabling" >&2
|
||||
fi
|
||||
|
||||
$CXX $PLATFORM_CXXFLAGS $COMMON_FLAGS $TRY_BMI -x c++ - -o test.o 2>/dev/null <<EOF
|
||||
#include <cstdint>
|
||||
#include <immintrin.h>
|
||||
int main(int argc, char *argv[]) {
|
||||
(void)argv;
|
||||
return (int)_tzcnt_u64((uint64_t)argc);
|
||||
}
|
||||
EOF
|
||||
if [ "$?" = 0 ]; then
|
||||
COMMON_FLAGS="$COMMON_FLAGS $TRY_BMI -DHAVE_BMI"
|
||||
elif test "$USE_SSE"; then
|
||||
echo "warning: USE_SSE specified but compiler could not use BMI intrinsics, disabling" >&2
|
||||
fi
|
||||
|
||||
$CXX $PLATFORM_CXXFLAGS $COMMON_FLAGS $TRY_LZCNT -x c++ - -o test.o 2>/dev/null <<EOF
|
||||
#include <cstdint>
|
||||
#include <immintrin.h>
|
||||
int main(int argc, char *argv[]) {
|
||||
(void)argv;
|
||||
return (int)_lzcnt_u64((uint64_t)argc);
|
||||
}
|
||||
EOF
|
||||
if [ "$?" = 0 ]; then
|
||||
COMMON_FLAGS="$COMMON_FLAGS $TRY_LZCNT -DHAVE_LZCNT"
|
||||
elif test "$USE_SSE"; then
|
||||
echo "warning: USE_SSE specified but compiler could not use LZCNT intrinsics, disabling" >&2
|
||||
fi
|
||||
|
||||
$CXX $PLATFORM_CXXFLAGS $COMMON_FLAGS -x c++ - -o test.o 2>/dev/null <<EOF
|
||||
#include <cstdint>
|
||||
int main() {
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
|
||||
GCC_BASE=/mnt/gvfs/third-party2/gcc/1795efe5f06778c15a92c8f9a2aba5dc496d9d4d/9.x/centos7-native/3bed279
|
||||
CLANG_BASE=/mnt/gvfs/third-party2/llvm-fb/7318eaac22659b6ff2fe43918e4b69fd0772a8a7/9.0.0/platform009/651ee30
|
||||
LIBGCC_BASE=/mnt/gvfs/third-party2/libgcc/4959b39cfbe5965a37c861c4c327fa7c5c759b87/9.x/platform009/9202ce7
|
||||
GLIBC_BASE=/mnt/gvfs/third-party2/glibc/45ce3375cdc77ecb2520bbf8f0ecddd3f98efd7a/2.30/platform009/f259413
|
||||
SNAPPY_BASE=/mnt/gvfs/third-party2/snappy/be4de3205e029101b18aa8103daa696c2bef3b19/1.1.3/platform009/7f3b187
|
||||
ZLIB_BASE=/mnt/gvfs/third-party2/zlib/3c160ac5c67e257501e24c6c1d00ad5e01d73db6/1.2.8/platform009/7f3b187
|
||||
BZIP2_BASE=/mnt/gvfs/third-party2/bzip2/73a237ac5bc0a5f5d67b39b8d253cfebaab88684/1.0.6/platform009/7f3b187
|
||||
LZ4_BASE=/mnt/gvfs/third-party2/lz4/6ca38d3c390be2774d61a300f151464bbd632d62/1.9.1/platform009/7f3b187
|
||||
ZSTD_BASE=/mnt/gvfs/third-party2/zstd/64c58a207d2495e83abc57a500a956df09b79a7c/1.4.x/platform009/ba86d1f
|
||||
GFLAGS_BASE=/mnt/gvfs/third-party2/gflags/824d0a8a5abb5b121afd1b35fc3896407ea50092/2.2.0/platform009/7f3b187
|
||||
JEMALLOC_BASE=/mnt/gvfs/third-party2/jemalloc/b62912d333ef33f9760efa6219dbe3fe6abb3b0e/master/platform009/c305944
|
||||
NUMA_BASE=/mnt/gvfs/third-party2/numa/0af65f71e23a67bf65dc91b11f95caa39325c432/2.0.11/platform009/7f3b187
|
||||
LIBUNWIND_BASE=/mnt/gvfs/third-party2/libunwind/02486dac347645d31dce116f44e1de3177315be2/1.4/platform009/5191652
|
||||
TBB_BASE=/mnt/gvfs/third-party2/tbb/2e0ec671e550bfca347300bf3f789d9c0fff24ad/2018_U5/platform009/7f3b187
|
||||
LIBURING_BASE=/mnt/gvfs/third-party2/liburing/70dbd9cfee63a25611417d09433a86d7711b3990/20200729/platform009/7f3b187
|
||||
KERNEL_HEADERS_BASE=/mnt/gvfs/third-party2/kernel-headers/32b8a2407b634df3f8f948ba373fc4acc6a18296/fb/platform009/da39a3e
|
||||
BINUTILS_BASE=/mnt/gvfs/third-party2/binutils/08634589372fa5f237bfd374e8c644a8364e78c1/2.32/platform009/ba86d1f/
|
||||
VALGRIND_BASE=/mnt/gvfs/third-party2/valgrind/6ae525939ad02e5e676855082fbbc7828dbafeac/3.15.0/platform009/7f3b187
|
||||
LUA_BASE=/mnt/gvfs/third-party2/lua/162efd9561a3d21f6869f4814011e9cf1b3ff4dc/5.3.4/platform009/a6271c4
|
||||
BENCHMARK_BASE=/mnt/gvfs/third-party2/benchmark/30bf49ad6414325e17f3425b0edcb64239427ae3/1.6.1/platform009/7f3b187
|
||||
GLOG_BASE=/mnt/gvfs/third-party2/glog/32d751bd5673375b438158717ab6a57c1cc57e3d/0.3.2_fb/platform009/10a364d
|
||||
@@ -147,7 +147,7 @@ else
|
||||
fi
|
||||
|
||||
CFLAGS+=" $DEPS_INCLUDE"
|
||||
CFLAGS+=" -DROCKSDB_PLATFORM_POSIX -DROCKSDB_LIB_IO_POSIX -DROCKSDB_FALLOCATE_PRESENT -DROCKSDB_MALLOC_USABLE_SIZE -DROCKSDB_RANGESYNC_PRESENT -DROCKSDB_SCHED_GETCPU_PRESENT -DHAVE_SSE42"
|
||||
CFLAGS+=" -DROCKSDB_PLATFORM_POSIX -DROCKSDB_LIB_IO_POSIX -DROCKSDB_FALLOCATE_PRESENT -DROCKSDB_MALLOC_USABLE_SIZE -DROCKSDB_RANGESYNC_PRESENT -DROCKSDB_SCHED_GETCPU_PRESENT"
|
||||
CXXFLAGS+=" $CFLAGS"
|
||||
|
||||
EXEC_LDFLAGS=" $SNAPPY_LIBS $ZLIB_LIBS $BZIP_LIBS $LZ4_LIBS $ZSTD_LIBS $GFLAGS_LIBS $NUMA_LIB $TBB_LIBS"
|
||||
|
||||
@@ -1,170 +0,0 @@
|
||||
#!/bin/sh
|
||||
# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
|
||||
#
|
||||
# Set environment variables so that we can compile rocksdb using
|
||||
# fbcode settings. It uses the latest g++ and clang compilers and also
|
||||
# uses jemalloc
|
||||
# Environment variables that change the behavior of this script:
|
||||
# PIC_BUILD -- if true, it will only take pic versions of libraries from fbcode. libraries that don't have pic variant will not be included
|
||||
|
||||
|
||||
BASEDIR=`dirname $BASH_SOURCE`
|
||||
source "$BASEDIR/dependencies_platform009.sh"
|
||||
|
||||
CFLAGS=""
|
||||
|
||||
# libgcc
|
||||
LIBGCC_INCLUDE="$LIBGCC_BASE/include/c++/9.3.0 -I $LIBGCC_BASE/include/c++/9.3.0/backward"
|
||||
LIBGCC_LIBS=" -L $LIBGCC_BASE/lib"
|
||||
|
||||
# glibc
|
||||
GLIBC_INCLUDE="$GLIBC_BASE/include"
|
||||
GLIBC_LIBS=" -L $GLIBC_BASE/lib"
|
||||
|
||||
if test -z $PIC_BUILD; then
|
||||
MAYBE_PIC=
|
||||
else
|
||||
MAYBE_PIC=_pic
|
||||
fi
|
||||
|
||||
if ! test $ROCKSDB_DISABLE_SNAPPY; then
|
||||
# snappy
|
||||
SNAPPY_INCLUDE=" -I $SNAPPY_BASE/include/"
|
||||
SNAPPY_LIBS=" $SNAPPY_BASE/lib/libsnappy${MAYBE_PIC}.a"
|
||||
CFLAGS+=" -DSNAPPY"
|
||||
fi
|
||||
|
||||
if ! test $ROCKSDB_DISABLE_ZLIB; then
|
||||
# location of zlib headers and libraries
|
||||
ZLIB_INCLUDE=" -I $ZLIB_BASE/include/"
|
||||
ZLIB_LIBS=" $ZLIB_BASE/lib/libz${MAYBE_PIC}.a"
|
||||
CFLAGS+=" -DZLIB"
|
||||
fi
|
||||
|
||||
if ! test $ROCKSDB_DISABLE_BZIP; then
|
||||
# location of bzip headers and libraries
|
||||
BZIP_INCLUDE=" -I $BZIP2_BASE/include/"
|
||||
BZIP_LIBS=" $BZIP2_BASE/lib/libbz2${MAYBE_PIC}.a"
|
||||
CFLAGS+=" -DBZIP2"
|
||||
fi
|
||||
|
||||
if ! test $ROCKSDB_DISABLE_LZ4; then
|
||||
LZ4_INCLUDE=" -I $LZ4_BASE/include/"
|
||||
LZ4_LIBS=" $LZ4_BASE/lib/liblz4${MAYBE_PIC}.a"
|
||||
CFLAGS+=" -DLZ4"
|
||||
fi
|
||||
|
||||
if ! test $ROCKSDB_DISABLE_ZSTD; then
|
||||
ZSTD_INCLUDE=" -I $ZSTD_BASE/include/"
|
||||
ZSTD_LIBS=" $ZSTD_BASE/lib/libzstd${MAYBE_PIC}.a"
|
||||
CFLAGS+=" -DZSTD"
|
||||
fi
|
||||
|
||||
# location of gflags headers and libraries
|
||||
GFLAGS_INCLUDE=" -I $GFLAGS_BASE/include/"
|
||||
GFLAGS_LIBS=" $GFLAGS_BASE/lib/libgflags${MAYBE_PIC}.a"
|
||||
CFLAGS+=" -DGFLAGS=gflags"
|
||||
|
||||
BENCHMARK_INCLUDE=" -I $BENCHMARK_BASE/include/"
|
||||
BENCHMARK_LIBS=" $BENCHMARK_BASE/lib/libbenchmark${MAYBE_PIC}.a"
|
||||
|
||||
GLOG_INCLUDE=" -I $GLOG_BASE/include/"
|
||||
GLOG_LIBS=" $GLOG_BASE/lib/libglog${MAYBE_PIC}.a"
|
||||
|
||||
# location of jemalloc
|
||||
JEMALLOC_INCLUDE=" -I $JEMALLOC_BASE/include/"
|
||||
JEMALLOC_LIB=" $JEMALLOC_BASE/lib/libjemalloc${MAYBE_PIC}.a"
|
||||
|
||||
# location of numa
|
||||
NUMA_INCLUDE=" -I $NUMA_BASE/include/"
|
||||
NUMA_LIB=" $NUMA_BASE/lib/libnuma${MAYBE_PIC}.a"
|
||||
CFLAGS+=" -DNUMA"
|
||||
|
||||
# location of libunwind
|
||||
LIBUNWIND="$LIBUNWIND_BASE/lib/libunwind${MAYBE_PIC}.a"
|
||||
|
||||
# location of TBB
|
||||
TBB_INCLUDE=" -isystem $TBB_BASE/include/"
|
||||
TBB_LIBS="$TBB_BASE/lib/libtbb${MAYBE_PIC}.a"
|
||||
CFLAGS+=" -DTBB"
|
||||
|
||||
# location of LIBURING
|
||||
LIBURING_INCLUDE=" -isystem $LIBURING_BASE/include/"
|
||||
LIBURING_LIBS="$LIBURING_BASE/lib/liburing${MAYBE_PIC}.a"
|
||||
CFLAGS+=" -DLIBURING"
|
||||
|
||||
test "$USE_SSE" || USE_SSE=1
|
||||
export USE_SSE
|
||||
test "$PORTABLE" || PORTABLE=1
|
||||
export PORTABLE
|
||||
|
||||
BINUTILS="$BINUTILS_BASE/bin"
|
||||
AR="$BINUTILS/ar"
|
||||
AS="$BINUTILS/as"
|
||||
|
||||
DEPS_INCLUDE="$SNAPPY_INCLUDE $ZLIB_INCLUDE $BZIP_INCLUDE $LZ4_INCLUDE $ZSTD_INCLUDE $GFLAGS_INCLUDE $NUMA_INCLUDE $TBB_INCLUDE $LIBURING_INCLUDE $BENCHMARK_INCLUDE $GLOG_INCLUDE"
|
||||
|
||||
STDLIBS="-L $GCC_BASE/lib64"
|
||||
|
||||
CLANG_BIN="$CLANG_BASE/bin"
|
||||
CLANG_LIB="$CLANG_BASE/lib"
|
||||
CLANG_SRC="$CLANG_BASE/../../src"
|
||||
|
||||
CLANG_ANALYZER="$CLANG_BIN/clang++"
|
||||
CLANG_SCAN_BUILD="$CLANG_SRC/llvm/clang/tools/scan-build/bin/scan-build"
|
||||
|
||||
if [ -z "$USE_CLANG" ]; then
|
||||
# gcc
|
||||
CC="$GCC_BASE/bin/gcc"
|
||||
CXX="$GCC_BASE/bin/g++"
|
||||
AR="$GCC_BASE/bin/gcc-ar"
|
||||
|
||||
CFLAGS+=" -B$BINUTILS"
|
||||
CFLAGS+=" -isystem $LIBGCC_INCLUDE"
|
||||
CFLAGS+=" -isystem $GLIBC_INCLUDE"
|
||||
JEMALLOC=1
|
||||
else
|
||||
# clang
|
||||
CLANG_INCLUDE="$CLANG_LIB/clang/stable/include"
|
||||
CC="$CLANG_BIN/clang"
|
||||
CXX="$CLANG_BIN/clang++"
|
||||
AR="$CLANG_BIN/llvm-ar"
|
||||
|
||||
KERNEL_HEADERS_INCLUDE="$KERNEL_HEADERS_BASE/include"
|
||||
|
||||
CFLAGS+=" -B$BINUTILS -nostdinc -nostdlib"
|
||||
CFLAGS+=" -isystem $LIBGCC_BASE/include/c++/9.x "
|
||||
CFLAGS+=" -isystem $LIBGCC_BASE/include/c++/9.x/x86_64-facebook-linux "
|
||||
CFLAGS+=" -isystem $GLIBC_INCLUDE"
|
||||
CFLAGS+=" -isystem $LIBGCC_INCLUDE"
|
||||
CFLAGS+=" -isystem $CLANG_INCLUDE"
|
||||
CFLAGS+=" -isystem $KERNEL_HEADERS_INCLUDE/linux "
|
||||
CFLAGS+=" -isystem $KERNEL_HEADERS_INCLUDE "
|
||||
CFLAGS+=" -Wno-expansion-to-defined "
|
||||
CXXFLAGS="-nostdinc++"
|
||||
fi
|
||||
|
||||
CFLAGS+=" $DEPS_INCLUDE"
|
||||
CFLAGS+=" -DROCKSDB_PLATFORM_POSIX -DROCKSDB_LIB_IO_POSIX -DROCKSDB_FALLOCATE_PRESENT -DROCKSDB_MALLOC_USABLE_SIZE -DROCKSDB_RANGESYNC_PRESENT -DROCKSDB_SCHED_GETCPU_PRESENT -DHAVE_SSE42 -DROCKSDB_IOURING_PRESENT"
|
||||
CXXFLAGS+=" $CFLAGS"
|
||||
|
||||
EXEC_LDFLAGS=" $SNAPPY_LIBS $ZLIB_LIBS $BZIP_LIBS $LZ4_LIBS $ZSTD_LIBS $GFLAGS_LIBS $NUMA_LIB $TBB_LIBS $LIBURING_LIBS $BENCHMARK_LIBS"
|
||||
EXEC_LDFLAGS+=" -Wl,--dynamic-linker,/usr/local/fbcode/platform009/lib/ld.so"
|
||||
EXEC_LDFLAGS+=" $LIBUNWIND"
|
||||
EXEC_LDFLAGS+=" -Wl,-rpath=/usr/local/fbcode/platform009/lib"
|
||||
EXEC_LDFLAGS+=" -Wl,-rpath=$GCC_BASE/lib64"
|
||||
# required by libtbb
|
||||
EXEC_LDFLAGS+=" -ldl"
|
||||
|
||||
PLATFORM_LDFLAGS="$LIBGCC_LIBS $GLIBC_LIBS $STDLIBS -lgcc -lstdc++"
|
||||
PLATFORM_LDFLAGS+=" -B$BINUTILS"
|
||||
|
||||
EXEC_LDFLAGS_SHARED="$SNAPPY_LIBS $ZLIB_LIBS $BZIP_LIBS $LZ4_LIBS $ZSTD_LIBS $GFLAGS_LIBS $TBB_LIBS $LIBURING_LIBS $BENCHMARK_LIBS"
|
||||
|
||||
VALGRIND_VER="$VALGRIND_BASE/bin/"
|
||||
|
||||
# lua not supported because it's on track for deprecation, I think
|
||||
LUA_PATH=
|
||||
LUA_LIB=
|
||||
|
||||
export CC CXX AR AS CFLAGS CXXFLAGS EXEC_LDFLAGS EXEC_LDFLAGS_SHARED VALGRIND_VER JEMALLOC_LIB JEMALLOC_INCLUDE CLANG_ANALYZER CLANG_SCAN_BUILD LUA_PATH LUA_LIB
|
||||
@@ -154,7 +154,7 @@ CFLAGS+=" -isystem $KERNEL_HEADERS_INCLUDE/linux "
|
||||
CFLAGS+=" -isystem $KERNEL_HEADERS_INCLUDE "
|
||||
|
||||
CFLAGS+=" $DEPS_INCLUDE"
|
||||
CFLAGS+=" -DROCKSDB_PLATFORM_POSIX -DROCKSDB_LIB_IO_POSIX -DROCKSDB_FALLOCATE_PRESENT -DROCKSDB_MALLOC_USABLE_SIZE -DROCKSDB_RANGESYNC_PRESENT -DROCKSDB_SCHED_GETCPU_PRESENT -DHAVE_SSE42 -DROCKSDB_IOURING_PRESENT"
|
||||
CFLAGS+=" -DROCKSDB_PLATFORM_POSIX -DROCKSDB_LIB_IO_POSIX -DROCKSDB_FALLOCATE_PRESENT -DROCKSDB_MALLOC_USABLE_SIZE -DROCKSDB_RANGESYNC_PRESENT -DROCKSDB_SCHED_GETCPU_PRESENT -DROCKSDB_IOURING_PRESENT"
|
||||
CXXFLAGS+=" $CFLAGS"
|
||||
|
||||
EXEC_LDFLAGS=" $SNAPPY_LIBS $ZLIB_LIBS $BZIP_LIBS $LZ4_LIBS $ZSTD_LIBS $GFLAGS_LIBS $NUMA_LIB $TBB_LIBS $LIBURING_LIBS $BENCHMARK_LIBS"
|
||||
|
||||
@@ -360,7 +360,7 @@ function send_to_ods {
|
||||
echo >&2 "ERROR: Key $key doesn't have a value."
|
||||
return
|
||||
fi
|
||||
curl --silent "https://www.intern.facebook.com/intern/agent/ods_set.php?entity=rocksdb_build&key=$key&value=$value" \
|
||||
curl --silent "https://www.facebook.com/intern/agent/ods_set.php?entity=rocksdb_build&key=$key&value=$value" \
|
||||
--connect-timeout 60
|
||||
}
|
||||
|
||||
|
||||
@@ -104,46 +104,3 @@ get_lib_base valgrind LATEST platform010
|
||||
get_lib_base lua 5.3.4 platform010
|
||||
|
||||
git diff $OUTPUT
|
||||
|
||||
|
||||
###########################################################
|
||||
# platform009 dependencies #
|
||||
###########################################################
|
||||
|
||||
OUTPUT="$BASEDIR/dependencies_platform009.sh"
|
||||
|
||||
rm -f "$OUTPUT"
|
||||
touch "$OUTPUT"
|
||||
|
||||
echo "Writing dependencies to $OUTPUT"
|
||||
|
||||
# Compilers locations
|
||||
GCC_BASE=`readlink -f $TP2_LATEST/gcc/9.x/centos7-native/*/`
|
||||
CLANG_BASE=`readlink -f $TP2_LATEST/llvm-fb/9.0.0/platform009/*/`
|
||||
|
||||
log_header
|
||||
log_variable GCC_BASE
|
||||
log_variable CLANG_BASE
|
||||
|
||||
# Libraries locations
|
||||
get_lib_base libgcc 9.x platform009
|
||||
get_lib_base glibc 2.30 platform009
|
||||
get_lib_base snappy LATEST platform009
|
||||
get_lib_base zlib LATEST platform009
|
||||
get_lib_base bzip2 LATEST platform009
|
||||
get_lib_base lz4 LATEST platform009
|
||||
get_lib_base zstd LATEST platform009
|
||||
get_lib_base gflags LATEST platform009
|
||||
get_lib_base jemalloc LATEST platform009
|
||||
get_lib_base numa LATEST platform009
|
||||
get_lib_base libunwind LATEST platform009
|
||||
get_lib_base tbb 2018_U5 platform009
|
||||
get_lib_base liburing LATEST platform009
|
||||
get_lib_base benchmark LATEST platform009
|
||||
|
||||
get_lib_base kernel-headers fb platform009
|
||||
get_lib_base binutils LATEST centos7-native
|
||||
get_lib_base valgrind LATEST platform009
|
||||
get_lib_base lua 5.3.4 platform009
|
||||
|
||||
git diff $OUTPUT
|
||||
|
||||
Vendored
+44
-2
@@ -16,6 +16,8 @@
|
||||
#include "util/string_util.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
const Cache::CacheItemHelper kNoopCacheItemHelper{};
|
||||
|
||||
static std::unordered_map<std::string, OptionTypeInfo>
|
||||
lru_cache_options_type_info = {
|
||||
{"capacity",
|
||||
@@ -87,8 +89,7 @@ Status SecondaryCache::CreateFromString(
|
||||
}
|
||||
return status;
|
||||
} else {
|
||||
return LoadSharedObject<SecondaryCache>(config_options, value, nullptr,
|
||||
result);
|
||||
return LoadSharedObject<SecondaryCache>(config_options, value, result);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,4 +114,45 @@ Status Cache::CreateFromString(const ConfigOptions& config_options,
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
bool Cache::AsyncLookupHandle::IsReady() {
|
||||
return pending_handle == nullptr || pending_handle->IsReady();
|
||||
}
|
||||
|
||||
bool Cache::AsyncLookupHandle::IsPending() { return pending_handle != nullptr; }
|
||||
|
||||
Cache::Handle* Cache::AsyncLookupHandle::Result() {
|
||||
assert(!IsPending());
|
||||
return result_handle;
|
||||
}
|
||||
|
||||
void Cache::StartAsyncLookup(AsyncLookupHandle& async_handle) {
|
||||
async_handle.found_dummy_entry = false; // in case re-used
|
||||
assert(!async_handle.IsPending());
|
||||
async_handle.result_handle =
|
||||
Lookup(async_handle.key, async_handle.helper, async_handle.create_context,
|
||||
async_handle.priority, async_handle.stats);
|
||||
}
|
||||
|
||||
Cache::Handle* Cache::Wait(AsyncLookupHandle& async_handle) {
|
||||
WaitAll(&async_handle, 1);
|
||||
return async_handle.Result();
|
||||
}
|
||||
|
||||
void Cache::WaitAll(AsyncLookupHandle* async_handles, size_t count) {
|
||||
for (size_t i = 0; i < count; ++i) {
|
||||
if (async_handles[i].IsPending()) {
|
||||
// If a pending handle gets here, it should be marked at "to be handled
|
||||
// by a caller" by that caller erasing the pending_cache on it.
|
||||
assert(async_handles[i].pending_cache == nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Cache::SetEvictionCallback(EvictionCallback&& fn) {
|
||||
// Overwriting non-empty with non-empty could indicate a bug
|
||||
assert(!eviction_callback_ || !fn);
|
||||
eviction_callback_ = std::move(fn);
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
Vendored
+9
-6
@@ -16,7 +16,7 @@
|
||||
#include "db/db_impl/db_impl.h"
|
||||
#include "monitoring/histogram.h"
|
||||
#include "port/port.h"
|
||||
#include "rocksdb/cache.h"
|
||||
#include "rocksdb/advanced_cache.h"
|
||||
#include "rocksdb/convenience.h"
|
||||
#include "rocksdb/db.h"
|
||||
#include "rocksdb/env.h"
|
||||
@@ -255,12 +255,15 @@ void DeleteFn(Cache::ObjectPtr value, MemoryAllocator* /*alloc*/) {
|
||||
delete[] static_cast<char*>(value);
|
||||
}
|
||||
|
||||
Cache::CacheItemHelper helper1_wos(CacheEntryRole::kDataBlock, DeleteFn);
|
||||
Cache::CacheItemHelper helper1(CacheEntryRole::kDataBlock, DeleteFn, SizeFn,
|
||||
SaveToFn, CreateFn);
|
||||
SaveToFn, CreateFn, &helper1_wos);
|
||||
Cache::CacheItemHelper helper2_wos(CacheEntryRole::kIndexBlock, DeleteFn);
|
||||
Cache::CacheItemHelper helper2(CacheEntryRole::kIndexBlock, DeleteFn, SizeFn,
|
||||
SaveToFn, CreateFn);
|
||||
SaveToFn, CreateFn, &helper2_wos);
|
||||
Cache::CacheItemHelper helper3_wos(CacheEntryRole::kFilterBlock, DeleteFn);
|
||||
Cache::CacheItemHelper helper3(CacheEntryRole::kFilterBlock, DeleteFn, SizeFn,
|
||||
SaveToFn, CreateFn);
|
||||
SaveToFn, CreateFn, &helper3_wos);
|
||||
} // namespace
|
||||
|
||||
class CacheBench {
|
||||
@@ -544,7 +547,7 @@ class CacheBench {
|
||||
}
|
||||
// do lookup
|
||||
handle = cache_->Lookup(key, &helper2, /*context*/ nullptr,
|
||||
Cache::Priority::LOW, true);
|
||||
Cache::Priority::LOW);
|
||||
if (handle) {
|
||||
if (!FLAGS_lean) {
|
||||
// do something with the data
|
||||
@@ -573,7 +576,7 @@ class CacheBench {
|
||||
}
|
||||
// do lookup
|
||||
handle = cache_->Lookup(key, &helper2, /*context*/ nullptr,
|
||||
Cache::Priority::LOW, true);
|
||||
Cache::Priority::LOW);
|
||||
if (handle) {
|
||||
if (!FLAGS_lean) {
|
||||
// do something with the data
|
||||
|
||||
Vendored
+1
-1
@@ -143,7 +143,7 @@ class CacheEntryStatsCollector {
|
||||
}
|
||||
}
|
||||
// If we reach here, shared entry is in cache with handle `h`.
|
||||
assert(cache.get()->GetCacheItemHelper(h) == &cache.kBasicHelper);
|
||||
assert(cache.get()->GetCacheItemHelper(h) == cache.GetBasicHelper());
|
||||
|
||||
// Build an aliasing shared_ptr that keeps `ptr` in cache while there
|
||||
// are references.
|
||||
|
||||
Vendored
+1
-1
@@ -7,7 +7,7 @@
|
||||
|
||||
#include <cassert>
|
||||
|
||||
#include "rocksdb/cache.h"
|
||||
#include "rocksdb/advanced_cache.h"
|
||||
#include "rocksdb/rocksdb_namespace.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
Vendored
+1
-1
@@ -8,7 +8,7 @@
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
|
||||
#include "rocksdb/cache.h"
|
||||
#include "rocksdb/advanced_cache.h"
|
||||
#include "table/unique_id_impl.h"
|
||||
#include "util/hash.h"
|
||||
#include "util/math.h"
|
||||
|
||||
Vendored
+1
-1
@@ -169,7 +169,7 @@ Slice CacheReservationManagerImpl<R>::GetNextCacheKey() {
|
||||
template <CacheEntryRole R>
|
||||
const Cache::CacheItemHelper*
|
||||
CacheReservationManagerImpl<R>::TEST_GetCacheItemHelperForRole() {
|
||||
return &CacheInterface::kHelper;
|
||||
return CacheInterface::GetHelper();
|
||||
}
|
||||
|
||||
template class CacheReservationManagerImpl<
|
||||
|
||||
Vendored
+104
-48
@@ -18,8 +18,10 @@
|
||||
#include "cache/lru_cache.h"
|
||||
#include "cache/typed_cache.h"
|
||||
#include "port/stack_trace.h"
|
||||
#include "test_util/secondary_cache_test_util.h"
|
||||
#include "test_util/testharness.h"
|
||||
#include "util/coding.h"
|
||||
#include "util/hash_containers.h"
|
||||
#include "util/string_util.h"
|
||||
|
||||
// HyperClockCache only supports 16-byte keys, so some of the tests
|
||||
@@ -81,13 +83,10 @@ const Cache::CacheItemHelper kEraseOnDeleteHelper2{
|
||||
Cache* cache = static_cast<Cache*>(value);
|
||||
cache->Erase(EncodeKey16Bytes(1234));
|
||||
}};
|
||||
|
||||
const std::string kLRU = "lru";
|
||||
const std::string kHyperClock = "hyper_clock";
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
class CacheTest : public testing::TestWithParam<std::string> {
|
||||
class CacheTest : public testing::Test,
|
||||
public secondary_cache_test_util::WithCacheTypeParam {
|
||||
public:
|
||||
static CacheTest* current_;
|
||||
static std::string type_;
|
||||
@@ -95,8 +94,7 @@ class CacheTest : public testing::TestWithParam<std::string> {
|
||||
static void Deleter(Cache::ObjectPtr v, MemoryAllocator*) {
|
||||
current_->deleted_values_.push_back(DecodeValue(v));
|
||||
}
|
||||
static constexpr Cache::CacheItemHelper kHelper{CacheEntryRole::kMisc,
|
||||
&Deleter};
|
||||
static const Cache::CacheItemHelper kHelper;
|
||||
|
||||
static const int kCacheSize = 1000;
|
||||
static const int kNumShardBits = 4;
|
||||
@@ -108,8 +106,6 @@ class CacheTest : public testing::TestWithParam<std::string> {
|
||||
std::shared_ptr<Cache> cache_;
|
||||
std::shared_ptr<Cache> cache2_;
|
||||
|
||||
size_t estimated_value_size_ = 1;
|
||||
|
||||
CacheTest()
|
||||
: cache_(NewCache(kCacheSize, kNumShardBits, false)),
|
||||
cache2_(NewCache(kCacheSize2, kNumShardBits2, false)) {
|
||||
@@ -119,41 +115,6 @@ class CacheTest : public testing::TestWithParam<std::string> {
|
||||
|
||||
~CacheTest() override {}
|
||||
|
||||
std::shared_ptr<Cache> NewCache(size_t capacity) {
|
||||
auto type = GetParam();
|
||||
if (type == kLRU) {
|
||||
return NewLRUCache(capacity);
|
||||
}
|
||||
if (type == kHyperClock) {
|
||||
return HyperClockCacheOptions(
|
||||
capacity, estimated_value_size_ /*estimated_value_size*/)
|
||||
.MakeSharedCache();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::shared_ptr<Cache> NewCache(
|
||||
size_t capacity, int num_shard_bits, bool strict_capacity_limit,
|
||||
CacheMetadataChargePolicy charge_policy = kDontChargeCacheMetadata) {
|
||||
auto type = GetParam();
|
||||
if (type == kLRU) {
|
||||
LRUCacheOptions co;
|
||||
co.capacity = capacity;
|
||||
co.num_shard_bits = num_shard_bits;
|
||||
co.strict_capacity_limit = strict_capacity_limit;
|
||||
co.high_pri_pool_ratio = 0;
|
||||
co.metadata_charge_policy = charge_policy;
|
||||
return NewLRUCache(co);
|
||||
}
|
||||
if (type == kHyperClock) {
|
||||
return HyperClockCacheOptions(capacity, 1 /*estimated_value_size*/,
|
||||
num_shard_bits, strict_capacity_limit,
|
||||
nullptr /*allocator*/, charge_policy)
|
||||
.MakeSharedCache();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// These functions encode/decode keys in tests cases that use
|
||||
// int keys.
|
||||
// Currently, HyperClockCache requires keys to be 16B long, whereas
|
||||
@@ -187,8 +148,8 @@ class CacheTest : public testing::TestWithParam<std::string> {
|
||||
|
||||
void Insert(std::shared_ptr<Cache> cache, int key, int value,
|
||||
int charge = 1) {
|
||||
EXPECT_OK(
|
||||
cache->Insert(EncodeKey(key), EncodeValue(value), &kHelper, charge));
|
||||
EXPECT_OK(cache->Insert(EncodeKey(key), EncodeValue(value), &kHelper,
|
||||
charge, /*handle*/ nullptr, Cache::Priority::HIGH));
|
||||
}
|
||||
|
||||
void Erase(std::shared_ptr<Cache> cache, int key) {
|
||||
@@ -212,6 +173,9 @@ class CacheTest : public testing::TestWithParam<std::string> {
|
||||
void Erase2(int key) { Erase(cache2_, key); }
|
||||
};
|
||||
|
||||
const Cache::CacheItemHelper CacheTest::kHelper{CacheEntryRole::kMisc,
|
||||
&CacheTest::Deleter};
|
||||
|
||||
CacheTest* CacheTest::current_;
|
||||
std::string CacheTest::type_;
|
||||
|
||||
@@ -992,9 +956,101 @@ TEST_P(CacheTest, GetChargeAndDeleter) {
|
||||
cache_->Release(h1);
|
||||
}
|
||||
|
||||
namespace {
|
||||
bool AreTwoCacheKeysOrdered(Cache* cache) {
|
||||
std::vector<std::string> keys;
|
||||
const auto callback = [&](const Slice& key, Cache::ObjectPtr /*value*/,
|
||||
size_t /*charge*/,
|
||||
const Cache::CacheItemHelper* /*helper*/) {
|
||||
keys.push_back(key.ToString());
|
||||
};
|
||||
cache->ApplyToAllEntries(callback, /*opts*/ {});
|
||||
EXPECT_EQ(keys.size(), 2U);
|
||||
EXPECT_NE(keys[0], keys[1]);
|
||||
return keys[0] < keys[1];
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST_P(CacheTest, CacheUniqueSeeds) {
|
||||
// kQuasiRandomHashSeed should generate unique seeds (up to 2 billion before
|
||||
// repeating)
|
||||
UnorderedSet<uint32_t> seeds_seen;
|
||||
// Roughly sqrt(number of possible values) for a decent chance at detecting
|
||||
// a random collision if it's possible (shouldn't be)
|
||||
uint16_t kSamples = 20000;
|
||||
seeds_seen.reserve(kSamples);
|
||||
|
||||
// Hash seed should affect ordering of entries in the table, so we should
|
||||
// have extremely high chance of seeing two entries ordered both ways.
|
||||
bool seen_forward_order = false;
|
||||
bool seen_reverse_order = false;
|
||||
|
||||
for (int i = 0; i < kSamples; ++i) {
|
||||
auto cache = NewCache(2, [=](ShardedCacheOptions& opts) {
|
||||
opts.hash_seed = LRUCacheOptions::kQuasiRandomHashSeed;
|
||||
opts.num_shard_bits = 0;
|
||||
opts.metadata_charge_policy = kDontChargeCacheMetadata;
|
||||
});
|
||||
auto val = cache->GetHashSeed();
|
||||
ASSERT_TRUE(seeds_seen.insert(val).second);
|
||||
|
||||
ASSERT_OK(cache->Insert(EncodeKey(1), nullptr, &kHelper, /*charge*/ 1));
|
||||
ASSERT_OK(cache->Insert(EncodeKey(2), nullptr, &kHelper, /*charge*/ 1));
|
||||
|
||||
if (AreTwoCacheKeysOrdered(cache.get())) {
|
||||
seen_forward_order = true;
|
||||
} else {
|
||||
seen_reverse_order = true;
|
||||
}
|
||||
}
|
||||
|
||||
ASSERT_TRUE(seen_forward_order);
|
||||
ASSERT_TRUE(seen_reverse_order);
|
||||
}
|
||||
|
||||
TEST_P(CacheTest, CacheHostSeed) {
|
||||
// kHostHashSeed should generate a consistent seed within this process
|
||||
// (and other processes on the same host, but not unit testing that).
|
||||
// And we should be able to use that chosen seed as an explicit option
|
||||
// (for debugging).
|
||||
// And we should verify consistent ordering of entries.
|
||||
uint32_t expected_seed = 0;
|
||||
bool expected_order = false;
|
||||
// 10 iterations -> chance of a random seed falsely appearing consistent
|
||||
// should be low, just 1 in 2^9.
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
auto cache = NewCache(2, [=](ShardedCacheOptions& opts) {
|
||||
if (i != 5) {
|
||||
opts.hash_seed = LRUCacheOptions::kHostHashSeed;
|
||||
} else {
|
||||
// Can be used as explicit seed
|
||||
opts.hash_seed = static_cast<int32_t>(expected_seed);
|
||||
ASSERT_GE(opts.hash_seed, 0);
|
||||
}
|
||||
opts.num_shard_bits = 0;
|
||||
opts.metadata_charge_policy = kDontChargeCacheMetadata;
|
||||
});
|
||||
ASSERT_OK(cache->Insert(EncodeKey(1), nullptr, &kHelper, /*charge*/ 1));
|
||||
ASSERT_OK(cache->Insert(EncodeKey(2), nullptr, &kHelper, /*charge*/ 1));
|
||||
uint32_t val = cache->GetHashSeed();
|
||||
bool order = AreTwoCacheKeysOrdered(cache.get());
|
||||
if (i != 0) {
|
||||
ASSERT_EQ(val, expected_seed);
|
||||
ASSERT_EQ(order, expected_order);
|
||||
} else {
|
||||
expected_seed = val;
|
||||
expected_order = order;
|
||||
}
|
||||
}
|
||||
// Printed for reference in case it's needed to reproduce other unit test
|
||||
// failures on another host
|
||||
fprintf(stderr, "kHostHashSeed -> %u\n", (unsigned)expected_seed);
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(CacheTestInstance, CacheTest,
|
||||
testing::Values(kLRU, kHyperClock));
|
||||
INSTANTIATE_TEST_CASE_P(CacheTestInstance, LRUCacheTest, testing::Values(kLRU));
|
||||
secondary_cache_test_util::GetTestingCacheTypes());
|
||||
INSTANTIATE_TEST_CASE_P(CacheTestInstance, LRUCacheTest,
|
||||
testing::Values(secondary_cache_test_util::kLRU));
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
|
||||
Vendored
+26
-18
@@ -11,7 +11,7 @@ namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
ChargedCache::ChargedCache(std::shared_ptr<Cache> cache,
|
||||
std::shared_ptr<Cache> block_cache)
|
||||
: cache_(cache),
|
||||
: CacheWrapper(cache),
|
||||
cache_res_mgr_(std::make_shared<ConcurrentCacheReservationManager>(
|
||||
std::make_shared<
|
||||
CacheReservationManagerImpl<CacheEntryRole::kBlobCache>>(
|
||||
@@ -20,13 +20,13 @@ ChargedCache::ChargedCache(std::shared_ptr<Cache> cache,
|
||||
Status ChargedCache::Insert(const Slice& key, ObjectPtr obj,
|
||||
const CacheItemHelper* helper, size_t charge,
|
||||
Handle** handle, Priority priority) {
|
||||
Status s = cache_->Insert(key, obj, helper, charge, handle, priority);
|
||||
Status s = target_->Insert(key, obj, helper, charge, handle, priority);
|
||||
if (s.ok()) {
|
||||
// Insert may cause the cache entry eviction if the cache is full. So we
|
||||
// directly call the reservation manager to update the total memory used
|
||||
// in the cache.
|
||||
assert(cache_res_mgr_);
|
||||
cache_res_mgr_->UpdateCacheReservation(cache_->GetUsage())
|
||||
cache_res_mgr_->UpdateCacheReservation(target_->GetUsage())
|
||||
.PermitUncheckedError();
|
||||
}
|
||||
return s;
|
||||
@@ -35,25 +35,33 @@ Status ChargedCache::Insert(const Slice& key, ObjectPtr obj,
|
||||
Cache::Handle* ChargedCache::Lookup(const Slice& key,
|
||||
const CacheItemHelper* helper,
|
||||
CreateContext* create_context,
|
||||
Priority priority, bool wait,
|
||||
Statistics* stats) {
|
||||
auto handle =
|
||||
cache_->Lookup(key, helper, create_context, priority, wait, stats);
|
||||
Priority priority, Statistics* stats) {
|
||||
auto handle = target_->Lookup(key, helper, create_context, priority, stats);
|
||||
// Lookup may promote the KV pair from the secondary cache to the primary
|
||||
// cache. So we directly call the reservation manager to update the total
|
||||
// memory used in the cache.
|
||||
if (helper && helper->create_cb) {
|
||||
assert(cache_res_mgr_);
|
||||
cache_res_mgr_->UpdateCacheReservation(cache_->GetUsage())
|
||||
cache_res_mgr_->UpdateCacheReservation(target_->GetUsage())
|
||||
.PermitUncheckedError();
|
||||
}
|
||||
return handle;
|
||||
}
|
||||
|
||||
void ChargedCache::WaitAll(AsyncLookupHandle* async_handles, size_t count) {
|
||||
target_->WaitAll(async_handles, count);
|
||||
// In case of any promotions. Although some could finish by return of
|
||||
// StartAsyncLookup, Wait/WaitAll will generally be used, so simpler to
|
||||
// update here.
|
||||
assert(cache_res_mgr_);
|
||||
cache_res_mgr_->UpdateCacheReservation(target_->GetUsage())
|
||||
.PermitUncheckedError();
|
||||
}
|
||||
|
||||
bool ChargedCache::Release(Cache::Handle* handle, bool useful,
|
||||
bool erase_if_last_ref) {
|
||||
size_t memory_used_delta = cache_->GetUsage(handle);
|
||||
bool erased = cache_->Release(handle, useful, erase_if_last_ref);
|
||||
size_t memory_used_delta = target_->GetUsage(handle);
|
||||
bool erased = target_->Release(handle, useful, erase_if_last_ref);
|
||||
if (erased) {
|
||||
assert(cache_res_mgr_);
|
||||
cache_res_mgr_
|
||||
@@ -64,8 +72,8 @@ bool ChargedCache::Release(Cache::Handle* handle, bool useful,
|
||||
}
|
||||
|
||||
bool ChargedCache::Release(Cache::Handle* handle, bool erase_if_last_ref) {
|
||||
size_t memory_used_delta = cache_->GetUsage(handle);
|
||||
bool erased = cache_->Release(handle, erase_if_last_ref);
|
||||
size_t memory_used_delta = target_->GetUsage(handle);
|
||||
bool erased = target_->Release(handle, erase_if_last_ref);
|
||||
if (erased) {
|
||||
assert(cache_res_mgr_);
|
||||
cache_res_mgr_
|
||||
@@ -76,25 +84,25 @@ bool ChargedCache::Release(Cache::Handle* handle, bool erase_if_last_ref) {
|
||||
}
|
||||
|
||||
void ChargedCache::Erase(const Slice& key) {
|
||||
cache_->Erase(key);
|
||||
target_->Erase(key);
|
||||
assert(cache_res_mgr_);
|
||||
cache_res_mgr_->UpdateCacheReservation(cache_->GetUsage())
|
||||
cache_res_mgr_->UpdateCacheReservation(target_->GetUsage())
|
||||
.PermitUncheckedError();
|
||||
}
|
||||
|
||||
void ChargedCache::EraseUnRefEntries() {
|
||||
cache_->EraseUnRefEntries();
|
||||
target_->EraseUnRefEntries();
|
||||
assert(cache_res_mgr_);
|
||||
cache_res_mgr_->UpdateCacheReservation(cache_->GetUsage())
|
||||
cache_res_mgr_->UpdateCacheReservation(target_->GetUsage())
|
||||
.PermitUncheckedError();
|
||||
}
|
||||
|
||||
void ChargedCache::SetCapacity(size_t capacity) {
|
||||
cache_->SetCapacity(capacity);
|
||||
target_->SetCapacity(capacity);
|
||||
// SetCapacity can result in evictions when the cache capacity is decreased,
|
||||
// so we would want to update the cache reservation here as well.
|
||||
assert(cache_res_mgr_);
|
||||
cache_res_mgr_->UpdateCacheReservation(cache_->GetUsage())
|
||||
cache_res_mgr_->UpdateCacheReservation(target_->GetUsage())
|
||||
.PermitUncheckedError();
|
||||
}
|
||||
|
||||
|
||||
Vendored
+6
-63
@@ -8,7 +8,7 @@
|
||||
#include <string>
|
||||
|
||||
#include "port/port.h"
|
||||
#include "rocksdb/cache.h"
|
||||
#include "rocksdb/advanced_cache.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
@@ -17,11 +17,10 @@ class ConcurrentCacheReservationManager;
|
||||
// A cache interface which wraps around another cache and takes care of
|
||||
// reserving space in block cache towards a single global memory limit, and
|
||||
// forwards all the calls to the underlying cache.
|
||||
class ChargedCache : public Cache {
|
||||
class ChargedCache : public CacheWrapper {
|
||||
public:
|
||||
ChargedCache(std::shared_ptr<Cache> cache,
|
||||
std::shared_ptr<Cache> block_cache);
|
||||
~ChargedCache() override = default;
|
||||
|
||||
Status Insert(const Slice& key, ObjectPtr obj, const CacheItemHelper* helper,
|
||||
size_t charge, Handle** handle = nullptr,
|
||||
@@ -29,9 +28,11 @@ class ChargedCache : public Cache {
|
||||
|
||||
Cache::Handle* Lookup(const Slice& key, const CacheItemHelper* helper,
|
||||
CreateContext* create_context,
|
||||
Priority priority = Priority::LOW, bool wait = true,
|
||||
Priority priority = Priority::LOW,
|
||||
Statistics* stats = nullptr) override;
|
||||
|
||||
void WaitAll(AsyncLookupHandle* async_handles, size_t count) override;
|
||||
|
||||
bool Release(Cache::Handle* handle, bool useful,
|
||||
bool erase_if_last_ref = false) override;
|
||||
bool Release(Cache::Handle* handle, bool erase_if_last_ref = false) override;
|
||||
@@ -42,66 +43,9 @@ class ChargedCache : public Cache {
|
||||
static const char* kClassName() { return "ChargedCache"; }
|
||||
const char* Name() const override { return kClassName(); }
|
||||
|
||||
uint64_t NewId() override { return cache_->NewId(); }
|
||||
|
||||
void SetCapacity(size_t capacity) override;
|
||||
|
||||
void SetStrictCapacityLimit(bool strict_capacity_limit) override {
|
||||
cache_->SetStrictCapacityLimit(strict_capacity_limit);
|
||||
}
|
||||
|
||||
bool HasStrictCapacityLimit() const override {
|
||||
return cache_->HasStrictCapacityLimit();
|
||||
}
|
||||
|
||||
ObjectPtr Value(Cache::Handle* handle) override {
|
||||
return cache_->Value(handle);
|
||||
}
|
||||
|
||||
bool IsReady(Cache::Handle* handle) override {
|
||||
return cache_->IsReady(handle);
|
||||
}
|
||||
|
||||
void Wait(Cache::Handle* handle) override { cache_->Wait(handle); }
|
||||
|
||||
void WaitAll(std::vector<Handle*>& handles) override {
|
||||
cache_->WaitAll(handles);
|
||||
}
|
||||
|
||||
bool Ref(Cache::Handle* handle) override { return cache_->Ref(handle); }
|
||||
|
||||
size_t GetCapacity() const override { return cache_->GetCapacity(); }
|
||||
|
||||
size_t GetUsage() const override { return cache_->GetUsage(); }
|
||||
|
||||
size_t GetUsage(Cache::Handle* handle) const override {
|
||||
return cache_->GetUsage(handle);
|
||||
}
|
||||
|
||||
size_t GetPinnedUsage() const override { return cache_->GetPinnedUsage(); }
|
||||
|
||||
size_t GetCharge(Cache::Handle* handle) const override {
|
||||
return cache_->GetCharge(handle);
|
||||
}
|
||||
|
||||
const CacheItemHelper* GetCacheItemHelper(Handle* handle) const override {
|
||||
return cache_->GetCacheItemHelper(handle);
|
||||
}
|
||||
|
||||
void ApplyToAllEntries(
|
||||
const std::function<void(const Slice& key, ObjectPtr value, size_t charge,
|
||||
const CacheItemHelper* helper)>& callback,
|
||||
const Cache::ApplyToAllEntriesOptions& opts) override {
|
||||
cache_->ApplyToAllEntries(callback, opts);
|
||||
}
|
||||
|
||||
std::string GetPrintableOptions() const override {
|
||||
return cache_->GetPrintableOptions();
|
||||
}
|
||||
|
||||
void DisownData() override { return cache_->DisownData(); }
|
||||
|
||||
inline Cache* GetCache() const { return cache_.get(); }
|
||||
inline Cache* GetCache() const { return target_.get(); }
|
||||
|
||||
inline ConcurrentCacheReservationManager* TEST_GetCacheReservationManager()
|
||||
const {
|
||||
@@ -109,7 +53,6 @@ class ChargedCache : public Cache {
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr<Cache> cache_;
|
||||
std::shared_ptr<ConcurrentCacheReservationManager> cache_res_mgr_;
|
||||
};
|
||||
|
||||
|
||||
Vendored
+142
-67
@@ -9,15 +9,16 @@
|
||||
|
||||
#include "cache/clock_cache.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <functional>
|
||||
#include <numeric>
|
||||
|
||||
#include "cache/cache_key.h"
|
||||
#include "cache/secondary_cache_adapter.h"
|
||||
#include "logging/logging.h"
|
||||
#include "monitoring/perf_context_imp.h"
|
||||
#include "monitoring/statistics.h"
|
||||
#include "monitoring/statistics_impl.h"
|
||||
#include "port/lang.h"
|
||||
#include "rocksdb/env.h"
|
||||
#include "util/hash.h"
|
||||
#include "util/math.h"
|
||||
#include "util/random.h"
|
||||
@@ -50,13 +51,7 @@ inline uint64_t GetInitialCountdown(Cache::Priority priority) {
|
||||
}
|
||||
}
|
||||
|
||||
inline void FreeDataMarkEmpty(ClockHandle& h, MemoryAllocator* allocator) {
|
||||
// NOTE: in theory there's more room for parallelism if we copy the handle
|
||||
// data and delay actions like this until after marking the entry as empty,
|
||||
// but performance tests only show a regression by copying the few words
|
||||
// of data.
|
||||
h.FreeData(allocator);
|
||||
|
||||
inline void MarkEmpty(ClockHandle& h) {
|
||||
#ifndef NDEBUG
|
||||
// Mark slot as empty, with assertion
|
||||
uint64_t meta = h.meta.exchange(0, std::memory_order_release);
|
||||
@@ -67,6 +62,16 @@ inline void FreeDataMarkEmpty(ClockHandle& h, MemoryAllocator* allocator) {
|
||||
#endif
|
||||
}
|
||||
|
||||
inline void FreeDataMarkEmpty(ClockHandle& h, MemoryAllocator* allocator) {
|
||||
// NOTE: in theory there's more room for parallelism if we copy the handle
|
||||
// data and delay actions like this until after marking the entry as empty,
|
||||
// but performance tests only show a regression by copying the few words
|
||||
// of data.
|
||||
h.FreeData(allocator);
|
||||
|
||||
MarkEmpty(h);
|
||||
}
|
||||
|
||||
inline bool ClockUpdate(ClockHandle& h) {
|
||||
uint64_t meta = h.meta.load(std::memory_order_relaxed);
|
||||
|
||||
@@ -124,14 +129,18 @@ void ClockHandleBasicData::FreeData(MemoryAllocator* allocator) const {
|
||||
HyperClockTable::HyperClockTable(
|
||||
size_t capacity, bool /*strict_capacity_limit*/,
|
||||
CacheMetadataChargePolicy metadata_charge_policy,
|
||||
MemoryAllocator* allocator, const Opts& opts)
|
||||
MemoryAllocator* allocator,
|
||||
const Cache::EvictionCallback* eviction_callback, const uint32_t* hash_seed,
|
||||
const Opts& opts)
|
||||
: length_bits_(CalcHashBits(capacity, opts.estimated_value_size,
|
||||
metadata_charge_policy)),
|
||||
length_bits_mask_((size_t{1} << length_bits_) - 1),
|
||||
occupancy_limit_(static_cast<size_t>((uint64_t{1} << length_bits_) *
|
||||
kStrictLoadFactor)),
|
||||
array_(new HandleImpl[size_t{1} << length_bits_]),
|
||||
allocator_(allocator) {
|
||||
allocator_(allocator),
|
||||
eviction_callback_(*eviction_callback),
|
||||
hash_seed_(*hash_seed) {
|
||||
if (metadata_charge_policy ==
|
||||
CacheMetadataChargePolicy::kFullChargeCacheMetadata) {
|
||||
usage_ += size_t{GetTableSize()} * sizeof(HandleImpl);
|
||||
@@ -364,21 +373,22 @@ inline bool HyperClockTable::ChargeUsageMaybeEvictNonStrict(
|
||||
return true;
|
||||
}
|
||||
|
||||
inline HyperClockTable::HandleImpl* HyperClockTable::DetachedInsert(
|
||||
inline HyperClockTable::HandleImpl* HyperClockTable::StandaloneInsert(
|
||||
const ClockHandleBasicData& proto) {
|
||||
// Heap allocated separate from table
|
||||
HandleImpl* h = new HandleImpl();
|
||||
ClockHandleBasicData* h_alias = h;
|
||||
*h_alias = proto;
|
||||
h->SetDetached();
|
||||
// Single reference (detached entries only created if returning a refed
|
||||
h->SetStandalone();
|
||||
// Single reference (standalone entries only created if returning a refed
|
||||
// Handle back to user)
|
||||
uint64_t meta = uint64_t{ClockHandle::kStateInvisible}
|
||||
<< ClockHandle::kStateShift;
|
||||
meta |= uint64_t{1} << ClockHandle::kAcquireCounterShift;
|
||||
h->meta.store(meta, std::memory_order_release);
|
||||
// Keep track of how much of usage is detached
|
||||
detached_usage_.fetch_add(proto.GetTotalCharge(), std::memory_order_relaxed);
|
||||
// Keep track of how much of usage is standalone
|
||||
standalone_usage_.fetch_add(proto.GetTotalCharge(),
|
||||
std::memory_order_relaxed);
|
||||
return h;
|
||||
}
|
||||
|
||||
@@ -396,7 +406,7 @@ Status HyperClockTable::Insert(const ClockHandleBasicData& proto,
|
||||
|
||||
// Usage/capacity handling is somewhat different depending on
|
||||
// strict_capacity_limit, but mostly pessimistic.
|
||||
bool use_detached_insert = false;
|
||||
bool use_standalone_insert = false;
|
||||
const size_t total_charge = proto.GetTotalCharge();
|
||||
if (strict_capacity_limit) {
|
||||
Status s = ChargeUsageMaybeEvictStrict(total_charge, capacity,
|
||||
@@ -417,9 +427,9 @@ Status HyperClockTable::Insert(const ClockHandleBasicData& proto,
|
||||
proto.FreeData(allocator_);
|
||||
return Status::OK();
|
||||
} else {
|
||||
// Need to track usage of fallback detached insert
|
||||
// Need to track usage of fallback standalone insert
|
||||
usage_.fetch_add(total_charge, std::memory_order_relaxed);
|
||||
use_detached_insert = true;
|
||||
use_standalone_insert = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -429,7 +439,7 @@ Status HyperClockTable::Insert(const ClockHandleBasicData& proto,
|
||||
assert(usage_.load(std::memory_order_relaxed) < SIZE_MAX / 2);
|
||||
};
|
||||
|
||||
if (!use_detached_insert) {
|
||||
if (!use_standalone_insert) {
|
||||
// Attempt a table insert, but abort if we find an existing entry for the
|
||||
// key. If we were to overwrite old entries, we would either
|
||||
// * Have to gain ownership over an existing entry to overwrite it, which
|
||||
@@ -500,8 +510,8 @@ Status HyperClockTable::Insert(const ClockHandleBasicData& proto,
|
||||
std::memory_order_acq_rel);
|
||||
// Correct for possible (but rare) overflow
|
||||
CorrectNearOverflow(old_meta, h->meta);
|
||||
// Insert detached instead (only if return handle needed)
|
||||
use_detached_insert = true;
|
||||
// Insert standalone instead (only if return handle needed)
|
||||
use_standalone_insert = true;
|
||||
return true;
|
||||
} else {
|
||||
// Mismatch. Pretend we never took the reference
|
||||
@@ -539,9 +549,9 @@ Status HyperClockTable::Insert(const ClockHandleBasicData& proto,
|
||||
// That should be infeasible for roughly n >= 256, so if this assertion
|
||||
// fails, that suggests something is going wrong.
|
||||
assert(GetTableSize() < 256);
|
||||
use_detached_insert = true;
|
||||
use_standalone_insert = true;
|
||||
}
|
||||
if (!use_detached_insert) {
|
||||
if (!use_standalone_insert) {
|
||||
// Successfully inserted
|
||||
if (handle) {
|
||||
*handle = e;
|
||||
@@ -551,7 +561,7 @@ Status HyperClockTable::Insert(const ClockHandleBasicData& proto,
|
||||
// Roll back table insertion
|
||||
Rollback(proto.hashed_key, e);
|
||||
revert_occupancy_fn();
|
||||
// Maybe fall back on detached insert
|
||||
// Maybe fall back on standalone insert
|
||||
if (handle == nullptr) {
|
||||
revert_usage_fn();
|
||||
// As if unrefed entry immdiately evicted
|
||||
@@ -560,19 +570,47 @@ Status HyperClockTable::Insert(const ClockHandleBasicData& proto,
|
||||
}
|
||||
}
|
||||
|
||||
// Run detached insert
|
||||
assert(use_detached_insert);
|
||||
// Run standalone insert
|
||||
assert(use_standalone_insert);
|
||||
|
||||
*handle = DetachedInsert(proto);
|
||||
*handle = StandaloneInsert(proto);
|
||||
|
||||
// The OkOverwritten status is used to count "redundant" insertions into
|
||||
// block cache. This implementation doesn't strictly check for redundant
|
||||
// insertions, but we instead are probably interested in how many insertions
|
||||
// didn't go into the table (instead "detached"), which could be redundant
|
||||
// Insert or some other reason (use_detached_insert reasons above).
|
||||
// didn't go into the table (instead "standalone"), which could be redundant
|
||||
// Insert or some other reason (use_standalone_insert reasons above).
|
||||
return Status::OkOverwritten();
|
||||
}
|
||||
|
||||
HyperClockTable::HandleImpl* HyperClockTable::CreateStandalone(
|
||||
ClockHandleBasicData& proto, size_t capacity, bool strict_capacity_limit,
|
||||
bool allow_uncharged) {
|
||||
const size_t total_charge = proto.GetTotalCharge();
|
||||
if (strict_capacity_limit) {
|
||||
Status s = ChargeUsageMaybeEvictStrict(total_charge, capacity,
|
||||
/*need_evict_for_occupancy=*/false);
|
||||
if (!s.ok()) {
|
||||
if (allow_uncharged) {
|
||||
proto.total_charge = 0;
|
||||
} else {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Case strict_capacity_limit == false
|
||||
bool success =
|
||||
ChargeUsageMaybeEvictNonStrict(total_charge, capacity,
|
||||
/*need_evict_for_occupancy=*/false);
|
||||
if (!success) {
|
||||
// Force the issue
|
||||
usage_.fetch_add(total_charge, std::memory_order_relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
return StandaloneInsert(proto);
|
||||
}
|
||||
|
||||
HyperClockTable::HandleImpl* HyperClockTable::Lookup(
|
||||
const UniqueId64x2& hashed_key) {
|
||||
size_t probe = 0;
|
||||
@@ -696,11 +734,11 @@ bool HyperClockTable::Release(HandleImpl* h, bool useful,
|
||||
std::memory_order_acquire));
|
||||
// Took ownership
|
||||
size_t total_charge = h->GetTotalCharge();
|
||||
if (UNLIKELY(h->IsDetached())) {
|
||||
if (UNLIKELY(h->IsStandalone())) {
|
||||
h->FreeData(allocator_);
|
||||
// Delete detached handle
|
||||
// Delete standalone handle
|
||||
delete h;
|
||||
detached_usage_.fetch_sub(total_charge, std::memory_order_relaxed);
|
||||
standalone_usage_.fetch_sub(total_charge, std::memory_order_relaxed);
|
||||
usage_.fetch_sub(total_charge, std::memory_order_relaxed);
|
||||
} else {
|
||||
Rollback(h->hashed_key, h);
|
||||
@@ -959,6 +997,9 @@ inline void HyperClockTable::Evict(size_t requested_charge,
|
||||
uint64_t max_clock_pointer =
|
||||
old_clock_pointer + (ClockHandle::kMaxCountdown << length_bits_);
|
||||
|
||||
// For key reconstructed from hash
|
||||
UniqueId64x2 unhashed;
|
||||
|
||||
for (;;) {
|
||||
for (size_t i = 0; i < step_size; i++) {
|
||||
HandleImpl& h = array_[ModTableSize(Lower32of64(old_clock_pointer + i))];
|
||||
@@ -967,7 +1008,17 @@ inline void HyperClockTable::Evict(size_t requested_charge,
|
||||
Rollback(h.hashed_key, &h);
|
||||
*freed_charge += h.GetTotalCharge();
|
||||
*freed_count += 1;
|
||||
FreeDataMarkEmpty(h, allocator_);
|
||||
bool took_ownership = false;
|
||||
if (eviction_callback_) {
|
||||
took_ownership =
|
||||
eviction_callback_(ClockCacheShard<HyperClockTable>::ReverseHash(
|
||||
h.GetHash(), &unhashed, hash_seed_),
|
||||
reinterpret_cast<Cache::Handle*>(&h));
|
||||
}
|
||||
if (!took_ownership) {
|
||||
h.FreeData(allocator_);
|
||||
}
|
||||
MarkEmpty(h);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -989,10 +1040,12 @@ template <class Table>
|
||||
ClockCacheShard<Table>::ClockCacheShard(
|
||||
size_t capacity, bool strict_capacity_limit,
|
||||
CacheMetadataChargePolicy metadata_charge_policy,
|
||||
MemoryAllocator* allocator, const typename Table::Opts& opts)
|
||||
MemoryAllocator* allocator,
|
||||
const Cache::EvictionCallback* eviction_callback, const uint32_t* hash_seed,
|
||||
const typename Table::Opts& opts)
|
||||
: CacheShardBase(metadata_charge_policy),
|
||||
table_(capacity, strict_capacity_limit, metadata_charge_policy, allocator,
|
||||
opts),
|
||||
eviction_callback, hash_seed, opts),
|
||||
capacity_(capacity),
|
||||
strict_capacity_limit_(strict_capacity_limit) {
|
||||
// Initial charge metadata should not exceed capacity
|
||||
@@ -1031,10 +1084,11 @@ void ClockCacheShard<Table>::ApplyToSomeEntries(
|
||||
*state = index_end << (sizeof(size_t) * 8u - length_bits);
|
||||
}
|
||||
|
||||
auto hash_seed = table_.GetHashSeed();
|
||||
table_.ConstApplyToEntriesRange(
|
||||
[callback](const HandleImpl& h) {
|
||||
[callback, hash_seed](const HandleImpl& h) {
|
||||
UniqueId64x2 unhashed;
|
||||
callback(ReverseHash(h.hashed_key, &unhashed), h.value,
|
||||
callback(ReverseHash(h.hashed_key, &unhashed, hash_seed), h.value,
|
||||
h.GetTotalCharge(), h.helper);
|
||||
},
|
||||
index_begin, index_end, false);
|
||||
@@ -1092,10 +1146,29 @@ Status ClockCacheShard<Table>::Insert(const Slice& key,
|
||||
proto.value = value;
|
||||
proto.helper = helper;
|
||||
proto.total_charge = charge;
|
||||
Status s = table_.Insert(
|
||||
proto, handle, priority, capacity_.load(std::memory_order_relaxed),
|
||||
strict_capacity_limit_.load(std::memory_order_relaxed));
|
||||
return s;
|
||||
return table_.Insert(proto, handle, priority,
|
||||
capacity_.load(std::memory_order_relaxed),
|
||||
strict_capacity_limit_.load(std::memory_order_relaxed));
|
||||
}
|
||||
|
||||
template <class Table>
|
||||
typename ClockCacheShard<Table>::HandleImpl*
|
||||
ClockCacheShard<Table>::CreateStandalone(const Slice& key,
|
||||
const UniqueId64x2& hashed_key,
|
||||
Cache::ObjectPtr obj,
|
||||
const Cache::CacheItemHelper* helper,
|
||||
size_t charge, bool allow_uncharged) {
|
||||
if (UNLIKELY(key.size() != kCacheKeySize)) {
|
||||
return nullptr;
|
||||
}
|
||||
ClockHandleBasicData proto;
|
||||
proto.hashed_key = hashed_key;
|
||||
proto.value = obj;
|
||||
proto.helper = helper;
|
||||
proto.total_charge = charge;
|
||||
return table_.CreateStandalone(
|
||||
proto, capacity_.load(std::memory_order_relaxed),
|
||||
strict_capacity_limit_.load(std::memory_order_relaxed), allow_uncharged);
|
||||
}
|
||||
|
||||
template <class Table>
|
||||
@@ -1156,8 +1229,8 @@ size_t ClockCacheShard<Table>::GetUsage() const {
|
||||
}
|
||||
|
||||
template <class Table>
|
||||
size_t ClockCacheShard<Table>::GetDetachedUsage() const {
|
||||
return table_.GetDetachedUsage();
|
||||
size_t ClockCacheShard<Table>::GetStandaloneUsage() const {
|
||||
return table_.GetStandaloneUsage();
|
||||
}
|
||||
|
||||
template <class Table>
|
||||
@@ -1191,7 +1264,7 @@ size_t ClockCacheShard<Table>::GetPinnedUsage() const {
|
||||
},
|
||||
0, table_.GetTableSize(), true);
|
||||
|
||||
return table_pinned_usage + table_.GetDetachedUsage();
|
||||
return table_pinned_usage + table_.GetStandaloneUsage();
|
||||
}
|
||||
|
||||
template <class Table>
|
||||
@@ -1212,24 +1285,20 @@ size_t ClockCacheShard<Table>::GetTableAddressCount() const {
|
||||
// Explicit instantiation
|
||||
template class ClockCacheShard<HyperClockTable>;
|
||||
|
||||
HyperClockCache::HyperClockCache(
|
||||
size_t capacity, size_t estimated_value_size, int num_shard_bits,
|
||||
bool strict_capacity_limit,
|
||||
CacheMetadataChargePolicy metadata_charge_policy,
|
||||
std::shared_ptr<MemoryAllocator> memory_allocator)
|
||||
: ShardedCache(capacity, num_shard_bits, strict_capacity_limit,
|
||||
std::move(memory_allocator)) {
|
||||
assert(estimated_value_size > 0 ||
|
||||
metadata_charge_policy != kDontChargeCacheMetadata);
|
||||
HyperClockCache::HyperClockCache(const HyperClockCacheOptions& opts)
|
||||
: ShardedCache(opts) {
|
||||
assert(opts.estimated_entry_charge > 0 ||
|
||||
opts.metadata_charge_policy != kDontChargeCacheMetadata);
|
||||
// TODO: should not need to go through two levels of pointer indirection to
|
||||
// get to table entries
|
||||
size_t per_shard = GetPerShardCapacity();
|
||||
MemoryAllocator* alloc = this->memory_allocator();
|
||||
InitShards([=](Shard* cs) {
|
||||
HyperClockTable::Opts opts;
|
||||
opts.estimated_value_size = estimated_value_size;
|
||||
new (cs) Shard(per_shard, strict_capacity_limit, metadata_charge_policy,
|
||||
alloc, opts);
|
||||
InitShards([&](Shard* cs) {
|
||||
HyperClockTable::Opts table_opts;
|
||||
table_opts.estimated_value_size = opts.estimated_entry_charge;
|
||||
new (cs) Shard(per_shard, opts.strict_capacity_limit,
|
||||
opts.metadata_charge_policy, alloc, &eviction_callback_,
|
||||
&hash_seed_, table_opts);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1259,7 +1328,7 @@ namespace {
|
||||
void AddShardEvaluation(const HyperClockCache::Shard& shard,
|
||||
std::vector<double>& predicted_load_factors,
|
||||
size_t& min_recommendation) {
|
||||
size_t usage = shard.GetUsage() - shard.GetDetachedUsage();
|
||||
size_t usage = shard.GetUsage() - shard.GetStandaloneUsage();
|
||||
size_t capacity = shard.GetCapacity();
|
||||
double usage_ratio = 1.0 * usage / capacity;
|
||||
|
||||
@@ -1389,19 +1458,25 @@ std::shared_ptr<Cache> NewClockCache(
|
||||
}
|
||||
|
||||
std::shared_ptr<Cache> HyperClockCacheOptions::MakeSharedCache() const {
|
||||
auto my_num_shard_bits = num_shard_bits;
|
||||
if (my_num_shard_bits >= 20) {
|
||||
// For sanitized options
|
||||
HyperClockCacheOptions opts = *this;
|
||||
if (opts.num_shard_bits >= 20) {
|
||||
return nullptr; // The cache cannot be sharded into too many fine pieces.
|
||||
}
|
||||
if (my_num_shard_bits < 0) {
|
||||
if (opts.num_shard_bits < 0) {
|
||||
// Use larger shard size to reduce risk of large entries clustering
|
||||
// or skewing individual shards.
|
||||
constexpr size_t min_shard_size = 32U * 1024U * 1024U;
|
||||
my_num_shard_bits = GetDefaultCacheShardBits(capacity, min_shard_size);
|
||||
opts.num_shard_bits =
|
||||
GetDefaultCacheShardBits(opts.capacity, min_shard_size);
|
||||
}
|
||||
return std::make_shared<clock_cache::HyperClockCache>(
|
||||
capacity, estimated_entry_charge, my_num_shard_bits,
|
||||
strict_capacity_limit, metadata_charge_policy, memory_allocator);
|
||||
std::shared_ptr<Cache> cache =
|
||||
std::make_shared<clock_cache::HyperClockCache>(opts);
|
||||
if (opts.secondary_cache) {
|
||||
cache = std::make_shared<CacheWithSecondaryAdapter>(cache,
|
||||
opts.secondary_cache);
|
||||
}
|
||||
return cache;
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
Vendored
+44
-36
@@ -145,7 +145,7 @@ class ClockCacheTest;
|
||||
// (erased by user) but can be read by existing references, and ref count
|
||||
// changed by Ref and Release.
|
||||
//
|
||||
// A special case is "detached" entries, which are heap-allocated handles
|
||||
// A special case is "standalone" entries, which are heap-allocated handles
|
||||
// not in the table. They are always Invisible and freed on zero refs.
|
||||
//
|
||||
// State transitions:
|
||||
@@ -200,8 +200,8 @@ class ClockCacheTest;
|
||||
// table occupancy limit has been reached. If strict_capacity_limit=false,
|
||||
// we must never fail Insert, and if a Handle* is provided, we have to return
|
||||
// a usable Cache handle on success. The solution to this (typically rare)
|
||||
// problem is "detached" handles, which are usable by the caller but not
|
||||
// actually available for Lookup in the Cache. Detached handles are allocated
|
||||
// problem is "standalone" handles, which are usable by the caller but not
|
||||
// actually available for Lookup in the Cache. Standalone handles are allocated
|
||||
// independently on the heap and specially marked so that they are freed on
|
||||
// the heap when their last reference is released.
|
||||
//
|
||||
@@ -312,12 +312,6 @@ struct ClockHandleBasicData {
|
||||
UniqueId64x2 hashed_key = kNullUniqueId64x2;
|
||||
size_t total_charge = 0;
|
||||
|
||||
// For total_charge_and_flags
|
||||
// "Detached" means the handle is allocated separately from hash table.
|
||||
static constexpr uint64_t kFlagDetached = uint64_t{1} << 63;
|
||||
// Extract just the total charge
|
||||
static constexpr uint64_t kTotalChargeMask = kFlagDetached - 1;
|
||||
|
||||
inline size_t GetTotalCharge() const { return total_charge; }
|
||||
|
||||
// Calls deleter (if non-null) on cache key and value
|
||||
@@ -398,11 +392,11 @@ class HyperClockTable {
|
||||
// TODO: ideally this would be packed into some other data field, such
|
||||
// as upper bits of total_charge, but that incurs a measurable performance
|
||||
// regression.
|
||||
bool detached = false;
|
||||
bool standalone = false;
|
||||
|
||||
inline bool IsDetached() const { return detached; }
|
||||
inline bool IsStandalone() const { return standalone; }
|
||||
|
||||
inline void SetDetached() { detached = true; }
|
||||
inline void SetStandalone() { standalone = true; }
|
||||
}; // struct HandleImpl
|
||||
|
||||
struct Opts {
|
||||
@@ -411,13 +405,19 @@ class HyperClockTable {
|
||||
|
||||
HyperClockTable(size_t capacity, bool strict_capacity_limit,
|
||||
CacheMetadataChargePolicy metadata_charge_policy,
|
||||
MemoryAllocator* allocator, const Opts& opts);
|
||||
MemoryAllocator* allocator,
|
||||
const Cache::EvictionCallback* eviction_callback,
|
||||
const uint32_t* hash_seed, const Opts& opts);
|
||||
~HyperClockTable();
|
||||
|
||||
Status Insert(const ClockHandleBasicData& proto, HandleImpl** handle,
|
||||
Cache::Priority priority, size_t capacity,
|
||||
bool strict_capacity_limit);
|
||||
|
||||
HandleImpl* CreateStandalone(ClockHandleBasicData& proto, size_t capacity,
|
||||
bool strict_capacity_limit,
|
||||
bool allow_uncharged);
|
||||
|
||||
HandleImpl* Lookup(const UniqueId64x2& hashed_key);
|
||||
|
||||
bool Release(HandleImpl* handle, bool useful, bool erase_if_last_ref);
|
||||
@@ -444,10 +444,12 @@ class HyperClockTable {
|
||||
|
||||
size_t GetUsage() const { return usage_.load(std::memory_order_relaxed); }
|
||||
|
||||
size_t GetDetachedUsage() const {
|
||||
return detached_usage_.load(std::memory_order_relaxed);
|
||||
size_t GetStandaloneUsage() const {
|
||||
return standalone_usage_.load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
uint32_t GetHashSeed() const { return hash_seed_; }
|
||||
|
||||
// Acquire/release N references
|
||||
void TEST_RefN(HandleImpl& handle, size_t n);
|
||||
void TEST_ReleaseN(HandleImpl* handle, size_t n);
|
||||
@@ -514,10 +516,10 @@ class HyperClockTable {
|
||||
size_t capacity,
|
||||
bool need_evict_for_occupancy);
|
||||
|
||||
// Creates a "detached" handle for returning from an Insert operation that
|
||||
// Creates a "standalone" handle for returning from an Insert operation that
|
||||
// cannot be completed by actually inserting into the table.
|
||||
// Updates `detached_usage_` but not `usage_` nor `occupancy_`.
|
||||
inline HandleImpl* DetachedInsert(const ClockHandleBasicData& proto);
|
||||
// Updates `standalone_usage_` but not `usage_` nor `occupancy_`.
|
||||
inline HandleImpl* StandaloneInsert(const ClockHandleBasicData& proto);
|
||||
|
||||
MemoryAllocator* GetAllocator() const { return allocator_; }
|
||||
|
||||
@@ -543,6 +545,12 @@ class HyperClockTable {
|
||||
// From Cache, for deleter
|
||||
MemoryAllocator* const allocator_;
|
||||
|
||||
// A reference to Cache::eviction_callback_
|
||||
const Cache::EvictionCallback& eviction_callback_;
|
||||
|
||||
// A reference to ShardedCacheBase::hash_seed_
|
||||
const uint32_t& hash_seed_;
|
||||
|
||||
// We partition the following members into different cache lines
|
||||
// to avoid false sharing among Lookup, Release, Erase and Insert
|
||||
// operations in ClockCacheShard.
|
||||
@@ -555,11 +563,11 @@ class HyperClockTable {
|
||||
// Number of elements in the table.
|
||||
std::atomic<size_t> occupancy_{};
|
||||
|
||||
// Memory usage by entries tracked by the cache (including detached)
|
||||
// Memory usage by entries tracked by the cache (including standalone)
|
||||
std::atomic<size_t> usage_{};
|
||||
|
||||
// Part of usage by detached entries (not in table)
|
||||
std::atomic<size_t> detached_usage_{};
|
||||
// Part of usage by standalone entries (not in table)
|
||||
std::atomic<size_t> standalone_usage_{};
|
||||
}; // class HyperClockTable
|
||||
|
||||
// A single shard of sharded cache.
|
||||
@@ -568,7 +576,9 @@ class ALIGN_AS(CACHE_LINE_SIZE) ClockCacheShard final : public CacheShardBase {
|
||||
public:
|
||||
ClockCacheShard(size_t capacity, bool strict_capacity_limit,
|
||||
CacheMetadataChargePolicy metadata_charge_policy,
|
||||
MemoryAllocator* allocator, const typename Table::Opts& opts);
|
||||
MemoryAllocator* allocator,
|
||||
const Cache::EvictionCallback* eviction_callback,
|
||||
const uint32_t* hash_seed, const typename Table::Opts& opts);
|
||||
|
||||
// For CacheShard concept
|
||||
using HandleImpl = typename Table::HandleImpl;
|
||||
@@ -578,22 +588,23 @@ class ALIGN_AS(CACHE_LINE_SIZE) ClockCacheShard final : public CacheShardBase {
|
||||
static inline uint32_t HashPieceForSharding(HashCref hash) {
|
||||
return Upper32of64(hash[0]);
|
||||
}
|
||||
static inline HashVal ComputeHash(const Slice& key) {
|
||||
static inline HashVal ComputeHash(const Slice& key, uint32_t seed) {
|
||||
assert(key.size() == kCacheKeySize);
|
||||
HashVal in;
|
||||
HashVal out;
|
||||
// NOTE: endian dependence
|
||||
// TODO: use GetUnaligned?
|
||||
std::memcpy(&in, key.data(), kCacheKeySize);
|
||||
BijectiveHash2x64(in[1], in[0], &out[1], &out[0]);
|
||||
BijectiveHash2x64(in[1], in[0] ^ seed, &out[1], &out[0]);
|
||||
return out;
|
||||
}
|
||||
|
||||
// For reconstructing key from hashed_key. Requires the caller to provide
|
||||
// backing storage for the Slice in `unhashed`
|
||||
static inline Slice ReverseHash(const UniqueId64x2& hashed,
|
||||
UniqueId64x2* unhashed) {
|
||||
UniqueId64x2* unhashed, uint32_t seed) {
|
||||
BijectiveUnhash2x64(hashed[1], hashed[0], &(*unhashed)[1], &(*unhashed)[0]);
|
||||
(*unhashed)[0] ^= seed;
|
||||
// NOTE: endian dependence
|
||||
return Slice(reinterpret_cast<const char*>(unhashed), kCacheKeySize);
|
||||
}
|
||||
@@ -609,6 +620,11 @@ class ALIGN_AS(CACHE_LINE_SIZE) ClockCacheShard final : public CacheShardBase {
|
||||
Cache::ObjectPtr value, const Cache::CacheItemHelper* helper,
|
||||
size_t charge, HandleImpl** handle, Cache::Priority priority);
|
||||
|
||||
HandleImpl* CreateStandalone(const Slice& key, const UniqueId64x2& hashed_key,
|
||||
Cache::ObjectPtr obj,
|
||||
const Cache::CacheItemHelper* helper,
|
||||
size_t charge, bool allow_uncharged);
|
||||
|
||||
HandleImpl* Lookup(const Slice& key, const UniqueId64x2& hashed_key);
|
||||
|
||||
bool Release(HandleImpl* handle, bool useful, bool erase_if_last_ref);
|
||||
@@ -623,7 +639,7 @@ class ALIGN_AS(CACHE_LINE_SIZE) ClockCacheShard final : public CacheShardBase {
|
||||
|
||||
size_t GetUsage() const;
|
||||
|
||||
size_t GetDetachedUsage() const;
|
||||
size_t GetStandaloneUsage() const;
|
||||
|
||||
size_t GetPinnedUsage() const;
|
||||
|
||||
@@ -646,15 +662,10 @@ class ALIGN_AS(CACHE_LINE_SIZE) ClockCacheShard final : public CacheShardBase {
|
||||
HandleImpl* Lookup(const Slice& key, const UniqueId64x2& hashed_key,
|
||||
const Cache::CacheItemHelper* /*helper*/,
|
||||
Cache::CreateContext* /*create_context*/,
|
||||
Cache::Priority /*priority*/, bool /*wait*/,
|
||||
Statistics* /*stats*/) {
|
||||
Cache::Priority /*priority*/, Statistics* /*stats*/) {
|
||||
return Lookup(key, hashed_key);
|
||||
}
|
||||
|
||||
bool IsReady(HandleImpl* /*handle*/) { return true; }
|
||||
|
||||
void Wait(HandleImpl* /*handle*/) {}
|
||||
|
||||
// Acquire/release N references
|
||||
void TEST_RefN(HandleImpl* handle, size_t n);
|
||||
void TEST_ReleaseN(HandleImpl* handle, size_t n);
|
||||
@@ -677,10 +688,7 @@ class HyperClockCache
|
||||
public:
|
||||
using Shard = ClockCacheShard<HyperClockTable>;
|
||||
|
||||
HyperClockCache(size_t capacity, size_t estimated_value_size,
|
||||
int num_shard_bits, bool strict_capacity_limit,
|
||||
CacheMetadataChargePolicy metadata_charge_policy,
|
||||
std::shared_ptr<MemoryAllocator> memory_allocator);
|
||||
explicit HyperClockCache(const HyperClockCacheOptions& opts);
|
||||
|
||||
const char* Name() const override { return "HyperClockCache"; }
|
||||
|
||||
|
||||
Vendored
+13
-46
@@ -9,7 +9,7 @@
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
|
||||
#include "memory/memory_allocator.h"
|
||||
#include "memory/memory_allocator_impl.h"
|
||||
#include "monitoring/perf_context_imp.h"
|
||||
#include "util/compression.h"
|
||||
#include "util/string_util.h"
|
||||
@@ -17,32 +17,18 @@
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
CompressedSecondaryCache::CompressedSecondaryCache(
|
||||
size_t capacity, int num_shard_bits, bool strict_capacity_limit,
|
||||
double high_pri_pool_ratio, double low_pri_pool_ratio,
|
||||
std::shared_ptr<MemoryAllocator> memory_allocator, bool use_adaptive_mutex,
|
||||
CacheMetadataChargePolicy metadata_charge_policy,
|
||||
CompressionType compression_type, uint32_t compress_format_version,
|
||||
bool enable_custom_split_merge)
|
||||
: cache_options_(capacity, num_shard_bits, strict_capacity_limit,
|
||||
high_pri_pool_ratio, low_pri_pool_ratio, memory_allocator,
|
||||
use_adaptive_mutex, metadata_charge_policy,
|
||||
compression_type, compress_format_version,
|
||||
enable_custom_split_merge) {
|
||||
cache_ =
|
||||
NewLRUCache(capacity, num_shard_bits, strict_capacity_limit,
|
||||
high_pri_pool_ratio, memory_allocator, use_adaptive_mutex,
|
||||
metadata_charge_policy, low_pri_pool_ratio);
|
||||
}
|
||||
const CompressedSecondaryCacheOptions& opts)
|
||||
: cache_(opts.LRUCacheOptions::MakeSharedCache()), cache_options_(opts) {}
|
||||
|
||||
CompressedSecondaryCache::~CompressedSecondaryCache() { cache_.reset(); }
|
||||
|
||||
std::unique_ptr<SecondaryCacheResultHandle> CompressedSecondaryCache::Lookup(
|
||||
const Slice& key, const Cache::CacheItemHelper* helper,
|
||||
Cache::CreateContext* create_context, bool /*wait*/, bool advise_erase,
|
||||
bool& is_in_sec_cache) {
|
||||
bool& kept_in_sec_cache) {
|
||||
assert(helper);
|
||||
std::unique_ptr<SecondaryCacheResultHandle> handle;
|
||||
is_in_sec_cache = false;
|
||||
kept_in_sec_cache = false;
|
||||
Cache::Handle* lru_handle = cache_->Lookup(key);
|
||||
if (lru_handle == nullptr) {
|
||||
return nullptr;
|
||||
@@ -71,7 +57,8 @@ std::unique_ptr<SecondaryCacheResultHandle> CompressedSecondaryCache::Lookup(
|
||||
Status s;
|
||||
Cache::ObjectPtr value{nullptr};
|
||||
size_t charge{0};
|
||||
if (cache_options_.compression_type == kNoCompression) {
|
||||
if (cache_options_.compression_type == kNoCompression ||
|
||||
cache_options_.do_not_compress_roles.Contains(helper->role)) {
|
||||
s = helper->create_cb(Slice(ptr->get(), handle_value_charge),
|
||||
create_context, allocator, &value, &charge);
|
||||
} else {
|
||||
@@ -107,7 +94,7 @@ std::unique_ptr<SecondaryCacheResultHandle> CompressedSecondaryCache::Lookup(
|
||||
/*charge=*/0)
|
||||
.PermitUncheckedError();
|
||||
} else {
|
||||
is_in_sec_cache = true;
|
||||
kept_in_sec_cache = true;
|
||||
cache_->Release(lru_handle, /*erase_if_last_ref=*/false);
|
||||
}
|
||||
handle.reset(new CompressedSecondaryCacheResultHandle(value, charge));
|
||||
@@ -143,7 +130,8 @@ Status CompressedSecondaryCache::Insert(const Slice& key,
|
||||
Slice val(ptr.get(), size);
|
||||
|
||||
std::string compressed_val;
|
||||
if (cache_options_.compression_type != kNoCompression) {
|
||||
if (cache_options_.compression_type != kNoCompression &&
|
||||
!cache_options_.do_not_compress_roles.Contains(helper->role)) {
|
||||
PERF_COUNTER_ADD(compressed_sec_cache_uncompressed_bytes, size);
|
||||
CompressionOptions compression_opts;
|
||||
CompressionContext compression_context(cache_options_.compression_type);
|
||||
@@ -308,30 +296,9 @@ const Cache::CacheItemHelper* CompressedSecondaryCache::GetHelper(
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<SecondaryCache> NewCompressedSecondaryCache(
|
||||
size_t capacity, int num_shard_bits, bool strict_capacity_limit,
|
||||
double high_pri_pool_ratio, double low_pri_pool_ratio,
|
||||
std::shared_ptr<MemoryAllocator> memory_allocator, bool use_adaptive_mutex,
|
||||
CacheMetadataChargePolicy metadata_charge_policy,
|
||||
CompressionType compression_type, uint32_t compress_format_version,
|
||||
bool enable_custom_split_merge) {
|
||||
return std::make_shared<CompressedSecondaryCache>(
|
||||
capacity, num_shard_bits, strict_capacity_limit, high_pri_pool_ratio,
|
||||
low_pri_pool_ratio, memory_allocator, use_adaptive_mutex,
|
||||
metadata_charge_policy, compression_type, compress_format_version,
|
||||
enable_custom_split_merge);
|
||||
}
|
||||
|
||||
std::shared_ptr<SecondaryCache> NewCompressedSecondaryCache(
|
||||
const CompressedSecondaryCacheOptions& opts) {
|
||||
// The secondary_cache is disabled for this LRUCache instance.
|
||||
assert(opts.secondary_cache == nullptr);
|
||||
return NewCompressedSecondaryCache(
|
||||
opts.capacity, opts.num_shard_bits, opts.strict_capacity_limit,
|
||||
opts.high_pri_pool_ratio, opts.low_pri_pool_ratio, opts.memory_allocator,
|
||||
opts.use_adaptive_mutex, opts.metadata_charge_policy,
|
||||
opts.compression_type, opts.compress_format_version,
|
||||
opts.enable_custom_split_merge);
|
||||
std::shared_ptr<SecondaryCache>
|
||||
CompressedSecondaryCacheOptions::MakeSharedSecondaryCache() const {
|
||||
return std::make_shared<CompressedSecondaryCache>(*this);
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
Vendored
+5
-13
@@ -10,7 +10,7 @@
|
||||
#include <memory>
|
||||
|
||||
#include "cache/lru_cache.h"
|
||||
#include "memory/memory_allocator.h"
|
||||
#include "memory/memory_allocator_impl.h"
|
||||
#include "rocksdb/secondary_cache.h"
|
||||
#include "rocksdb/slice.h"
|
||||
#include "rocksdb/status.h"
|
||||
@@ -69,16 +69,8 @@ class CompressedSecondaryCacheResultHandle : public SecondaryCacheResultHandle {
|
||||
|
||||
class CompressedSecondaryCache : public SecondaryCache {
|
||||
public:
|
||||
CompressedSecondaryCache(
|
||||
size_t capacity, int num_shard_bits, bool strict_capacity_limit,
|
||||
double high_pri_pool_ratio, double low_pri_pool_ratio,
|
||||
std::shared_ptr<MemoryAllocator> memory_allocator = nullptr,
|
||||
bool use_adaptive_mutex = kDefaultToAdaptiveMutex,
|
||||
CacheMetadataChargePolicy metadata_charge_policy =
|
||||
kDefaultCacheMetadataChargePolicy,
|
||||
CompressionType compression_type = CompressionType::kLZ4Compression,
|
||||
uint32_t compress_format_version = 2,
|
||||
bool enable_custom_split_merge = false);
|
||||
explicit CompressedSecondaryCache(
|
||||
const CompressedSecondaryCacheOptions& opts);
|
||||
~CompressedSecondaryCache() override;
|
||||
|
||||
const char* Name() const override { return "CompressedSecondaryCache"; }
|
||||
@@ -89,7 +81,7 @@ class CompressedSecondaryCache : public SecondaryCache {
|
||||
std::unique_ptr<SecondaryCacheResultHandle> Lookup(
|
||||
const Slice& key, const Cache::CacheItemHelper* helper,
|
||||
Cache::CreateContext* create_context, bool /*wait*/, bool advise_erase,
|
||||
bool& is_in_sec_cache) override;
|
||||
bool& kept_in_sec_cache) override;
|
||||
|
||||
bool SupportForceErase() const override { return true; }
|
||||
|
||||
@@ -104,7 +96,7 @@ class CompressedSecondaryCache : public SecondaryCache {
|
||||
std::string GetPrintableOptions() const override;
|
||||
|
||||
private:
|
||||
friend class CompressedSecondaryCacheTest;
|
||||
friend class CompressedSecondaryCacheTestBase;
|
||||
static constexpr std::array<uint16_t, 8> malloc_bin_sizes_{
|
||||
128, 256, 512, 1024, 2048, 4096, 8192, 16384};
|
||||
|
||||
|
||||
+224
-192
@@ -5,93 +5,43 @@
|
||||
|
||||
#include "cache/compressed_secondary_cache.h"
|
||||
|
||||
#include <array>
|
||||
#include <iterator>
|
||||
#include <memory>
|
||||
#include <tuple>
|
||||
|
||||
#include "memory/jemalloc_nodump_allocator.h"
|
||||
#include "rocksdb/convenience.h"
|
||||
#include "test_util/secondary_cache_test_util.h"
|
||||
#include "test_util/testharness.h"
|
||||
#include "test_util/testutil.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
class CompressedSecondaryCacheTest : public testing::Test,
|
||||
public Cache::CreateContext {
|
||||
using secondary_cache_test_util::GetTestingCacheTypes;
|
||||
using secondary_cache_test_util::WithCacheType;
|
||||
|
||||
// 16 bytes for HCC compatibility
|
||||
const std::string key0 = "____ ____key0";
|
||||
const std::string key1 = "____ ____key1";
|
||||
const std::string key2 = "____ ____key2";
|
||||
const std::string key3 = "____ ____key3";
|
||||
|
||||
class CompressedSecondaryCacheTestBase : public testing::Test,
|
||||
public WithCacheType {
|
||||
public:
|
||||
CompressedSecondaryCacheTest() : fail_create_(false) {}
|
||||
~CompressedSecondaryCacheTest() override = default;
|
||||
CompressedSecondaryCacheTestBase() {}
|
||||
~CompressedSecondaryCacheTestBase() override = default;
|
||||
|
||||
protected:
|
||||
class TestItem {
|
||||
public:
|
||||
TestItem(const char* buf, size_t size) : buf_(new char[size]), size_(size) {
|
||||
memcpy(buf_.get(), buf, size);
|
||||
}
|
||||
~TestItem() = default;
|
||||
|
||||
char* Buf() { return buf_.get(); }
|
||||
[[nodiscard]] size_t Size() const { return size_; }
|
||||
|
||||
private:
|
||||
std::unique_ptr<char[]> buf_;
|
||||
size_t size_;
|
||||
};
|
||||
|
||||
static size_t SizeCallback(Cache::ObjectPtr obj) {
|
||||
return static_cast<TestItem*>(obj)->Size();
|
||||
}
|
||||
|
||||
static Status SaveToCallback(Cache::ObjectPtr from_obj, size_t from_offset,
|
||||
size_t length, char* out) {
|
||||
auto item = static_cast<TestItem*>(from_obj);
|
||||
const char* buf = item->Buf();
|
||||
EXPECT_EQ(length, item->Size());
|
||||
EXPECT_EQ(from_offset, 0);
|
||||
memcpy(out, buf, length);
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
static void DeletionCallback(Cache::ObjectPtr obj,
|
||||
MemoryAllocator* /*alloc*/) {
|
||||
delete static_cast<TestItem*>(obj);
|
||||
obj = nullptr;
|
||||
}
|
||||
|
||||
static Status SaveToCallbackFail(Cache::ObjectPtr /*obj*/, size_t /*offset*/,
|
||||
size_t /*size*/, char* /*out*/) {
|
||||
return Status::NotSupported();
|
||||
}
|
||||
|
||||
static Status CreateCallback(const Slice& data, Cache::CreateContext* context,
|
||||
MemoryAllocator* /*allocator*/,
|
||||
Cache::ObjectPtr* out_obj, size_t* out_charge) {
|
||||
auto t = static_cast<CompressedSecondaryCacheTest*>(context);
|
||||
if (t->fail_create_) {
|
||||
return Status::NotSupported();
|
||||
}
|
||||
*out_obj = new TestItem(data.data(), data.size());
|
||||
*out_charge = data.size();
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
static constexpr Cache::CacheItemHelper kHelper{
|
||||
CacheEntryRole::kMisc, &DeletionCallback, &SizeCallback, &SaveToCallback,
|
||||
&CreateCallback};
|
||||
|
||||
static constexpr Cache::CacheItemHelper kHelperFail{
|
||||
CacheEntryRole::kMisc, &DeletionCallback, &SizeCallback,
|
||||
&SaveToCallbackFail, &CreateCallback};
|
||||
|
||||
void SetFailCreate(bool fail) { fail_create_ = fail; }
|
||||
|
||||
void BasicTestHelper(std::shared_ptr<SecondaryCache> sec_cache,
|
||||
bool sec_cache_is_compressed) {
|
||||
get_perf_context()->Reset();
|
||||
bool is_in_sec_cache{true};
|
||||
bool kept_in_sec_cache{true};
|
||||
// Lookup an non-existent key.
|
||||
std::unique_ptr<SecondaryCacheResultHandle> handle0 = sec_cache->Lookup(
|
||||
"k0", &kHelper, this, true, /*advise_erase=*/true, is_in_sec_cache);
|
||||
std::unique_ptr<SecondaryCacheResultHandle> handle0 =
|
||||
sec_cache->Lookup(key0, GetHelper(), this, true, /*advise_erase=*/true,
|
||||
kept_in_sec_cache);
|
||||
ASSERT_EQ(handle0, nullptr);
|
||||
|
||||
Random rnd(301);
|
||||
@@ -99,23 +49,25 @@ class CompressedSecondaryCacheTest : public testing::Test,
|
||||
std::string str1(rnd.RandomString(1000));
|
||||
TestItem item1(str1.data(), str1.length());
|
||||
// A dummy handle is inserted if the item is inserted for the first time.
|
||||
ASSERT_OK(sec_cache->Insert("k1", &item1, &kHelper));
|
||||
ASSERT_OK(sec_cache->Insert(key1, &item1, GetHelper()));
|
||||
ASSERT_EQ(get_perf_context()->compressed_sec_cache_insert_dummy_count, 1);
|
||||
ASSERT_EQ(get_perf_context()->compressed_sec_cache_uncompressed_bytes, 0);
|
||||
ASSERT_EQ(get_perf_context()->compressed_sec_cache_compressed_bytes, 0);
|
||||
|
||||
std::unique_ptr<SecondaryCacheResultHandle> handle1_1 = sec_cache->Lookup(
|
||||
"k1", &kHelper, this, true, /*advise_erase=*/false, is_in_sec_cache);
|
||||
std::unique_ptr<SecondaryCacheResultHandle> handle1_1 =
|
||||
sec_cache->Lookup(key1, GetHelper(), this, true, /*advise_erase=*/false,
|
||||
kept_in_sec_cache);
|
||||
ASSERT_EQ(handle1_1, nullptr);
|
||||
|
||||
// Insert and Lookup the item k1 for the second time and advise erasing it.
|
||||
ASSERT_OK(sec_cache->Insert("k1", &item1, &kHelper));
|
||||
ASSERT_OK(sec_cache->Insert(key1, &item1, GetHelper()));
|
||||
ASSERT_EQ(get_perf_context()->compressed_sec_cache_insert_real_count, 1);
|
||||
|
||||
std::unique_ptr<SecondaryCacheResultHandle> handle1_2 = sec_cache->Lookup(
|
||||
"k1", &kHelper, this, true, /*advise_erase=*/true, is_in_sec_cache);
|
||||
std::unique_ptr<SecondaryCacheResultHandle> handle1_2 =
|
||||
sec_cache->Lookup(key1, GetHelper(), this, true, /*advise_erase=*/true,
|
||||
kept_in_sec_cache);
|
||||
ASSERT_NE(handle1_2, nullptr);
|
||||
ASSERT_FALSE(is_in_sec_cache);
|
||||
ASSERT_FALSE(kept_in_sec_cache);
|
||||
if (sec_cache_is_compressed) {
|
||||
ASSERT_EQ(get_perf_context()->compressed_sec_cache_uncompressed_bytes,
|
||||
1000);
|
||||
@@ -132,20 +84,22 @@ class CompressedSecondaryCacheTest : public testing::Test,
|
||||
ASSERT_EQ(memcmp(val1->Buf(), item1.Buf(), item1.Size()), 0);
|
||||
|
||||
// Lookup the item k1 again.
|
||||
std::unique_ptr<SecondaryCacheResultHandle> handle1_3 = sec_cache->Lookup(
|
||||
"k1", &kHelper, this, true, /*advise_erase=*/true, is_in_sec_cache);
|
||||
std::unique_ptr<SecondaryCacheResultHandle> handle1_3 =
|
||||
sec_cache->Lookup(key1, GetHelper(), this, true, /*advise_erase=*/true,
|
||||
kept_in_sec_cache);
|
||||
ASSERT_EQ(handle1_3, nullptr);
|
||||
|
||||
// Insert and Lookup the item k2.
|
||||
std::string str2(rnd.RandomString(1000));
|
||||
TestItem item2(str2.data(), str2.length());
|
||||
ASSERT_OK(sec_cache->Insert("k2", &item2, &kHelper));
|
||||
ASSERT_OK(sec_cache->Insert(key2, &item2, GetHelper()));
|
||||
ASSERT_EQ(get_perf_context()->compressed_sec_cache_insert_dummy_count, 2);
|
||||
std::unique_ptr<SecondaryCacheResultHandle> handle2_1 = sec_cache->Lookup(
|
||||
"k2", &kHelper, this, true, /*advise_erase=*/false, is_in_sec_cache);
|
||||
std::unique_ptr<SecondaryCacheResultHandle> handle2_1 =
|
||||
sec_cache->Lookup(key2, GetHelper(), this, true, /*advise_erase=*/false,
|
||||
kept_in_sec_cache);
|
||||
ASSERT_EQ(handle2_1, nullptr);
|
||||
|
||||
ASSERT_OK(sec_cache->Insert("k2", &item2, &kHelper));
|
||||
ASSERT_OK(sec_cache->Insert(key2, &item2, GetHelper()));
|
||||
ASSERT_EQ(get_perf_context()->compressed_sec_cache_insert_real_count, 2);
|
||||
if (sec_cache_is_compressed) {
|
||||
ASSERT_EQ(get_perf_context()->compressed_sec_cache_uncompressed_bytes,
|
||||
@@ -156,8 +110,9 @@ class CompressedSecondaryCacheTest : public testing::Test,
|
||||
ASSERT_EQ(get_perf_context()->compressed_sec_cache_uncompressed_bytes, 0);
|
||||
ASSERT_EQ(get_perf_context()->compressed_sec_cache_compressed_bytes, 0);
|
||||
}
|
||||
std::unique_ptr<SecondaryCacheResultHandle> handle2_2 = sec_cache->Lookup(
|
||||
"k2", &kHelper, this, true, /*advise_erase=*/false, is_in_sec_cache);
|
||||
std::unique_ptr<SecondaryCacheResultHandle> handle2_2 =
|
||||
sec_cache->Lookup(key2, GetHelper(), this, true, /*advise_erase=*/false,
|
||||
kept_in_sec_cache);
|
||||
ASSERT_NE(handle2_2, nullptr);
|
||||
std::unique_ptr<TestItem> val2 =
|
||||
std::unique_ptr<TestItem>(static_cast<TestItem*>(handle2_2->Value()));
|
||||
@@ -226,24 +181,26 @@ class CompressedSecondaryCacheTest : public testing::Test,
|
||||
std::string str1(rnd.RandomString(1000));
|
||||
TestItem item1(str1.data(), str1.length());
|
||||
// Insert a dummy handle.
|
||||
ASSERT_OK(sec_cache->Insert("k1", &item1, &kHelper));
|
||||
ASSERT_OK(sec_cache->Insert(key1, &item1, GetHelper()));
|
||||
// Insert k1.
|
||||
ASSERT_OK(sec_cache->Insert("k1", &item1, &kHelper));
|
||||
ASSERT_OK(sec_cache->Insert(key1, &item1, GetHelper()));
|
||||
|
||||
// Insert and Lookup the second item.
|
||||
std::string str2(rnd.RandomString(200));
|
||||
TestItem item2(str2.data(), str2.length());
|
||||
// Insert a dummy handle, k1 is not evicted.
|
||||
ASSERT_OK(sec_cache->Insert("k2", &item2, &kHelper));
|
||||
bool is_in_sec_cache{false};
|
||||
std::unique_ptr<SecondaryCacheResultHandle> handle1 = sec_cache->Lookup(
|
||||
"k1", &kHelper, this, true, /*advise_erase=*/false, is_in_sec_cache);
|
||||
ASSERT_OK(sec_cache->Insert(key2, &item2, GetHelper()));
|
||||
bool kept_in_sec_cache{false};
|
||||
std::unique_ptr<SecondaryCacheResultHandle> handle1 =
|
||||
sec_cache->Lookup(key1, GetHelper(), this, true, /*advise_erase=*/false,
|
||||
kept_in_sec_cache);
|
||||
ASSERT_EQ(handle1, nullptr);
|
||||
|
||||
// Insert k2 and k1 is evicted.
|
||||
ASSERT_OK(sec_cache->Insert("k2", &item2, &kHelper));
|
||||
std::unique_ptr<SecondaryCacheResultHandle> handle2 = sec_cache->Lookup(
|
||||
"k2", &kHelper, this, true, /*advise_erase=*/false, is_in_sec_cache);
|
||||
ASSERT_OK(sec_cache->Insert(key2, &item2, GetHelper()));
|
||||
std::unique_ptr<SecondaryCacheResultHandle> handle2 =
|
||||
sec_cache->Lookup(key2, GetHelper(), this, true, /*advise_erase=*/false,
|
||||
kept_in_sec_cache);
|
||||
ASSERT_NE(handle2, nullptr);
|
||||
std::unique_ptr<TestItem> val2 =
|
||||
std::unique_ptr<TestItem>(static_cast<TestItem*>(handle2->Value()));
|
||||
@@ -251,24 +208,26 @@ class CompressedSecondaryCacheTest : public testing::Test,
|
||||
ASSERT_EQ(memcmp(val2->Buf(), item2.Buf(), item2.Size()), 0);
|
||||
|
||||
// Insert k1 again and a dummy handle is inserted.
|
||||
ASSERT_OK(sec_cache->Insert("k1", &item1, &kHelper));
|
||||
ASSERT_OK(sec_cache->Insert(key1, &item1, GetHelper()));
|
||||
|
||||
std::unique_ptr<SecondaryCacheResultHandle> handle1_1 = sec_cache->Lookup(
|
||||
"k1", &kHelper, this, true, /*advise_erase=*/false, is_in_sec_cache);
|
||||
std::unique_ptr<SecondaryCacheResultHandle> handle1_1 =
|
||||
sec_cache->Lookup(key1, GetHelper(), this, true, /*advise_erase=*/false,
|
||||
kept_in_sec_cache);
|
||||
ASSERT_EQ(handle1_1, nullptr);
|
||||
|
||||
// Create Fails.
|
||||
SetFailCreate(true);
|
||||
std::unique_ptr<SecondaryCacheResultHandle> handle2_1 = sec_cache->Lookup(
|
||||
"k2", &kHelper, this, true, /*advise_erase=*/true, is_in_sec_cache);
|
||||
std::unique_ptr<SecondaryCacheResultHandle> handle2_1 =
|
||||
sec_cache->Lookup(key2, GetHelper(), this, true, /*advise_erase=*/true,
|
||||
kept_in_sec_cache);
|
||||
ASSERT_EQ(handle2_1, nullptr);
|
||||
|
||||
// Save Fails.
|
||||
std::string str3 = rnd.RandomString(10);
|
||||
TestItem item3(str3.data(), str3.length());
|
||||
// The Status is OK because a dummy handle is inserted.
|
||||
ASSERT_OK(sec_cache->Insert("k3", &item3, &kHelperFail));
|
||||
ASSERT_NOK(sec_cache->Insert("k3", &item3, &kHelperFail));
|
||||
ASSERT_OK(sec_cache->Insert(key3, &item3, GetHelperFail()));
|
||||
ASSERT_NOK(sec_cache->Insert(key3, &item3, GetHelperFail()));
|
||||
|
||||
sec_cache.reset();
|
||||
}
|
||||
@@ -292,26 +251,22 @@ class CompressedSecondaryCacheTest : public testing::Test,
|
||||
secondary_cache_opts.enable_custom_split_merge = enable_custom_split_merge;
|
||||
std::shared_ptr<SecondaryCache> secondary_cache =
|
||||
NewCompressedSecondaryCache(secondary_cache_opts);
|
||||
LRUCacheOptions lru_cache_opts(
|
||||
std::shared_ptr<Cache> cache = NewCache(
|
||||
/*_capacity =*/1300, /*_num_shard_bits =*/0,
|
||||
/*_strict_capacity_limit =*/false, /*_high_pri_pool_ratio =*/0.5,
|
||||
/*_memory_allocator =*/nullptr, kDefaultToAdaptiveMutex,
|
||||
kDefaultCacheMetadataChargePolicy, /*_low_pri_pool_ratio =*/0.0);
|
||||
lru_cache_opts.secondary_cache = secondary_cache;
|
||||
std::shared_ptr<Cache> cache = NewLRUCache(lru_cache_opts);
|
||||
/*_strict_capacity_limit =*/true, secondary_cache);
|
||||
std::shared_ptr<Statistics> stats = CreateDBStatistics();
|
||||
|
||||
get_perf_context()->Reset();
|
||||
Random rnd(301);
|
||||
std::string str1 = rnd.RandomString(1001);
|
||||
auto item1_1 = new TestItem(str1.data(), str1.length());
|
||||
ASSERT_OK(cache->Insert("k1", item1_1, &kHelper, str1.length()));
|
||||
ASSERT_OK(cache->Insert(key1, item1_1, GetHelper(), str1.length()));
|
||||
|
||||
std::string str2 = rnd.RandomString(1012);
|
||||
auto item2_1 = new TestItem(str2.data(), str2.length());
|
||||
// After this Insert, primary cache contains k2 and secondary cache contains
|
||||
// k1's dummy item.
|
||||
ASSERT_OK(cache->Insert("k2", item2_1, &kHelper, str2.length()));
|
||||
ASSERT_OK(cache->Insert(key2, item2_1, GetHelper(), str2.length()));
|
||||
ASSERT_EQ(get_perf_context()->compressed_sec_cache_insert_dummy_count, 1);
|
||||
ASSERT_EQ(get_perf_context()->compressed_sec_cache_uncompressed_bytes, 0);
|
||||
ASSERT_EQ(get_perf_context()->compressed_sec_cache_compressed_bytes, 0);
|
||||
@@ -320,19 +275,19 @@ class CompressedSecondaryCacheTest : public testing::Test,
|
||||
auto item3_1 = new TestItem(str3.data(), str3.length());
|
||||
// After this Insert, primary cache contains k3 and secondary cache contains
|
||||
// k1's dummy item and k2's dummy item.
|
||||
ASSERT_OK(cache->Insert("k3", item3_1, &kHelper, str3.length()));
|
||||
ASSERT_OK(cache->Insert(key3, item3_1, GetHelper(), str3.length()));
|
||||
ASSERT_EQ(get_perf_context()->compressed_sec_cache_insert_dummy_count, 2);
|
||||
|
||||
// After this Insert, primary cache contains k1 and secondary cache contains
|
||||
// k1's dummy item, k2's dummy item, and k3's dummy item.
|
||||
auto item1_2 = new TestItem(str1.data(), str1.length());
|
||||
ASSERT_OK(cache->Insert("k1", item1_2, &kHelper, str1.length()));
|
||||
ASSERT_OK(cache->Insert(key1, item1_2, GetHelper(), str1.length()));
|
||||
ASSERT_EQ(get_perf_context()->compressed_sec_cache_insert_dummy_count, 3);
|
||||
|
||||
// After this Insert, primary cache contains k2 and secondary cache contains
|
||||
// k1's item, k2's dummy item, and k3's dummy item.
|
||||
auto item2_2 = new TestItem(str2.data(), str2.length());
|
||||
ASSERT_OK(cache->Insert("k2", item2_2, &kHelper, str2.length()));
|
||||
ASSERT_OK(cache->Insert(key2, item2_2, GetHelper(), str2.length()));
|
||||
ASSERT_EQ(get_perf_context()->compressed_sec_cache_insert_real_count, 1);
|
||||
if (sec_cache_is_compressed) {
|
||||
ASSERT_EQ(get_perf_context()->compressed_sec_cache_uncompressed_bytes,
|
||||
@@ -347,7 +302,7 @@ class CompressedSecondaryCacheTest : public testing::Test,
|
||||
// After this Insert, primary cache contains k3 and secondary cache contains
|
||||
// k1's item and k2's item.
|
||||
auto item3_2 = new TestItem(str3.data(), str3.length());
|
||||
ASSERT_OK(cache->Insert("k3", item3_2, &kHelper, str3.length()));
|
||||
ASSERT_OK(cache->Insert(key3, item3_2, GetHelper(), str3.length()));
|
||||
ASSERT_EQ(get_perf_context()->compressed_sec_cache_insert_real_count, 2);
|
||||
if (sec_cache_is_compressed) {
|
||||
ASSERT_EQ(get_perf_context()->compressed_sec_cache_uncompressed_bytes,
|
||||
@@ -360,7 +315,7 @@ class CompressedSecondaryCacheTest : public testing::Test,
|
||||
}
|
||||
|
||||
Cache::Handle* handle;
|
||||
handle = cache->Lookup("k3", &kHelper, this, Cache::Priority::LOW, true,
|
||||
handle = cache->Lookup(key3, GetHelper(), this, Cache::Priority::LOW,
|
||||
stats.get());
|
||||
ASSERT_NE(handle, nullptr);
|
||||
auto val3 = static_cast<TestItem*>(cache->Value(handle));
|
||||
@@ -369,13 +324,13 @@ class CompressedSecondaryCacheTest : public testing::Test,
|
||||
cache->Release(handle);
|
||||
|
||||
// Lookup an non-existent key.
|
||||
handle = cache->Lookup("k0", &kHelper, this, Cache::Priority::LOW, true,
|
||||
handle = cache->Lookup(key0, GetHelper(), this, Cache::Priority::LOW,
|
||||
stats.get());
|
||||
ASSERT_EQ(handle, nullptr);
|
||||
|
||||
// This Lookup should just insert a dummy handle in the primary cache
|
||||
// and the k1 is still in the secondary cache.
|
||||
handle = cache->Lookup("k1", &kHelper, this, Cache::Priority::LOW, true,
|
||||
handle = cache->Lookup(key1, GetHelper(), this, Cache::Priority::LOW,
|
||||
stats.get());
|
||||
ASSERT_NE(handle, nullptr);
|
||||
ASSERT_EQ(get_perf_context()->block_cache_standalone_handle_count, 1);
|
||||
@@ -387,7 +342,7 @@ class CompressedSecondaryCacheTest : public testing::Test,
|
||||
// This Lookup should erase k1 from the secondary cache and insert
|
||||
// it into primary cache; then k3 is demoted.
|
||||
// k2 and k3 are in secondary cache.
|
||||
handle = cache->Lookup("k1", &kHelper, this, Cache::Priority::LOW, true,
|
||||
handle = cache->Lookup(key1, GetHelper(), this, Cache::Priority::LOW,
|
||||
stats.get());
|
||||
ASSERT_NE(handle, nullptr);
|
||||
ASSERT_EQ(get_perf_context()->block_cache_standalone_handle_count, 1);
|
||||
@@ -395,7 +350,7 @@ class CompressedSecondaryCacheTest : public testing::Test,
|
||||
cache->Release(handle);
|
||||
|
||||
// k2 is still in secondary cache.
|
||||
handle = cache->Lookup("k2", &kHelper, this, Cache::Priority::LOW, true,
|
||||
handle = cache->Lookup(key2, GetHelper(), this, Cache::Priority::LOW,
|
||||
stats.get());
|
||||
ASSERT_NE(handle, nullptr);
|
||||
ASSERT_EQ(get_perf_context()->block_cache_standalone_handle_count, 2);
|
||||
@@ -403,7 +358,7 @@ class CompressedSecondaryCacheTest : public testing::Test,
|
||||
|
||||
// Testing SetCapacity().
|
||||
ASSERT_OK(secondary_cache->SetCapacity(0));
|
||||
handle = cache->Lookup("k3", &kHelper, this, Cache::Priority::LOW, true,
|
||||
handle = cache->Lookup(key3, GetHelper(), this, Cache::Priority::LOW,
|
||||
stats.get());
|
||||
ASSERT_EQ(handle, nullptr);
|
||||
|
||||
@@ -413,30 +368,30 @@ class CompressedSecondaryCacheTest : public testing::Test,
|
||||
ASSERT_EQ(capacity, 7000);
|
||||
auto item1_3 = new TestItem(str1.data(), str1.length());
|
||||
// After this Insert, primary cache contains k1.
|
||||
ASSERT_OK(cache->Insert("k1", item1_3, &kHelper, str2.length()));
|
||||
ASSERT_OK(cache->Insert(key1, item1_3, GetHelper(), str2.length()));
|
||||
ASSERT_EQ(get_perf_context()->compressed_sec_cache_insert_dummy_count, 3);
|
||||
ASSERT_EQ(get_perf_context()->compressed_sec_cache_insert_real_count, 4);
|
||||
|
||||
auto item2_3 = new TestItem(str2.data(), str2.length());
|
||||
// After this Insert, primary cache contains k2 and secondary cache contains
|
||||
// k1's dummy item.
|
||||
ASSERT_OK(cache->Insert("k2", item2_3, &kHelper, str1.length()));
|
||||
ASSERT_OK(cache->Insert(key2, item2_3, GetHelper(), str1.length()));
|
||||
ASSERT_EQ(get_perf_context()->compressed_sec_cache_insert_dummy_count, 4);
|
||||
|
||||
auto item1_4 = new TestItem(str1.data(), str1.length());
|
||||
// After this Insert, primary cache contains k1 and secondary cache contains
|
||||
// k1's dummy item and k2's dummy item.
|
||||
ASSERT_OK(cache->Insert("k1", item1_4, &kHelper, str2.length()));
|
||||
ASSERT_OK(cache->Insert(key1, item1_4, GetHelper(), str2.length()));
|
||||
ASSERT_EQ(get_perf_context()->compressed_sec_cache_insert_dummy_count, 5);
|
||||
|
||||
auto item2_4 = new TestItem(str2.data(), str2.length());
|
||||
// After this Insert, primary cache contains k2 and secondary cache contains
|
||||
// k1's real item and k2's dummy item.
|
||||
ASSERT_OK(cache->Insert("k2", item2_4, &kHelper, str2.length()));
|
||||
ASSERT_OK(cache->Insert(key2, item2_4, GetHelper(), str2.length()));
|
||||
ASSERT_EQ(get_perf_context()->compressed_sec_cache_insert_real_count, 5);
|
||||
// This Lookup should just insert a dummy handle in the primary cache
|
||||
// and the k1 is still in the secondary cache.
|
||||
handle = cache->Lookup("k1", &kHelper, this, Cache::Priority::LOW, true,
|
||||
handle = cache->Lookup(key1, GetHelper(), this, Cache::Priority::LOW,
|
||||
stats.get());
|
||||
|
||||
ASSERT_NE(handle, nullptr);
|
||||
@@ -464,26 +419,31 @@ class CompressedSecondaryCacheTest : public testing::Test,
|
||||
std::shared_ptr<SecondaryCache> secondary_cache =
|
||||
NewCompressedSecondaryCache(secondary_cache_opts);
|
||||
|
||||
LRUCacheOptions opts(
|
||||
std::shared_ptr<Cache> cache = NewCache(
|
||||
/*_capacity=*/1300, /*_num_shard_bits=*/0,
|
||||
/*_strict_capacity_limit=*/false, /*_high_pri_pool_ratio=*/0.5,
|
||||
/*_memory_allocator=*/nullptr, kDefaultToAdaptiveMutex,
|
||||
kDefaultCacheMetadataChargePolicy, /*_low_pri_pool_ratio=*/0.0);
|
||||
opts.secondary_cache = secondary_cache;
|
||||
std::shared_ptr<Cache> cache = NewLRUCache(opts);
|
||||
/*_strict_capacity_limit=*/false, secondary_cache);
|
||||
|
||||
Random rnd(301);
|
||||
std::string str1 = rnd.RandomString(1001);
|
||||
auto item1 = std::make_unique<TestItem>(str1.data(), str1.length());
|
||||
ASSERT_OK(cache->Insert("k1", item1.get(), &kHelper, str1.length()));
|
||||
ASSERT_OK(cache->Insert(key1, item1.get(), GetHelper(), str1.length()));
|
||||
item1.release(); // Appease clang-analyze "potential memory leak"
|
||||
|
||||
Cache::Handle* handle;
|
||||
handle = cache->Lookup("k2", nullptr, this, Cache::Priority::LOW, true);
|
||||
handle = cache->Lookup(key2, nullptr, this, Cache::Priority::LOW);
|
||||
ASSERT_EQ(handle, nullptr);
|
||||
handle = cache->Lookup("k2", &kHelper, this, Cache::Priority::LOW, false);
|
||||
handle = cache->Lookup(key2, GetHelper(), this, Cache::Priority::LOW);
|
||||
ASSERT_EQ(handle, nullptr);
|
||||
|
||||
Cache::AsyncLookupHandle ah;
|
||||
ah.key = key2;
|
||||
ah.helper = GetHelper();
|
||||
ah.create_context = this;
|
||||
ah.priority = Cache::Priority::LOW;
|
||||
cache->StartAsyncLookup(ah);
|
||||
cache->Wait(ah);
|
||||
ASSERT_EQ(ah.Result(), nullptr);
|
||||
|
||||
cache.reset();
|
||||
secondary_cache.reset();
|
||||
}
|
||||
@@ -506,36 +466,29 @@ class CompressedSecondaryCacheTest : public testing::Test,
|
||||
std::shared_ptr<SecondaryCache> secondary_cache =
|
||||
NewCompressedSecondaryCache(secondary_cache_opts);
|
||||
|
||||
LRUCacheOptions opts(
|
||||
std::shared_ptr<Cache> cache = NewCache(
|
||||
/*_capacity=*/1300, /*_num_shard_bits=*/0,
|
||||
/*_strict_capacity_limit=*/false, /*_high_pri_pool_ratio=*/0.5,
|
||||
/*_memory_allocator=*/nullptr, kDefaultToAdaptiveMutex,
|
||||
kDefaultCacheMetadataChargePolicy, /*_low_pri_pool_ratio=*/0.0);
|
||||
opts.secondary_cache = secondary_cache;
|
||||
std::shared_ptr<Cache> cache = NewLRUCache(opts);
|
||||
/*_strict_capacity_limit=*/true, secondary_cache);
|
||||
|
||||
Random rnd(301);
|
||||
std::string str1 = rnd.RandomString(1001);
|
||||
auto item1 = new TestItem(str1.data(), str1.length());
|
||||
ASSERT_OK(cache->Insert("k1", item1, &kHelperFail, str1.length()));
|
||||
ASSERT_OK(cache->Insert(key1, item1, GetHelperFail(), str1.length()));
|
||||
|
||||
std::string str2 = rnd.RandomString(1002);
|
||||
auto item2 = new TestItem(str2.data(), str2.length());
|
||||
// k1 should be demoted to the secondary cache.
|
||||
ASSERT_OK(cache->Insert("k2", item2, &kHelperFail, str2.length()));
|
||||
ASSERT_OK(cache->Insert(key2, item2, GetHelperFail(), str2.length()));
|
||||
|
||||
Cache::Handle* handle;
|
||||
handle =
|
||||
cache->Lookup("k2", &kHelperFail, this, Cache::Priority::LOW, true);
|
||||
handle = cache->Lookup(key2, GetHelperFail(), this, Cache::Priority::LOW);
|
||||
ASSERT_NE(handle, nullptr);
|
||||
cache->Release(handle);
|
||||
// This lookup should fail, since k1 demotion would have failed.
|
||||
handle =
|
||||
cache->Lookup("k1", &kHelperFail, this, Cache::Priority::LOW, true);
|
||||
handle = cache->Lookup(key1, GetHelperFail(), this, Cache::Priority::LOW);
|
||||
ASSERT_EQ(handle, nullptr);
|
||||
// Since k1 was not promoted, k2 should still be in cache.
|
||||
handle =
|
||||
cache->Lookup("k2", &kHelperFail, this, Cache::Priority::LOW, true);
|
||||
handle = cache->Lookup(key2, GetHelperFail(), this, Cache::Priority::LOW);
|
||||
ASSERT_NE(handle, nullptr);
|
||||
cache->Release(handle);
|
||||
|
||||
@@ -561,34 +514,30 @@ class CompressedSecondaryCacheTest : public testing::Test,
|
||||
std::shared_ptr<SecondaryCache> secondary_cache =
|
||||
NewCompressedSecondaryCache(secondary_cache_opts);
|
||||
|
||||
LRUCacheOptions opts(
|
||||
std::shared_ptr<Cache> cache = NewCache(
|
||||
/*_capacity=*/1300, /*_num_shard_bits=*/0,
|
||||
/*_strict_capacity_limit=*/false, /*_high_pri_pool_ratio=*/0.5,
|
||||
/*_memory_allocator=*/nullptr, kDefaultToAdaptiveMutex,
|
||||
kDefaultCacheMetadataChargePolicy, /*_low_pri_pool_ratio=*/0.0);
|
||||
opts.secondary_cache = secondary_cache;
|
||||
std::shared_ptr<Cache> cache = NewLRUCache(opts);
|
||||
/*_strict_capacity_limit=*/true, secondary_cache);
|
||||
|
||||
Random rnd(301);
|
||||
std::string str1 = rnd.RandomString(1001);
|
||||
auto item1 = new TestItem(str1.data(), str1.length());
|
||||
ASSERT_OK(cache->Insert("k1", item1, &kHelper, str1.length()));
|
||||
ASSERT_OK(cache->Insert(key1, item1, GetHelper(), str1.length()));
|
||||
|
||||
std::string str2 = rnd.RandomString(1002);
|
||||
auto item2 = new TestItem(str2.data(), str2.length());
|
||||
// k1 should be demoted to the secondary cache.
|
||||
ASSERT_OK(cache->Insert("k2", item2, &kHelper, str2.length()));
|
||||
ASSERT_OK(cache->Insert(key2, item2, GetHelper(), str2.length()));
|
||||
|
||||
Cache::Handle* handle;
|
||||
SetFailCreate(true);
|
||||
handle = cache->Lookup("k2", &kHelper, this, Cache::Priority::LOW, true);
|
||||
handle = cache->Lookup(key2, GetHelper(), this, Cache::Priority::LOW);
|
||||
ASSERT_NE(handle, nullptr);
|
||||
cache->Release(handle);
|
||||
// This lookup should fail, since k1 creation would have failed
|
||||
handle = cache->Lookup("k1", &kHelper, this, Cache::Priority::LOW, true);
|
||||
handle = cache->Lookup(key1, GetHelper(), this, Cache::Priority::LOW);
|
||||
ASSERT_EQ(handle, nullptr);
|
||||
// Since k1 didn't get promoted, k2 should still be in cache
|
||||
handle = cache->Lookup("k2", &kHelper, this, Cache::Priority::LOW, true);
|
||||
handle = cache->Lookup(key2, GetHelper(), this, Cache::Priority::LOW);
|
||||
ASSERT_NE(handle, nullptr);
|
||||
cache->Release(handle);
|
||||
|
||||
@@ -614,38 +563,34 @@ class CompressedSecondaryCacheTest : public testing::Test,
|
||||
std::shared_ptr<SecondaryCache> secondary_cache =
|
||||
NewCompressedSecondaryCache(secondary_cache_opts);
|
||||
|
||||
LRUCacheOptions opts(
|
||||
std::shared_ptr<Cache> cache = NewCache(
|
||||
/*_capacity=*/1300, /*_num_shard_bits=*/0,
|
||||
/*_strict_capacity_limit=*/false, /*_high_pri_pool_ratio=*/0.5,
|
||||
/*_memory_allocator=*/nullptr, kDefaultToAdaptiveMutex,
|
||||
kDefaultCacheMetadataChargePolicy, /*_low_pri_pool_ratio=*/0.0);
|
||||
opts.secondary_cache = secondary_cache;
|
||||
std::shared_ptr<Cache> cache = NewLRUCache(opts);
|
||||
/*_strict_capacity_limit=*/false, secondary_cache);
|
||||
|
||||
Random rnd(301);
|
||||
std::string str1 = rnd.RandomString(1001);
|
||||
auto item1_1 = new TestItem(str1.data(), str1.length());
|
||||
ASSERT_OK(cache->Insert("k1", item1_1, &kHelper, str1.length()));
|
||||
ASSERT_OK(cache->Insert(key1, item1_1, GetHelper(), str1.length()));
|
||||
|
||||
std::string str2 = rnd.RandomString(1002);
|
||||
std::string str2_clone{str2};
|
||||
auto item2 = new TestItem(str2.data(), str2.length());
|
||||
// After this Insert, primary cache contains k2 and secondary cache contains
|
||||
// k1's dummy item.
|
||||
ASSERT_OK(cache->Insert("k2", item2, &kHelper, str2.length()));
|
||||
ASSERT_OK(cache->Insert(key2, item2, GetHelper(), str2.length()));
|
||||
|
||||
// After this Insert, primary cache contains k1 and secondary cache contains
|
||||
// k1's dummy item and k2's dummy item.
|
||||
auto item1_2 = new TestItem(str1.data(), str1.length());
|
||||
ASSERT_OK(cache->Insert("k1", item1_2, &kHelper, str1.length()));
|
||||
ASSERT_OK(cache->Insert(key1, item1_2, GetHelper(), str1.length()));
|
||||
|
||||
auto item2_2 = new TestItem(str2.data(), str2.length());
|
||||
// After this Insert, primary cache contains k2 and secondary cache contains
|
||||
// k1's item and k2's dummy item.
|
||||
ASSERT_OK(cache->Insert("k2", item2_2, &kHelper, str2.length()));
|
||||
ASSERT_OK(cache->Insert(key2, item2_2, GetHelper(), str2.length()));
|
||||
|
||||
Cache::Handle* handle2;
|
||||
handle2 = cache->Lookup("k2", &kHelper, this, Cache::Priority::LOW, true);
|
||||
handle2 = cache->Lookup(key2, GetHelper(), this, Cache::Priority::LOW);
|
||||
ASSERT_NE(handle2, nullptr);
|
||||
cache->Release(handle2);
|
||||
|
||||
@@ -653,12 +598,12 @@ class CompressedSecondaryCacheTest : public testing::Test,
|
||||
// strict_capacity_limit is true, but the lookup should still succeed.
|
||||
// A k1's dummy item is inserted into primary cache.
|
||||
Cache::Handle* handle1;
|
||||
handle1 = cache->Lookup("k1", &kHelper, this, Cache::Priority::LOW, true);
|
||||
handle1 = cache->Lookup(key1, GetHelper(), this, Cache::Priority::LOW);
|
||||
ASSERT_NE(handle1, nullptr);
|
||||
cache->Release(handle1);
|
||||
|
||||
// Since k1 didn't get inserted, k2 should still be in cache
|
||||
handle2 = cache->Lookup("k2", &kHelper, this, Cache::Priority::LOW, true);
|
||||
handle2 = cache->Lookup(key2, GetHelper(), this, Cache::Priority::LOW);
|
||||
ASSERT_NE(handle2, nullptr);
|
||||
cache->Release(handle2);
|
||||
|
||||
@@ -681,8 +626,9 @@ class CompressedSecondaryCacheTest : public testing::Test,
|
||||
|
||||
using CacheValueChunk = CompressedSecondaryCache::CacheValueChunk;
|
||||
std::unique_ptr<CompressedSecondaryCache> sec_cache =
|
||||
std::make_unique<CompressedSecondaryCache>(1000, 0, true, 0.5, 0.0,
|
||||
allocator);
|
||||
std::make_unique<CompressedSecondaryCache>(
|
||||
CompressedSecondaryCacheOptions(1000, 0, true, 0.5, 0.0,
|
||||
allocator));
|
||||
Random rnd(301);
|
||||
// 8500 = 8169 + 233 + 98, so there should be 3 chunks after split.
|
||||
size_t str_size{8500};
|
||||
@@ -733,7 +679,8 @@ class CompressedSecondaryCacheTest : public testing::Test,
|
||||
std::string str = str1 + str2 + str3;
|
||||
|
||||
std::unique_ptr<CompressedSecondaryCache> sec_cache =
|
||||
std::make_unique<CompressedSecondaryCache>(1000, 0, true, 0.5, 0.0);
|
||||
std::make_unique<CompressedSecondaryCache>(
|
||||
CompressedSecondaryCacheOptions(1000, 0, true, 0.5, 0.0));
|
||||
size_t charge{0};
|
||||
CacheAllocationPtr value =
|
||||
sec_cache->MergeChunksIntoValue(chunks_head, charge);
|
||||
@@ -763,8 +710,9 @@ class CompressedSecondaryCacheTest : public testing::Test,
|
||||
|
||||
using CacheValueChunk = CompressedSecondaryCache::CacheValueChunk;
|
||||
std::unique_ptr<CompressedSecondaryCache> sec_cache =
|
||||
std::make_unique<CompressedSecondaryCache>(1000, 0, true, 0.5, 0.0,
|
||||
allocator);
|
||||
std::make_unique<CompressedSecondaryCache>(
|
||||
CompressedSecondaryCacheOptions(1000, 0, true, 0.5, 0.0,
|
||||
allocator));
|
||||
Random rnd(301);
|
||||
// 8500 = 8169 + 233 + 98, so there should be 3 chunks after split.
|
||||
size_t str_size{8500};
|
||||
@@ -782,19 +730,27 @@ class CompressedSecondaryCacheTest : public testing::Test,
|
||||
|
||||
sec_cache->GetHelper(true)->del_cb(chunks_head, /*alloc*/ nullptr);
|
||||
}
|
||||
|
||||
private:
|
||||
bool fail_create_;
|
||||
};
|
||||
|
||||
class CompressedSecondaryCacheTest
|
||||
: public CompressedSecondaryCacheTestBase,
|
||||
public testing::WithParamInterface<std::string> {
|
||||
const std::string& Type() override { return GetParam(); }
|
||||
};
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(CompressedSecondaryCacheTest,
|
||||
CompressedSecondaryCacheTest, GetTestingCacheTypes());
|
||||
|
||||
class CompressedSecCacheTestWithCompressAndAllocatorParam
|
||||
: public CompressedSecondaryCacheTest,
|
||||
public ::testing::WithParamInterface<std::tuple<bool, bool>> {
|
||||
: public CompressedSecondaryCacheTestBase,
|
||||
public ::testing::WithParamInterface<
|
||||
std::tuple<bool, bool, std::string>> {
|
||||
public:
|
||||
CompressedSecCacheTestWithCompressAndAllocatorParam() {
|
||||
sec_cache_is_compressed_ = std::get<0>(GetParam());
|
||||
use_jemalloc_ = std::get<1>(GetParam());
|
||||
}
|
||||
const std::string& Type() override { return std::get<2>(GetParam()); }
|
||||
bool sec_cache_is_compressed_;
|
||||
bool use_jemalloc_;
|
||||
};
|
||||
@@ -805,19 +761,20 @@ TEST_P(CompressedSecCacheTestWithCompressAndAllocatorParam, BasicTes) {
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(CompressedSecCacheTests,
|
||||
CompressedSecCacheTestWithCompressAndAllocatorParam,
|
||||
::testing::Combine(testing::Bool(), testing::Bool()));
|
||||
::testing::Combine(testing::Bool(), testing::Bool(),
|
||||
GetTestingCacheTypes()));
|
||||
|
||||
class CompressedSecondaryCacheTestWithCompressionParam
|
||||
: public CompressedSecondaryCacheTest,
|
||||
public ::testing::WithParamInterface<bool> {
|
||||
: public CompressedSecondaryCacheTestBase,
|
||||
public ::testing::WithParamInterface<std::tuple<bool, std::string>> {
|
||||
public:
|
||||
CompressedSecondaryCacheTestWithCompressionParam() {
|
||||
sec_cache_is_compressed_ = GetParam();
|
||||
sec_cache_is_compressed_ = std::get<0>(GetParam());
|
||||
}
|
||||
const std::string& Type() override { return std::get<1>(GetParam()); }
|
||||
bool sec_cache_is_compressed_;
|
||||
};
|
||||
|
||||
|
||||
TEST_P(CompressedSecondaryCacheTestWithCompressionParam, BasicTestFromString) {
|
||||
std::shared_ptr<SecondaryCache> sec_cache{nullptr};
|
||||
std::string sec_cache_uri;
|
||||
@@ -906,18 +863,92 @@ TEST_P(CompressedSecondaryCacheTestWithCompressionParam,
|
||||
IntegrationFullCapacityTest(sec_cache_is_compressed_);
|
||||
}
|
||||
|
||||
TEST_P(CompressedSecondaryCacheTestWithCompressionParam, EntryRoles) {
|
||||
CompressedSecondaryCacheOptions opts;
|
||||
opts.capacity = 2048;
|
||||
opts.num_shard_bits = 0;
|
||||
|
||||
if (sec_cache_is_compressed_) {
|
||||
if (!LZ4_Supported()) {
|
||||
ROCKSDB_GTEST_SKIP("This test requires LZ4 support.");
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
opts.compression_type = CompressionType::kNoCompression;
|
||||
}
|
||||
|
||||
// Select a random subset to include, for fast test
|
||||
Random& r = *Random::GetTLSInstance();
|
||||
CacheEntryRoleSet do_not_compress;
|
||||
for (uint32_t i = 0; i < kNumCacheEntryRoles; ++i) {
|
||||
// A few included on average, but decent chance of zero
|
||||
if (r.OneIn(5)) {
|
||||
do_not_compress.Add(static_cast<CacheEntryRole>(i));
|
||||
}
|
||||
}
|
||||
opts.do_not_compress_roles = do_not_compress;
|
||||
|
||||
std::shared_ptr<SecondaryCache> sec_cache = NewCompressedSecondaryCache(opts);
|
||||
|
||||
// Fixed seed to ensure consistent compressibility (doesn't compress)
|
||||
std::string junk(Random(301).RandomString(1000));
|
||||
|
||||
for (uint32_t i = 0; i < kNumCacheEntryRoles; ++i) {
|
||||
CacheEntryRole role = static_cast<CacheEntryRole>(i);
|
||||
|
||||
// Uniquify `junk`
|
||||
junk[0] = static_cast<char>(i);
|
||||
TestItem item{junk.data(), junk.length()};
|
||||
Slice ith_key = Slice(junk.data(), 16);
|
||||
|
||||
get_perf_context()->Reset();
|
||||
ASSERT_OK(sec_cache->Insert(ith_key, &item, GetHelper(role)));
|
||||
ASSERT_EQ(get_perf_context()->compressed_sec_cache_insert_dummy_count, 1U);
|
||||
|
||||
ASSERT_OK(sec_cache->Insert(ith_key, &item, GetHelper(role)));
|
||||
ASSERT_EQ(get_perf_context()->compressed_sec_cache_insert_real_count, 1U);
|
||||
|
||||
bool kept_in_sec_cache{true};
|
||||
std::unique_ptr<SecondaryCacheResultHandle> handle =
|
||||
sec_cache->Lookup(ith_key, GetHelper(role), this, true,
|
||||
/*advise_erase=*/true, kept_in_sec_cache);
|
||||
ASSERT_NE(handle, nullptr);
|
||||
|
||||
// Lookup returns the right data
|
||||
std::unique_ptr<TestItem> val =
|
||||
std::unique_ptr<TestItem>(static_cast<TestItem*>(handle->Value()));
|
||||
ASSERT_NE(val, nullptr);
|
||||
ASSERT_EQ(memcmp(val->Buf(), item.Buf(), item.Size()), 0);
|
||||
|
||||
bool compressed =
|
||||
sec_cache_is_compressed_ && !do_not_compress.Contains(role);
|
||||
if (compressed) {
|
||||
ASSERT_EQ(get_perf_context()->compressed_sec_cache_uncompressed_bytes,
|
||||
1000);
|
||||
ASSERT_EQ(get_perf_context()->compressed_sec_cache_compressed_bytes,
|
||||
1007);
|
||||
} else {
|
||||
ASSERT_EQ(get_perf_context()->compressed_sec_cache_uncompressed_bytes, 0);
|
||||
ASSERT_EQ(get_perf_context()->compressed_sec_cache_compressed_bytes, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(CompressedSecCacheTests,
|
||||
CompressedSecondaryCacheTestWithCompressionParam,
|
||||
testing::Bool());
|
||||
testing::Combine(testing::Bool(),
|
||||
GetTestingCacheTypes()));
|
||||
|
||||
class CompressedSecCacheTestWithCompressAndSplitParam
|
||||
: public CompressedSecondaryCacheTest,
|
||||
public ::testing::WithParamInterface<std::tuple<bool, bool>> {
|
||||
: public CompressedSecondaryCacheTestBase,
|
||||
public ::testing::WithParamInterface<
|
||||
std::tuple<bool, bool, std::string>> {
|
||||
public:
|
||||
CompressedSecCacheTestWithCompressAndSplitParam() {
|
||||
sec_cache_is_compressed_ = std::get<0>(GetParam());
|
||||
enable_custom_split_merge_ = std::get<1>(GetParam());
|
||||
}
|
||||
const std::string& Type() override { return std::get<2>(GetParam()); }
|
||||
bool sec_cache_is_compressed_;
|
||||
bool enable_custom_split_merge_;
|
||||
};
|
||||
@@ -928,17 +959,18 @@ TEST_P(CompressedSecCacheTestWithCompressAndSplitParam, BasicIntegrationTest) {
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(CompressedSecCacheTests,
|
||||
CompressedSecCacheTestWithCompressAndSplitParam,
|
||||
::testing::Combine(testing::Bool(), testing::Bool()));
|
||||
::testing::Combine(testing::Bool(), testing::Bool(),
|
||||
GetTestingCacheTypes()));
|
||||
|
||||
TEST_F(CompressedSecondaryCacheTest, SplitValueIntoChunksTest) {
|
||||
TEST_P(CompressedSecondaryCacheTest, SplitValueIntoChunksTest) {
|
||||
SplitValueIntoChunksTest();
|
||||
}
|
||||
|
||||
TEST_F(CompressedSecondaryCacheTest, MergeChunksIntoValueTest) {
|
||||
TEST_P(CompressedSecondaryCacheTest, MergeChunksIntoValueTest) {
|
||||
MergeChunksIntoValueTest();
|
||||
}
|
||||
|
||||
TEST_F(CompressedSecondaryCacheTest, SplictValueAndMergeChunksTest) {
|
||||
TEST_P(CompressedSecondaryCacheTest, SplictValueAndMergeChunksTest) {
|
||||
SplictValueAndMergeChunksTest();
|
||||
}
|
||||
|
||||
|
||||
Vendored
+129
-321
@@ -14,22 +14,15 @@
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
|
||||
#include "cache/secondary_cache_adapter.h"
|
||||
#include "monitoring/perf_context_imp.h"
|
||||
#include "monitoring/statistics.h"
|
||||
#include "monitoring/statistics_impl.h"
|
||||
#include "port/lang.h"
|
||||
#include "util/distributed_mutex.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
namespace lru_cache {
|
||||
|
||||
namespace {
|
||||
// A distinct pointer value for marking "dummy" cache entries
|
||||
struct DummyValue {
|
||||
char val[12] = "kDummyValue";
|
||||
};
|
||||
DummyValue kDummyValue{};
|
||||
} // namespace
|
||||
|
||||
LRUHandleTable::LRUHandleTable(int max_upper_hash_bits,
|
||||
MemoryAllocator* allocator)
|
||||
: length_bits_(/* historical starting size*/ 4),
|
||||
@@ -103,7 +96,7 @@ void LRUHandleTable::Resize() {
|
||||
std::unique_ptr<LRUHandle* []> new_list {
|
||||
new LRUHandle* [size_t{1} << new_length_bits] {}
|
||||
};
|
||||
uint32_t count = 0;
|
||||
[[maybe_unused]] uint32_t count = 0;
|
||||
for (uint32_t i = 0; i < old_length; i++) {
|
||||
LRUHandle* h = list_[i];
|
||||
while (h != nullptr) {
|
||||
@@ -127,7 +120,7 @@ LRUCacheShard::LRUCacheShard(size_t capacity, bool strict_capacity_limit,
|
||||
CacheMetadataChargePolicy metadata_charge_policy,
|
||||
int max_upper_hash_bits,
|
||||
MemoryAllocator* allocator,
|
||||
SecondaryCache* secondary_cache)
|
||||
const Cache::EvictionCallback* eviction_callback)
|
||||
: CacheShardBase(metadata_charge_policy),
|
||||
capacity_(0),
|
||||
high_pri_pool_usage_(0),
|
||||
@@ -141,7 +134,7 @@ LRUCacheShard::LRUCacheShard(size_t capacity, bool strict_capacity_limit,
|
||||
usage_(0),
|
||||
lru_usage_(0),
|
||||
mutex_(use_adaptive_mutex),
|
||||
secondary_cache_(secondary_cache) {
|
||||
eviction_callback_(*eviction_callback) {
|
||||
// Make empty circular linked list.
|
||||
lru_.next = &lru_;
|
||||
lru_.prev = &lru_;
|
||||
@@ -341,16 +334,19 @@ void LRUCacheShard::EvictFromLRU(size_t charge,
|
||||
}
|
||||
}
|
||||
|
||||
void LRUCacheShard::TryInsertIntoSecondaryCache(
|
||||
autovector<LRUHandle*> evicted_handles) {
|
||||
for (auto entry : evicted_handles) {
|
||||
if (secondary_cache_ && entry->IsSecondaryCacheCompatible() &&
|
||||
!entry->IsInSecondaryCache()) {
|
||||
secondary_cache_->Insert(entry->key(), entry->value, entry->helper)
|
||||
.PermitUncheckedError();
|
||||
void LRUCacheShard::NotifyEvicted(
|
||||
const autovector<LRUHandle*>& evicted_handles) {
|
||||
MemoryAllocator* alloc = table_.GetAllocator();
|
||||
for (LRUHandle* entry : evicted_handles) {
|
||||
if (eviction_callback_ &&
|
||||
eviction_callback_(entry->key(),
|
||||
reinterpret_cast<Cache::Handle*>(entry))) {
|
||||
// Callback took ownership of obj; just free handle
|
||||
free(entry);
|
||||
} else {
|
||||
// Free the entries here outside of mutex for performance reasons.
|
||||
entry->Free(alloc);
|
||||
}
|
||||
// Free the entries here outside of mutex for performance reasons.
|
||||
entry->Free(table_.GetAllocator());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -364,7 +360,7 @@ void LRUCacheShard::SetCapacity(size_t capacity) {
|
||||
EvictFromLRU(0, &last_reference_list);
|
||||
}
|
||||
|
||||
TryInsertIntoSecondaryCache(last_reference_list);
|
||||
NotifyEvicted(last_reference_list);
|
||||
}
|
||||
|
||||
void LRUCacheShard::SetStrictCapacityLimit(bool strict_capacity_limit) {
|
||||
@@ -372,8 +368,7 @@ void LRUCacheShard::SetStrictCapacityLimit(bool strict_capacity_limit) {
|
||||
strict_capacity_limit_ = strict_capacity_limit;
|
||||
}
|
||||
|
||||
Status LRUCacheShard::InsertItem(LRUHandle* e, LRUHandle** handle,
|
||||
bool free_handle_on_fail) {
|
||||
Status LRUCacheShard::InsertItem(LRUHandle* e, LRUHandle** handle) {
|
||||
Status s = Status::OK();
|
||||
autovector<LRUHandle*> last_reference_list;
|
||||
|
||||
@@ -392,10 +387,9 @@ Status LRUCacheShard::InsertItem(LRUHandle* e, LRUHandle** handle,
|
||||
// into cache and get evicted immediately.
|
||||
last_reference_list.push_back(e);
|
||||
} else {
|
||||
if (free_handle_on_fail) {
|
||||
free(e);
|
||||
*handle = nullptr;
|
||||
}
|
||||
free(e);
|
||||
e = nullptr;
|
||||
*handle = nullptr;
|
||||
s = Status::MemoryLimit("Insert failed due to LRU cache being full.");
|
||||
}
|
||||
} else {
|
||||
@@ -427,185 +421,27 @@ Status LRUCacheShard::InsertItem(LRUHandle* e, LRUHandle** handle,
|
||||
}
|
||||
}
|
||||
|
||||
TryInsertIntoSecondaryCache(last_reference_list);
|
||||
NotifyEvicted(last_reference_list);
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
void LRUCacheShard::Promote(LRUHandle* e) {
|
||||
SecondaryCacheResultHandle* secondary_handle = e->sec_handle;
|
||||
|
||||
assert(secondary_handle->IsReady());
|
||||
// e is not thread-shared here; OK to modify "immutable" fields as well as
|
||||
// "mutable" (normally requiring mutex)
|
||||
e->SetIsPending(false);
|
||||
e->value = secondary_handle->Value();
|
||||
assert(e->total_charge == 0);
|
||||
size_t value_size = secondary_handle->Size();
|
||||
delete secondary_handle;
|
||||
|
||||
if (e->value) {
|
||||
e->CalcTotalCharge(value_size, metadata_charge_policy_);
|
||||
Status s;
|
||||
if (e->IsStandalone()) {
|
||||
assert(secondary_cache_ && secondary_cache_->SupportForceErase());
|
||||
|
||||
// Insert a dummy handle and return a standalone handle to caller.
|
||||
// Charge the standalone handle.
|
||||
autovector<LRUHandle*> last_reference_list;
|
||||
bool free_standalone_handle{false};
|
||||
{
|
||||
DMutexLock l(mutex_);
|
||||
|
||||
// Free the space following strict LRU policy until enough space
|
||||
// is freed or the lru list is empty.
|
||||
EvictFromLRU(e->total_charge, &last_reference_list);
|
||||
|
||||
if ((usage_ + e->total_charge) > capacity_ && strict_capacity_limit_) {
|
||||
free_standalone_handle = true;
|
||||
} else {
|
||||
usage_ += e->total_charge;
|
||||
}
|
||||
}
|
||||
|
||||
TryInsertIntoSecondaryCache(last_reference_list);
|
||||
if (free_standalone_handle) {
|
||||
e->Unref();
|
||||
e->Free(table_.GetAllocator());
|
||||
e = nullptr;
|
||||
} else {
|
||||
PERF_COUNTER_ADD(block_cache_standalone_handle_count, 1);
|
||||
}
|
||||
|
||||
// Insert a dummy handle into the primary cache. This dummy handle is
|
||||
// not IsSecondaryCacheCompatible().
|
||||
// FIXME? This should not overwrite an existing non-dummy entry in the
|
||||
// rare case that one exists
|
||||
Cache::Priority priority =
|
||||
e->IsHighPri() ? Cache::Priority::HIGH : Cache::Priority::LOW;
|
||||
s = Insert(e->key(), e->hash, &kDummyValue, &kNoopCacheItemHelper,
|
||||
/*charge=*/0,
|
||||
/*handle=*/nullptr, priority);
|
||||
} else {
|
||||
e->SetInCache(true);
|
||||
LRUHandle* handle = e;
|
||||
// This InsertItem() could fail if the cache is over capacity and
|
||||
// strict_capacity_limit_ is true. In such a case, we don't want
|
||||
// InsertItem() to free the handle, since the item is already in memory
|
||||
// and the caller will most likely just read it from disk if we erase it
|
||||
// here.
|
||||
s = InsertItem(e, &handle, /*free_handle_on_fail=*/false);
|
||||
if (s.ok()) {
|
||||
PERF_COUNTER_ADD(block_cache_real_handle_count, 1);
|
||||
}
|
||||
}
|
||||
|
||||
if (!s.ok()) {
|
||||
// Item is in memory, but not accounted against the cache capacity.
|
||||
// When the handle is released, the item should get deleted.
|
||||
assert(!e->InCache());
|
||||
}
|
||||
} else {
|
||||
// Secondary cache lookup failed. The caller will take care of detecting
|
||||
// this and eventually releasing e.
|
||||
assert(!e->value);
|
||||
assert(!e->InCache());
|
||||
}
|
||||
}
|
||||
|
||||
LRUHandle* LRUCacheShard::Lookup(const Slice& key, uint32_t hash,
|
||||
const Cache::CacheItemHelper* helper,
|
||||
Cache::CreateContext* create_context,
|
||||
Cache::Priority priority, bool wait,
|
||||
Statistics* stats) {
|
||||
LRUHandle* e = nullptr;
|
||||
bool found_dummy_entry{false};
|
||||
{
|
||||
DMutexLock l(mutex_);
|
||||
e = table_.Lookup(key, hash);
|
||||
if (e != nullptr) {
|
||||
assert(e->InCache());
|
||||
if (e->value == &kDummyValue) {
|
||||
// For a dummy handle, if it was retrieved from secondary cache,
|
||||
// it may still exist in secondary cache.
|
||||
// If the handle exists in secondary cache, the value should be
|
||||
// erased from sec cache and be inserted into primary cache.
|
||||
found_dummy_entry = true;
|
||||
// Let the dummy entry be overwritten
|
||||
e = nullptr;
|
||||
} else {
|
||||
if (!e->HasRefs()) {
|
||||
// The entry is in LRU since it's in hash and has no external
|
||||
// references.
|
||||
LRU_Remove(e);
|
||||
}
|
||||
e->Ref();
|
||||
e->SetHit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If handle table lookup failed or the handle is a dummy one, allocate
|
||||
// a handle outside the mutex if we re going to lookup in the secondary cache.
|
||||
//
|
||||
// When a block is firstly Lookup from CompressedSecondaryCache, we just
|
||||
// insert a dummy block into the primary cache (charging the actual size of
|
||||
// the block) and don't erase the block from CompressedSecondaryCache. A
|
||||
// standalone handle is returned to the caller. Only if the block is hit
|
||||
// again, we erase it from CompressedSecondaryCache and add it into the
|
||||
// primary cache.
|
||||
if (!e && secondary_cache_ && helper && helper->create_cb) {
|
||||
bool is_in_sec_cache{false};
|
||||
std::unique_ptr<SecondaryCacheResultHandle> secondary_handle =
|
||||
secondary_cache_->Lookup(key, helper, create_context, wait,
|
||||
found_dummy_entry, is_in_sec_cache);
|
||||
if (secondary_handle != nullptr) {
|
||||
e = static_cast<LRUHandle*>(malloc(sizeof(LRUHandle) - 1 + key.size()));
|
||||
|
||||
e->m_flags = 0;
|
||||
e->im_flags = 0;
|
||||
e->helper = helper;
|
||||
e->key_length = key.size();
|
||||
e->hash = hash;
|
||||
e->refs = 0;
|
||||
e->next = e->prev = nullptr;
|
||||
e->SetPriority(priority);
|
||||
memcpy(e->key_data, key.data(), key.size());
|
||||
e->value = nullptr;
|
||||
e->sec_handle = secondary_handle.release();
|
||||
e->total_charge = 0;
|
||||
e->Ref();
|
||||
e->SetIsInSecondaryCache(is_in_sec_cache);
|
||||
e->SetIsStandalone(secondary_cache_->SupportForceErase() &&
|
||||
!found_dummy_entry);
|
||||
|
||||
if (wait) {
|
||||
Promote(e);
|
||||
if (e) {
|
||||
if (!e->value) {
|
||||
// The secondary cache returned a handle, but the lookup failed.
|
||||
e->Unref();
|
||||
e->Free(table_.GetAllocator());
|
||||
e = nullptr;
|
||||
} else {
|
||||
PERF_COUNTER_ADD(secondary_cache_hit_count, 1);
|
||||
RecordTick(stats, SECONDARY_CACHE_HITS);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// If wait is false, we always return a handle and let the caller
|
||||
// release the handle after checking for success or failure.
|
||||
e->SetIsPending(true);
|
||||
// This may be slightly inaccurate, if the lookup eventually fails.
|
||||
// But the probability is very low.
|
||||
PERF_COUNTER_ADD(secondary_cache_hit_count, 1);
|
||||
RecordTick(stats, SECONDARY_CACHE_HITS);
|
||||
}
|
||||
} else {
|
||||
// Caller will most likely overwrite the dummy entry with an Insert
|
||||
// after this Lookup fails
|
||||
assert(e == nullptr);
|
||||
const Cache::CacheItemHelper* /*helper*/,
|
||||
Cache::CreateContext* /*create_context*/,
|
||||
Cache::Priority /*priority*/,
|
||||
Statistics* /*stats*/) {
|
||||
DMutexLock l(mutex_);
|
||||
LRUHandle* e = table_.Lookup(key, hash);
|
||||
if (e != nullptr) {
|
||||
assert(e->InCache());
|
||||
if (!e->HasRefs()) {
|
||||
// The entry is in LRU since it's in hash and has no external
|
||||
// references.
|
||||
LRU_Remove(e);
|
||||
}
|
||||
e->Ref();
|
||||
e->SetHit();
|
||||
}
|
||||
return e;
|
||||
}
|
||||
@@ -614,8 +450,6 @@ bool LRUCacheShard::Ref(LRUHandle* e) {
|
||||
DMutexLock l(mutex_);
|
||||
// To create another reference - entry must be already externally referenced.
|
||||
assert(e->HasRefs());
|
||||
// Pending handles are not for sharing
|
||||
assert(!e->IsPending());
|
||||
e->Ref();
|
||||
return true;
|
||||
}
|
||||
@@ -639,14 +473,13 @@ bool LRUCacheShard::Release(LRUHandle* e, bool /*useful*/,
|
||||
if (e == nullptr) {
|
||||
return false;
|
||||
}
|
||||
bool last_reference = false;
|
||||
// Must Wait or WaitAll first on pending handles. Otherwise, would leak
|
||||
// a secondary cache handle.
|
||||
assert(!e->IsPending());
|
||||
bool must_free;
|
||||
bool was_in_cache;
|
||||
{
|
||||
DMutexLock l(mutex_);
|
||||
last_reference = e->Unref();
|
||||
if (last_reference && e->InCache()) {
|
||||
must_free = e->Unref();
|
||||
was_in_cache = e->InCache();
|
||||
if (must_free && was_in_cache) {
|
||||
// The item is still in cache, and nobody else holds a reference to it.
|
||||
if (usage_ > capacity_ || erase_if_last_ref) {
|
||||
// The LRU list must be empty since the cache is full.
|
||||
@@ -657,29 +490,38 @@ bool LRUCacheShard::Release(LRUHandle* e, bool /*useful*/,
|
||||
} else {
|
||||
// Put the item back on the LRU list, and don't free it.
|
||||
LRU_Insert(e);
|
||||
last_reference = false;
|
||||
must_free = false;
|
||||
}
|
||||
}
|
||||
// If it was the last reference, then decrement the cache usage.
|
||||
if (last_reference) {
|
||||
// If about to be freed, then decrement the cache usage.
|
||||
if (must_free) {
|
||||
assert(usage_ >= e->total_charge);
|
||||
usage_ -= e->total_charge;
|
||||
}
|
||||
}
|
||||
|
||||
// Free the entry here outside of mutex for performance reasons.
|
||||
if (last_reference) {
|
||||
e->Free(table_.GetAllocator());
|
||||
if (must_free) {
|
||||
// Only call eviction callback if we're sure no one requested erasure
|
||||
// FIXME: disabled because of test churn
|
||||
if (false && was_in_cache && !erase_if_last_ref && eviction_callback_ &&
|
||||
eviction_callback_(e->key(), reinterpret_cast<Cache::Handle*>(e))) {
|
||||
// Callback took ownership of obj; just free handle
|
||||
free(e);
|
||||
} else {
|
||||
e->Free(table_.GetAllocator());
|
||||
}
|
||||
}
|
||||
return last_reference;
|
||||
return must_free;
|
||||
}
|
||||
|
||||
Status LRUCacheShard::Insert(const Slice& key, uint32_t hash,
|
||||
Cache::ObjectPtr value,
|
||||
const Cache::CacheItemHelper* helper,
|
||||
size_t charge, LRUHandle** handle,
|
||||
Cache::Priority priority) {
|
||||
LRUHandle* LRUCacheShard::CreateHandle(const Slice& key, uint32_t hash,
|
||||
Cache::ObjectPtr value,
|
||||
const Cache::CacheItemHelper* helper,
|
||||
size_t charge) {
|
||||
assert(helper);
|
||||
// value == nullptr is reserved for indicating failure in SecondaryCache
|
||||
assert(!(helper->IsSecondaryCacheCompatible() && value == nullptr));
|
||||
|
||||
// Allocate the memory here outside of the mutex.
|
||||
// If the cache is full, we'll have to release it.
|
||||
@@ -695,16 +537,53 @@ Status LRUCacheShard::Insert(const Slice& key, uint32_t hash,
|
||||
e->hash = hash;
|
||||
e->refs = 0;
|
||||
e->next = e->prev = nullptr;
|
||||
e->SetInCache(true);
|
||||
e->SetPriority(priority);
|
||||
memcpy(e->key_data, key.data(), key.size());
|
||||
e->CalcTotalCharge(charge, metadata_charge_policy_);
|
||||
|
||||
// value == nullptr is reserved for indicating failure for when secondary
|
||||
// cache compatible
|
||||
assert(!(e->IsSecondaryCacheCompatible() && value == nullptr));
|
||||
return e;
|
||||
}
|
||||
|
||||
return InsertItem(e, handle, /* free_handle_on_fail */ true);
|
||||
Status LRUCacheShard::Insert(const Slice& key, uint32_t hash,
|
||||
Cache::ObjectPtr value,
|
||||
const Cache::CacheItemHelper* helper,
|
||||
size_t charge, LRUHandle** handle,
|
||||
Cache::Priority priority) {
|
||||
LRUHandle* e = CreateHandle(key, hash, value, helper, charge);
|
||||
e->SetPriority(priority);
|
||||
e->SetInCache(true);
|
||||
return InsertItem(e, handle);
|
||||
}
|
||||
|
||||
LRUHandle* LRUCacheShard::CreateStandalone(const Slice& key, uint32_t hash,
|
||||
Cache::ObjectPtr value,
|
||||
const Cache::CacheItemHelper* helper,
|
||||
size_t charge,
|
||||
bool allow_uncharged) {
|
||||
LRUHandle* e = CreateHandle(key, hash, value, helper, charge);
|
||||
e->SetIsStandalone(true);
|
||||
e->Ref();
|
||||
|
||||
autovector<LRUHandle*> last_reference_list;
|
||||
|
||||
{
|
||||
DMutexLock l(mutex_);
|
||||
|
||||
EvictFromLRU(e->total_charge, &last_reference_list);
|
||||
|
||||
if (strict_capacity_limit_ && (usage_ + e->total_charge) > capacity_) {
|
||||
if (allow_uncharged) {
|
||||
e->total_charge = 0;
|
||||
} else {
|
||||
free(e);
|
||||
e = nullptr;
|
||||
}
|
||||
} else {
|
||||
usage_ += e->total_charge;
|
||||
}
|
||||
}
|
||||
|
||||
NotifyEvicted(last_reference_list);
|
||||
return e;
|
||||
}
|
||||
|
||||
void LRUCacheShard::Erase(const Slice& key, uint32_t hash) {
|
||||
@@ -733,16 +612,6 @@ void LRUCacheShard::Erase(const Slice& key, uint32_t hash) {
|
||||
}
|
||||
}
|
||||
|
||||
bool LRUCacheShard::IsReady(LRUHandle* e) {
|
||||
bool ready = true;
|
||||
if (e->IsPending()) {
|
||||
assert(secondary_cache_);
|
||||
assert(e->sec_handle);
|
||||
ready = e->sec_handle->IsReady();
|
||||
}
|
||||
return ready;
|
||||
}
|
||||
|
||||
size_t LRUCacheShard::GetUsage() const {
|
||||
DMutexLock l(mutex_);
|
||||
return usage_;
|
||||
@@ -777,31 +646,20 @@ void LRUCacheShard::AppendPrintableOptions(std::string& str) const {
|
||||
str.append(buffer);
|
||||
}
|
||||
|
||||
LRUCache::LRUCache(size_t capacity, int num_shard_bits,
|
||||
bool strict_capacity_limit, double high_pri_pool_ratio,
|
||||
double low_pri_pool_ratio,
|
||||
std::shared_ptr<MemoryAllocator> allocator,
|
||||
bool use_adaptive_mutex,
|
||||
CacheMetadataChargePolicy metadata_charge_policy,
|
||||
std::shared_ptr<SecondaryCache> _secondary_cache)
|
||||
: ShardedCache(capacity, num_shard_bits, strict_capacity_limit,
|
||||
std::move(allocator)),
|
||||
secondary_cache_(std::move(_secondary_cache)) {
|
||||
LRUCache::LRUCache(const LRUCacheOptions& opts) : ShardedCache(opts) {
|
||||
size_t per_shard = GetPerShardCapacity();
|
||||
SecondaryCache* secondary_cache = secondary_cache_.get();
|
||||
MemoryAllocator* alloc = memory_allocator();
|
||||
InitShards([=](LRUCacheShard* cs) {
|
||||
new (cs) LRUCacheShard(
|
||||
per_shard, strict_capacity_limit, high_pri_pool_ratio,
|
||||
low_pri_pool_ratio, use_adaptive_mutex, metadata_charge_policy,
|
||||
/* max_upper_hash_bits */ 32 - num_shard_bits, alloc, secondary_cache);
|
||||
InitShards([&](LRUCacheShard* cs) {
|
||||
new (cs) LRUCacheShard(per_shard, opts.strict_capacity_limit,
|
||||
opts.high_pri_pool_ratio, opts.low_pri_pool_ratio,
|
||||
opts.use_adaptive_mutex, opts.metadata_charge_policy,
|
||||
/* max_upper_hash_bits */ 32 - opts.num_shard_bits,
|
||||
alloc, &eviction_callback_);
|
||||
});
|
||||
}
|
||||
|
||||
Cache::ObjectPtr LRUCache::Value(Handle* handle) {
|
||||
auto h = reinterpret_cast<const LRUHandle*>(handle);
|
||||
assert(!h->IsPending() || h->value == nullptr);
|
||||
assert(h->value != &kDummyValue);
|
||||
return h->value;
|
||||
}
|
||||
|
||||
@@ -824,51 +682,9 @@ double LRUCache::GetHighPriPoolRatio() {
|
||||
return GetShard(0).GetHighPriPoolRatio();
|
||||
}
|
||||
|
||||
void LRUCache::WaitAll(std::vector<Handle*>& handles) {
|
||||
if (secondary_cache_) {
|
||||
std::vector<SecondaryCacheResultHandle*> sec_handles;
|
||||
sec_handles.reserve(handles.size());
|
||||
for (Handle* handle : handles) {
|
||||
if (!handle) {
|
||||
continue;
|
||||
}
|
||||
LRUHandle* lru_handle = reinterpret_cast<LRUHandle*>(handle);
|
||||
if (!lru_handle->IsPending()) {
|
||||
continue;
|
||||
}
|
||||
sec_handles.emplace_back(lru_handle->sec_handle);
|
||||
}
|
||||
secondary_cache_->WaitAll(sec_handles);
|
||||
for (Handle* handle : handles) {
|
||||
if (!handle) {
|
||||
continue;
|
||||
}
|
||||
LRUHandle* lru_handle = reinterpret_cast<LRUHandle*>(handle);
|
||||
if (!lru_handle->IsPending()) {
|
||||
continue;
|
||||
}
|
||||
GetShard(lru_handle->hash).Promote(lru_handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LRUCache::AppendPrintableOptions(std::string& str) const {
|
||||
ShardedCache::AppendPrintableOptions(str); // options from shard
|
||||
if (secondary_cache_) {
|
||||
str.append(" secondary_cache:\n");
|
||||
str.append(secondary_cache_->GetPrintableOptions());
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace lru_cache
|
||||
|
||||
std::shared_ptr<Cache> NewLRUCache(
|
||||
size_t capacity, int num_shard_bits, bool strict_capacity_limit,
|
||||
double high_pri_pool_ratio,
|
||||
std::shared_ptr<MemoryAllocator> memory_allocator, bool use_adaptive_mutex,
|
||||
CacheMetadataChargePolicy metadata_charge_policy,
|
||||
const std::shared_ptr<SecondaryCache>& secondary_cache,
|
||||
double low_pri_pool_ratio) {
|
||||
std::shared_ptr<Cache> LRUCacheOptions::MakeSharedCache() const {
|
||||
if (num_shard_bits >= 20) {
|
||||
return nullptr; // The cache cannot be sharded into too many fine pieces.
|
||||
}
|
||||
@@ -884,32 +700,24 @@ std::shared_ptr<Cache> NewLRUCache(
|
||||
// Invalid high_pri_pool_ratio and low_pri_pool_ratio combination
|
||||
return nullptr;
|
||||
}
|
||||
if (num_shard_bits < 0) {
|
||||
num_shard_bits = GetDefaultCacheShardBits(capacity);
|
||||
// For sanitized options
|
||||
LRUCacheOptions opts = *this;
|
||||
if (opts.num_shard_bits < 0) {
|
||||
opts.num_shard_bits = GetDefaultCacheShardBits(capacity);
|
||||
}
|
||||
return std::make_shared<LRUCache>(
|
||||
capacity, num_shard_bits, strict_capacity_limit, high_pri_pool_ratio,
|
||||
low_pri_pool_ratio, std::move(memory_allocator), use_adaptive_mutex,
|
||||
metadata_charge_policy, secondary_cache);
|
||||
std::shared_ptr<Cache> cache = std::make_shared<LRUCache>(opts);
|
||||
if (secondary_cache) {
|
||||
cache = std::make_shared<CacheWithSecondaryAdapter>(cache, secondary_cache);
|
||||
}
|
||||
return cache;
|
||||
}
|
||||
|
||||
std::shared_ptr<Cache> NewLRUCache(const LRUCacheOptions& cache_opts) {
|
||||
return NewLRUCache(cache_opts.capacity, cache_opts.num_shard_bits,
|
||||
cache_opts.strict_capacity_limit,
|
||||
cache_opts.high_pri_pool_ratio,
|
||||
cache_opts.memory_allocator, cache_opts.use_adaptive_mutex,
|
||||
cache_opts.metadata_charge_policy,
|
||||
cache_opts.secondary_cache, cache_opts.low_pri_pool_ratio);
|
||||
}
|
||||
|
||||
std::shared_ptr<Cache> NewLRUCache(
|
||||
size_t capacity, int num_shard_bits, bool strict_capacity_limit,
|
||||
double high_pri_pool_ratio,
|
||||
std::shared_ptr<MemoryAllocator> memory_allocator, bool use_adaptive_mutex,
|
||||
CacheMetadataChargePolicy metadata_charge_policy,
|
||||
double low_pri_pool_ratio) {
|
||||
return NewLRUCache(capacity, num_shard_bits, strict_capacity_limit,
|
||||
high_pri_pool_ratio, memory_allocator, use_adaptive_mutex,
|
||||
metadata_charge_policy, nullptr, low_pri_pool_ratio);
|
||||
std::shared_ptr<GeneralCache> LRUCacheOptions::MakeSharedGeneralCache() const {
|
||||
if (secondary_cache) {
|
||||
// Not allowed for a GeneralCache
|
||||
return nullptr;
|
||||
}
|
||||
// Works while GeneralCache is an alias for Cache
|
||||
return MakeSharedCache();
|
||||
}
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
Vendored
+25
-78
@@ -16,7 +16,6 @@
|
||||
#include "port/likely.h"
|
||||
#include "port/malloc.h"
|
||||
#include "port/port.h"
|
||||
#include "rocksdb/secondary_cache.h"
|
||||
#include "util/autovector.h"
|
||||
#include "util/distributed_mutex.h"
|
||||
|
||||
@@ -51,12 +50,7 @@ namespace lru_cache {
|
||||
struct LRUHandle {
|
||||
Cache::ObjectPtr value;
|
||||
const Cache::CacheItemHelper* helper;
|
||||
// An entry is not added to the LRUHandleTable until the secondary cache
|
||||
// lookup is complete, so its safe to have this union.
|
||||
union {
|
||||
LRUHandle* next_hash;
|
||||
SecondaryCacheResultHandle* sec_handle;
|
||||
};
|
||||
LRUHandle* next_hash;
|
||||
LRUHandle* next;
|
||||
LRUHandle* prev;
|
||||
size_t total_charge; // TODO(opt): Only allow uint32_t?
|
||||
@@ -89,12 +83,8 @@ struct LRUHandle {
|
||||
IM_IS_HIGH_PRI = (1 << 0),
|
||||
// Whether this entry is low priority entry.
|
||||
IM_IS_LOW_PRI = (1 << 1),
|
||||
// Is the handle still being read from a lower tier.
|
||||
IM_IS_PENDING = (1 << 2),
|
||||
// Whether this handle is still in a lower tier
|
||||
IM_IS_IN_SECONDARY_CACHE = (1 << 3),
|
||||
// Marks result handles that should not be inserted into cache
|
||||
IM_IS_STANDALONE = (1 << 4),
|
||||
IM_IS_STANDALONE = (1 << 2),
|
||||
};
|
||||
|
||||
// Beginning of the key (MUST BE THE LAST FIELD IN THIS STRUCT!)
|
||||
@@ -124,11 +114,6 @@ struct LRUHandle {
|
||||
bool IsLowPri() const { return im_flags & IM_IS_LOW_PRI; }
|
||||
bool InLowPriPool() const { return m_flags & M_IN_LOW_PRI_POOL; }
|
||||
bool HasHit() const { return m_flags & M_HAS_HIT; }
|
||||
bool IsSecondaryCacheCompatible() const { return helper->size_cb != nullptr; }
|
||||
bool IsPending() const { return im_flags & IM_IS_PENDING; }
|
||||
bool IsInSecondaryCache() const {
|
||||
return im_flags & IM_IS_IN_SECONDARY_CACHE;
|
||||
}
|
||||
bool IsStandalone() const { return im_flags & IM_IS_STANDALONE; }
|
||||
|
||||
void SetInCache(bool in_cache) {
|
||||
@@ -170,22 +155,6 @@ struct LRUHandle {
|
||||
|
||||
void SetHit() { m_flags |= M_HAS_HIT; }
|
||||
|
||||
void SetIsPending(bool pending) {
|
||||
if (pending) {
|
||||
im_flags |= IM_IS_PENDING;
|
||||
} else {
|
||||
im_flags &= ~IM_IS_PENDING;
|
||||
}
|
||||
}
|
||||
|
||||
void SetIsInSecondaryCache(bool is_in_secondary_cache) {
|
||||
if (is_in_secondary_cache) {
|
||||
im_flags |= IM_IS_IN_SECONDARY_CACHE;
|
||||
} else {
|
||||
im_flags &= ~IM_IS_IN_SECONDARY_CACHE;
|
||||
}
|
||||
}
|
||||
|
||||
void SetIsStandalone(bool is_standalone) {
|
||||
if (is_standalone) {
|
||||
im_flags |= IM_IS_STANDALONE;
|
||||
@@ -196,14 +165,6 @@ struct LRUHandle {
|
||||
|
||||
void Free(MemoryAllocator* allocator) {
|
||||
assert(refs == 0);
|
||||
|
||||
if (UNLIKELY(IsPending())) {
|
||||
assert(sec_handle != nullptr);
|
||||
SecondaryCacheResultHandle* tmp_sec_handle = sec_handle;
|
||||
tmp_sec_handle->Wait();
|
||||
value = tmp_sec_handle->Value();
|
||||
delete tmp_sec_handle;
|
||||
}
|
||||
assert(helper);
|
||||
if (helper->del_cb) {
|
||||
helper->del_cb(value, allocator);
|
||||
@@ -303,12 +264,14 @@ class LRUHandleTable {
|
||||
// A single shard of sharded cache.
|
||||
class ALIGN_AS(CACHE_LINE_SIZE) LRUCacheShard final : public CacheShardBase {
|
||||
public:
|
||||
// NOTE: the eviction_callback ptr is saved, as is it assumed to be kept
|
||||
// alive in Cache.
|
||||
LRUCacheShard(size_t capacity, bool strict_capacity_limit,
|
||||
double high_pri_pool_ratio, double low_pri_pool_ratio,
|
||||
bool use_adaptive_mutex,
|
||||
CacheMetadataChargePolicy metadata_charge_policy,
|
||||
int max_upper_hash_bits, MemoryAllocator* allocator,
|
||||
SecondaryCache* secondary_cache);
|
||||
const Cache::EvictionCallback* eviction_callback);
|
||||
|
||||
public: // Type definitions expected as parameter to ShardedCache
|
||||
using HandleImpl = LRUHandle;
|
||||
@@ -316,8 +279,8 @@ class ALIGN_AS(CACHE_LINE_SIZE) LRUCacheShard final : public CacheShardBase {
|
||||
using HashCref = uint32_t;
|
||||
|
||||
public: // Function definitions expected as parameter to ShardedCache
|
||||
static inline HashVal ComputeHash(const Slice& key) {
|
||||
return Lower32of64(GetSliceNPHash64(key));
|
||||
static inline HashVal ComputeHash(const Slice& key, uint32_t seed) {
|
||||
return Lower32of64(GetSliceNPHash64(key, seed));
|
||||
}
|
||||
|
||||
// Separate from constructor so caller can easily make an array of LRUCache
|
||||
@@ -339,14 +302,17 @@ class ALIGN_AS(CACHE_LINE_SIZE) LRUCacheShard final : public CacheShardBase {
|
||||
const Cache::CacheItemHelper* helper, size_t charge,
|
||||
LRUHandle** handle, Cache::Priority priority);
|
||||
|
||||
LRUHandle* CreateStandalone(const Slice& key, uint32_t hash,
|
||||
Cache::ObjectPtr obj,
|
||||
const Cache::CacheItemHelper* helper,
|
||||
size_t charge, bool allow_uncharged);
|
||||
|
||||
LRUHandle* Lookup(const Slice& key, uint32_t hash,
|
||||
const Cache::CacheItemHelper* helper,
|
||||
Cache::CreateContext* create_context,
|
||||
Cache::Priority priority, bool wait, Statistics* stats);
|
||||
Cache::Priority priority, Statistics* stats);
|
||||
|
||||
bool Release(LRUHandle* handle, bool useful, bool erase_if_last_ref);
|
||||
bool IsReady(LRUHandle* /*handle*/);
|
||||
void Wait(LRUHandle* /*handle*/) {}
|
||||
bool Ref(LRUHandle* handle);
|
||||
void Erase(const Slice& key, uint32_t hash);
|
||||
|
||||
@@ -386,20 +352,10 @@ class ALIGN_AS(CACHE_LINE_SIZE) LRUCacheShard final : public CacheShardBase {
|
||||
private:
|
||||
friend class LRUCache;
|
||||
// Insert an item into the hash table and, if handle is null, insert into
|
||||
// the LRU list. Older items are evicted as necessary. If the cache is full
|
||||
// and free_handle_on_fail is true, the item is deleted and handle is set to
|
||||
// nullptr.
|
||||
Status InsertItem(LRUHandle* item, LRUHandle** handle,
|
||||
bool free_handle_on_fail);
|
||||
// Promote an item looked up from the secondary cache to the LRU cache.
|
||||
// The item may be still in the secondary cache.
|
||||
// It is only inserted into the hash table and not the LRU list, and only
|
||||
// if the cache is not at full capacity, as is the case during Insert. The
|
||||
// caller should hold a reference on the LRUHandle. When the caller releases
|
||||
// the last reference, the item is added to the LRU list.
|
||||
// The item is promoted to the high pri or low pri pool as specified by the
|
||||
// caller in Lookup.
|
||||
void Promote(LRUHandle* e);
|
||||
// the LRU list. Older items are evicted as necessary. Frees `item` on
|
||||
// non-OK status.
|
||||
Status InsertItem(LRUHandle* item, LRUHandle** handle);
|
||||
|
||||
void LRU_Remove(LRUHandle* e);
|
||||
void LRU_Insert(LRUHandle* e);
|
||||
|
||||
@@ -413,8 +369,11 @@ class ALIGN_AS(CACHE_LINE_SIZE) LRUCacheShard final : public CacheShardBase {
|
||||
// holding the mutex_.
|
||||
void EvictFromLRU(size_t charge, autovector<LRUHandle*>* deleted);
|
||||
|
||||
// Try to insert the evicted handles into the secondary cache.
|
||||
void TryInsertIntoSecondaryCache(autovector<LRUHandle*> evicted_handles);
|
||||
void NotifyEvicted(const autovector<LRUHandle*>& evicted_handles);
|
||||
|
||||
LRUHandle* CreateHandle(const Slice& key, uint32_t hash,
|
||||
Cache::ObjectPtr value,
|
||||
const Cache::CacheItemHelper* helper, size_t charge);
|
||||
|
||||
// Initialized before use.
|
||||
size_t capacity_;
|
||||
@@ -477,8 +436,8 @@ class ALIGN_AS(CACHE_LINE_SIZE) LRUCacheShard final : public CacheShardBase {
|
||||
// don't mind mutex_ invoking the non-const actions.
|
||||
mutable DMutex mutex_;
|
||||
|
||||
// Owned by LRUCache
|
||||
SecondaryCache* secondary_cache_;
|
||||
// A reference to Cache::eviction_callback_
|
||||
const Cache::EvictionCallback& eviction_callback_;
|
||||
};
|
||||
|
||||
class LRUCache
|
||||
@@ -487,28 +446,16 @@ class LRUCache
|
||||
#endif
|
||||
: public ShardedCache<LRUCacheShard> {
|
||||
public:
|
||||
LRUCache(size_t capacity, int num_shard_bits, bool strict_capacity_limit,
|
||||
double high_pri_pool_ratio, double low_pri_pool_ratio,
|
||||
std::shared_ptr<MemoryAllocator> memory_allocator = nullptr,
|
||||
bool use_adaptive_mutex = kDefaultToAdaptiveMutex,
|
||||
CacheMetadataChargePolicy metadata_charge_policy =
|
||||
kDontChargeCacheMetadata,
|
||||
std::shared_ptr<SecondaryCache> secondary_cache = nullptr);
|
||||
explicit LRUCache(const LRUCacheOptions& opts);
|
||||
const char* Name() const override { return "LRUCache"; }
|
||||
ObjectPtr Value(Handle* handle) override;
|
||||
size_t GetCharge(Handle* handle) const override;
|
||||
const CacheItemHelper* GetCacheItemHelper(Handle* handle) const override;
|
||||
void WaitAll(std::vector<Handle*>& handles) override;
|
||||
|
||||
// Retrieves number of elements in LRU, for unit test purpose only.
|
||||
size_t TEST_GetLRUSize();
|
||||
// Retrieves high pri pool ratio.
|
||||
double GetHighPriPoolRatio();
|
||||
|
||||
void AppendPrintableOptions(std::string& str) const override;
|
||||
|
||||
private:
|
||||
std::shared_ptr<SecondaryCache> secondary_cache_;
|
||||
};
|
||||
|
||||
} // namespace lru_cache
|
||||
|
||||
Vendored
+349
-389
File diff suppressed because it is too large
Load Diff
Vendored
+5
-2
@@ -32,8 +32,11 @@ Status FailCreate(const Slice&, Cache::CreateContext*, MemoryAllocator*,
|
||||
} // namespace
|
||||
|
||||
Status SecondaryCache::InsertSaved(const Slice& key, const Slice& saved) {
|
||||
static Cache::CacheItemHelper helper{CacheEntryRole::kMisc, &NoopDelete,
|
||||
&SliceSize, &SliceSaveTo, &FailCreate};
|
||||
static Cache::CacheItemHelper helper_no_secondary{CacheEntryRole::kMisc,
|
||||
&NoopDelete};
|
||||
static Cache::CacheItemHelper helper{
|
||||
CacheEntryRole::kMisc, &NoopDelete, &SliceSize,
|
||||
&SliceSaveTo, &FailCreate, &helper_no_secondary};
|
||||
// NOTE: depends on Insert() being synchronous, not keeping pointer `&saved`
|
||||
return Insert(key, const_cast<Slice*>(&saved), &helper);
|
||||
}
|
||||
|
||||
Vendored
+295
@@ -0,0 +1,295 @@
|
||||
// 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).
|
||||
|
||||
#include "cache/secondary_cache_adapter.h"
|
||||
|
||||
#include "monitoring/perf_context_imp.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
namespace {
|
||||
// A distinct pointer value for marking "dummy" cache entries
|
||||
struct Dummy {
|
||||
char val[7] = "kDummy";
|
||||
};
|
||||
const Dummy kDummy{};
|
||||
Cache::ObjectPtr const kDummyObj = const_cast<Dummy*>(&kDummy);
|
||||
} // namespace
|
||||
|
||||
CacheWithSecondaryAdapter::CacheWithSecondaryAdapter(
|
||||
std::shared_ptr<Cache> target,
|
||||
std::shared_ptr<SecondaryCache> secondary_cache)
|
||||
: CacheWrapper(std::move(target)),
|
||||
secondary_cache_(std::move(secondary_cache)) {
|
||||
target_->SetEvictionCallback([this](const Slice& key, Handle* handle) {
|
||||
return EvictionHandler(key, handle);
|
||||
});
|
||||
}
|
||||
|
||||
CacheWithSecondaryAdapter::~CacheWithSecondaryAdapter() {
|
||||
// `*this` will be destroyed before `*target_`, so we have to prevent
|
||||
// use after free
|
||||
target_->SetEvictionCallback({});
|
||||
}
|
||||
|
||||
bool CacheWithSecondaryAdapter::EvictionHandler(const Slice& key,
|
||||
Handle* handle) {
|
||||
auto helper = GetCacheItemHelper(handle);
|
||||
if (helper->IsSecondaryCacheCompatible()) {
|
||||
auto obj = target_->Value(handle);
|
||||
// Ignore dummy entry
|
||||
if (obj != kDummyObj) {
|
||||
// Spill into secondary cache.
|
||||
secondary_cache_->Insert(key, obj, helper).PermitUncheckedError();
|
||||
}
|
||||
}
|
||||
// Never takes ownership of obj
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CacheWithSecondaryAdapter::ProcessDummyResult(Cache::Handle** handle,
|
||||
bool erase) {
|
||||
if (*handle && target_->Value(*handle) == kDummyObj) {
|
||||
target_->Release(*handle, erase);
|
||||
*handle = nullptr;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void CacheWithSecondaryAdapter::CleanupCacheObject(
|
||||
ObjectPtr obj, const CacheItemHelper* helper) {
|
||||
if (helper->del_cb) {
|
||||
helper->del_cb(obj, memory_allocator());
|
||||
}
|
||||
}
|
||||
|
||||
Cache::Handle* CacheWithSecondaryAdapter::Promote(
|
||||
std::unique_ptr<SecondaryCacheResultHandle>&& secondary_handle,
|
||||
const Slice& key, const CacheItemHelper* helper, Priority priority,
|
||||
Statistics* stats, bool found_dummy_entry, bool kept_in_sec_cache) {
|
||||
assert(secondary_handle->IsReady());
|
||||
|
||||
ObjectPtr obj = secondary_handle->Value();
|
||||
if (!obj) {
|
||||
// Nothing found.
|
||||
return nullptr;
|
||||
}
|
||||
// Found something.
|
||||
switch (helper->role) {
|
||||
case CacheEntryRole::kFilterBlock:
|
||||
RecordTick(stats, SECONDARY_CACHE_FILTER_HITS);
|
||||
break;
|
||||
case CacheEntryRole::kIndexBlock:
|
||||
RecordTick(stats, SECONDARY_CACHE_INDEX_HITS);
|
||||
break;
|
||||
case CacheEntryRole::kDataBlock:
|
||||
RecordTick(stats, SECONDARY_CACHE_DATA_HITS);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
PERF_COUNTER_ADD(secondary_cache_hit_count, 1);
|
||||
RecordTick(stats, SECONDARY_CACHE_HITS);
|
||||
|
||||
// Note: SecondaryCache::Size() is really charge (from the CreateCallback)
|
||||
size_t charge = secondary_handle->Size();
|
||||
Handle* result = nullptr;
|
||||
// Insert into primary cache, possibly as a standalone+dummy entries.
|
||||
if (secondary_cache_->SupportForceErase() && !found_dummy_entry) {
|
||||
// Create standalone and insert dummy
|
||||
// Allow standalone to be created even if cache is full, to avoid
|
||||
// reading the entry from storage.
|
||||
result =
|
||||
CreateStandalone(key, obj, helper, charge, /*allow_uncharged*/ true);
|
||||
assert(result);
|
||||
PERF_COUNTER_ADD(block_cache_standalone_handle_count, 1);
|
||||
|
||||
// Insert dummy to record recent use
|
||||
// TODO: try to avoid case where inserting this dummy could overwrite a
|
||||
// regular entry
|
||||
Status s = Insert(key, kDummyObj, &kNoopCacheItemHelper, /*charge=*/0,
|
||||
/*handle=*/nullptr, priority);
|
||||
s.PermitUncheckedError();
|
||||
// Nothing to do or clean up on dummy insertion failure
|
||||
} else {
|
||||
// Insert regular entry into primary cache.
|
||||
// Don't allow it to spill into secondary cache again if it was kept there.
|
||||
Status s = Insert(
|
||||
key, obj, kept_in_sec_cache ? helper->without_secondary_compat : helper,
|
||||
charge, &result, priority);
|
||||
if (s.ok()) {
|
||||
assert(result);
|
||||
PERF_COUNTER_ADD(block_cache_real_handle_count, 1);
|
||||
} else {
|
||||
// Create standalone result instead, even if cache is full, to avoid
|
||||
// reading the entry from storage.
|
||||
result =
|
||||
CreateStandalone(key, obj, helper, charge, /*allow_uncharged*/ true);
|
||||
assert(result);
|
||||
PERF_COUNTER_ADD(block_cache_standalone_handle_count, 1);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
Cache::Handle* CacheWithSecondaryAdapter::Lookup(const Slice& key,
|
||||
const CacheItemHelper* helper,
|
||||
CreateContext* create_context,
|
||||
Priority priority,
|
||||
Statistics* stats) {
|
||||
// NOTE: we could just StartAsyncLookup() and Wait(), but this should be a bit
|
||||
// more efficient
|
||||
Handle* result =
|
||||
target_->Lookup(key, helper, create_context, priority, stats);
|
||||
bool secondary_compatible = helper && helper->IsSecondaryCacheCompatible();
|
||||
bool found_dummy_entry =
|
||||
ProcessDummyResult(&result, /*erase=*/secondary_compatible);
|
||||
if (!result && secondary_compatible) {
|
||||
// Try our secondary cache
|
||||
bool kept_in_sec_cache = false;
|
||||
std::unique_ptr<SecondaryCacheResultHandle> secondary_handle =
|
||||
secondary_cache_->Lookup(key, helper, create_context, /*wait*/ true,
|
||||
found_dummy_entry, /*out*/ kept_in_sec_cache);
|
||||
if (secondary_handle) {
|
||||
result = Promote(std::move(secondary_handle), key, helper, priority,
|
||||
stats, found_dummy_entry, kept_in_sec_cache);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
Cache::ObjectPtr CacheWithSecondaryAdapter::Value(Handle* handle) {
|
||||
ObjectPtr v = target_->Value(handle);
|
||||
// TODO with stacked secondaries: might fail in EvictionHandler
|
||||
assert(v != kDummyObj);
|
||||
return v;
|
||||
}
|
||||
|
||||
void CacheWithSecondaryAdapter::StartAsyncLookupOnMySecondary(
|
||||
AsyncLookupHandle& async_handle) {
|
||||
assert(!async_handle.IsPending());
|
||||
assert(async_handle.result_handle == nullptr);
|
||||
|
||||
std::unique_ptr<SecondaryCacheResultHandle> secondary_handle =
|
||||
secondary_cache_->Lookup(async_handle.key, async_handle.helper,
|
||||
async_handle.create_context, /*wait*/ false,
|
||||
async_handle.found_dummy_entry,
|
||||
/*out*/ async_handle.kept_in_sec_cache);
|
||||
if (secondary_handle) {
|
||||
// TODO with stacked secondaries: Check & process if already ready?
|
||||
async_handle.pending_handle = secondary_handle.release();
|
||||
async_handle.pending_cache = secondary_cache_.get();
|
||||
}
|
||||
}
|
||||
|
||||
void CacheWithSecondaryAdapter::StartAsyncLookup(
|
||||
AsyncLookupHandle& async_handle) {
|
||||
target_->StartAsyncLookup(async_handle);
|
||||
if (!async_handle.IsPending()) {
|
||||
bool secondary_compatible =
|
||||
async_handle.helper &&
|
||||
async_handle.helper->IsSecondaryCacheCompatible();
|
||||
async_handle.found_dummy_entry |= ProcessDummyResult(
|
||||
&async_handle.result_handle, /*erase=*/secondary_compatible);
|
||||
|
||||
if (async_handle.Result() == nullptr && secondary_compatible) {
|
||||
// Not found and not pending on another secondary cache
|
||||
StartAsyncLookupOnMySecondary(async_handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CacheWithSecondaryAdapter::WaitAll(AsyncLookupHandle* async_handles,
|
||||
size_t count) {
|
||||
if (count == 0) {
|
||||
// Nothing to do
|
||||
return;
|
||||
}
|
||||
// Requests that are pending on *my* secondary cache, at the start of this
|
||||
// function
|
||||
std::vector<AsyncLookupHandle*> my_pending;
|
||||
// Requests that are pending on an "inner" secondary cache (managed somewhere
|
||||
// under target_), as of the start of this function
|
||||
std::vector<AsyncLookupHandle*> inner_pending;
|
||||
|
||||
// Initial accounting of pending handles, excluding those already handled
|
||||
// by "outer" secondary caches. (See cur->pending_cache = nullptr.)
|
||||
for (size_t i = 0; i < count; ++i) {
|
||||
AsyncLookupHandle* cur = async_handles + i;
|
||||
if (cur->pending_cache) {
|
||||
assert(cur->IsPending());
|
||||
assert(cur->helper);
|
||||
assert(cur->helper->IsSecondaryCacheCompatible());
|
||||
if (cur->pending_cache == secondary_cache_.get()) {
|
||||
my_pending.push_back(cur);
|
||||
// Mark as "to be handled by this caller"
|
||||
cur->pending_cache = nullptr;
|
||||
} else {
|
||||
// Remember as potentially needing a lookup in my secondary
|
||||
inner_pending.push_back(cur);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Wait on inner-most cache lookups first
|
||||
// TODO with stacked secondaries: because we are not using proper
|
||||
// async/await constructs here yet, there is a false synchronization point
|
||||
// here where all the results at one level are needed before initiating
|
||||
// any lookups at the next level. Probably not a big deal, but worth noting.
|
||||
if (!inner_pending.empty()) {
|
||||
target_->WaitAll(async_handles, count);
|
||||
}
|
||||
|
||||
// For those that failed to find something, convert to lookup in my
|
||||
// secondary cache.
|
||||
for (AsyncLookupHandle* cur : inner_pending) {
|
||||
if (cur->Result() == nullptr) {
|
||||
// Not found, try my secondary
|
||||
StartAsyncLookupOnMySecondary(*cur);
|
||||
if (cur->IsPending()) {
|
||||
assert(cur->pending_cache == secondary_cache_.get());
|
||||
my_pending.push_back(cur);
|
||||
// Mark as "to be handled by this caller"
|
||||
cur->pending_cache = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Wait on all lookups on my secondary cache
|
||||
{
|
||||
std::vector<SecondaryCacheResultHandle*> my_secondary_handles;
|
||||
for (AsyncLookupHandle* cur : my_pending) {
|
||||
my_secondary_handles.push_back(cur->pending_handle);
|
||||
}
|
||||
secondary_cache_->WaitAll(my_secondary_handles);
|
||||
}
|
||||
|
||||
// Process results
|
||||
for (AsyncLookupHandle* cur : my_pending) {
|
||||
std::unique_ptr<SecondaryCacheResultHandle> secondary_handle(
|
||||
cur->pending_handle);
|
||||
cur->pending_handle = nullptr;
|
||||
cur->result_handle = Promote(
|
||||
std::move(secondary_handle), cur->key, cur->helper, cur->priority,
|
||||
cur->stats, cur->found_dummy_entry, cur->kept_in_sec_cache);
|
||||
assert(cur->pending_cache == nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
std::string CacheWithSecondaryAdapter::GetPrintableOptions() const {
|
||||
std::string str = target_->GetPrintableOptions();
|
||||
str.append(" secondary_cache:\n");
|
||||
str.append(secondary_cache_->GetPrintableOptions());
|
||||
return str;
|
||||
}
|
||||
|
||||
const char* CacheWithSecondaryAdapter::Name() const {
|
||||
// To the user, at least for now, configure the underlying cache with
|
||||
// a secondary cache. So we pretend to be that cache
|
||||
return target_->Name();
|
||||
}
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
Vendored
+52
@@ -0,0 +1,52 @@
|
||||
// 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 "rocksdb/secondary_cache.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
class CacheWithSecondaryAdapter : public CacheWrapper {
|
||||
public:
|
||||
explicit CacheWithSecondaryAdapter(
|
||||
std::shared_ptr<Cache> target,
|
||||
std::shared_ptr<SecondaryCache> secondary_cache);
|
||||
|
||||
~CacheWithSecondaryAdapter() override;
|
||||
|
||||
Handle* Lookup(const Slice& key, const CacheItemHelper* helper,
|
||||
CreateContext* create_context,
|
||||
Priority priority = Priority::LOW,
|
||||
Statistics* stats = nullptr) override;
|
||||
|
||||
ObjectPtr Value(Handle* handle) override;
|
||||
|
||||
void StartAsyncLookup(AsyncLookupHandle& async_handle) override;
|
||||
|
||||
void WaitAll(AsyncLookupHandle* async_handles, size_t count) override;
|
||||
|
||||
std::string GetPrintableOptions() const override;
|
||||
|
||||
const char* Name() const override;
|
||||
|
||||
private:
|
||||
bool EvictionHandler(const Slice& key, Handle* handle);
|
||||
|
||||
void StartAsyncLookupOnMySecondary(AsyncLookupHandle& async_handle);
|
||||
|
||||
Handle* Promote(
|
||||
std::unique_ptr<SecondaryCacheResultHandle>&& secondary_handle,
|
||||
const Slice& key, const CacheItemHelper* helper, Priority priority,
|
||||
Statistics* stats, bool found_dummy_entry, bool kept_in_sec_cache);
|
||||
|
||||
bool ProcessDummyResult(Cache::Handle** handle, bool erase);
|
||||
|
||||
void CleanupCacheObject(ObjectPtr obj, const CacheItemHelper* helper);
|
||||
|
||||
std::shared_ptr<SecondaryCache> secondary_cache_;
|
||||
};
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
Vendored
+44
-7
@@ -13,20 +13,57 @@
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
|
||||
#include "env/unique_id_gen.h"
|
||||
#include "rocksdb/env.h"
|
||||
#include "util/hash.h"
|
||||
#include "util/math.h"
|
||||
#include "util/mutexlock.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
namespace {
|
||||
// The generated seeds must fit in 31 bits so that
|
||||
// ShardedCacheOptions::hash_seed can be set to it explicitly, for
|
||||
// diagnostic/debugging purposes.
|
||||
constexpr uint32_t kSeedMask = 0x7fffffff;
|
||||
uint32_t DetermineSeed(int32_t hash_seed_option) {
|
||||
if (hash_seed_option >= 0) {
|
||||
// User-specified exact seed
|
||||
return static_cast<uint32_t>(hash_seed_option);
|
||||
}
|
||||
static SemiStructuredUniqueIdGen gen;
|
||||
if (hash_seed_option == ShardedCacheOptions::kHostHashSeed) {
|
||||
std::string hostname;
|
||||
Status s = Env::Default()->GetHostNameString(&hostname);
|
||||
if (s.ok()) {
|
||||
return GetSliceHash(hostname) & kSeedMask;
|
||||
} else {
|
||||
// Fall back on something stable within the process.
|
||||
return static_cast<uint32_t>(gen.GetBaseUpper()) & kSeedMask;
|
||||
}
|
||||
} else {
|
||||
// for kQuasiRandomHashSeed and fallback
|
||||
uint32_t val = gen.GenerateNext<uint32_t>() & kSeedMask;
|
||||
// Perform some 31-bit bijective transformations so that we get
|
||||
// quasirandom, not just incrementing. (An incrementing seed from a
|
||||
// random starting point would be fine, but hard to describe in a name.)
|
||||
// See https://en.wikipedia.org/wiki/Quasirandom and using a murmur-like
|
||||
// transformation here for our bijection in the lower 31 bits.
|
||||
// See https://en.wikipedia.org/wiki/MurmurHash
|
||||
val *= /*31-bit prime*/ 1150630961;
|
||||
val ^= (val & kSeedMask) >> 17;
|
||||
val *= /*31-bit prime*/ 1320603883;
|
||||
return val & kSeedMask;
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
ShardedCacheBase::ShardedCacheBase(size_t capacity, int num_shard_bits,
|
||||
bool strict_capacity_limit,
|
||||
std::shared_ptr<MemoryAllocator> allocator)
|
||||
: Cache(std::move(allocator)),
|
||||
ShardedCacheBase::ShardedCacheBase(const ShardedCacheOptions& opts)
|
||||
: Cache(opts.memory_allocator),
|
||||
last_id_(1),
|
||||
shard_mask_((uint32_t{1} << num_shard_bits) - 1),
|
||||
strict_capacity_limit_(strict_capacity_limit),
|
||||
capacity_(capacity) {}
|
||||
shard_mask_((uint32_t{1} << opts.num_shard_bits) - 1),
|
||||
hash_seed_(DetermineSeed(opts.hash_seed)),
|
||||
strict_capacity_limit_(opts.strict_capacity_limit),
|
||||
capacity_(opts.capacity) {}
|
||||
|
||||
size_t ShardedCacheBase::ComputePerShardCapacity(size_t capacity) const {
|
||||
uint32_t num_shards = GetNumShards();
|
||||
|
||||
Vendored
+35
-33
@@ -15,7 +15,7 @@
|
||||
|
||||
#include "port/lang.h"
|
||||
#include "port/port.h"
|
||||
#include "rocksdb/cache.h"
|
||||
#include "rocksdb/advanced_cache.h"
|
||||
#include "util/hash.h"
|
||||
#include "util/mutexlock.h"
|
||||
|
||||
@@ -34,8 +34,8 @@ class CacheShardBase {
|
||||
std::string GetPrintableOptions() const { return ""; }
|
||||
using HashVal = uint64_t;
|
||||
using HashCref = uint64_t;
|
||||
static inline HashVal ComputeHash(const Slice& key) {
|
||||
return GetSliceNPHash64(key);
|
||||
static inline HashVal ComputeHash(const Slice& key, uint32_t seed) {
|
||||
return GetSliceNPHash64(key, seed);
|
||||
}
|
||||
static inline uint32_t HashPieceForSharding(HashCref hash) {
|
||||
return Lower32of64(hash);
|
||||
@@ -51,15 +51,17 @@ class CacheShardBase {
|
||||
};
|
||||
Status Insert(const Slice& key, HashCref hash, Cache::ObjectPtr value,
|
||||
const Cache::CacheItemHelper* helper, size_t charge,
|
||||
HandleImpl** handle, Cache::Priority priority) = 0;
|
||||
HandleImpl** handle, Cache::Priority priority,
|
||||
bool standalone) = 0;
|
||||
Handle* CreateStandalone(const Slice& key, HashCref hash, ObjectPtr obj,
|
||||
const CacheItemHelper* helper,
|
||||
size_t charge, bool allow_uncharged) = 0;
|
||||
HandleImpl* Lookup(const Slice& key, HashCref hash,
|
||||
const Cache::CacheItemHelper* helper,
|
||||
Cache::CreateContext* create_context,
|
||||
Cache::Priority priority, bool wait,
|
||||
Cache::Priority priority,
|
||||
Statistics* stats) = 0;
|
||||
bool Release(HandleImpl* handle, bool useful, bool erase_if_last_ref) = 0;
|
||||
bool IsReady(HandleImpl* handle) = 0;
|
||||
void Wait(HandleImpl* handle) = 0;
|
||||
bool Ref(HandleImpl* handle) = 0;
|
||||
void Erase(const Slice& key, HashCref hash) = 0;
|
||||
void SetCapacity(size_t capacity) = 0;
|
||||
@@ -87,9 +89,7 @@ class CacheShardBase {
|
||||
// Portions of ShardedCache that do not depend on the template parameter
|
||||
class ShardedCacheBase : public Cache {
|
||||
public:
|
||||
ShardedCacheBase(size_t capacity, int num_shard_bits,
|
||||
bool strict_capacity_limit,
|
||||
std::shared_ptr<MemoryAllocator> memory_allocator);
|
||||
explicit ShardedCacheBase(const ShardedCacheOptions& opts);
|
||||
virtual ~ShardedCacheBase() = default;
|
||||
|
||||
int GetNumShardBits() const;
|
||||
@@ -104,6 +104,8 @@ class ShardedCacheBase : public Cache {
|
||||
size_t GetUsage(Handle* handle) const override;
|
||||
std::string GetPrintableOptions() const override;
|
||||
|
||||
uint32_t GetHashSeed() const override { return hash_seed_; }
|
||||
|
||||
protected: // fns
|
||||
virtual void AppendPrintableOptions(std::string& str) const = 0;
|
||||
size_t GetPerShardCapacity() const;
|
||||
@@ -112,6 +114,7 @@ class ShardedCacheBase : public Cache {
|
||||
protected: // data
|
||||
std::atomic<uint64_t> last_id_; // For NewId
|
||||
const uint32_t shard_mask_;
|
||||
const uint32_t hash_seed_;
|
||||
|
||||
// Dynamic configuration parameters, guarded by config_mutex_
|
||||
bool strict_capacity_limit_;
|
||||
@@ -132,10 +135,8 @@ class ShardedCache : public ShardedCacheBase {
|
||||
using HashCref = typename CacheShard::HashCref;
|
||||
using HandleImpl = typename CacheShard::HandleImpl;
|
||||
|
||||
ShardedCache(size_t capacity, int num_shard_bits, bool strict_capacity_limit,
|
||||
std::shared_ptr<MemoryAllocator> allocator)
|
||||
: ShardedCacheBase(capacity, num_shard_bits, strict_capacity_limit,
|
||||
allocator),
|
||||
explicit ShardedCache(const ShardedCacheOptions& opts)
|
||||
: ShardedCacheBase(opts),
|
||||
shards_(reinterpret_cast<CacheShard*>(port::cacheline_aligned_alloc(
|
||||
sizeof(CacheShard) * GetNumShards()))),
|
||||
destroy_shards_in_dtor_(false) {}
|
||||
@@ -169,29 +170,38 @@ class ShardedCache : public ShardedCacheBase {
|
||||
[s_c_l](CacheShard* cs) { cs->SetStrictCapacityLimit(s_c_l); });
|
||||
}
|
||||
|
||||
Status Insert(const Slice& key, ObjectPtr value,
|
||||
const CacheItemHelper* helper, size_t charge,
|
||||
Handle** handle = nullptr,
|
||||
Status Insert(const Slice& key, ObjectPtr obj, const CacheItemHelper* helper,
|
||||
size_t charge, Handle** handle = nullptr,
|
||||
Priority priority = Priority::LOW) override {
|
||||
assert(helper);
|
||||
HashVal hash = CacheShard::ComputeHash(key);
|
||||
HashVal hash = CacheShard::ComputeHash(key, hash_seed_);
|
||||
auto h_out = reinterpret_cast<HandleImpl**>(handle);
|
||||
return GetShard(hash).Insert(key, hash, value, helper, charge, h_out,
|
||||
return GetShard(hash).Insert(key, hash, obj, helper, charge, h_out,
|
||||
priority);
|
||||
}
|
||||
|
||||
Handle* CreateStandalone(const Slice& key, ObjectPtr obj,
|
||||
const CacheItemHelper* helper, size_t charge,
|
||||
bool allow_uncharged) override {
|
||||
assert(helper);
|
||||
HashVal hash = CacheShard::ComputeHash(key, hash_seed_);
|
||||
HandleImpl* result = GetShard(hash).CreateStandalone(
|
||||
key, hash, obj, helper, charge, allow_uncharged);
|
||||
return reinterpret_cast<Handle*>(result);
|
||||
}
|
||||
|
||||
Handle* Lookup(const Slice& key, const CacheItemHelper* helper = nullptr,
|
||||
CreateContext* create_context = nullptr,
|
||||
Priority priority = Priority::LOW, bool wait = true,
|
||||
Priority priority = Priority::LOW,
|
||||
Statistics* stats = nullptr) override {
|
||||
HashVal hash = CacheShard::ComputeHash(key);
|
||||
HandleImpl* result = GetShard(hash).Lookup(
|
||||
key, hash, helper, create_context, priority, wait, stats);
|
||||
HashVal hash = CacheShard::ComputeHash(key, hash_seed_);
|
||||
HandleImpl* result = GetShard(hash).Lookup(key, hash, helper,
|
||||
create_context, priority, stats);
|
||||
return reinterpret_cast<Handle*>(result);
|
||||
}
|
||||
|
||||
void Erase(const Slice& key) override {
|
||||
HashVal hash = CacheShard::ComputeHash(key);
|
||||
HashVal hash = CacheShard::ComputeHash(key, hash_seed_);
|
||||
GetShard(hash).Erase(key, hash);
|
||||
}
|
||||
|
||||
@@ -200,14 +210,6 @@ class ShardedCache : public ShardedCacheBase {
|
||||
auto h = reinterpret_cast<HandleImpl*>(handle);
|
||||
return GetShard(h->GetHash()).Release(h, useful, erase_if_last_ref);
|
||||
}
|
||||
bool IsReady(Handle* handle) override {
|
||||
auto h = reinterpret_cast<HandleImpl*>(handle);
|
||||
return GetShard(h->GetHash()).IsReady(h);
|
||||
}
|
||||
void Wait(Handle* handle) override {
|
||||
auto h = reinterpret_cast<HandleImpl*>(handle);
|
||||
GetShard(h->GetHash()).Wait(h);
|
||||
}
|
||||
bool Ref(Handle* handle) override {
|
||||
auto h = reinterpret_cast<HandleImpl*>(handle);
|
||||
return GetShard(h->GetHash()).Ref(h);
|
||||
@@ -223,7 +225,7 @@ class ShardedCache : public ShardedCacheBase {
|
||||
return SumOverShards2(&CacheShard::GetPinnedUsage);
|
||||
}
|
||||
size_t GetOccupancyCount() const override {
|
||||
return SumOverShards2(&CacheShard::GetPinnedUsage);
|
||||
return SumOverShards2(&CacheShard::GetOccupancyCount);
|
||||
}
|
||||
size_t GetTableAddressCount() const override {
|
||||
return SumOverShards2(&CacheShard::GetTableAddressCount);
|
||||
|
||||
Vendored
+57
-21
@@ -29,8 +29,8 @@
|
||||
#include <type_traits>
|
||||
|
||||
#include "cache/cache_helpers.h"
|
||||
#include "rocksdb/advanced_cache.h"
|
||||
#include "rocksdb/advanced_options.h"
|
||||
#include "rocksdb/cache.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
@@ -83,11 +83,14 @@ class PlaceholderCacheInterface : public BaseCacheInterface<CachePtr> {
|
||||
using BaseCacheInterface<CachePtr>::BaseCacheInterface;
|
||||
|
||||
inline Status Insert(const Slice& key, size_t charge, Handle** handle) {
|
||||
return this->cache_->Insert(key, /*value=*/nullptr, &kHelper, charge,
|
||||
return this->cache_->Insert(key, /*value=*/nullptr, GetHelper(), charge,
|
||||
handle);
|
||||
}
|
||||
|
||||
static constexpr Cache::CacheItemHelper kHelper{kRole};
|
||||
static const Cache::CacheItemHelper* GetHelper() {
|
||||
static const Cache::CacheItemHelper kHelper{kRole};
|
||||
return &kHelper;
|
||||
}
|
||||
};
|
||||
|
||||
template <CacheEntryRole kRole>
|
||||
@@ -128,8 +131,11 @@ class BasicTypedCacheHelperFns {
|
||||
template <class TValue, CacheEntryRole kRole>
|
||||
class BasicTypedCacheHelper : public BasicTypedCacheHelperFns<TValue> {
|
||||
public:
|
||||
static constexpr Cache::CacheItemHelper kBasicHelper{
|
||||
kRole, &BasicTypedCacheHelper::Delete};
|
||||
static const Cache::CacheItemHelper* GetBasicHelper() {
|
||||
static const Cache::CacheItemHelper kHelper{kRole,
|
||||
&BasicTypedCacheHelper::Delete};
|
||||
return &kHelper;
|
||||
}
|
||||
};
|
||||
|
||||
// BasicTypedCacheInterface - Used for primary cache storage of objects of
|
||||
@@ -144,9 +150,14 @@ class BasicTypedCacheInterface : public BaseCacheInterface<CachePtr>,
|
||||
CACHE_TYPE_DEFS();
|
||||
using typename BasicTypedCacheHelperFns<TValue>::TValuePtr;
|
||||
struct TypedHandle : public Handle {};
|
||||
using BasicTypedCacheHelper<TValue, kRole>::kBasicHelper;
|
||||
using BasicTypedCacheHelper<TValue, kRole>::GetBasicHelper;
|
||||
// ctor
|
||||
using BaseCacheInterface<CachePtr>::BaseCacheInterface;
|
||||
struct TypedAsyncLookupHandle : public Cache::AsyncLookupHandle {
|
||||
TypedHandle* Result() {
|
||||
return reinterpret_cast<TypedHandle*>(Cache::AsyncLookupHandle::Result());
|
||||
}
|
||||
};
|
||||
|
||||
inline Status Insert(const Slice& key, TValuePtr value, size_t charge,
|
||||
TypedHandle** handle = nullptr,
|
||||
@@ -154,7 +165,7 @@ class BasicTypedCacheInterface : public BaseCacheInterface<CachePtr>,
|
||||
auto untyped_handle = reinterpret_cast<Handle**>(handle);
|
||||
return this->cache_->Insert(
|
||||
key, BasicTypedCacheHelperFns<TValue>::UpCastValue(value),
|
||||
&kBasicHelper, charge, untyped_handle, priority);
|
||||
GetBasicHelper(), charge, untyped_handle, priority);
|
||||
}
|
||||
|
||||
inline TypedHandle* Lookup(const Slice& key, Statistics* stats = nullptr) {
|
||||
@@ -162,6 +173,11 @@ class BasicTypedCacheInterface : public BaseCacheInterface<CachePtr>,
|
||||
this->cache_->BasicLookup(key, stats));
|
||||
}
|
||||
|
||||
inline void StartAsyncLookup(TypedAsyncLookupHandle& async_handle) {
|
||||
assert(async_handle.helper == nullptr);
|
||||
this->cache_->StartAsyncLookup(async_handle);
|
||||
}
|
||||
|
||||
inline CacheHandleGuard<TValue> Guard(TypedHandle* handle) {
|
||||
if (handle) {
|
||||
return CacheHandleGuard<TValue>(&*this->cache_, handle);
|
||||
@@ -239,9 +255,16 @@ template <class TValue, class TCreateContext, CacheEntryRole kRole>
|
||||
class FullTypedCacheHelper
|
||||
: public FullTypedCacheHelperFns<TValue, TCreateContext> {
|
||||
public:
|
||||
static constexpr Cache::CacheItemHelper kFullHelper{
|
||||
kRole, &FullTypedCacheHelper::Delete, &FullTypedCacheHelper::Size,
|
||||
&FullTypedCacheHelper::SaveTo, &FullTypedCacheHelper::Create};
|
||||
static const Cache::CacheItemHelper* GetFullHelper() {
|
||||
static const Cache::CacheItemHelper kHelper{
|
||||
kRole,
|
||||
&FullTypedCacheHelper::Delete,
|
||||
&FullTypedCacheHelper::Size,
|
||||
&FullTypedCacheHelper::SaveTo,
|
||||
&FullTypedCacheHelper::Create,
|
||||
BasicTypedCacheHelper<TValue, kRole>::GetBasicHelper()};
|
||||
return &kHelper;
|
||||
}
|
||||
};
|
||||
|
||||
// FullTypedCacheHelper - Used for secondary cache compatible storage of
|
||||
@@ -262,9 +285,11 @@ class FullTypedCacheInterface
|
||||
public:
|
||||
CACHE_TYPE_DEFS();
|
||||
using typename BasicTypedCacheInterface<TValue, kRole, CachePtr>::TypedHandle;
|
||||
using typename BasicTypedCacheInterface<TValue, kRole,
|
||||
CachePtr>::TypedAsyncLookupHandle;
|
||||
using typename BasicTypedCacheHelperFns<TValue>::TValuePtr;
|
||||
using BasicTypedCacheHelper<TValue, kRole>::kBasicHelper;
|
||||
using FullTypedCacheHelper<TValue, TCreateContext, kRole>::kFullHelper;
|
||||
using BasicTypedCacheHelper<TValue, kRole>::GetBasicHelper;
|
||||
using FullTypedCacheHelper<TValue, TCreateContext, kRole>::GetFullHelper;
|
||||
using BasicTypedCacheHelperFns<TValue>::UpCastValue;
|
||||
using BasicTypedCacheHelperFns<TValue>::DownCastValue;
|
||||
// ctor
|
||||
@@ -279,8 +304,8 @@ class FullTypedCacheInterface
|
||||
CacheTier lowest_used_cache_tier = CacheTier::kNonVolatileBlockTier) {
|
||||
auto untyped_handle = reinterpret_cast<Handle**>(handle);
|
||||
auto helper = lowest_used_cache_tier == CacheTier::kNonVolatileBlockTier
|
||||
? &kFullHelper
|
||||
: &kBasicHelper;
|
||||
? GetFullHelper()
|
||||
: GetBasicHelper();
|
||||
return this->cache_->Insert(key, UpCastValue(value), helper, charge,
|
||||
untyped_handle, priority);
|
||||
}
|
||||
@@ -294,9 +319,9 @@ class FullTypedCacheInterface
|
||||
size_t* out_charge = nullptr) {
|
||||
ObjectPtr value;
|
||||
size_t charge;
|
||||
Status st = kFullHelper.create_cb(data, create_context,
|
||||
this->cache_->memory_allocator(), &value,
|
||||
&charge);
|
||||
Status st = GetFullHelper()->create_cb(data, create_context,
|
||||
this->cache_->memory_allocator(),
|
||||
&value, &charge);
|
||||
if (out_charge) {
|
||||
*out_charge = charge;
|
||||
}
|
||||
@@ -304,7 +329,7 @@ class FullTypedCacheInterface
|
||||
st = InsertFull(key, DownCastValue(value), charge, nullptr /*handle*/,
|
||||
priority, lowest_used_cache_tier);
|
||||
} else {
|
||||
kFullHelper.del_cb(value, this->cache_->memory_allocator());
|
||||
GetFullHelper()->del_cb(value, this->cache_->memory_allocator());
|
||||
}
|
||||
return st;
|
||||
}
|
||||
@@ -313,17 +338,28 @@ class FullTypedCacheInterface
|
||||
// (Basic Lookup() also inherited.)
|
||||
inline TypedHandle* LookupFull(
|
||||
const Slice& key, TCreateContext* create_context = nullptr,
|
||||
Priority priority = Priority::LOW, bool wait = true,
|
||||
Statistics* stats = nullptr,
|
||||
Priority priority = Priority::LOW, Statistics* stats = nullptr,
|
||||
CacheTier lowest_used_cache_tier = CacheTier::kNonVolatileBlockTier) {
|
||||
if (lowest_used_cache_tier == CacheTier::kNonVolatileBlockTier) {
|
||||
return reinterpret_cast<TypedHandle*>(this->cache_->Lookup(
|
||||
key, &kFullHelper, create_context, priority, wait, stats));
|
||||
key, GetFullHelper(), create_context, priority, stats));
|
||||
} else {
|
||||
return BasicTypedCacheInterface<TValue, kRole, CachePtr>::Lookup(key,
|
||||
stats);
|
||||
}
|
||||
}
|
||||
|
||||
inline void StartAsyncLookupFull(
|
||||
TypedAsyncLookupHandle& async_handle,
|
||||
CacheTier lowest_used_cache_tier = CacheTier::kNonVolatileBlockTier) {
|
||||
if (lowest_used_cache_tier == CacheTier::kNonVolatileBlockTier) {
|
||||
async_handle.helper = GetFullHelper();
|
||||
this->cache_->StartAsyncLookup(async_handle);
|
||||
} else {
|
||||
BasicTypedCacheInterface<TValue, kRole, CachePtr>::StartAsyncLookup(
|
||||
async_handle);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// FullTypedSharedCacheInterface - Like FullTypedCacheInterface but with a
|
||||
|
||||
@@ -12,7 +12,7 @@ fi
|
||||
ROOT=".."
|
||||
# Fetch right version of gcov
|
||||
if [ -d /mnt/gvfs/third-party -a -z "$CXX" ]; then
|
||||
source $ROOT/build_tools/fbcode_config_platform009.sh
|
||||
source $ROOT/build_tools/fbcode_config_platform010.sh
|
||||
GCOV=$GCC_BASE/bin/gcov
|
||||
else
|
||||
GCOV=$(which gcov)
|
||||
|
||||
@@ -47,6 +47,9 @@ void ArenaWrappedDBIter::Init(
|
||||
read_options_ = read_options;
|
||||
allow_refresh_ = allow_refresh;
|
||||
memtable_range_tombstone_iter_ = nullptr;
|
||||
if (!env->GetFileSystem()->use_async_io()) {
|
||||
read_options_.async_io = false;
|
||||
}
|
||||
}
|
||||
|
||||
Status ArenaWrappedDBIter::Refresh() {
|
||||
|
||||
@@ -7,8 +7,8 @@
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "memory/memory_allocator.h"
|
||||
#include "rocksdb/cache.h"
|
||||
#include "memory/memory_allocator_impl.h"
|
||||
#include "rocksdb/advanced_cache.h"
|
||||
#include "rocksdb/rocksdb_namespace.h"
|
||||
#include "rocksdb/slice.h"
|
||||
#include "rocksdb/status.h"
|
||||
|
||||
@@ -123,6 +123,10 @@ class BlobCountingIterator : public InternalIterator {
|
||||
return iter_->GetProperty(prop_name, prop);
|
||||
}
|
||||
|
||||
bool IsDeleteRangeSentinelKey() const override {
|
||||
return iter_->IsDeleteRangeSentinelKey();
|
||||
}
|
||||
|
||||
private:
|
||||
void UpdateAndCountBlobIfNeeded() {
|
||||
assert(!iter_->Valid() || iter_->status().ok());
|
||||
|
||||
@@ -259,6 +259,7 @@ Status BlobFileBuilder::CompressBlobIfNeeded(
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
// TODO: allow user CompressionOptions, including max_compressed_bytes_per_kb
|
||||
CompressionOptions opts;
|
||||
CompressionContext context(blob_compression_type_);
|
||||
constexpr uint64_t sample_for_compression = 0;
|
||||
|
||||
@@ -37,7 +37,7 @@ BlobFileCache::BlobFileCache(Cache* cache,
|
||||
}
|
||||
|
||||
Status BlobFileCache::GetBlobFileReader(
|
||||
uint64_t blob_file_number,
|
||||
const ReadOptions& read_options, uint64_t blob_file_number,
|
||||
CacheHandleGuard<BlobFileReader>* blob_file_reader) {
|
||||
assert(blob_file_reader);
|
||||
assert(blob_file_reader->IsEmpty());
|
||||
@@ -73,7 +73,7 @@ Status BlobFileCache::GetBlobFileReader(
|
||||
{
|
||||
assert(file_options_);
|
||||
const Status s = BlobFileReader::Create(
|
||||
*immutable_options_, *file_options_, column_family_id_,
|
||||
*immutable_options_, read_options, *file_options_, column_family_id_,
|
||||
blob_file_read_hist_, blob_file_number, io_tracer_, &reader);
|
||||
if (!s.ok()) {
|
||||
RecordTick(statistics, NO_FILE_ERRORS);
|
||||
|
||||
@@ -32,7 +32,8 @@ class BlobFileCache {
|
||||
BlobFileCache(const BlobFileCache&) = delete;
|
||||
BlobFileCache& operator=(const BlobFileCache&) = delete;
|
||||
|
||||
Status GetBlobFileReader(uint64_t blob_file_number,
|
||||
Status GetBlobFileReader(const ReadOptions& read_options,
|
||||
uint64_t blob_file_number,
|
||||
CacheHandleGuard<BlobFileReader>* blob_file_reader);
|
||||
|
||||
private:
|
||||
|
||||
@@ -118,7 +118,9 @@ TEST_F(BlobFileCacheTest, GetBlobFileReader) {
|
||||
// First try: reader should be opened and put in cache
|
||||
CacheHandleGuard<BlobFileReader> first;
|
||||
|
||||
ASSERT_OK(blob_file_cache.GetBlobFileReader(blob_file_number, &first));
|
||||
const ReadOptions read_options;
|
||||
ASSERT_OK(blob_file_cache.GetBlobFileReader(read_options, blob_file_number,
|
||||
&first));
|
||||
ASSERT_NE(first.GetValue(), nullptr);
|
||||
ASSERT_EQ(options.statistics->getTickerCount(NO_FILE_OPENS), 1);
|
||||
ASSERT_EQ(options.statistics->getTickerCount(NO_FILE_ERRORS), 0);
|
||||
@@ -126,7 +128,8 @@ TEST_F(BlobFileCacheTest, GetBlobFileReader) {
|
||||
// Second try: reader should be served from cache
|
||||
CacheHandleGuard<BlobFileReader> second;
|
||||
|
||||
ASSERT_OK(blob_file_cache.GetBlobFileReader(blob_file_number, &second));
|
||||
ASSERT_OK(blob_file_cache.GetBlobFileReader(read_options, blob_file_number,
|
||||
&second));
|
||||
ASSERT_NE(second.GetValue(), nullptr);
|
||||
ASSERT_EQ(options.statistics->getTickerCount(NO_FILE_OPENS), 1);
|
||||
ASSERT_EQ(options.statistics->getTickerCount(NO_FILE_ERRORS), 0);
|
||||
@@ -163,19 +166,21 @@ TEST_F(BlobFileCacheTest, GetBlobFileReader_Race) {
|
||||
CacheHandleGuard<BlobFileReader> first;
|
||||
CacheHandleGuard<BlobFileReader> second;
|
||||
|
||||
const ReadOptions read_options;
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"BlobFileCache::GetBlobFileReader:DoubleCheck", [&](void* /* arg */) {
|
||||
// Disabling sync points to prevent infinite recursion
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
|
||||
ASSERT_OK(blob_file_cache.GetBlobFileReader(blob_file_number, &second));
|
||||
ASSERT_OK(blob_file_cache.GetBlobFileReader(read_options,
|
||||
blob_file_number, &second));
|
||||
ASSERT_NE(second.GetValue(), nullptr);
|
||||
ASSERT_EQ(options.statistics->getTickerCount(NO_FILE_OPENS), 1);
|
||||
ASSERT_EQ(options.statistics->getTickerCount(NO_FILE_ERRORS), 0);
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
ASSERT_OK(blob_file_cache.GetBlobFileReader(blob_file_number, &first));
|
||||
ASSERT_OK(blob_file_cache.GetBlobFileReader(read_options, blob_file_number,
|
||||
&first));
|
||||
ASSERT_NE(first.GetValue(), nullptr);
|
||||
ASSERT_EQ(options.statistics->getTickerCount(NO_FILE_OPENS), 1);
|
||||
ASSERT_EQ(options.statistics->getTickerCount(NO_FILE_ERRORS), 0);
|
||||
@@ -213,8 +218,10 @@ TEST_F(BlobFileCacheTest, GetBlobFileReader_IOError) {
|
||||
|
||||
CacheHandleGuard<BlobFileReader> reader;
|
||||
|
||||
const ReadOptions read_options;
|
||||
ASSERT_TRUE(
|
||||
blob_file_cache.GetBlobFileReader(blob_file_number, &reader).IsIOError());
|
||||
blob_file_cache.GetBlobFileReader(read_options, blob_file_number, &reader)
|
||||
.IsIOError());
|
||||
ASSERT_EQ(reader.GetValue(), nullptr);
|
||||
ASSERT_EQ(options.statistics->getTickerCount(NO_FILE_OPENS), 1);
|
||||
ASSERT_EQ(options.statistics->getTickerCount(NO_FILE_ERRORS), 1);
|
||||
@@ -253,8 +260,10 @@ TEST_F(BlobFileCacheTest, GetBlobFileReader_CacheFull) {
|
||||
// strict_capacity_limit is set
|
||||
CacheHandleGuard<BlobFileReader> reader;
|
||||
|
||||
ASSERT_TRUE(blob_file_cache.GetBlobFileReader(blob_file_number, &reader)
|
||||
.IsMemoryLimit());
|
||||
const ReadOptions read_options;
|
||||
ASSERT_TRUE(
|
||||
blob_file_cache.GetBlobFileReader(read_options, blob_file_number, &reader)
|
||||
.IsMemoryLimit());
|
||||
ASSERT_EQ(reader.GetValue(), nullptr);
|
||||
ASSERT_EQ(options.statistics->getTickerCount(NO_FILE_OPENS), 1);
|
||||
ASSERT_EQ(options.statistics->getTickerCount(NO_FILE_ERRORS), 1);
|
||||
|
||||
+40
-21
@@ -12,7 +12,7 @@
|
||||
#include "db/blob/blob_log_format.h"
|
||||
#include "file/file_prefetch_buffer.h"
|
||||
#include "file/filename.h"
|
||||
#include "monitoring/statistics.h"
|
||||
#include "monitoring/statistics_impl.h"
|
||||
#include "options/cf_options.h"
|
||||
#include "rocksdb/file_system.h"
|
||||
#include "rocksdb/slice.h"
|
||||
@@ -26,9 +26,10 @@
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
Status BlobFileReader::Create(
|
||||
const ImmutableOptions& immutable_options, const FileOptions& file_options,
|
||||
uint32_t column_family_id, HistogramImpl* blob_file_read_hist,
|
||||
uint64_t blob_file_number, const std::shared_ptr<IOTracer>& io_tracer,
|
||||
const ImmutableOptions& immutable_options, const ReadOptions& read_options,
|
||||
const FileOptions& file_options, uint32_t column_family_id,
|
||||
HistogramImpl* blob_file_read_hist, uint64_t blob_file_number,
|
||||
const std::shared_ptr<IOTracer>& io_tracer,
|
||||
std::unique_ptr<BlobFileReader>* blob_file_reader) {
|
||||
assert(blob_file_reader);
|
||||
assert(!*blob_file_reader);
|
||||
@@ -52,15 +53,17 @@ Status BlobFileReader::Create(
|
||||
CompressionType compression_type = kNoCompression;
|
||||
|
||||
{
|
||||
const Status s = ReadHeader(file_reader.get(), column_family_id, statistics,
|
||||
&compression_type);
|
||||
const Status s =
|
||||
ReadHeader(file_reader.get(), read_options, column_family_id,
|
||||
statistics, &compression_type);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
const Status s = ReadFooter(file_reader.get(), file_size, statistics);
|
||||
const Status s =
|
||||
ReadFooter(file_reader.get(), read_options, file_size, statistics);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
@@ -134,6 +137,7 @@ Status BlobFileReader::OpenFile(
|
||||
}
|
||||
|
||||
Status BlobFileReader::ReadHeader(const RandomAccessFileReader* file_reader,
|
||||
const ReadOptions& read_options,
|
||||
uint32_t column_family_id,
|
||||
Statistics* statistics,
|
||||
CompressionType* compression_type) {
|
||||
@@ -151,9 +155,10 @@ Status BlobFileReader::ReadHeader(const RandomAccessFileReader* file_reader,
|
||||
constexpr size_t read_size = BlobLogHeader::kSize;
|
||||
|
||||
// TODO: rate limit reading headers from blob files.
|
||||
const Status s = ReadFromFile(file_reader, read_offset, read_size,
|
||||
statistics, &header_slice, &buf, &aligned_buf,
|
||||
Env::IO_TOTAL /* rate_limiter_priority */);
|
||||
const Status s =
|
||||
ReadFromFile(file_reader, read_options, read_offset, read_size,
|
||||
statistics, &header_slice, &buf, &aligned_buf,
|
||||
Env::IO_TOTAL /* rate_limiter_priority */);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
@@ -187,6 +192,7 @@ Status BlobFileReader::ReadHeader(const RandomAccessFileReader* file_reader,
|
||||
}
|
||||
|
||||
Status BlobFileReader::ReadFooter(const RandomAccessFileReader* file_reader,
|
||||
const ReadOptions& read_options,
|
||||
uint64_t file_size, Statistics* statistics) {
|
||||
assert(file_size >= BlobLogHeader::kSize + BlobLogFooter::kSize);
|
||||
assert(file_reader);
|
||||
@@ -202,9 +208,10 @@ Status BlobFileReader::ReadFooter(const RandomAccessFileReader* file_reader,
|
||||
constexpr size_t read_size = BlobLogFooter::kSize;
|
||||
|
||||
// TODO: rate limit reading footers from blob files.
|
||||
const Status s = ReadFromFile(file_reader, read_offset, read_size,
|
||||
statistics, &footer_slice, &buf, &aligned_buf,
|
||||
Env::IO_TOTAL /* rate_limiter_priority */);
|
||||
const Status s =
|
||||
ReadFromFile(file_reader, read_options, read_offset, read_size,
|
||||
statistics, &footer_slice, &buf, &aligned_buf,
|
||||
Env::IO_TOTAL /* rate_limiter_priority */);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
@@ -232,6 +239,7 @@ Status BlobFileReader::ReadFooter(const RandomAccessFileReader* file_reader,
|
||||
}
|
||||
|
||||
Status BlobFileReader::ReadFromFile(const RandomAccessFileReader* file_reader,
|
||||
const ReadOptions& read_options,
|
||||
uint64_t read_offset, size_t read_size,
|
||||
Statistics* statistics, Slice* slice,
|
||||
Buffer* buf, AlignedBuf* aligned_buf,
|
||||
@@ -246,17 +254,23 @@ Status BlobFileReader::ReadFromFile(const RandomAccessFileReader* file_reader,
|
||||
|
||||
Status s;
|
||||
|
||||
IOOptions io_options;
|
||||
s = file_reader->PrepareIOOptions(read_options, io_options);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
|
||||
if (file_reader->use_direct_io()) {
|
||||
constexpr char* scratch = nullptr;
|
||||
|
||||
s = file_reader->Read(IOOptions(), read_offset, read_size, slice, scratch,
|
||||
s = file_reader->Read(io_options, read_offset, read_size, slice, scratch,
|
||||
aligned_buf, rate_limiter_priority);
|
||||
} else {
|
||||
buf->reset(new char[read_size]);
|
||||
constexpr AlignedBuf* aligned_scratch = nullptr;
|
||||
|
||||
s = file_reader->Read(IOOptions(), read_offset, read_size, slice,
|
||||
buf->get(), aligned_scratch, rate_limiter_priority);
|
||||
s = file_reader->Read(io_options, read_offset, read_size, slice, buf->get(),
|
||||
aligned_scratch, rate_limiter_priority);
|
||||
}
|
||||
|
||||
if (!s.ok()) {
|
||||
@@ -324,8 +338,13 @@ Status BlobFileReader::GetBlob(
|
||||
Status s;
|
||||
constexpr bool for_compaction = true;
|
||||
|
||||
IOOptions io_options;
|
||||
s = file_reader_->PrepareIOOptions(read_options, io_options);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
prefetched = prefetch_buffer->TryReadFromCache(
|
||||
IOOptions(), file_reader_.get(), record_offset,
|
||||
io_options, file_reader_.get(), record_offset,
|
||||
static_cast<size_t>(record_size), &record_slice, &s,
|
||||
read_options.rate_limiter_priority, for_compaction);
|
||||
if (!s.ok()) {
|
||||
@@ -338,10 +357,10 @@ Status BlobFileReader::GetBlob(
|
||||
PERF_COUNTER_ADD(blob_read_count, 1);
|
||||
PERF_COUNTER_ADD(blob_read_byte, record_size);
|
||||
PERF_TIMER_GUARD(blob_read_time);
|
||||
const Status s = ReadFromFile(file_reader_.get(), record_offset,
|
||||
static_cast<size_t>(record_size), statistics_,
|
||||
&record_slice, &buf, &aligned_buf,
|
||||
read_options.rate_limiter_priority);
|
||||
const Status s = ReadFromFile(
|
||||
file_reader_.get(), read_options, record_offset,
|
||||
static_cast<size_t>(record_size), statistics_, &record_slice, &buf,
|
||||
&aligned_buf, read_options.rate_limiter_priority);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ class Statistics;
|
||||
class BlobFileReader {
|
||||
public:
|
||||
static Status Create(const ImmutableOptions& immutable_options,
|
||||
const ReadOptions& read_options,
|
||||
const FileOptions& file_options,
|
||||
uint32_t column_family_id,
|
||||
HistogramImpl* blob_file_read_hist,
|
||||
@@ -74,15 +75,18 @@ class BlobFileReader {
|
||||
std::unique_ptr<RandomAccessFileReader>* file_reader);
|
||||
|
||||
static Status ReadHeader(const RandomAccessFileReader* file_reader,
|
||||
const ReadOptions& read_options,
|
||||
uint32_t column_family_id, Statistics* statistics,
|
||||
CompressionType* compression_type);
|
||||
|
||||
static Status ReadFooter(const RandomAccessFileReader* file_reader,
|
||||
uint64_t file_size, Statistics* statistics);
|
||||
const ReadOptions& read_options, uint64_t file_size,
|
||||
Statistics* statistics);
|
||||
|
||||
using Buffer = std::unique_ptr<char[]>;
|
||||
|
||||
static Status ReadFromFile(const RandomAccessFileReader* file_reader,
|
||||
const ReadOptions& read_options,
|
||||
uint64_t read_offset, size_t read_size,
|
||||
Statistics* statistics, Slice* slice, Buffer* buf,
|
||||
AlignedBuf* aligned_buf,
|
||||
|
||||
@@ -172,12 +172,12 @@ TEST_F(BlobFileReaderTest, CreateReaderAndGetBlob) {
|
||||
|
||||
std::unique_ptr<BlobFileReader> reader;
|
||||
|
||||
ReadOptions read_options;
|
||||
ASSERT_OK(BlobFileReader::Create(
|
||||
immutable_options, FileOptions(), column_family_id, blob_file_read_hist,
|
||||
blob_file_number, nullptr /*IOTracer*/, &reader));
|
||||
immutable_options, read_options, FileOptions(), column_family_id,
|
||||
blob_file_read_hist, blob_file_number, nullptr /*IOTracer*/, &reader));
|
||||
|
||||
// Make sure the blob can be retrieved with and without checksum verification
|
||||
ReadOptions read_options;
|
||||
read_options.verify_checksums = false;
|
||||
|
||||
constexpr FilePrefetchBuffer* prefetch_buffer = nullptr;
|
||||
@@ -479,11 +479,11 @@ TEST_F(BlobFileReaderTest, Malformed) {
|
||||
constexpr HistogramImpl* blob_file_read_hist = nullptr;
|
||||
|
||||
std::unique_ptr<BlobFileReader> reader;
|
||||
|
||||
ASSERT_TRUE(BlobFileReader::Create(immutable_options, FileOptions(),
|
||||
column_family_id, blob_file_read_hist,
|
||||
blob_file_number, nullptr /*IOTracer*/,
|
||||
&reader)
|
||||
const ReadOptions read_options;
|
||||
ASSERT_TRUE(BlobFileReader::Create(immutable_options, read_options,
|
||||
FileOptions(), column_family_id,
|
||||
blob_file_read_hist, blob_file_number,
|
||||
nullptr /*IOTracer*/, &reader)
|
||||
.IsCorruption());
|
||||
}
|
||||
|
||||
@@ -513,11 +513,11 @@ TEST_F(BlobFileReaderTest, TTL) {
|
||||
constexpr HistogramImpl* blob_file_read_hist = nullptr;
|
||||
|
||||
std::unique_ptr<BlobFileReader> reader;
|
||||
|
||||
ASSERT_TRUE(BlobFileReader::Create(immutable_options, FileOptions(),
|
||||
column_family_id, blob_file_read_hist,
|
||||
blob_file_number, nullptr /*IOTracer*/,
|
||||
&reader)
|
||||
const ReadOptions read_options;
|
||||
ASSERT_TRUE(BlobFileReader::Create(immutable_options, read_options,
|
||||
FileOptions(), column_family_id,
|
||||
blob_file_read_hist, blob_file_number,
|
||||
nullptr /*IOTracer*/, &reader)
|
||||
.IsCorruption());
|
||||
}
|
||||
|
||||
@@ -552,11 +552,11 @@ TEST_F(BlobFileReaderTest, ExpirationRangeInHeader) {
|
||||
constexpr HistogramImpl* blob_file_read_hist = nullptr;
|
||||
|
||||
std::unique_ptr<BlobFileReader> reader;
|
||||
|
||||
ASSERT_TRUE(BlobFileReader::Create(immutable_options, FileOptions(),
|
||||
column_family_id, blob_file_read_hist,
|
||||
blob_file_number, nullptr /*IOTracer*/,
|
||||
&reader)
|
||||
const ReadOptions read_options;
|
||||
ASSERT_TRUE(BlobFileReader::Create(immutable_options, read_options,
|
||||
FileOptions(), column_family_id,
|
||||
blob_file_read_hist, blob_file_number,
|
||||
nullptr /*IOTracer*/, &reader)
|
||||
.IsCorruption());
|
||||
}
|
||||
|
||||
@@ -591,11 +591,11 @@ TEST_F(BlobFileReaderTest, ExpirationRangeInFooter) {
|
||||
constexpr HistogramImpl* blob_file_read_hist = nullptr;
|
||||
|
||||
std::unique_ptr<BlobFileReader> reader;
|
||||
|
||||
ASSERT_TRUE(BlobFileReader::Create(immutable_options, FileOptions(),
|
||||
column_family_id, blob_file_read_hist,
|
||||
blob_file_number, nullptr /*IOTracer*/,
|
||||
&reader)
|
||||
const ReadOptions read_options;
|
||||
ASSERT_TRUE(BlobFileReader::Create(immutable_options, read_options,
|
||||
FileOptions(), column_family_id,
|
||||
blob_file_read_hist, blob_file_number,
|
||||
nullptr /*IOTracer*/, &reader)
|
||||
.IsCorruption());
|
||||
}
|
||||
|
||||
@@ -629,9 +629,9 @@ TEST_F(BlobFileReaderTest, IncorrectColumnFamily) {
|
||||
std::unique_ptr<BlobFileReader> reader;
|
||||
|
||||
constexpr uint32_t incorrect_column_family_id = 2;
|
||||
|
||||
ASSERT_TRUE(BlobFileReader::Create(immutable_options, FileOptions(),
|
||||
incorrect_column_family_id,
|
||||
const ReadOptions read_options;
|
||||
ASSERT_TRUE(BlobFileReader::Create(immutable_options, read_options,
|
||||
FileOptions(), incorrect_column_family_id,
|
||||
blob_file_read_hist, blob_file_number,
|
||||
nullptr /*IOTracer*/, &reader)
|
||||
.IsCorruption());
|
||||
@@ -664,10 +664,10 @@ TEST_F(BlobFileReaderTest, BlobCRCError) {
|
||||
constexpr HistogramImpl* blob_file_read_hist = nullptr;
|
||||
|
||||
std::unique_ptr<BlobFileReader> reader;
|
||||
|
||||
const ReadOptions read_options;
|
||||
ASSERT_OK(BlobFileReader::Create(
|
||||
immutable_options, FileOptions(), column_family_id, blob_file_read_hist,
|
||||
blob_file_number, nullptr /*IOTracer*/, &reader));
|
||||
immutable_options, read_options, FileOptions(), column_family_id,
|
||||
blob_file_read_hist, blob_file_number, nullptr /*IOTracer*/, &reader));
|
||||
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"BlobFileReader::VerifyBlob:CheckBlobCRC", [](void* arg) {
|
||||
@@ -728,13 +728,12 @@ TEST_F(BlobFileReaderTest, Compression) {
|
||||
constexpr HistogramImpl* blob_file_read_hist = nullptr;
|
||||
|
||||
std::unique_ptr<BlobFileReader> reader;
|
||||
|
||||
ReadOptions read_options;
|
||||
ASSERT_OK(BlobFileReader::Create(
|
||||
immutable_options, FileOptions(), column_family_id, blob_file_read_hist,
|
||||
blob_file_number, nullptr /*IOTracer*/, &reader));
|
||||
immutable_options, read_options, FileOptions(), column_family_id,
|
||||
blob_file_read_hist, blob_file_number, nullptr /*IOTracer*/, &reader));
|
||||
|
||||
// Make sure the blob can be retrieved with and without checksum verification
|
||||
ReadOptions read_options;
|
||||
read_options.verify_checksums = false;
|
||||
|
||||
constexpr FilePrefetchBuffer* prefetch_buffer = nullptr;
|
||||
@@ -803,10 +802,10 @@ TEST_F(BlobFileReaderTest, UncompressionError) {
|
||||
constexpr HistogramImpl* blob_file_read_hist = nullptr;
|
||||
|
||||
std::unique_ptr<BlobFileReader> reader;
|
||||
|
||||
const ReadOptions read_options;
|
||||
ASSERT_OK(BlobFileReader::Create(
|
||||
immutable_options, FileOptions(), column_family_id, blob_file_read_hist,
|
||||
blob_file_number, nullptr /*IOTracer*/, &reader));
|
||||
immutable_options, read_options, FileOptions(), column_family_id,
|
||||
blob_file_read_hist, blob_file_number, nullptr /*IOTracer*/, &reader));
|
||||
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"BlobFileReader::UncompressBlobIfNeeded:TamperWithResult", [](void* arg) {
|
||||
@@ -895,10 +894,10 @@ TEST_P(BlobFileReaderIOErrorTest, IOError) {
|
||||
constexpr HistogramImpl* blob_file_read_hist = nullptr;
|
||||
|
||||
std::unique_ptr<BlobFileReader> reader;
|
||||
|
||||
const ReadOptions read_options;
|
||||
const Status s = BlobFileReader::Create(
|
||||
immutable_options, FileOptions(), column_family_id, blob_file_read_hist,
|
||||
blob_file_number, nullptr /*IOTracer*/, &reader);
|
||||
immutable_options, read_options, FileOptions(), column_family_id,
|
||||
blob_file_read_hist, blob_file_number, nullptr /*IOTracer*/, &reader);
|
||||
|
||||
const bool fail_during_create =
|
||||
(sync_point_ != "BlobFileReader::GetBlob:ReadFromFile");
|
||||
@@ -983,10 +982,10 @@ TEST_P(BlobFileReaderDecodingErrorTest, DecodingError) {
|
||||
constexpr HistogramImpl* blob_file_read_hist = nullptr;
|
||||
|
||||
std::unique_ptr<BlobFileReader> reader;
|
||||
|
||||
const ReadOptions read_options;
|
||||
const Status s = BlobFileReader::Create(
|
||||
immutable_options, FileOptions(), column_family_id, blob_file_read_hist,
|
||||
blob_file_number, nullptr /*IOTracer*/, &reader);
|
||||
immutable_options, read_options, FileOptions(), column_family_id,
|
||||
blob_file_read_hist, blob_file_number, nullptr /*IOTracer*/, &reader);
|
||||
|
||||
const bool fail_during_create =
|
||||
sync_point_ != "BlobFileReader::GetBlob:TamperWithResult";
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
#include "db/blob/blob_log_sequential_reader.h"
|
||||
|
||||
#include "file/random_access_file_reader.h"
|
||||
#include "monitoring/statistics.h"
|
||||
#include "monitoring/statistics_impl.h"
|
||||
#include "util/stop_watch.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
#include "db/blob/blob_log_format.h"
|
||||
#include "file/writable_file_writer.h"
|
||||
#include "monitoring/statistics.h"
|
||||
#include "monitoring/statistics_impl.h"
|
||||
#include "rocksdb/system_clock.h"
|
||||
#include "test_util/sync_point.h"
|
||||
#include "util/coding.h"
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
#include "db/blob/blob_contents.h"
|
||||
#include "db/blob/blob_file_reader.h"
|
||||
#include "db/blob/blob_log_format.h"
|
||||
#include "monitoring/statistics.h"
|
||||
#include "monitoring/statistics_impl.h"
|
||||
#include "options/cf_options.h"
|
||||
#include "table/get_context.h"
|
||||
#include "table/multiget_context.h"
|
||||
@@ -104,9 +104,9 @@ Status BlobSource::PutBlobIntoCache(
|
||||
}
|
||||
|
||||
BlobSource::TypedHandle* BlobSource::GetEntryFromCache(const Slice& key) const {
|
||||
return blob_cache_.LookupFull(
|
||||
key, nullptr /* context */, Cache::Priority::BOTTOM,
|
||||
true /* wait_for_cache */, statistics_, lowest_used_cache_tier_);
|
||||
return blob_cache_.LookupFull(key, nullptr /* context */,
|
||||
Cache::Priority::BOTTOM, statistics_,
|
||||
lowest_used_cache_tier_);
|
||||
}
|
||||
|
||||
void BlobSource::PinCachedBlob(CacheHandleGuard<BlobContents>* cached_blob,
|
||||
@@ -209,7 +209,8 @@ Status BlobSource::GetBlob(const ReadOptions& read_options,
|
||||
|
||||
{
|
||||
CacheHandleGuard<BlobFileReader> blob_file_reader;
|
||||
s = blob_file_cache_->GetBlobFileReader(file_number, &blob_file_reader);
|
||||
s = blob_file_cache_->GetBlobFileReader(read_options, file_number,
|
||||
&blob_file_reader);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
@@ -372,8 +373,8 @@ void BlobSource::MultiGetBlobFromOneFile(const ReadOptions& read_options,
|
||||
}
|
||||
|
||||
CacheHandleGuard<BlobFileReader> blob_file_reader;
|
||||
Status s =
|
||||
blob_file_cache_->GetBlobFileReader(file_number, &blob_file_reader);
|
||||
Status s = blob_file_cache_->GetBlobFileReader(read_options, file_number,
|
||||
&blob_file_reader);
|
||||
if (!s.ok()) {
|
||||
for (size_t i = 0; i < _blob_reqs.size(); ++i) {
|
||||
BlobReadRequest* const req = _blob_reqs[i].first;
|
||||
|
||||
@@ -95,9 +95,9 @@ class BlobSource {
|
||||
uint64_t* bytes_read);
|
||||
|
||||
inline Status GetBlobFileReader(
|
||||
uint64_t blob_file_number,
|
||||
const ReadOptions& read_options, uint64_t blob_file_number,
|
||||
CacheHandleGuard<BlobFileReader>* blob_file_reader) {
|
||||
return blob_file_cache_->GetBlobFileReader(blob_file_number,
|
||||
return blob_file_cache_->GetBlobFileReader(read_options, blob_file_number,
|
||||
blob_file_reader);
|
||||
}
|
||||
|
||||
|
||||
+24
-28
@@ -517,7 +517,8 @@ TEST_F(BlobSourceTest, GetCompressedBlobs) {
|
||||
compression, blob_offsets, blob_sizes);
|
||||
|
||||
CacheHandleGuard<BlobFileReader> blob_file_reader;
|
||||
ASSERT_OK(blob_source.GetBlobFileReader(file_number, &blob_file_reader));
|
||||
ASSERT_OK(blob_source.GetBlobFileReader(read_options, file_number,
|
||||
&blob_file_reader));
|
||||
ASSERT_NE(blob_file_reader.GetValue(), nullptr);
|
||||
|
||||
const uint64_t file_size = blob_file_reader.GetValue()->GetFileSize();
|
||||
@@ -1139,12 +1140,13 @@ TEST_F(BlobSecondaryCacheTest, GetBlobsFromSecondaryCache) {
|
||||
blob_file_cache.get());
|
||||
|
||||
CacheHandleGuard<BlobFileReader> file_reader;
|
||||
ASSERT_OK(blob_source.GetBlobFileReader(file_number, &file_reader));
|
||||
ReadOptions read_options;
|
||||
ASSERT_OK(
|
||||
blob_source.GetBlobFileReader(read_options, file_number, &file_reader));
|
||||
ASSERT_NE(file_reader.GetValue(), nullptr);
|
||||
const uint64_t file_size = file_reader.GetValue()->GetFileSize();
|
||||
ASSERT_EQ(file_reader.GetValue()->GetCompressionType(), kNoCompression);
|
||||
|
||||
ReadOptions read_options;
|
||||
read_options.verify_checksums = true;
|
||||
|
||||
auto blob_cache = options_.blob_cache;
|
||||
@@ -1214,12 +1216,12 @@ TEST_F(BlobSecondaryCacheTest, GetBlobsFromSecondaryCache) {
|
||||
ASSERT_EQ(handle0, nullptr);
|
||||
|
||||
// key0's item should be in the secondary cache.
|
||||
bool is_in_sec_cache = false;
|
||||
bool kept_in_sec_cache = false;
|
||||
auto sec_handle0 = secondary_cache->Lookup(
|
||||
key0, &BlobSource::SharedCacheInterface::kFullHelper,
|
||||
key0, BlobSource::SharedCacheInterface::GetFullHelper(),
|
||||
/*context*/ nullptr, true,
|
||||
/*advise_erase=*/true, is_in_sec_cache);
|
||||
ASSERT_FALSE(is_in_sec_cache);
|
||||
/*advise_erase=*/true, kept_in_sec_cache);
|
||||
ASSERT_FALSE(kept_in_sec_cache);
|
||||
ASSERT_NE(sec_handle0, nullptr);
|
||||
ASSERT_TRUE(sec_handle0->IsReady());
|
||||
auto value = static_cast<BlobContents*>(sec_handle0->Value());
|
||||
@@ -1242,12 +1244,12 @@ TEST_F(BlobSecondaryCacheTest, GetBlobsFromSecondaryCache) {
|
||||
ASSERT_NE(handle1, nullptr);
|
||||
blob_cache->Release(handle1);
|
||||
|
||||
bool is_in_sec_cache = false;
|
||||
bool kept_in_sec_cache = false;
|
||||
auto sec_handle1 = secondary_cache->Lookup(
|
||||
key1, &BlobSource::SharedCacheInterface::kFullHelper,
|
||||
key1, BlobSource::SharedCacheInterface::GetFullHelper(),
|
||||
/*context*/ nullptr, true,
|
||||
/*advise_erase=*/true, is_in_sec_cache);
|
||||
ASSERT_FALSE(is_in_sec_cache);
|
||||
/*advise_erase=*/true, kept_in_sec_cache);
|
||||
ASSERT_FALSE(kept_in_sec_cache);
|
||||
ASSERT_EQ(sec_handle1, nullptr);
|
||||
|
||||
ASSERT_TRUE(blob_source.TEST_BlobInCache(file_number, file_size,
|
||||
@@ -1372,7 +1374,7 @@ class BlobSourceCacheReservationTest : public DBTestBase {
|
||||
|
||||
static constexpr std::size_t kSizeDummyEntry = CacheReservationManagerImpl<
|
||||
CacheEntryRole::kBlobCache>::GetDummyEntrySize();
|
||||
static constexpr std::size_t kCacheCapacity = 1 * kSizeDummyEntry;
|
||||
static constexpr std::size_t kCacheCapacity = 2 * kSizeDummyEntry;
|
||||
static constexpr int kNumShardBits = 0; // 2^0 shard
|
||||
|
||||
static constexpr uint32_t kColumnFamilyId = 1;
|
||||
@@ -1505,11 +1507,10 @@ TEST_F(BlobSourceCacheReservationTest, SimpleCacheReservation) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(BlobSourceCacheReservationTest, IncreaseCacheReservationOnFullCache) {
|
||||
TEST_F(BlobSourceCacheReservationTest, IncreaseCacheReservation) {
|
||||
options_.cf_paths.emplace_back(
|
||||
test::PerThreadDBPath(
|
||||
env_,
|
||||
"BlobSourceCacheReservationTest_IncreaseCacheReservationOnFullCache"),
|
||||
env_, "BlobSourceCacheReservationTest_IncreaseCacheReservation"),
|
||||
0);
|
||||
|
||||
GenerateKeysAndBlobs();
|
||||
@@ -1517,7 +1518,7 @@ TEST_F(BlobSourceCacheReservationTest, IncreaseCacheReservationOnFullCache) {
|
||||
DestroyAndReopen(options_);
|
||||
|
||||
ImmutableOptions immutable_options(options_);
|
||||
constexpr size_t blob_size = kSizeDummyEntry / (kNumBlobs / 2);
|
||||
constexpr size_t blob_size = 24 << 10; // 24KB
|
||||
for (size_t i = 0; i < kNumBlobs; ++i) {
|
||||
blob_file_size_ -= blobs_[i].size(); // old blob size
|
||||
blob_strs_[i].resize(blob_size, '@');
|
||||
@@ -1575,11 +1576,6 @@ TEST_F(BlobSourceCacheReservationTest, IncreaseCacheReservationOnFullCache) {
|
||||
|
||||
std::vector<PinnableSlice> values(keys_.size());
|
||||
|
||||
// Since we resized each blob to be kSizeDummyEntry / (num_blobs / 2), we
|
||||
// can't fit all the blobs in the cache at the same time, which means we
|
||||
// should observe cache evictions once we reach the cache's capacity.
|
||||
// Due to the overhead of the cache and the BlobContents objects, as well as
|
||||
// jemalloc bin sizes, this happens after inserting seven blobs.
|
||||
uint64_t blob_bytes = 0;
|
||||
for (size_t i = 0; i < kNumBlobs; ++i) {
|
||||
ASSERT_OK(blob_source.GetBlob(
|
||||
@@ -1590,15 +1586,15 @@ TEST_F(BlobSourceCacheReservationTest, IncreaseCacheReservationOnFullCache) {
|
||||
// Release cache handle
|
||||
values[i].Reset();
|
||||
|
||||
if (i < kNumBlobs / 2 - 1) {
|
||||
size_t charge = 0;
|
||||
ASSERT_TRUE(blob_source.TEST_BlobInCache(
|
||||
kBlobFileNumber, blob_file_size_, blob_offsets[i], &charge));
|
||||
size_t charge = 0;
|
||||
ASSERT_TRUE(blob_source.TEST_BlobInCache(kBlobFileNumber, blob_file_size_,
|
||||
blob_offsets[i], &charge));
|
||||
|
||||
blob_bytes += charge;
|
||||
}
|
||||
blob_bytes += charge;
|
||||
|
||||
ASSERT_EQ(cache_res_mgr->GetTotalReservedCacheSize(), kSizeDummyEntry);
|
||||
ASSERT_EQ(cache_res_mgr->GetTotalReservedCacheSize(),
|
||||
(blob_bytes <= kSizeDummyEntry) ? kSizeDummyEntry
|
||||
: (2 * kSizeDummyEntry));
|
||||
ASSERT_EQ(cache_res_mgr->GetTotalMemoryUsed(), blob_bytes);
|
||||
ASSERT_EQ(cache_res_mgr->GetTotalMemoryUsed(),
|
||||
options_.blob_cache->GetUsage());
|
||||
|
||||
@@ -1782,16 +1782,49 @@ TEST_F(DBBlobBasicTest, GetEntityBlob) {
|
||||
constexpr char key[] = "key";
|
||||
constexpr char blob_value[] = "blob_value";
|
||||
|
||||
constexpr char other_key[] = "other_key";
|
||||
constexpr char other_blob_value[] = "other_blob_value";
|
||||
|
||||
ASSERT_OK(Put(key, blob_value));
|
||||
ASSERT_OK(Put(other_key, other_blob_value));
|
||||
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
PinnableWideColumns result;
|
||||
ASSERT_OK(
|
||||
db_->GetEntity(ReadOptions(), db_->DefaultColumnFamily(), key, &result));
|
||||
|
||||
WideColumns expected_columns{{kDefaultWideColumnName, blob_value}};
|
||||
ASSERT_EQ(result.columns(), expected_columns);
|
||||
WideColumns other_expected_columns{
|
||||
{kDefaultWideColumnName, other_blob_value}};
|
||||
|
||||
{
|
||||
PinnableWideColumns result;
|
||||
ASSERT_OK(db_->GetEntity(ReadOptions(), db_->DefaultColumnFamily(), key,
|
||||
&result));
|
||||
ASSERT_EQ(result.columns(), expected_columns);
|
||||
}
|
||||
|
||||
{
|
||||
PinnableWideColumns result;
|
||||
ASSERT_OK(db_->GetEntity(ReadOptions(), db_->DefaultColumnFamily(),
|
||||
other_key, &result));
|
||||
|
||||
ASSERT_EQ(result.columns(), other_expected_columns);
|
||||
}
|
||||
|
||||
{
|
||||
constexpr size_t num_keys = 2;
|
||||
|
||||
std::array<Slice, num_keys> keys{{key, other_key}};
|
||||
std::array<PinnableWideColumns, num_keys> results;
|
||||
std::array<Status, num_keys> statuses;
|
||||
|
||||
db_->MultiGetEntity(ReadOptions(), db_->DefaultColumnFamily(), num_keys,
|
||||
&keys[0], &results[0], &statuses[0]);
|
||||
|
||||
ASSERT_OK(statuses[0]);
|
||||
ASSERT_EQ(results[0].columns(), expected_columns);
|
||||
|
||||
ASSERT_OK(statuses[1]);
|
||||
ASSERT_EQ(results[1].columns(), other_expected_columns);
|
||||
}
|
||||
}
|
||||
|
||||
class DBBlobWithTimestampTest : public DBBasicTestWithTimestampBase {
|
||||
@@ -1978,6 +2011,222 @@ TEST_F(DBBlobWithTimestampTest, MultiGetMergeBlobWithPut) {
|
||||
ASSERT_EQ(values[2], "v2_0");
|
||||
}
|
||||
|
||||
TEST_F(DBBlobWithTimestampTest, IterateBlobs) {
|
||||
Options options = GetDefaultOptions();
|
||||
options.enable_blob_files = true;
|
||||
options.create_if_missing = true;
|
||||
const size_t kTimestampSize = Timestamp(0, 0).size();
|
||||
TestComparator test_cmp(kTimestampSize);
|
||||
options.comparator = &test_cmp;
|
||||
|
||||
DestroyAndReopen(options);
|
||||
|
||||
int num_blobs = 5;
|
||||
std::vector<std::string> keys;
|
||||
std::vector<std::string> blobs;
|
||||
|
||||
WriteOptions write_opts;
|
||||
std::vector<std::string> write_timestamps = {Timestamp(1, 0),
|
||||
Timestamp(2, 0)};
|
||||
|
||||
// For each key in ["key0", ... "keyi", ...], write two versions:
|
||||
// Timestamp(1, 0), "blobi0"
|
||||
// Timestamp(2, 0), "blobi1"
|
||||
for (int i = 0; i < num_blobs; i++) {
|
||||
keys.push_back("key" + std::to_string(i));
|
||||
blobs.push_back("blob" + std::to_string(i));
|
||||
for (size_t j = 0; j < write_timestamps.size(); j++) {
|
||||
ASSERT_OK(db_->Put(write_opts, keys[i], write_timestamps[j],
|
||||
blobs[i] + std::to_string(j)));
|
||||
}
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
ReadOptions read_options;
|
||||
std::vector<std::string> read_timestamps = {Timestamp(0, 0), Timestamp(3, 0)};
|
||||
Slice ts_upper_bound(read_timestamps[1]);
|
||||
read_options.timestamp = &ts_upper_bound;
|
||||
|
||||
auto check_iter_entry =
|
||||
[](const Iterator* iter, const std::string& expected_key,
|
||||
const std::string& expected_ts, const std::string& expected_value,
|
||||
bool key_is_internal = true) {
|
||||
ASSERT_OK(iter->status());
|
||||
if (key_is_internal) {
|
||||
std::string expected_ukey_and_ts;
|
||||
expected_ukey_and_ts.assign(expected_key.data(), expected_key.size());
|
||||
expected_ukey_and_ts.append(expected_ts.data(), expected_ts.size());
|
||||
|
||||
ParsedInternalKey parsed_ikey;
|
||||
ASSERT_OK(ParseInternalKey(iter->key(), &parsed_ikey,
|
||||
true /* log_err_key */));
|
||||
ASSERT_EQ(parsed_ikey.user_key, expected_ukey_and_ts);
|
||||
} else {
|
||||
ASSERT_EQ(iter->key(), expected_key);
|
||||
}
|
||||
ASSERT_EQ(iter->timestamp(), expected_ts);
|
||||
ASSERT_EQ(iter->value(), expected_value);
|
||||
};
|
||||
|
||||
// Forward iterating one version of each key, get in this order:
|
||||
// [("key0", Timestamp(2, 0), "blob01"),
|
||||
// ("key1", Timestamp(2, 0), "blob11")...]
|
||||
{
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options));
|
||||
ASSERT_OK(iter->status());
|
||||
|
||||
iter->SeekToFirst();
|
||||
for (int i = 0; i < num_blobs; i++) {
|
||||
check_iter_entry(iter.get(), keys[i], write_timestamps[1],
|
||||
blobs[i] + std::to_string(1), /*key_is_internal*/ false);
|
||||
iter->Next();
|
||||
}
|
||||
}
|
||||
|
||||
// Forward iteration, then reverse to backward.
|
||||
{
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options));
|
||||
ASSERT_OK(iter->status());
|
||||
|
||||
iter->SeekToFirst();
|
||||
for (int i = 0; i < num_blobs * 2 - 1; i++) {
|
||||
if (i < num_blobs) {
|
||||
check_iter_entry(iter.get(), keys[i], write_timestamps[1],
|
||||
blobs[i] + std::to_string(1),
|
||||
/*key_is_internal*/ false);
|
||||
if (i != num_blobs - 1) {
|
||||
iter->Next();
|
||||
}
|
||||
} else {
|
||||
if (i != num_blobs) {
|
||||
check_iter_entry(iter.get(), keys[num_blobs * 2 - 1 - i],
|
||||
write_timestamps[1],
|
||||
blobs[num_blobs * 2 - 1 - i] + std::to_string(1),
|
||||
/*key_is_internal*/ false);
|
||||
}
|
||||
iter->Prev();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Backward iterating one versions of each key, get in this order:
|
||||
// [("key4", Timestamp(2, 0), "blob41"),
|
||||
// ("key3", Timestamp(2, 0), "blob31")...]
|
||||
{
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options));
|
||||
ASSERT_OK(iter->status());
|
||||
|
||||
iter->SeekToLast();
|
||||
for (int i = 0; i < num_blobs; i++) {
|
||||
check_iter_entry(iter.get(), keys[num_blobs - 1 - i], write_timestamps[1],
|
||||
blobs[num_blobs - 1 - i] + std::to_string(1),
|
||||
/*key_is_internal*/ false);
|
||||
iter->Prev();
|
||||
}
|
||||
}
|
||||
|
||||
// Backward iteration, then reverse to forward.
|
||||
{
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options));
|
||||
ASSERT_OK(iter->status());
|
||||
|
||||
iter->SeekToLast();
|
||||
for (int i = 0; i < num_blobs * 2 - 1; i++) {
|
||||
if (i < num_blobs) {
|
||||
check_iter_entry(iter.get(), keys[num_blobs - 1 - i],
|
||||
write_timestamps[1],
|
||||
blobs[num_blobs - 1 - i] + std::to_string(1),
|
||||
/*key_is_internal*/ false);
|
||||
if (i != num_blobs - 1) {
|
||||
iter->Prev();
|
||||
}
|
||||
} else {
|
||||
if (i != num_blobs) {
|
||||
check_iter_entry(iter.get(), keys[i - num_blobs], write_timestamps[1],
|
||||
blobs[i - num_blobs] + std::to_string(1),
|
||||
/*key_is_internal*/ false);
|
||||
}
|
||||
iter->Next();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Slice ts_lower_bound(read_timestamps[0]);
|
||||
read_options.iter_start_ts = &ts_lower_bound;
|
||||
// Forward iterating multiple versions of the same key, get in this order:
|
||||
// [("key0", Timestamp(2, 0), "blob01"),
|
||||
// ("key0", Timestamp(1, 0), "blob00"),
|
||||
// ("key1", Timestamp(2, 0), "blob11")...]
|
||||
{
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options));
|
||||
ASSERT_OK(iter->status());
|
||||
|
||||
iter->SeekToFirst();
|
||||
for (int i = 0; i < num_blobs; i++) {
|
||||
for (size_t j = write_timestamps.size(); j > 0; --j) {
|
||||
check_iter_entry(iter.get(), keys[i], write_timestamps[j - 1],
|
||||
blobs[i] + std::to_string(j - 1));
|
||||
iter->Next();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Backward iterating multiple versions of the same key, get in this order:
|
||||
// [("key4", Timestamp(1, 0), "blob00"),
|
||||
// ("key4", Timestamp(2, 0), "blob01"),
|
||||
// ("key3", Timestamp(1, 0), "blob10")...]
|
||||
{
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options));
|
||||
ASSERT_OK(iter->status());
|
||||
|
||||
iter->SeekToLast();
|
||||
for (int i = num_blobs; i > 0; i--) {
|
||||
for (size_t j = 0; j < write_timestamps.size(); j++) {
|
||||
check_iter_entry(iter.get(), keys[i - 1], write_timestamps[j],
|
||||
blobs[i - 1] + std::to_string(j));
|
||||
iter->Prev();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int upper_bound_idx = num_blobs - 2;
|
||||
int lower_bound_idx = 1;
|
||||
Slice upper_bound_slice(keys[upper_bound_idx]);
|
||||
Slice lower_bound_slice(keys[lower_bound_idx]);
|
||||
read_options.iterate_upper_bound = &upper_bound_slice;
|
||||
read_options.iterate_lower_bound = &lower_bound_slice;
|
||||
|
||||
// Forward iteration with upper and lower bound.
|
||||
{
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options));
|
||||
ASSERT_OK(iter->status());
|
||||
|
||||
iter->SeekToFirst();
|
||||
for (int i = lower_bound_idx; i < upper_bound_idx; i++) {
|
||||
for (size_t j = write_timestamps.size(); j > 0; --j) {
|
||||
check_iter_entry(iter.get(), keys[i], write_timestamps[j - 1],
|
||||
blobs[i] + std::to_string(j - 1));
|
||||
iter->Next();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Backward iteration with upper and lower bound.
|
||||
{
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options));
|
||||
ASSERT_OK(iter->status());
|
||||
|
||||
iter->SeekToLast();
|
||||
for (int i = upper_bound_idx; i > lower_bound_idx; i--) {
|
||||
for (size_t j = 0; j < write_timestamps.size(); j++) {
|
||||
check_iter_entry(iter.get(), keys[i - 1], write_timestamps[j],
|
||||
blobs[i - 1] + std::to_string(j));
|
||||
iter->Prev();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
+7
-6
@@ -56,8 +56,8 @@ TableBuilder* NewTableBuilder(const TableBuilderOptions& tboptions,
|
||||
Status BuildTable(
|
||||
const std::string& dbname, VersionSet* versions,
|
||||
const ImmutableDBOptions& db_options, const TableBuilderOptions& tboptions,
|
||||
const FileOptions& file_options, TableCache* table_cache,
|
||||
InternalIterator* iter,
|
||||
const FileOptions& file_options, const ReadOptions& read_options,
|
||||
TableCache* table_cache, InternalIterator* iter,
|
||||
std::vector<std::unique_ptr<FragmentedRangeTombstoneIterator>>
|
||||
range_del_iters,
|
||||
FileMetaData* meta, std::vector<BlobFileAddition>* blob_file_additions,
|
||||
@@ -255,8 +255,8 @@ Status BuildTable(
|
||||
SizeApproximationOptions approx_opts;
|
||||
approx_opts.files_size_error_margin = 0.1;
|
||||
meta->compensated_range_deletion_size += versions->ApproximateSize(
|
||||
approx_opts, version, kv.first.Encode(), tombstone_end.Encode(),
|
||||
0 /* start_level */, -1 /* end_level */,
|
||||
approx_opts, read_options, version, kv.first.Encode(),
|
||||
tombstone_end.Encode(), 0 /* start_level */, -1 /* end_level */,
|
||||
TableReaderCaller::kFlush);
|
||||
}
|
||||
last_tombstone_start_user_key = range_del_it->start_key();
|
||||
@@ -291,6 +291,7 @@ Status BuildTable(
|
||||
if (s.ok() && !empty) {
|
||||
uint64_t file_size = builder->FileSize();
|
||||
meta->fd.file_size = file_size;
|
||||
meta->tail_size = builder->GetTailSize();
|
||||
meta->marked_for_compaction = builder->NeedCompact();
|
||||
assert(meta->fd.GetFileSize() > 0);
|
||||
tp = builder
|
||||
@@ -369,7 +370,6 @@ Status BuildTable(
|
||||
// here because this is a special case after we finish the table building.
|
||||
// No matter whether use_direct_io_for_flush_and_compaction is true,
|
||||
// the goal is to cache it here for further user reads.
|
||||
ReadOptions read_options;
|
||||
std::unique_ptr<InternalIterator> it(table_cache->NewIterator(
|
||||
read_options, file_options, tboptions.internal_comparator, *meta,
|
||||
nullptr /* range_del_agg */, mutable_cf_options.prefix_extractor,
|
||||
@@ -381,7 +381,8 @@ Status BuildTable(
|
||||
MaxFileSizeForL0MetaPin(mutable_cf_options),
|
||||
/*smallest_compaction_key=*/nullptr,
|
||||
/*largest_compaction_key*/ nullptr,
|
||||
/*allow_unprepared_value*/ false));
|
||||
/*allow_unprepared_value*/ false,
|
||||
mutable_cf_options.block_protection_bytes_per_key));
|
||||
s = it->status();
|
||||
if (s.ok() && paranoid_file_checks) {
|
||||
OutputValidator file_validator(tboptions.internal_comparator,
|
||||
|
||||
+2
-2
@@ -53,8 +53,8 @@ TableBuilder* NewTableBuilder(const TableBuilderOptions& tboptions,
|
||||
extern Status BuildTable(
|
||||
const std::string& dbname, VersionSet* versions,
|
||||
const ImmutableDBOptions& db_options, const TableBuilderOptions& tboptions,
|
||||
const FileOptions& file_options, TableCache* table_cache,
|
||||
InternalIterator* iter,
|
||||
const FileOptions& file_options, const ReadOptions& read_options,
|
||||
TableCache* table_cache, InternalIterator* iter,
|
||||
std::vector<std::unique_ptr<FragmentedRangeTombstoneIterator>>
|
||||
range_del_iters,
|
||||
FileMetaData* meta, std::vector<BlobFileAddition>* blob_file_additions,
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
// 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 "rocksdb/c.h"
|
||||
|
||||
#include <cstdlib>
|
||||
@@ -16,7 +15,7 @@
|
||||
#include <vector>
|
||||
|
||||
#include "port/port.h"
|
||||
#include "rocksdb/cache.h"
|
||||
#include "rocksdb/advanced_cache.h"
|
||||
#include "rocksdb/compaction_filter.h"
|
||||
#include "rocksdb/comparator.h"
|
||||
#include "rocksdb/convenience.h"
|
||||
@@ -78,6 +77,7 @@ using ROCKSDB_NAMESPACE::EnvOptions;
|
||||
using ROCKSDB_NAMESPACE::FileLock;
|
||||
using ROCKSDB_NAMESPACE::FilterPolicy;
|
||||
using ROCKSDB_NAMESPACE::FlushOptions;
|
||||
using ROCKSDB_NAMESPACE::HyperClockCacheOptions;
|
||||
using ROCKSDB_NAMESPACE::InfoLogLevel;
|
||||
using ROCKSDB_NAMESPACE::IngestExternalFileOptions;
|
||||
using ROCKSDB_NAMESPACE::Iterator;
|
||||
@@ -208,6 +208,9 @@ struct rocksdb_logger_t {
|
||||
struct rocksdb_lru_cache_options_t {
|
||||
LRUCacheOptions rep;
|
||||
};
|
||||
struct rocksdb_hyper_clock_cache_options_t {
|
||||
HyperClockCacheOptions rep;
|
||||
};
|
||||
struct rocksdb_memory_allocator_t {
|
||||
std::shared_ptr<MemoryAllocator> rep;
|
||||
};
|
||||
@@ -1805,6 +1808,17 @@ void rocksdb_flush_cf(rocksdb_t* db, const rocksdb_flushoptions_t* options,
|
||||
SaveError(errptr, db->rep->Flush(options->rep, column_family->rep));
|
||||
}
|
||||
|
||||
void rocksdb_flush_cfs(rocksdb_t* db, const rocksdb_flushoptions_t* options,
|
||||
rocksdb_column_family_handle_t** column_families,
|
||||
int num_column_families, char** errptr) {
|
||||
std::vector<ColumnFamilyHandle*> column_family_handles;
|
||||
for (int i = 0; i < num_column_families; i++) {
|
||||
column_family_handles.push_back(column_families[i]->rep);
|
||||
}
|
||||
|
||||
SaveError(errptr, db->rep->Flush(options->rep, column_family_handles));
|
||||
}
|
||||
|
||||
void rocksdb_flush_wal(rocksdb_t* db, unsigned char sync, char** errptr) {
|
||||
SaveError(errptr, db->rep->FlushWAL(sync));
|
||||
}
|
||||
@@ -4050,6 +4064,8 @@ uint64_t rocksdb_perfcontext_metric(rocksdb_perfcontext_t* context,
|
||||
return rep->blob_decompress_time;
|
||||
case rocksdb_internal_range_del_reseek_count:
|
||||
return rep->internal_range_del_reseek_count;
|
||||
case rocksdb_block_read_cpu_time:
|
||||
return rep->block_read_cpu_time;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -4665,12 +4681,59 @@ rocksdb_cache_t* rocksdb_cache_create_lru_with_strict_capacity_limit(
|
||||
}
|
||||
|
||||
rocksdb_cache_t* rocksdb_cache_create_lru_opts(
|
||||
rocksdb_lru_cache_options_t* opt) {
|
||||
const rocksdb_lru_cache_options_t* opt) {
|
||||
rocksdb_cache_t* c = new rocksdb_cache_t;
|
||||
c->rep = NewLRUCache(opt->rep);
|
||||
return c;
|
||||
}
|
||||
|
||||
rocksdb_hyper_clock_cache_options_t* rocksdb_hyper_clock_cache_options_create(
|
||||
size_t capacity, size_t estimated_entry_charge) {
|
||||
return new rocksdb_hyper_clock_cache_options_t{
|
||||
HyperClockCacheOptions(capacity, estimated_entry_charge)};
|
||||
}
|
||||
|
||||
void rocksdb_hyper_clock_cache_options_destroy(
|
||||
rocksdb_hyper_clock_cache_options_t* opt) {
|
||||
delete opt;
|
||||
}
|
||||
|
||||
void rocksdb_hyper_clock_cache_options_set_capacity(
|
||||
rocksdb_hyper_clock_cache_options_t* opts, size_t capacity) {
|
||||
opts->rep.capacity = capacity;
|
||||
}
|
||||
|
||||
void rocksdb_hyper_clock_cache_options_set_estimated_entry_charge(
|
||||
rocksdb_hyper_clock_cache_options_t* opts, size_t estimated_entry_charge) {
|
||||
opts->rep.estimated_entry_charge = estimated_entry_charge;
|
||||
}
|
||||
|
||||
void rocksdb_hyper_clock_cache_options_set_num_shard_bits(
|
||||
rocksdb_hyper_clock_cache_options_t* opts, int num_shard_bits) {
|
||||
opts->rep.num_shard_bits = num_shard_bits;
|
||||
}
|
||||
|
||||
void rocksdb_hyper_clock_cache_options_set_memory_allocator(
|
||||
rocksdb_hyper_clock_cache_options_t* opts,
|
||||
rocksdb_memory_allocator_t* memory_allocator) {
|
||||
opts->rep.memory_allocator = memory_allocator->rep;
|
||||
}
|
||||
|
||||
rocksdb_cache_t* rocksdb_cache_create_hyper_clock(
|
||||
size_t capacity, size_t estimated_entry_charge) {
|
||||
HyperClockCacheOptions opts(capacity, estimated_entry_charge);
|
||||
rocksdb_cache_t* c = new rocksdb_cache_t;
|
||||
c->rep = opts.MakeSharedCache();
|
||||
return c;
|
||||
}
|
||||
|
||||
rocksdb_cache_t* rocksdb_cache_create_hyper_clock_opts(
|
||||
const rocksdb_hyper_clock_cache_options_t* opts) {
|
||||
rocksdb_cache_t* c = new rocksdb_cache_t;
|
||||
c->rep = opts->rep.MakeSharedCache();
|
||||
return c;
|
||||
}
|
||||
|
||||
void rocksdb_cache_destroy(rocksdb_cache_t* cache) { delete cache; }
|
||||
|
||||
void rocksdb_cache_disown_data(rocksdb_cache_t* cache) {
|
||||
@@ -4681,18 +4744,26 @@ void rocksdb_cache_set_capacity(rocksdb_cache_t* cache, size_t capacity) {
|
||||
cache->rep->SetCapacity(capacity);
|
||||
}
|
||||
|
||||
size_t rocksdb_cache_get_capacity(rocksdb_cache_t* cache) {
|
||||
size_t rocksdb_cache_get_capacity(const rocksdb_cache_t* cache) {
|
||||
return cache->rep->GetCapacity();
|
||||
}
|
||||
|
||||
size_t rocksdb_cache_get_usage(rocksdb_cache_t* cache) {
|
||||
size_t rocksdb_cache_get_usage(const rocksdb_cache_t* cache) {
|
||||
return cache->rep->GetUsage();
|
||||
}
|
||||
|
||||
size_t rocksdb_cache_get_pinned_usage(rocksdb_cache_t* cache) {
|
||||
size_t rocksdb_cache_get_pinned_usage(const rocksdb_cache_t* cache) {
|
||||
return cache->rep->GetPinnedUsage();
|
||||
}
|
||||
|
||||
size_t rocksdb_cache_get_table_address_count(const rocksdb_cache_t* cache) {
|
||||
return cache->rep->GetTableAddressCount();
|
||||
}
|
||||
|
||||
size_t rocksdb_cache_get_occupancy_count(const rocksdb_cache_t* cache) {
|
||||
return cache->rep->GetOccupancyCount();
|
||||
}
|
||||
|
||||
rocksdb_dbpath_t* rocksdb_dbpath_create(const char* path,
|
||||
uint64_t target_size) {
|
||||
rocksdb_dbpath_t* result = new rocksdb_dbpath_t;
|
||||
@@ -4911,6 +4982,12 @@ void rocksdb_ingestexternalfileoptions_set_ingest_behind(
|
||||
opt->rep.ingest_behind = ingest_behind;
|
||||
}
|
||||
|
||||
void rocksdb_ingestexternalfileoptions_set_fail_if_not_bottommost_level(
|
||||
rocksdb_ingestexternalfileoptions_t* opt,
|
||||
unsigned char fail_if_not_bottommost_level) {
|
||||
opt->rep.fail_if_not_bottommost_level = fail_if_not_bottommost_level;
|
||||
}
|
||||
|
||||
void rocksdb_ingestexternalfileoptions_destroy(
|
||||
rocksdb_ingestexternalfileoptions_t* opt) {
|
||||
delete opt;
|
||||
@@ -5072,6 +5149,16 @@ rocksdb_fifo_compaction_options_t* rocksdb_fifo_compaction_options_create() {
|
||||
return result;
|
||||
}
|
||||
|
||||
void rocksdb_fifo_compaction_options_set_allow_compaction(
|
||||
rocksdb_fifo_compaction_options_t* fifo_opts, unsigned char allow_compaction) {
|
||||
fifo_opts->rep.allow_compaction = allow_compaction;
|
||||
}
|
||||
|
||||
unsigned char rocksdb_fifo_compaction_options_get_allow_compaction(
|
||||
rocksdb_fifo_compaction_options_t* fifo_opts) {
|
||||
return fifo_opts->rep.allow_compaction;
|
||||
}
|
||||
|
||||
void rocksdb_fifo_compaction_options_set_max_table_files_size(
|
||||
rocksdb_fifo_compaction_options_t* fifo_opts, uint64_t size) {
|
||||
fifo_opts->rep.max_table_files_size = size;
|
||||
@@ -5776,6 +5863,35 @@ void rocksdb_transaction_multi_get(rocksdb_transaction_t* txn,
|
||||
}
|
||||
}
|
||||
|
||||
void rocksdb_transaction_multi_get_for_update(
|
||||
rocksdb_transaction_t* txn, const rocksdb_readoptions_t* options,
|
||||
size_t num_keys, const char* const* keys_list,
|
||||
const size_t* keys_list_sizes, char** values_list,
|
||||
size_t* values_list_sizes, char** errs) {
|
||||
std::vector<Slice> keys(num_keys);
|
||||
for (size_t i = 0; i < num_keys; i++) {
|
||||
keys[i] = Slice(keys_list[i], keys_list_sizes[i]);
|
||||
}
|
||||
std::vector<std::string> values(num_keys);
|
||||
std::vector<Status> statuses =
|
||||
txn->rep->MultiGetForUpdate(options->rep, keys, &values);
|
||||
for (size_t i = 0; i < num_keys; i++) {
|
||||
if (statuses[i].ok()) {
|
||||
values_list[i] = CopyString(values[i]);
|
||||
values_list_sizes[i] = values[i].size();
|
||||
errs[i] = nullptr;
|
||||
} else {
|
||||
values_list[i] = nullptr;
|
||||
values_list_sizes[i] = 0;
|
||||
if (!statuses[i].IsNotFound()) {
|
||||
errs[i] = strdup(statuses[i].ToString().c_str());
|
||||
} else {
|
||||
errs[i] = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void rocksdb_transaction_multi_get_cf(
|
||||
rocksdb_transaction_t* txn, const rocksdb_readoptions_t* options,
|
||||
const rocksdb_column_family_handle_t* const* column_families,
|
||||
@@ -5808,6 +5924,38 @@ void rocksdb_transaction_multi_get_cf(
|
||||
}
|
||||
}
|
||||
|
||||
void rocksdb_transaction_multi_get_for_update_cf(
|
||||
rocksdb_transaction_t* txn, const rocksdb_readoptions_t* options,
|
||||
const rocksdb_column_family_handle_t* const* column_families,
|
||||
size_t num_keys, const char* const* keys_list,
|
||||
const size_t* keys_list_sizes, char** values_list,
|
||||
size_t* values_list_sizes, char** errs) {
|
||||
std::vector<Slice> keys(num_keys);
|
||||
std::vector<ColumnFamilyHandle*> cfs(num_keys);
|
||||
for (size_t i = 0; i < num_keys; i++) {
|
||||
keys[i] = Slice(keys_list[i], keys_list_sizes[i]);
|
||||
cfs[i] = column_families[i]->rep;
|
||||
}
|
||||
std::vector<std::string> values(num_keys);
|
||||
std::vector<Status> statuses =
|
||||
txn->rep->MultiGetForUpdate(options->rep, cfs, keys, &values);
|
||||
for (size_t i = 0; i < num_keys; i++) {
|
||||
if (statuses[i].ok()) {
|
||||
values_list[i] = CopyString(values[i]);
|
||||
values_list_sizes[i] = values[i].size();
|
||||
errs[i] = nullptr;
|
||||
} else {
|
||||
values_list[i] = nullptr;
|
||||
values_list_sizes[i] = 0;
|
||||
if (!statuses[i].IsNotFound()) {
|
||||
errs[i] = strdup(statuses[i].ToString().c_str());
|
||||
} else {
|
||||
errs[i] = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Read a key outside a transaction
|
||||
char* rocksdb_transactiondb_get(rocksdb_transactiondb_t* txn_db,
|
||||
const rocksdb_readoptions_t* options,
|
||||
@@ -6109,6 +6257,18 @@ void rocksdb_transactiondb_flush_cf(
|
||||
SaveError(errptr, txn_db->rep->Flush(options->rep, column_family->rep));
|
||||
}
|
||||
|
||||
void rocksdb_transactiondb_flush_cfs(
|
||||
rocksdb_transactiondb_t* txn_db, const rocksdb_flushoptions_t* options,
|
||||
rocksdb_column_family_handle_t** column_families, int num_column_families,
|
||||
char** errptr) {
|
||||
std::vector<ColumnFamilyHandle*> column_family_handles;
|
||||
for (int i = 0; i < num_column_families; i++) {
|
||||
column_family_handles.push_back(column_families[i]->rep);
|
||||
}
|
||||
|
||||
SaveError(errptr, txn_db->rep->Flush(options->rep, column_family_handles));
|
||||
}
|
||||
|
||||
rocksdb_checkpoint_t* rocksdb_transactiondb_checkpoint_object_create(
|
||||
rocksdb_transactiondb_t* txn_db, char** errptr) {
|
||||
Checkpoint* checkpoint;
|
||||
@@ -6397,4 +6557,3 @@ void rocksdb_enable_manual_compaction(rocksdb_t* db) {
|
||||
}
|
||||
|
||||
} // end extern "C"
|
||||
|
||||
|
||||
+141
@@ -488,6 +488,19 @@ static void CheckTxnPinGetCF(rocksdb_transaction_t* txn,
|
||||
rocksdb_pinnableslice_destroy(p);
|
||||
}
|
||||
|
||||
static void CheckTxnGetForUpdate(rocksdb_transaction_t* txn,
|
||||
const rocksdb_readoptions_t* options,
|
||||
const char* key, const char* expected) {
|
||||
char* err = NULL;
|
||||
size_t val_len;
|
||||
char* val;
|
||||
val = rocksdb_transaction_get_for_update(txn, options, key, strlen(key),
|
||||
&val_len, true, &err);
|
||||
CheckNoError(err);
|
||||
CheckEqual(expected, val, val_len);
|
||||
Free(&val);
|
||||
}
|
||||
|
||||
static void CheckTxnDBGet(rocksdb_transactiondb_t* txn_db,
|
||||
const rocksdb_readoptions_t* options, const char* key,
|
||||
const char* expected) {
|
||||
@@ -515,6 +528,20 @@ static void CheckTxnDBGetCF(rocksdb_transactiondb_t* txn_db,
|
||||
Free(&val);
|
||||
}
|
||||
|
||||
static void CheckTxnGetForUpdateCF(
|
||||
rocksdb_transaction_t* txn, const rocksdb_readoptions_t* options,
|
||||
rocksdb_column_family_handle_t* column_family, const char* key,
|
||||
const char* expected) {
|
||||
char* err = NULL;
|
||||
size_t val_len;
|
||||
char* val;
|
||||
val = rocksdb_transaction_get_for_update_cf(
|
||||
txn, options, column_family, key, strlen(key), &val_len, true, &err);
|
||||
CheckNoError(err);
|
||||
CheckEqual(expected, val, val_len);
|
||||
Free(&val);
|
||||
}
|
||||
|
||||
static void CheckTxnDBPinGet(rocksdb_transactiondb_t* txn_db,
|
||||
const rocksdb_readoptions_t* options,
|
||||
const char* key, const char* expected) {
|
||||
@@ -3204,6 +3231,120 @@ int main(int argc, char** argv) {
|
||||
rocksdb_transactiondb_options_destroy(txn_db_options);
|
||||
}
|
||||
|
||||
StartPhase("transactions_multi_get_for_update");
|
||||
{
|
||||
// open a TransactionDB
|
||||
txn_db_options = rocksdb_transactiondb_options_create();
|
||||
rocksdb_transactiondb_options_set_transaction_lock_timeout(txn_db_options,
|
||||
0);
|
||||
txn_options = rocksdb_transaction_options_create();
|
||||
rocksdb_options_set_create_if_missing(options, 1);
|
||||
txn_db = rocksdb_transactiondb_open(options, txn_db_options, dbname, &err);
|
||||
CheckNoError(err);
|
||||
|
||||
rocksdb_transactiondb_put(txn_db, woptions, "foo", 3, "hey", 3, &err);
|
||||
CheckNoError(err);
|
||||
rocksdb_transactiondb_put(txn_db, woptions, "bar", 3, "hello", 5, &err);
|
||||
CheckNoError(err);
|
||||
|
||||
// begin transactions
|
||||
txn = rocksdb_transaction_begin(txn_db, woptions, txn_options, NULL);
|
||||
rocksdb_transaction_t* txn2 =
|
||||
rocksdb_transaction_begin(txn_db, woptions, txn_options, NULL);
|
||||
|
||||
// multi get
|
||||
{
|
||||
const char* keys[2] = {"foo", "bar"};
|
||||
const size_t keys_sizes[2] = {3, 3};
|
||||
char* vals[2];
|
||||
size_t vals_sizes[2];
|
||||
char* errs[2];
|
||||
const char* expected[2] = {"hey", "hello"};
|
||||
rocksdb_transaction_multi_get_for_update(
|
||||
txn, roptions, 2, keys, keys_sizes, vals, vals_sizes, errs);
|
||||
CheckMultiGetValues(2, vals, vals_sizes, errs, expected);
|
||||
}
|
||||
|
||||
char* conflict_err = NULL;
|
||||
size_t val_len;
|
||||
rocksdb_transaction_get_for_update(txn2, roptions, "foo", 3, &val_len, true,
|
||||
&conflict_err);
|
||||
// get-for-update conflict
|
||||
CheckCondition(conflict_err != NULL);
|
||||
Free(&conflict_err);
|
||||
|
||||
// commit
|
||||
rocksdb_transaction_commit(txn, &err);
|
||||
CheckNoError(err);
|
||||
|
||||
// should work after first tx is commited
|
||||
CheckTxnGetForUpdate(txn2, roptions, "foo", "hey");
|
||||
|
||||
// commit the second one
|
||||
rocksdb_transaction_commit(txn2, &err);
|
||||
CheckNoError(err);
|
||||
|
||||
// destroy txns
|
||||
rocksdb_transaction_destroy(txn);
|
||||
rocksdb_transaction_destroy(txn2);
|
||||
|
||||
// same for column families
|
||||
|
||||
rocksdb_column_family_handle_t* cfh;
|
||||
cfh = rocksdb_transactiondb_create_column_family(txn_db, options,
|
||||
"txn_db_cf", &err);
|
||||
CheckNoError(err);
|
||||
|
||||
rocksdb_transactiondb_put_cf(txn_db, woptions, cfh, "cf_foo", 6, "cf_hello",
|
||||
8, &err);
|
||||
CheckNoError(err);
|
||||
rocksdb_transactiondb_put_cf(txn_db, woptions, cfh, "cf_bar", 6, "cf_hey",
|
||||
6, &err);
|
||||
CheckNoError(err);
|
||||
|
||||
txn = rocksdb_transaction_begin(txn_db, woptions, txn_options, NULL);
|
||||
txn2 = rocksdb_transaction_begin(txn_db, woptions, txn_options, NULL);
|
||||
|
||||
{
|
||||
const rocksdb_column_family_handle_t* get_handles[2] = {cfh, cfh};
|
||||
const char* keys[2] = {"cf_foo", "cf_bar"};
|
||||
const size_t keys_sizes[2] = {6, 6};
|
||||
char* vals[2];
|
||||
size_t vals_sizes[2];
|
||||
char* errs[2];
|
||||
const char* expected[2] = {"cf_hello", "cf_hey"};
|
||||
rocksdb_transaction_multi_get_for_update_cf(txn, roptions, get_handles, 2,
|
||||
keys, keys_sizes, vals,
|
||||
vals_sizes, errs);
|
||||
CheckMultiGetValues(2, vals, vals_sizes, errs, expected);
|
||||
}
|
||||
|
||||
char* conflict_err_cf = NULL;
|
||||
size_t val_len_cf;
|
||||
rocksdb_transaction_get_for_update_cf(txn2, roptions, cfh, "cf_foo", 6,
|
||||
&val_len_cf, true, &conflict_err_cf);
|
||||
CheckCondition(conflict_err_cf != NULL);
|
||||
Free(&conflict_err_cf);
|
||||
|
||||
rocksdb_transaction_commit(txn, &err);
|
||||
CheckNoError(err);
|
||||
|
||||
CheckTxnGetForUpdateCF(txn2, roptions, cfh, "cf_foo", "cf_hello");
|
||||
|
||||
rocksdb_transaction_commit(txn2, &err);
|
||||
CheckNoError(err);
|
||||
|
||||
// close and destroy
|
||||
rocksdb_column_family_handle_destroy(cfh);
|
||||
rocksdb_transaction_destroy(txn);
|
||||
rocksdb_transaction_destroy(txn2);
|
||||
rocksdb_transactiondb_close(txn_db);
|
||||
rocksdb_destroy_db(options, dbname, &err);
|
||||
CheckNoError(err);
|
||||
rocksdb_transaction_options_destroy(txn_options);
|
||||
rocksdb_transactiondb_options_destroy(txn_db_options);
|
||||
}
|
||||
|
||||
StartPhase("optimistic_transactions");
|
||||
{
|
||||
rocksdb_options_t* db_options = rocksdb_options_create();
|
||||
|
||||
+40
-7
@@ -869,7 +869,7 @@ int GetL0ThresholdSpeedupCompaction(int level0_file_num_compaction_trigger,
|
||||
}
|
||||
} // anonymous namespace
|
||||
|
||||
std::pair<WriteStallCondition, ColumnFamilyData::WriteStallCause>
|
||||
std::pair<WriteStallCondition, WriteStallCause>
|
||||
ColumnFamilyData::GetWriteStallConditionAndCause(
|
||||
int num_unflushed_memtables, int num_l0_files,
|
||||
uint64_t num_compaction_needed_bytes,
|
||||
@@ -942,7 +942,8 @@ WriteStallCondition ColumnFamilyData::RecalculateWriteStallConditions(
|
||||
internal_stats_->AddCFStats(InternalStats::L0_FILE_COUNT_LIMIT_STOPS, 1);
|
||||
if (compaction_picker_->IsLevel0CompactionInProgress()) {
|
||||
internal_stats_->AddCFStats(
|
||||
InternalStats::LOCKED_L0_FILE_COUNT_LIMIT_STOPS, 1);
|
||||
InternalStats::L0_FILE_COUNT_LIMIT_STOPS_WITH_ONGOING_COMPACTION,
|
||||
1);
|
||||
}
|
||||
ROCKS_LOG_WARN(ioptions_.logger,
|
||||
"[%s] Stopping writes because we have %d level-0 files",
|
||||
@@ -963,7 +964,7 @@ WriteStallCondition ColumnFamilyData::RecalculateWriteStallConditions(
|
||||
SetupDelay(write_controller, compaction_needed_bytes,
|
||||
prev_compaction_needed_bytes_, was_stopped,
|
||||
mutable_cf_options.disable_auto_compactions);
|
||||
internal_stats_->AddCFStats(InternalStats::MEMTABLE_LIMIT_SLOWDOWNS, 1);
|
||||
internal_stats_->AddCFStats(InternalStats::MEMTABLE_LIMIT_DELAYS, 1);
|
||||
ROCKS_LOG_WARN(
|
||||
ioptions_.logger,
|
||||
"[%s] Stalling writes because we have %d immutable memtables "
|
||||
@@ -981,11 +982,11 @@ WriteStallCondition ColumnFamilyData::RecalculateWriteStallConditions(
|
||||
SetupDelay(write_controller, compaction_needed_bytes,
|
||||
prev_compaction_needed_bytes_, was_stopped || near_stop,
|
||||
mutable_cf_options.disable_auto_compactions);
|
||||
internal_stats_->AddCFStats(InternalStats::L0_FILE_COUNT_LIMIT_SLOWDOWNS,
|
||||
1);
|
||||
internal_stats_->AddCFStats(InternalStats::L0_FILE_COUNT_LIMIT_DELAYS, 1);
|
||||
if (compaction_picker_->IsLevel0CompactionInProgress()) {
|
||||
internal_stats_->AddCFStats(
|
||||
InternalStats::LOCKED_L0_FILE_COUNT_LIMIT_SLOWDOWNS, 1);
|
||||
InternalStats::L0_FILE_COUNT_LIMIT_DELAYS_WITH_ONGOING_COMPACTION,
|
||||
1);
|
||||
}
|
||||
ROCKS_LOG_WARN(ioptions_.logger,
|
||||
"[%s] Stalling writes because we have %d level-0 files "
|
||||
@@ -1011,7 +1012,7 @@ WriteStallCondition ColumnFamilyData::RecalculateWriteStallConditions(
|
||||
prev_compaction_needed_bytes_, was_stopped || near_stop,
|
||||
mutable_cf_options.disable_auto_compactions);
|
||||
internal_stats_->AddCFStats(
|
||||
InternalStats::PENDING_COMPACTION_BYTES_LIMIT_SLOWDOWNS, 1);
|
||||
InternalStats::PENDING_COMPACTION_BYTES_LIMIT_DELAYS, 1);
|
||||
ROCKS_LOG_WARN(
|
||||
ioptions_.logger,
|
||||
"[%s] Stalling writes because of estimated pending compaction "
|
||||
@@ -1140,6 +1141,7 @@ Status ColumnFamilyData::RangesOverlapWithMemtables(
|
||||
*overlap = false;
|
||||
// Create an InternalIterator over all unflushed memtables
|
||||
Arena arena;
|
||||
// TODO: plumb Env::IOActivity
|
||||
ReadOptions read_opts;
|
||||
read_opts.total_order_seek = true;
|
||||
MergeIteratorBuilder merge_iter_builder(&internal_comparator_, &arena);
|
||||
@@ -1426,6 +1428,37 @@ Status ColumnFamilyData::ValidateOptions(
|
||||
"Memtable per key-value checksum protection only supports 0, 1, 2, 4 "
|
||||
"or 8 bytes per key.");
|
||||
}
|
||||
if (std::find(supported.begin(), supported.end(),
|
||||
cf_options.block_protection_bytes_per_key) == supported.end()) {
|
||||
return Status::NotSupported(
|
||||
"Block per key-value checksum protection only supports 0, 1, 2, 4 "
|
||||
"or 8 bytes per key.");
|
||||
}
|
||||
|
||||
if (!cf_options.compaction_options_fifo.file_temperature_age_thresholds
|
||||
.empty()) {
|
||||
if (cf_options.compaction_style != kCompactionStyleFIFO) {
|
||||
return Status::NotSupported(
|
||||
"Option file_temperature_age_thresholds only supports FIFO "
|
||||
"compaction.");
|
||||
} else if (cf_options.num_levels > 1) {
|
||||
return Status::NotSupported(
|
||||
"Option file_temperature_age_thresholds is only supported when "
|
||||
"num_levels = 1.");
|
||||
} else {
|
||||
const auto& ages =
|
||||
cf_options.compaction_options_fifo.file_temperature_age_thresholds;
|
||||
assert(ages.size() >= 1);
|
||||
// check that age is sorted
|
||||
for (size_t i = 0; i < ages.size() - 1; ++i) {
|
||||
if (ages[i].age >= ages[i + 1].age) {
|
||||
return Status::NotSupported(
|
||||
"Option file_temperature_age_thresholds requires elements to be "
|
||||
"sorted in increasing order with respect to `age` field.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
|
||||
@@ -462,12 +462,6 @@ class ColumnFamilyData {
|
||||
bool queued_for_flush() { return queued_for_flush_; }
|
||||
bool queued_for_compaction() { return queued_for_compaction_; }
|
||||
|
||||
enum class WriteStallCause {
|
||||
kNone,
|
||||
kMemtableLimit,
|
||||
kL0FileCountLimit,
|
||||
kPendingCompactionBytes,
|
||||
};
|
||||
static std::pair<WriteStallCondition, WriteStallCause>
|
||||
GetWriteStallConditionAndCause(
|
||||
int num_unflushed_memtables, int num_l0_files,
|
||||
|
||||
@@ -120,7 +120,9 @@ TEST_F(CompactFilesTest, L0ConflictsFiles) {
|
||||
TEST_F(CompactFilesTest, MultipleLevel) {
|
||||
Options options;
|
||||
options.create_if_missing = true;
|
||||
options.level_compaction_dynamic_level_bytes = true;
|
||||
// Otherwise background compaction can happen to
|
||||
// drain unnecessary level
|
||||
options.level_compaction_dynamic_level_bytes = false;
|
||||
options.num_levels = 6;
|
||||
// Add listener
|
||||
FlushedFileCollector* collector = new FlushedFileCollector();
|
||||
@@ -181,7 +183,6 @@ TEST_F(CompactFilesTest, MultipleLevel) {
|
||||
for (int invalid_output_level = 0; invalid_output_level < 5;
|
||||
invalid_output_level++) {
|
||||
s = db->CompactFiles(CompactionOptions(), files, invalid_output_level);
|
||||
std::cout << s.ToString() << std::endl;
|
||||
ASSERT_TRUE(s.IsInvalidArgument());
|
||||
}
|
||||
|
||||
|
||||
@@ -188,6 +188,11 @@ class ClippingIterator : public InternalIterator {
|
||||
return iter_->GetProperty(prop_name, prop);
|
||||
}
|
||||
|
||||
bool IsDeleteRangeSentinelKey() const override {
|
||||
assert(valid_);
|
||||
return iter_->IsDeleteRangeSentinelKey();
|
||||
}
|
||||
|
||||
private:
|
||||
void UpdateValid() {
|
||||
assert(!iter_->Valid() || iter_->status().ok());
|
||||
|
||||
@@ -465,6 +465,11 @@ bool Compaction::IsTrivialMove() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (compaction_reason_ == CompactionReason::kChangeTemperature) {
|
||||
// Changing temperature usually requires rewriting the file.
|
||||
return false;
|
||||
}
|
||||
|
||||
// Used in universal compaction, where trivial move can be done if the
|
||||
// input files are non overlapping
|
||||
if ((mutable_cf_options_.compaction_options_universal.allow_trivial_move) &&
|
||||
|
||||
@@ -546,13 +546,16 @@ struct PerKeyPlacementContext {
|
||||
const Slice value;
|
||||
const SequenceNumber seq_num;
|
||||
|
||||
bool output_to_penultimate_level;
|
||||
bool& output_to_penultimate_level;
|
||||
|
||||
PerKeyPlacementContext(int _level, Slice _key, Slice _value,
|
||||
SequenceNumber _seq_num)
|
||||
: level(_level), key(_key), value(_value), seq_num(_seq_num) {
|
||||
output_to_penultimate_level = false;
|
||||
}
|
||||
SequenceNumber _seq_num,
|
||||
bool& _output_to_penultimate_level)
|
||||
: level(_level),
|
||||
key(_key),
|
||||
value(_value),
|
||||
seq_num(_seq_num),
|
||||
output_to_penultimate_level(_output_to_penultimate_level) {}
|
||||
};
|
||||
#endif /* !NDEBUG */
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include "db/blob/blob_index.h"
|
||||
#include "db/blob/prefetch_buffer_collection.h"
|
||||
#include "db/snapshot_checker.h"
|
||||
#include "db/wide/wide_column_serialization.h"
|
||||
#include "logging/logging.h"
|
||||
#include "port/likely.h"
|
||||
#include "rocksdb/listener.h"
|
||||
@@ -225,8 +226,8 @@ bool CompactionIterator::InvokeFilterIfNeeded(bool* need_skip,
|
||||
return true;
|
||||
}
|
||||
|
||||
// TODO: support compaction filter for wide-column entities
|
||||
if (ikey_.type != kTypeValue && ikey_.type != kTypeBlobIndex) {
|
||||
if (ikey_.type != kTypeValue && ikey_.type != kTypeBlobIndex &&
|
||||
ikey_.type != kTypeWideColumnEntity) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -234,7 +235,9 @@ bool CompactionIterator::InvokeFilterIfNeeded(bool* need_skip,
|
||||
CompactionFilter::Decision::kUndetermined;
|
||||
CompactionFilter::ValueType value_type =
|
||||
ikey_.type == kTypeValue ? CompactionFilter::ValueType::kValue
|
||||
: CompactionFilter::ValueType::kBlobIndex;
|
||||
: ikey_.type == kTypeBlobIndex
|
||||
? CompactionFilter::ValueType::kBlobIndex
|
||||
: CompactionFilter::ValueType::kWideColumnEntity;
|
||||
|
||||
// Hack: pass internal key to BlobIndexCompactionFilter since it needs
|
||||
// to get sequence number.
|
||||
@@ -248,6 +251,8 @@ bool CompactionIterator::InvokeFilterIfNeeded(bool* need_skip,
|
||||
compaction_filter_value_.clear();
|
||||
compaction_filter_skip_until_.Clear();
|
||||
|
||||
std::vector<std::pair<std::string, std::string>> new_columns;
|
||||
|
||||
{
|
||||
StopWatchNano timer(clock_, report_detailed_time_);
|
||||
|
||||
@@ -303,10 +308,36 @@ bool CompactionIterator::InvokeFilterIfNeeded(bool* need_skip,
|
||||
value_type = CompactionFilter::ValueType::kValue;
|
||||
}
|
||||
}
|
||||
|
||||
if (decision == CompactionFilter::Decision::kUndetermined) {
|
||||
decision = compaction_filter_->FilterV2(
|
||||
level_, filter_key, value_type,
|
||||
blob_value_.empty() ? value_ : blob_value_, &compaction_filter_value_,
|
||||
const Slice* existing_val = nullptr;
|
||||
const WideColumns* existing_col = nullptr;
|
||||
|
||||
WideColumns existing_columns;
|
||||
|
||||
if (ikey_.type != kTypeWideColumnEntity) {
|
||||
if (!blob_value_.empty()) {
|
||||
existing_val = &blob_value_;
|
||||
} else {
|
||||
existing_val = &value_;
|
||||
}
|
||||
} else {
|
||||
Slice value_copy = value_;
|
||||
const Status s =
|
||||
WideColumnSerialization::Deserialize(value_copy, existing_columns);
|
||||
|
||||
if (!s.ok()) {
|
||||
status_ = s;
|
||||
validity_info_.Invalidate();
|
||||
return false;
|
||||
}
|
||||
|
||||
existing_col = &existing_columns;
|
||||
}
|
||||
|
||||
decision = compaction_filter_->FilterV3(
|
||||
level_, filter_key, value_type, existing_val, existing_col,
|
||||
&compaction_filter_value_, &new_columns,
|
||||
compaction_filter_skip_until_.rep());
|
||||
}
|
||||
|
||||
@@ -315,9 +346,10 @@ bool CompactionIterator::InvokeFilterIfNeeded(bool* need_skip,
|
||||
}
|
||||
|
||||
if (decision == CompactionFilter::Decision::kUndetermined) {
|
||||
// Should not reach here, since FilterV2 should never return kUndetermined.
|
||||
status_ =
|
||||
Status::NotSupported("FilterV2() should never return kUndetermined");
|
||||
// Should not reach here, since FilterV2/FilterV3 should never return
|
||||
// kUndetermined.
|
||||
status_ = Status::NotSupported(
|
||||
"FilterV2/FilterV3 should never return kUndetermined");
|
||||
validity_info_.Invalidate();
|
||||
return false;
|
||||
}
|
||||
@@ -326,7 +358,7 @@ bool CompactionIterator::InvokeFilterIfNeeded(bool* need_skip,
|
||||
cmp_->Compare(*compaction_filter_skip_until_.rep(), ikey_.user_key) <=
|
||||
0) {
|
||||
// Can't skip to a key smaller than the current one.
|
||||
// Keep the key as per FilterV2 documentation.
|
||||
// Keep the key as per FilterV2/FilterV3 documentation.
|
||||
decision = CompactionFilter::Decision::kKeep;
|
||||
}
|
||||
|
||||
@@ -388,6 +420,35 @@ bool CompactionIterator::InvokeFilterIfNeeded(bool* need_skip,
|
||||
status_ = Status::IOError("Failed to access blob during compaction filter");
|
||||
validity_info_.Invalidate();
|
||||
return false;
|
||||
} else if (decision == CompactionFilter::Decision::kChangeWideColumnEntity) {
|
||||
WideColumns sorted_columns;
|
||||
|
||||
sorted_columns.reserve(new_columns.size());
|
||||
for (const auto& column : new_columns) {
|
||||
sorted_columns.emplace_back(column.first, column.second);
|
||||
}
|
||||
|
||||
std::sort(sorted_columns.begin(), sorted_columns.end(),
|
||||
[](const WideColumn& lhs, const WideColumn& rhs) {
|
||||
return lhs.name().compare(rhs.name()) < 0;
|
||||
});
|
||||
|
||||
{
|
||||
const Status s = WideColumnSerialization::Serialize(
|
||||
sorted_columns, compaction_filter_value_);
|
||||
if (!s.ok()) {
|
||||
status_ = s;
|
||||
validity_info_.Invalidate();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (ikey_.type != kTypeWideColumnEntity) {
|
||||
ikey_.type = kTypeWideColumnEntity;
|
||||
current_key_.UpdateInternalKey(ikey_.sequence, kTypeWideColumnEntity);
|
||||
}
|
||||
|
||||
value_ = compaction_filter_value_;
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -403,6 +464,7 @@ void CompactionIterator::NextFromInput() {
|
||||
value_ = input_.value();
|
||||
blob_value_.Reset();
|
||||
iter_stats_.num_input_records++;
|
||||
is_range_del_ = input_.IsDeleteRangeSentinelKey();
|
||||
|
||||
Status pik_status = ParseInternalKey(key_, &ikey_, allow_data_in_errors_);
|
||||
if (!pik_status.ok()) {
|
||||
@@ -422,7 +484,10 @@ void CompactionIterator::NextFromInput() {
|
||||
break;
|
||||
}
|
||||
TEST_SYNC_POINT_CALLBACK("CompactionIterator:ProcessKV", &ikey_);
|
||||
|
||||
if (is_range_del_) {
|
||||
validity_info_.SetValid(kRangeDeletion);
|
||||
break;
|
||||
}
|
||||
// Update input statistics
|
||||
if (ikey_.type == kTypeDeletion || ikey_.type == kTypeSingleDeletion ||
|
||||
ikey_.type == kTypeDeletionWithTimestamp) {
|
||||
@@ -644,6 +709,14 @@ void CompactionIterator::NextFromInput() {
|
||||
|
||||
ParsedInternalKey next_ikey;
|
||||
AdvanceInputIter();
|
||||
while (input_.Valid() && input_.IsDeleteRangeSentinelKey() &&
|
||||
ParseInternalKey(input_.key(), &next_ikey, allow_data_in_errors_)
|
||||
.ok() &&
|
||||
cmp_->EqualWithoutTimestamp(ikey_.user_key, next_ikey.user_key)) {
|
||||
// skip range tombstone start keys with the same user key
|
||||
// since they are not "real" point keys.
|
||||
AdvanceInputIter();
|
||||
}
|
||||
|
||||
// Check whether the next key exists, is not corrupt, and is the same key
|
||||
// as the single delete.
|
||||
@@ -651,6 +724,7 @@ void CompactionIterator::NextFromInput() {
|
||||
ParseInternalKey(input_.key(), &next_ikey, allow_data_in_errors_)
|
||||
.ok() &&
|
||||
cmp_->EqualWithoutTimestamp(ikey_.user_key, next_ikey.user_key)) {
|
||||
assert(!input_.IsDeleteRangeSentinelKey());
|
||||
#ifndef NDEBUG
|
||||
const Compaction* c =
|
||||
compaction_ ? compaction_->real_compaction() : nullptr;
|
||||
@@ -875,12 +949,14 @@ void CompactionIterator::NextFromInput() {
|
||||
// Note that a deletion marker of type kTypeDeletionWithTimestamp will be
|
||||
// considered to have a different user key unless the timestamp is older
|
||||
// than *full_history_ts_low_.
|
||||
//
|
||||
// Range tombstone start keys are skipped as they are not "real" keys.
|
||||
while (!IsPausingManualCompaction() && !IsShuttingDown() &&
|
||||
input_.Valid() &&
|
||||
(ParseInternalKey(input_.key(), &next_ikey, allow_data_in_errors_)
|
||||
.ok()) &&
|
||||
cmp_->EqualWithoutTimestamp(ikey_.user_key, next_ikey.user_key) &&
|
||||
(prev_snapshot == 0 ||
|
||||
(prev_snapshot == 0 || input_.IsDeleteRangeSentinelKey() ||
|
||||
DefinitelyNotInSnapshot(next_ikey.sequence, prev_snapshot))) {
|
||||
AdvanceInputIter();
|
||||
}
|
||||
@@ -1125,17 +1201,7 @@ void CompactionIterator::GarbageCollectBlobIfNeeded() {
|
||||
|
||||
void CompactionIterator::DecideOutputLevel() {
|
||||
assert(compaction_->SupportsPerKeyPlacement());
|
||||
#ifndef NDEBUG
|
||||
// Could be overridden by unittest
|
||||
PerKeyPlacementContext context(level_, ikey_.user_key, value_,
|
||||
ikey_.sequence);
|
||||
TEST_SYNC_POINT_CALLBACK("CompactionIterator::PrepareOutput.context",
|
||||
&context);
|
||||
output_to_penultimate_level_ = context.output_to_penultimate_level;
|
||||
#else
|
||||
output_to_penultimate_level_ = false;
|
||||
#endif // NDEBUG
|
||||
|
||||
// if the key is newer than the cutoff sequence or within the earliest
|
||||
// snapshot, it should output to the penultimate level.
|
||||
if (ikey_.sequence > preclude_last_level_min_seqno_ ||
|
||||
@@ -1143,6 +1209,17 @@ void CompactionIterator::DecideOutputLevel() {
|
||||
output_to_penultimate_level_ = true;
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
// Could be overridden by unittest
|
||||
PerKeyPlacementContext context(level_, ikey_.user_key, value_, ikey_.sequence,
|
||||
output_to_penultimate_level_);
|
||||
TEST_SYNC_POINT_CALLBACK("CompactionIterator::PrepareOutput.context",
|
||||
&context);
|
||||
if (ikey_.sequence > earliest_snapshot_) {
|
||||
output_to_penultimate_level_ = true;
|
||||
}
|
||||
#endif // NDEBUG
|
||||
|
||||
if (output_to_penultimate_level_) {
|
||||
// If it's decided to output to the penultimate level, but unsafe to do so,
|
||||
// still output to the last level. For example, moving the data from a lower
|
||||
@@ -1174,10 +1251,12 @@ void CompactionIterator::DecideOutputLevel() {
|
||||
|
||||
void CompactionIterator::PrepareOutput() {
|
||||
if (Valid()) {
|
||||
if (ikey_.type == kTypeValue) {
|
||||
ExtractLargeValueIfNeeded();
|
||||
} else if (ikey_.type == kTypeBlobIndex) {
|
||||
GarbageCollectBlobIfNeeded();
|
||||
if (LIKELY(!is_range_del_)) {
|
||||
if (ikey_.type == kTypeValue) {
|
||||
ExtractLargeValueIfNeeded();
|
||||
} else if (ikey_.type == kTypeBlobIndex) {
|
||||
GarbageCollectBlobIfNeeded();
|
||||
}
|
||||
}
|
||||
|
||||
if (compaction_ != nullptr && compaction_->SupportsPerKeyPlacement()) {
|
||||
@@ -1200,7 +1279,7 @@ void CompactionIterator::PrepareOutput() {
|
||||
DefinitelyInSnapshot(ikey_.sequence, earliest_snapshot_) &&
|
||||
ikey_.type != kTypeMerge && current_key_committed_ &&
|
||||
!output_to_penultimate_level_ &&
|
||||
ikey_.sequence < preserve_time_min_seqno_) {
|
||||
ikey_.sequence < preserve_time_min_seqno_ && !is_range_del_) {
|
||||
if (ikey_.type == kTypeDeletion ||
|
||||
(ikey_.type == kTypeSingleDeletion && timestamp_size_ == 0)) {
|
||||
ROCKS_LOG_FATAL(
|
||||
@@ -1333,6 +1412,7 @@ std::unique_ptr<BlobFetcher> CompactionIterator::CreateBlobFetcherIfNeeded(
|
||||
}
|
||||
|
||||
ReadOptions read_options;
|
||||
read_options.io_activity = Env::IOActivity::kCompaction;
|
||||
read_options.fill_cache = false;
|
||||
|
||||
return std::unique_ptr<BlobFetcher>(new BlobFetcher(version, read_options));
|
||||
|
||||
@@ -63,6 +63,10 @@ class SequenceIterWrapper : public InternalIterator {
|
||||
void SeekToLast() override { assert(false); }
|
||||
|
||||
uint64_t num_itered() const { return num_itered_; }
|
||||
bool IsDeleteRangeSentinelKey() const override {
|
||||
assert(Valid());
|
||||
return inner_iter_->IsDeleteRangeSentinelKey();
|
||||
}
|
||||
|
||||
private:
|
||||
InternalKeyComparator icmp_;
|
||||
@@ -242,7 +246,12 @@ class CompactionIterator {
|
||||
const Status& status() const { return status_; }
|
||||
const ParsedInternalKey& ikey() const { return ikey_; }
|
||||
inline bool Valid() const { return validity_info_.IsValid(); }
|
||||
const Slice& user_key() const { return current_user_key_; }
|
||||
const Slice& user_key() const {
|
||||
if (UNLIKELY(is_range_del_)) {
|
||||
return ikey_.user_key;
|
||||
}
|
||||
return current_user_key_;
|
||||
}
|
||||
const CompactionIterationStats& iter_stats() const { return iter_stats_; }
|
||||
uint64_t num_input_entry_scanned() const { return input_.num_itered(); }
|
||||
// If the current key should be placed on penultimate level, only valid if
|
||||
@@ -252,6 +261,8 @@ class CompactionIterator {
|
||||
}
|
||||
Status InputStatus() const { return input_.status(); }
|
||||
|
||||
bool IsDeleteRangeSentinelKey() const { return is_range_del_; }
|
||||
|
||||
private:
|
||||
// Processes the input stream to find the next output
|
||||
void NextFromInput();
|
||||
@@ -385,6 +396,7 @@ class CompactionIterator {
|
||||
kKeepSD = 8,
|
||||
kKeepDel = 9,
|
||||
kNewUserKey = 10,
|
||||
kRangeDeletion = 11,
|
||||
};
|
||||
|
||||
struct ValidityInfo {
|
||||
@@ -493,6 +505,10 @@ class CompactionIterator {
|
||||
// This is a best-effort facility, so memory_order_relaxed is sufficient.
|
||||
return manual_compaction_canceled_.load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
// Stores whether the current compaction iterator output
|
||||
// is a range tombstone start key.
|
||||
bool is_range_del_{false};
|
||||
};
|
||||
|
||||
inline bool CompactionIterator::DefinitelyInSnapshot(SequenceNumber seq,
|
||||
|
||||
@@ -192,8 +192,8 @@ CompactionJob::CompactionJob(
|
||||
assert(log_buffer_ != nullptr);
|
||||
|
||||
const auto* cfd = compact_->compaction->column_family_data();
|
||||
ThreadStatusUtil::SetColumnFamily(cfd, cfd->ioptions()->env,
|
||||
db_options_.enable_thread_tracking);
|
||||
ThreadStatusUtil::SetEnableTracking(db_options_.enable_thread_tracking);
|
||||
ThreadStatusUtil::SetColumnFamily(cfd);
|
||||
ThreadStatusUtil::SetThreadOperation(ThreadStatus::OP_COMPACTION);
|
||||
ReportStartedCompaction(compaction);
|
||||
}
|
||||
@@ -204,10 +204,6 @@ CompactionJob::~CompactionJob() {
|
||||
}
|
||||
|
||||
void CompactionJob::ReportStartedCompaction(Compaction* compaction) {
|
||||
const auto* cfd = compact_->compaction->column_family_data();
|
||||
ThreadStatusUtil::SetColumnFamily(cfd, cfd->ioptions()->env,
|
||||
db_options_.enable_thread_tracking);
|
||||
|
||||
ThreadStatusUtil::SetThreadOperationProperty(ThreadStatus::COMPACTION_JOB_ID,
|
||||
job_id_);
|
||||
|
||||
@@ -291,12 +287,14 @@ void CompactionJob::Prepare() {
|
||||
c->immutable_options()->preclude_last_level_data_seconds);
|
||||
|
||||
if (preserve_time_duration > 0) {
|
||||
const ReadOptions read_options(Env::IOActivity::kCompaction);
|
||||
// setup seqno_time_mapping_
|
||||
seqno_time_mapping_.SetMaxTimeDuration(preserve_time_duration);
|
||||
for (const auto& each_level : *c->inputs()) {
|
||||
for (const auto& fmd : each_level.files) {
|
||||
std::shared_ptr<const TableProperties> tp;
|
||||
Status s = cfd->current()->GetTableProperties(&tp, fmd, nullptr);
|
||||
Status s =
|
||||
cfd->current()->GetTableProperties(read_options, &tp, fmd, nullptr);
|
||||
if (s.ok()) {
|
||||
seqno_time_mapping_.Add(tp->seqno_to_time_mapping)
|
||||
.PermitUncheckedError();
|
||||
@@ -472,7 +470,7 @@ void CompactionJob::GenSubcompactionBoundaries() {
|
||||
// overlap with N-1 other ranges. Since we requested a relatively large number
|
||||
// (128) of ranges from each input files, even N range overlapping would
|
||||
// cause relatively small inaccuracy.
|
||||
|
||||
const ReadOptions read_options(Env::IOActivity::kCompaction);
|
||||
auto* c = compact_->compaction;
|
||||
if (c->max_subcompactions() <= 1 &&
|
||||
!(c->immutable_options()->compaction_pri == kRoundRobin &&
|
||||
@@ -506,7 +504,9 @@ void CompactionJob::GenSubcompactionBoundaries() {
|
||||
FileMetaData* f = flevel->files[i].file_metadata;
|
||||
std::vector<TableReader::Anchor> my_anchors;
|
||||
Status s = cfd->table_cache()->ApproximateKeyAnchors(
|
||||
ReadOptions(), icomp, *f, my_anchors);
|
||||
read_options, icomp, *f,
|
||||
c->mutable_cf_options()->block_protection_bytes_per_key,
|
||||
my_anchors);
|
||||
if (!s.ok() || my_anchors.empty()) {
|
||||
my_anchors.emplace_back(f->largest.user_key(), f->fd.GetFileSize());
|
||||
}
|
||||
@@ -722,11 +722,12 @@ Status CompactionJob::Run() {
|
||||
// use_direct_io_for_flush_and_compaction is true, we will regard this
|
||||
// verification as user reads since the goal is to cache it here for
|
||||
// further user reads
|
||||
ReadOptions read_options;
|
||||
const ReadOptions verify_table_read_options(
|
||||
Env::IOActivity::kCompaction);
|
||||
InternalIterator* iter = cfd->table_cache()->NewIterator(
|
||||
read_options, file_options_, cfd->internal_comparator(),
|
||||
files_output[file_idx]->meta, /*range_del_agg=*/nullptr,
|
||||
prefix_extractor,
|
||||
verify_table_read_options, file_options_,
|
||||
cfd->internal_comparator(), files_output[file_idx]->meta,
|
||||
/*range_del_agg=*/nullptr, prefix_extractor,
|
||||
/*table_reader_ptr=*/nullptr,
|
||||
cfd->internal_stats()->GetFileReadHist(
|
||||
compact_->compaction->output_level()),
|
||||
@@ -736,7 +737,9 @@ Status CompactionJob::Run() {
|
||||
*compact_->compaction->mutable_cf_options()),
|
||||
/*smallest_compaction_key=*/nullptr,
|
||||
/*largest_compaction_key=*/nullptr,
|
||||
/*allow_unprepared_value=*/false);
|
||||
/*allow_unprepared_value=*/false,
|
||||
compact_->compaction->mutable_cf_options()
|
||||
->block_protection_bytes_per_key);
|
||||
auto s = iter->status();
|
||||
|
||||
if (s.ok() && paranoid_file_checks_) {
|
||||
@@ -1032,7 +1035,6 @@ void CompactionJob::NotifyOnSubcompactionCompleted(
|
||||
void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
|
||||
assert(sub_compact);
|
||||
assert(sub_compact->compaction);
|
||||
|
||||
if (db_options_.compaction_service) {
|
||||
CompactionServiceJobStatus comp_status =
|
||||
ProcessKeyValueCompactionWithCompactionService(sub_compact);
|
||||
@@ -1083,6 +1085,7 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
|
||||
read_options.verify_checksums = true;
|
||||
read_options.fill_cache = false;
|
||||
read_options.rate_limiter_priority = GetRateLimiterPriority();
|
||||
read_options.io_activity = Env::IOActivity::kCompaction;
|
||||
// Compaction iterators shouldn't be confined to a single prefix.
|
||||
// Compactions use Seek() for
|
||||
// (a) concurrent compactions,
|
||||
@@ -1118,6 +1121,8 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
|
||||
IterKey end_ikey;
|
||||
Slice start_slice;
|
||||
Slice end_slice;
|
||||
Slice start_user_key{};
|
||||
Slice end_user_key{};
|
||||
|
||||
static constexpr char kMaxTs[] =
|
||||
"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff";
|
||||
@@ -1140,6 +1145,7 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
|
||||
&ts_slice);
|
||||
}
|
||||
start_slice = start_ikey.GetInternalKey();
|
||||
start_user_key = start_ikey.GetUserKey();
|
||||
}
|
||||
if (end.has_value()) {
|
||||
end_ikey.SetInternalKey(end.value(), kMaxSequenceNumber, kValueTypeForSeek);
|
||||
@@ -1148,6 +1154,7 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
|
||||
&ts_slice);
|
||||
}
|
||||
end_slice = end_ikey.GetInternalKey();
|
||||
end_user_key = end_ikey.GetUserKey();
|
||||
}
|
||||
|
||||
std::unique_ptr<InternalIterator> clip;
|
||||
@@ -1263,11 +1270,15 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
|
||||
[this, sub_compact](CompactionOutputs& outputs) {
|
||||
return this->OpenCompactionOutputFile(sub_compact, outputs);
|
||||
};
|
||||
|
||||
const CompactionFileCloseFunc close_file_func =
|
||||
[this, sub_compact](CompactionOutputs& outputs, const Status& status,
|
||||
const Slice& next_table_min_key) {
|
||||
return this->FinishCompactionOutputFile(status, sub_compact, outputs,
|
||||
next_table_min_key);
|
||||
[this, sub_compact, start_user_key, end_user_key](
|
||||
CompactionOutputs& outputs, const Status& status,
|
||||
const Slice& next_table_min_key) {
|
||||
return this->FinishCompactionOutputFile(
|
||||
status, sub_compact, outputs, next_table_min_key,
|
||||
sub_compact->start.has_value() ? &start_user_key : nullptr,
|
||||
sub_compact->end.has_value() ? &end_user_key : nullptr);
|
||||
};
|
||||
|
||||
Status status;
|
||||
@@ -1278,7 +1289,6 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
|
||||
while (status.ok() && !cfd->IsDropped() && c_iter->Valid()) {
|
||||
// Invariant: c_iter.status() is guaranteed to be OK if c_iter->Valid()
|
||||
// returns true.
|
||||
|
||||
assert(!end.has_value() || cfd->user_comparator()->Compare(
|
||||
c_iter->user_key(), end.value()) < 0);
|
||||
|
||||
@@ -1458,7 +1468,8 @@ void CompactionJob::RecordDroppedKeys(
|
||||
|
||||
Status CompactionJob::FinishCompactionOutputFile(
|
||||
const Status& input_status, SubcompactionState* sub_compact,
|
||||
CompactionOutputs& outputs, const Slice& next_table_min_key) {
|
||||
CompactionOutputs& outputs, const Slice& next_table_min_key,
|
||||
const Slice* comp_start_user_key, const Slice* comp_end_user_key) {
|
||||
AutoThreadOperationStageUpdater stage_updater(
|
||||
ThreadStatus::STAGE_COMPACTION_SYNC_FILE);
|
||||
assert(sub_compact != nullptr);
|
||||
@@ -1488,12 +1499,10 @@ Status CompactionJob::FinishCompactionOutputFile(
|
||||
// output_to_penultimate_level compaction here, as it's only used to decide
|
||||
// if range dels could be dropped.
|
||||
if (outputs.HasRangeDel()) {
|
||||
s = outputs.AddRangeDels(
|
||||
sub_compact->start.has_value() ? &(sub_compact->start.value())
|
||||
: nullptr,
|
||||
sub_compact->end.has_value() ? &(sub_compact->end.value()) : nullptr,
|
||||
range_del_out_stats, bottommost_level_, cfd->internal_comparator(),
|
||||
earliest_snapshot, next_table_min_key, full_history_ts_low_);
|
||||
s = outputs.AddRangeDels(comp_start_user_key, comp_end_user_key,
|
||||
range_del_out_stats, bottommost_level_,
|
||||
cfd->internal_comparator(), earliest_snapshot,
|
||||
next_table_min_key, full_history_ts_low_);
|
||||
}
|
||||
RecordDroppedKeys(range_del_out_stats, &sub_compact->compaction_job_stats);
|
||||
TEST_SYNC_POINT("CompactionJob::FinishCompactionOutputFile1");
|
||||
@@ -1634,6 +1643,7 @@ Status CompactionJob::InstallCompactionResults(
|
||||
|
||||
db_mutex_->AssertHeld();
|
||||
|
||||
const ReadOptions read_options(Env::IOActivity::kCompaction);
|
||||
auto* compaction = compact_->compaction;
|
||||
assert(compaction);
|
||||
|
||||
@@ -1711,8 +1721,8 @@ Status CompactionJob::InstallCompactionResults(
|
||||
}
|
||||
|
||||
return versions_->LogAndApply(compaction->column_family_data(),
|
||||
mutable_cf_options, edit, db_mutex_,
|
||||
db_directory_);
|
||||
mutable_cf_options, read_options, edit,
|
||||
db_mutex_, db_directory_);
|
||||
}
|
||||
|
||||
void CompactionJob::RecordCompactionIOStats() {
|
||||
|
||||
@@ -256,7 +256,9 @@ class CompactionJob {
|
||||
Status FinishCompactionOutputFile(const Status& input_status,
|
||||
SubcompactionState* sub_compact,
|
||||
CompactionOutputs& outputs,
|
||||
const Slice& next_table_min_key);
|
||||
const Slice& next_table_min_key,
|
||||
const Slice* comp_start_user_key,
|
||||
const Slice* comp_end_user_key);
|
||||
Status InstallCompactionResults(const MutableCFOptions& mutable_cf_options);
|
||||
Status OpenCompactionOutputFile(SubcompactionState* sub_compact,
|
||||
CompactionOutputs& outputs);
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
#include "db/write_batch_internal.h"
|
||||
#include "env/mock_env.h"
|
||||
#include "file/filename.h"
|
||||
#include "monitoring/statistics.h"
|
||||
#include "monitoring/statistics_impl.h"
|
||||
#include "monitoring/thread_status_util.h"
|
||||
#include "port/stack_trace.h"
|
||||
#include "rocksdb/cache.h"
|
||||
@@ -54,7 +54,7 @@
|
||||
#include "util/compression.h"
|
||||
#include "util/hash.h"
|
||||
#include "util/mutexlock.h"
|
||||
#include "util/rate_limiter.h"
|
||||
#include "util/rate_limiter_impl.h"
|
||||
#include "util/string_util.h"
|
||||
#include "utilities/merge_operators.h"
|
||||
|
||||
|
||||
@@ -373,7 +373,7 @@ class CompactionJobTestBase : public testing::Test {
|
||||
} else if (table_type_ == TableTypeForTest::kMockTable) {
|
||||
file_size = 10;
|
||||
EXPECT_OK(mock_table_factory_->CreateMockTable(
|
||||
env_, GenerateFileName(file_number), std::move(contents)));
|
||||
env_, GenerateFileName(file_number), contents));
|
||||
} else {
|
||||
assert(false);
|
||||
}
|
||||
@@ -386,12 +386,12 @@ class CompactionJobTestBase : public testing::Test {
|
||||
kUnknownFileCreationTime,
|
||||
versions_->GetColumnFamilySet()->GetDefault()->NewEpochNumber(),
|
||||
kUnknownFileChecksum, kUnknownFileChecksumFuncName, kNullUniqueId64x2,
|
||||
0);
|
||||
0, 0);
|
||||
|
||||
mutex_.Lock();
|
||||
EXPECT_OK(
|
||||
versions_->LogAndApply(versions_->GetColumnFamilySet()->GetDefault(),
|
||||
mutable_cf_options_, &edit, &mutex_, nullptr));
|
||||
EXPECT_OK(versions_->LogAndApply(
|
||||
versions_->GetColumnFamilySet()->GetDefault(), mutable_cf_options_,
|
||||
read_options_, &edit, &mutex_, nullptr));
|
||||
mutex_.Unlock();
|
||||
}
|
||||
|
||||
@@ -454,7 +454,8 @@ class CompactionJobTestBase : public testing::Test {
|
||||
Status s = cf_options_.table_factory->NewTableReader(
|
||||
read_opts,
|
||||
TableReaderOptions(*cfd->ioptions(), nullptr, FileOptions(),
|
||||
cfd_->internal_comparator()),
|
||||
cfd_->internal_comparator(),
|
||||
0 /* block_protection_bytes_per_key */),
|
||||
std::move(freader), file_size, &table_reader, false);
|
||||
ASSERT_OK(s);
|
||||
assert(table_reader);
|
||||
@@ -727,6 +728,7 @@ class CompactionJobTestBase : public testing::Test {
|
||||
ColumnFamilyOptions cf_options_;
|
||||
MutableCFOptions mutable_cf_options_;
|
||||
MutableDBOptions mutable_db_options_;
|
||||
const ReadOptions read_options_;
|
||||
std::shared_ptr<Cache> table_cache_;
|
||||
WriteController write_controller_;
|
||||
WriteBufferManager write_buffer_manager_;
|
||||
@@ -2440,4 +2442,3 @@ int main(int argc, char** argv) {
|
||||
RegisterCustomObjects(argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
|
||||
|
||||
+249
-304
@@ -43,6 +43,7 @@ Status CompactionOutputs::Finish(const Status& intput_status,
|
||||
const uint64_t current_bytes = builder_->FileSize();
|
||||
if (s.ok()) {
|
||||
meta->fd.file_size = current_bytes;
|
||||
meta->tail_size = builder_->GetTailSize();
|
||||
meta->marked_for_compaction = builder_->NeedCompact();
|
||||
}
|
||||
current_output().finished = true;
|
||||
@@ -226,6 +227,15 @@ uint64_t CompactionOutputs::GetCurrentKeyGrandparentOverlappedBytes(
|
||||
bool CompactionOutputs::ShouldStopBefore(const CompactionIterator& c_iter) {
|
||||
assert(c_iter.Valid());
|
||||
const Slice& internal_key = c_iter.key();
|
||||
#ifndef NDEBUG
|
||||
bool should_stop = false;
|
||||
std::pair<bool*, const Slice> p{&should_stop, internal_key};
|
||||
TEST_SYNC_POINT_CALLBACK(
|
||||
"CompactionOutputs::ShouldStopBefore::manual_decision", (void*)&p);
|
||||
if (should_stop) {
|
||||
return true;
|
||||
}
|
||||
#endif // NDEBUG
|
||||
const uint64_t previous_overlapped_bytes = grandparent_overlapped_bytes_;
|
||||
const InternalKeyComparator* icmp =
|
||||
&compaction_->column_family_data()->internal_comparator();
|
||||
@@ -347,8 +357,14 @@ Status CompactionOutputs::AddToOutput(
|
||||
const CompactionFileOpenFunc& open_file_func,
|
||||
const CompactionFileCloseFunc& close_file_func) {
|
||||
Status s;
|
||||
bool is_range_del = c_iter.IsDeleteRangeSentinelKey();
|
||||
if (is_range_del && compaction_->bottommost_level()) {
|
||||
// We don't consider range tombstone for bottommost level since:
|
||||
// 1. there is no grandparent and hence no overlap to consider
|
||||
// 2. range tombstone may be dropped at bottommost level.
|
||||
return s;
|
||||
}
|
||||
const Slice& key = c_iter.key();
|
||||
|
||||
if (ShouldStopBefore(c_iter) && HasBuilder()) {
|
||||
s = close_file_func(*this, c_iter.InputStatus(), key);
|
||||
if (!s.ok()) {
|
||||
@@ -358,6 +374,13 @@ Status CompactionOutputs::AddToOutput(
|
||||
grandparent_boundary_switched_num_ = 0;
|
||||
grandparent_overlapped_bytes_ =
|
||||
GetCurrentKeyGrandparentOverlappedBytes(key);
|
||||
if (UNLIKELY(is_range_del)) {
|
||||
// lower bound for this new output file, this is needed as the lower bound
|
||||
// does not come from the smallest point key in this case.
|
||||
range_tombstone_lower_bound_.DecodeFrom(key);
|
||||
} else {
|
||||
range_tombstone_lower_bound_.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
// Open output file if necessary
|
||||
@@ -368,6 +391,17 @@ Status CompactionOutputs::AddToOutput(
|
||||
}
|
||||
}
|
||||
|
||||
// c_iter may emit range deletion keys, so update `last_key_for_partitioner_`
|
||||
// here before returning below when `is_range_del` is true
|
||||
if (partitioner_) {
|
||||
last_key_for_partitioner_.assign(c_iter.user_key().data_,
|
||||
c_iter.user_key().size_);
|
||||
}
|
||||
|
||||
if (UNLIKELY(is_range_del)) {
|
||||
return s;
|
||||
}
|
||||
|
||||
assert(builder_ != nullptr);
|
||||
const Slice& value = c_iter.value();
|
||||
s = current_output().validator.Add(key, value);
|
||||
@@ -391,28 +425,33 @@ Status CompactionOutputs::AddToOutput(
|
||||
s = current_output().meta.UpdateBoundaries(key, value, ikey.sequence,
|
||||
ikey.type);
|
||||
|
||||
if (partitioner_) {
|
||||
last_key_for_partitioner_.assign(c_iter.user_key().data_,
|
||||
c_iter.user_key().size_);
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
namespace {
|
||||
void SetMaxSeqAndTs(InternalKey& internal_key, const Slice& user_key,
|
||||
const size_t ts_sz) {
|
||||
if (ts_sz) {
|
||||
static constexpr char kTsMax[] = "\xff\xff\xff\xff\xff\xff\xff\xff\xff";
|
||||
if (ts_sz <= strlen(kTsMax)) {
|
||||
internal_key = InternalKey(user_key, kMaxSequenceNumber,
|
||||
kTypeRangeDeletion, Slice(kTsMax, ts_sz));
|
||||
} else {
|
||||
internal_key =
|
||||
InternalKey(user_key, kMaxSequenceNumber, kTypeRangeDeletion,
|
||||
std::string(ts_sz, '\xff'));
|
||||
}
|
||||
} else {
|
||||
internal_key.Set(user_key, kMaxSequenceNumber, kTypeRangeDeletion);
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
Status CompactionOutputs::AddRangeDels(
|
||||
const Slice* comp_start_user_key, const Slice* comp_end_user_key,
|
||||
CompactionIterationStats& range_del_out_stats, bool bottommost_level,
|
||||
const InternalKeyComparator& icmp, SequenceNumber earliest_snapshot,
|
||||
const Slice& next_table_min_key, const std::string& full_history_ts_low) {
|
||||
assert(HasRangeDel());
|
||||
FileMetaData& meta = current_output().meta;
|
||||
const Comparator* ucmp = icmp.user_comparator();
|
||||
|
||||
Slice lower_bound_guard, upper_bound_guard;
|
||||
std::string smallest_user_key;
|
||||
const Slice *lower_bound, *upper_bound;
|
||||
bool lower_bound_from_sub_compact = false;
|
||||
|
||||
// The following example does not happen since
|
||||
// CompactionOutput::ShouldStopBefore() always return false for the first
|
||||
// point key. But we should consider removing this dependency. Suppose for the
|
||||
@@ -424,98 +463,135 @@ Status CompactionOutputs::AddRangeDels(
|
||||
// Then meta.smallest will be set to comp_start_user_key@seqno
|
||||
// and meta.largest will be set to comp_start_user_key@kMaxSequenceNumber
|
||||
// which violates the assumption that meta.smallest should be <= meta.largest.
|
||||
assert(HasRangeDel());
|
||||
FileMetaData& meta = current_output().meta;
|
||||
const Comparator* ucmp = icmp.user_comparator();
|
||||
InternalKey lower_bound_buf, upper_bound_buf;
|
||||
Slice lower_bound_guard, upper_bound_guard;
|
||||
std::string smallest_user_key;
|
||||
const Slice *lower_bound, *upper_bound;
|
||||
|
||||
// We first determine the internal key lower_bound and upper_bound for
|
||||
// this output file. All and only range tombstones that overlap with
|
||||
// [lower_bound, upper_bound] should be added to this file. File
|
||||
// boundaries (meta.smallest/largest) should be updated accordingly when
|
||||
// extended by range tombstones.
|
||||
size_t output_size = outputs_.size();
|
||||
if (output_size == 1) {
|
||||
// For the first output table, include range tombstones before the min
|
||||
// key but after the subcompaction boundary.
|
||||
lower_bound = comp_start_user_key;
|
||||
lower_bound_from_sub_compact = true;
|
||||
} else if (meta.smallest.size() > 0) {
|
||||
// This is the first file in the subcompaction.
|
||||
//
|
||||
// When outputting a range tombstone that spans a subcompaction boundary,
|
||||
// the files on either side of that boundary need to include that
|
||||
// boundary's user key. Otherwise, the spanning range tombstone would lose
|
||||
// coverage.
|
||||
//
|
||||
// To achieve this while preventing files from overlapping in internal key
|
||||
// (an LSM invariant violation), we allow the earlier file to include the
|
||||
// boundary user key up to `kMaxSequenceNumber,kTypeRangeDeletion`. The
|
||||
// later file can begin at the boundary user key at the newest key version
|
||||
// it contains. At this point that version number is unknown since we have
|
||||
// not processed the range tombstones yet, so permit any version. Same story
|
||||
// applies to timestamp, and a non-nullptr `comp_start_user_key` should have
|
||||
// `kMaxTs` here, which similarly permits any timestamp.
|
||||
if (comp_start_user_key) {
|
||||
lower_bound_buf.Set(*comp_start_user_key, kMaxSequenceNumber,
|
||||
kTypeRangeDeletion);
|
||||
lower_bound_guard = lower_bound_buf.Encode();
|
||||
lower_bound = &lower_bound_guard;
|
||||
} else {
|
||||
lower_bound = nullptr;
|
||||
}
|
||||
} else {
|
||||
// For subsequent output tables, only include range tombstones from min
|
||||
// key onwards since the previous file was extended to contain range
|
||||
// tombstones falling before min key.
|
||||
smallest_user_key = meta.smallest.user_key().ToString(false /*hex*/);
|
||||
lower_bound_guard = Slice(smallest_user_key);
|
||||
lower_bound = &lower_bound_guard;
|
||||
} else {
|
||||
lower_bound = nullptr;
|
||||
}
|
||||
if (!next_table_min_key.empty()) {
|
||||
// This may be the last file in the subcompaction in some cases, so we
|
||||
// need to compare the end key of subcompaction with the next file start
|
||||
// key. When the end key is chosen by the subcompaction, we know that
|
||||
// it must be the biggest key in output file. Therefore, it is safe to
|
||||
// use the smaller key as the upper bound of the output file, to ensure
|
||||
// that there is no overlapping between different output files.
|
||||
upper_bound_guard = ExtractUserKey(next_table_min_key);
|
||||
if (comp_end_user_key != nullptr &&
|
||||
ucmp->CompareWithoutTimestamp(upper_bound_guard, *comp_end_user_key) >=
|
||||
0) {
|
||||
upper_bound = comp_end_user_key;
|
||||
if (range_tombstone_lower_bound_.size() > 0) {
|
||||
assert(meta.smallest.size() == 0 ||
|
||||
icmp.Compare(range_tombstone_lower_bound_, meta.smallest) < 0);
|
||||
lower_bound_guard = range_tombstone_lower_bound_.Encode();
|
||||
} else {
|
||||
upper_bound = &upper_bound_guard;
|
||||
assert(meta.smallest.size() > 0);
|
||||
lower_bound_guard = meta.smallest.Encode();
|
||||
}
|
||||
} else {
|
||||
// This is the last file in the subcompaction, so extend until the
|
||||
// subcompaction ends.
|
||||
upper_bound = comp_end_user_key;
|
||||
}
|
||||
bool has_overlapping_endpoints;
|
||||
if (upper_bound != nullptr && meta.largest.size() > 0) {
|
||||
has_overlapping_endpoints = ucmp->CompareWithoutTimestamp(
|
||||
meta.largest.user_key(), *upper_bound) == 0;
|
||||
} else {
|
||||
has_overlapping_endpoints = false;
|
||||
lower_bound = &lower_bound_guard;
|
||||
}
|
||||
|
||||
const size_t ts_sz = ucmp->timestamp_size();
|
||||
if (next_table_min_key.empty()) {
|
||||
// Last file of the subcompaction.
|
||||
if (comp_end_user_key) {
|
||||
upper_bound_buf.Set(*comp_end_user_key, kMaxSequenceNumber,
|
||||
kTypeRangeDeletion);
|
||||
upper_bound_guard = upper_bound_buf.Encode();
|
||||
upper_bound = &upper_bound_guard;
|
||||
} else {
|
||||
upper_bound = nullptr;
|
||||
}
|
||||
} else {
|
||||
// There is another file coming whose coverage will begin at
|
||||
// `next_table_min_key`. The current file needs to extend range tombstone
|
||||
// coverage through its own keys (through `meta.largest`) and through user
|
||||
// keys preceding `next_table_min_key`'s user key.
|
||||
ParsedInternalKey next_table_min_key_parsed;
|
||||
ParseInternalKey(next_table_min_key, &next_table_min_key_parsed,
|
||||
false /* log_err_key */)
|
||||
.PermitUncheckedError();
|
||||
assert(next_table_min_key_parsed.sequence < kMaxSequenceNumber);
|
||||
assert(meta.largest.size() == 0 ||
|
||||
icmp.Compare(meta.largest.Encode(), next_table_min_key) < 0);
|
||||
assert(!lower_bound || icmp.Compare(*lower_bound, next_table_min_key) <= 0);
|
||||
if (meta.largest.size() > 0 &&
|
||||
ucmp->EqualWithoutTimestamp(meta.largest.user_key(),
|
||||
next_table_min_key_parsed.user_key)) {
|
||||
// Caution: this assumes meta.largest.Encode() lives longer than
|
||||
// upper_bound, which is only true if meta.largest is never updated.
|
||||
// This just happens to be the case here since meta.largest serves
|
||||
// as the upper_bound.
|
||||
upper_bound_guard = meta.largest.Encode();
|
||||
} else {
|
||||
SetMaxSeqAndTs(upper_bound_buf, next_table_min_key_parsed.user_key,
|
||||
ts_sz);
|
||||
upper_bound_guard = upper_bound_buf.Encode();
|
||||
}
|
||||
upper_bound = &upper_bound_guard;
|
||||
}
|
||||
if (lower_bound && upper_bound &&
|
||||
icmp.Compare(*lower_bound, *upper_bound) > 0) {
|
||||
assert(meta.smallest.size() == 0 &&
|
||||
ucmp->EqualWithoutTimestamp(ExtractUserKey(*lower_bound),
|
||||
ExtractUserKey(*upper_bound)));
|
||||
// This can only happen when lower_bound have the same user key as
|
||||
// next_table_min_key and that there is no point key in the current
|
||||
// compaction output file.
|
||||
return Status::OK();
|
||||
}
|
||||
// The end key of the subcompaction must be bigger or equal to the upper
|
||||
// bound. If the end of subcompaction is null or the upper bound is null,
|
||||
// it means that this file is the last file in the compaction. So there
|
||||
// will be no overlapping between this file and others.
|
||||
assert(comp_end_user_key == nullptr || upper_bound == nullptr ||
|
||||
ucmp->CompareWithoutTimestamp(*upper_bound, *comp_end_user_key) <= 0);
|
||||
auto it = range_del_agg_->NewIterator(lower_bound, upper_bound,
|
||||
has_overlapping_endpoints);
|
||||
// Position the range tombstone output iterator. There may be tombstone
|
||||
// fragments that are entirely out of range, so make sure that we do not
|
||||
// include those.
|
||||
if (lower_bound != nullptr) {
|
||||
it->Seek(*lower_bound);
|
||||
} else {
|
||||
it->SeekToFirst();
|
||||
}
|
||||
ucmp->CompareWithoutTimestamp(ExtractUserKey(*upper_bound),
|
||||
*comp_end_user_key) <= 0);
|
||||
auto it = range_del_agg_->NewIterator(lower_bound, upper_bound);
|
||||
Slice last_tombstone_start_user_key{};
|
||||
for (; it->Valid(); it->Next()) {
|
||||
bool reached_lower_bound = false;
|
||||
const ReadOptions read_options(Env::IOActivity::kCompaction);
|
||||
for (it->SeekToFirst(); it->Valid(); it->Next()) {
|
||||
auto tombstone = it->Tombstone();
|
||||
if (upper_bound != nullptr) {
|
||||
int cmp =
|
||||
ucmp->CompareWithoutTimestamp(*upper_bound, tombstone.start_key_);
|
||||
// Tombstones starting after upper_bound only need to be included in
|
||||
// the next table.
|
||||
// If the current SST ends before upper_bound, i.e.,
|
||||
// `has_overlapping_endpoints == false`, we can also skip over range
|
||||
// tombstones that start exactly at upper_bound. Such range
|
||||
// tombstones will be included in the next file and are not relevant
|
||||
// to the point keys or endpoints of the current file.
|
||||
// If the current SST ends at the same user key at upper_bound,
|
||||
// i.e., `has_overlapping_endpoints == true`, AND the tombstone has
|
||||
// the same start key as upper_bound, i.e., cmp == 0, then
|
||||
// the tombstone is relevant only if the tombstone's sequence number
|
||||
// is no larger than this file's largest key's sequence number. This
|
||||
// is because the upper bound to truncate this file's range tombstone
|
||||
// will be meta.largest in this case, and any tombstone that starts after
|
||||
// it will not be relevant.
|
||||
if (cmp < 0) {
|
||||
break;
|
||||
} else if (cmp == 0) {
|
||||
if (!has_overlapping_endpoints ||
|
||||
tombstone.seq_ < GetInternalKeySeqno(meta.largest.Encode())) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
auto kv = tombstone.Serialize();
|
||||
InternalKey tombstone_end = tombstone.SerializeEndKey();
|
||||
// TODO: the underlying iterator should support clamping the bounds.
|
||||
// tombstone_end.Encode is of form user_key@kMaxSeqno
|
||||
// if it is equal to lower_bound, there is no need to include
|
||||
// such range tombstone.
|
||||
if (!reached_lower_bound && lower_bound &&
|
||||
icmp.Compare(tombstone_end.Encode(), *lower_bound) <= 0) {
|
||||
continue;
|
||||
}
|
||||
assert(!lower_bound ||
|
||||
icmp.Compare(*lower_bound, tombstone_end.Encode()) <= 0);
|
||||
reached_lower_bound = true;
|
||||
|
||||
const size_t ts_sz = ucmp->timestamp_size();
|
||||
// Garbage collection for range tombstones.
|
||||
// If user-defined timestamp is enabled, range tombstones are dropped if
|
||||
// they are at bottommost_level, below full_history_ts_low and not visible
|
||||
@@ -534,83 +610,93 @@ Status CompactionOutputs::AddRangeDels(
|
||||
continue;
|
||||
}
|
||||
|
||||
auto kv = tombstone.Serialize();
|
||||
assert(lower_bound == nullptr ||
|
||||
ucmp->CompareWithoutTimestamp(*lower_bound, kv.second) < 0);
|
||||
ucmp->CompareWithoutTimestamp(ExtractUserKey(*lower_bound),
|
||||
kv.second) < 0);
|
||||
InternalKey tombstone_start = kv.first;
|
||||
if (lower_bound &&
|
||||
ucmp->CompareWithoutTimestamp(tombstone_start.user_key(),
|
||||
ExtractUserKey(*lower_bound)) < 0) {
|
||||
// This just updates the non-timestamp portion of `tombstone_start`'s user
|
||||
// key. Ideally there would be a simpler API usage
|
||||
ParsedInternalKey tombstone_start_parsed;
|
||||
ParseInternalKey(tombstone_start.Encode(), &tombstone_start_parsed,
|
||||
false /* log_err_key */)
|
||||
.PermitUncheckedError();
|
||||
// timestamp should be from where sequence number is from, which is from
|
||||
// tombstone in this case
|
||||
std::string ts =
|
||||
tombstone_start_parsed.GetTimestamp(ucmp->timestamp_size())
|
||||
.ToString();
|
||||
tombstone_start_parsed.user_key = ExtractUserKey(*lower_bound);
|
||||
tombstone_start.SetFrom(tombstone_start_parsed, ts);
|
||||
}
|
||||
if (upper_bound != nullptr &&
|
||||
icmp.Compare(*upper_bound, tombstone_start.Encode()) < 0) {
|
||||
break;
|
||||
}
|
||||
// Here we show that *only* range tombstones that overlap with
|
||||
// [lower_bound, upper_bound] are added to the current file, and
|
||||
// sanity checking invariants that should hold:
|
||||
// - [tombstone_start, tombstone_end] overlaps with [lower_bound,
|
||||
// upper_bound]
|
||||
// - meta.smallest <= meta.largest
|
||||
// Corresponding assertions are made, the proof is broken is any of them
|
||||
// fails.
|
||||
// TODO: show that *all* range tombstones that overlap with
|
||||
// [lower_bound, upper_bound] are added.
|
||||
// TODO: some invariant about boundaries are correctly updated.
|
||||
//
|
||||
// Note that `tombstone_start` is updated in the if condition above, we use
|
||||
// tombstone_start to refer to its initial value, i.e.,
|
||||
// it->Tombstone().first, and use tombstone_start* to refer to its value
|
||||
// after the update.
|
||||
//
|
||||
// To show [lower_bound, upper_bound] overlaps with [tombstone_start,
|
||||
// tombstone_end]:
|
||||
// lower_bound <= upper_bound from the if condition right after all
|
||||
// bounds are initialized. We assume each tombstone fragment has
|
||||
// start_key.user_key < end_key.user_key, so
|
||||
// tombstone_start < tombstone_end by
|
||||
// FragmentedTombstoneIterator::Tombstone(). So these two ranges are both
|
||||
// non-emtpy. The flag `reached_lower_bound` and the if logic before it
|
||||
// ensures lower_bound <= tombstone_end. tombstone_start is only updated
|
||||
// if it has a smaller user_key than lower_bound user_key, so
|
||||
// tombstone_start <= tombstone_start*. The above if condition implies
|
||||
// tombstone_start* <= upper_bound. So we have
|
||||
// tombstone_start <= upper_bound and lower_bound <= tombstone_end
|
||||
// and the two ranges overlap.
|
||||
//
|
||||
// To show meta.smallest <= meta.largest:
|
||||
// From the implementation of UpdateBoundariesForRange(), it suffices to
|
||||
// prove that when it is first called in this function, its parameters
|
||||
// satisfy `start <= end`, where start = max(tombstone_start*, lower_bound)
|
||||
// and end = min(tombstone_end, upper_bound). From the above proof we have
|
||||
// lower_bound <= tombstone_end and lower_bound <= upper_bound. We only need
|
||||
// to show that tombstone_start* <= min(tombstone_end, upper_bound).
|
||||
// Note that tombstone_start*.user_key = max(tombstone_start.user_key,
|
||||
// lower_bound.user_key). Assuming tombstone_end always has
|
||||
// kMaxSequenceNumber and lower_bound.seqno < kMaxSequenceNumber.
|
||||
// Since lower_bound <= tombstone_end and lower_bound.seqno <
|
||||
// tombstone_end.seqno (in absolute number order, not internal key order),
|
||||
// lower_bound.user_key < tombstone_end.user_key.
|
||||
// Since lower_bound.user_key < tombstone_end.user_key and
|
||||
// tombstone_start.user_key < tombstone_end.user_key, tombstone_start* <
|
||||
// tombstone_end. Since tombstone_start* <= upper_bound from the above proof
|
||||
// and tombstone_start* < tombstone_end, tombstone_start* <=
|
||||
// min(tombstone_end, upper_bound), so the two ranges overlap.
|
||||
|
||||
// Range tombstone is not supported by output validator yet.
|
||||
builder_->Add(kv.first.Encode(), kv.second);
|
||||
InternalKey tombstone_start = std::move(kv.first);
|
||||
InternalKey smallest_candidate{tombstone_start};
|
||||
if (lower_bound != nullptr &&
|
||||
ucmp->CompareWithoutTimestamp(smallest_candidate.user_key(),
|
||||
*lower_bound) <= 0) {
|
||||
// Pretend the smallest key has the same user key as lower_bound
|
||||
// (the max key in the previous table or subcompaction) in order for
|
||||
// files to appear key-space partitioned.
|
||||
if (lower_bound_from_sub_compact) {
|
||||
// When lower_bound is chosen by a subcompaction
|
||||
// (lower_bound_from_sub_compact), we know that subcompactions over
|
||||
// smaller keys cannot contain any keys at lower_bound. We also know
|
||||
// that smaller subcompactions exist, because otherwise the
|
||||
// subcompaction woud be unbounded on the left. As a result, we know
|
||||
// that no other files on the output level will contain actual keys at
|
||||
// lower_bound (an output file may have a largest key of
|
||||
// lower_bound@kMaxSequenceNumber, but this only indicates a large range
|
||||
// tombstone was truncated). Therefore, it is safe to use the
|
||||
// tombstone's sequence number, to ensure that keys at lower_bound at
|
||||
// lower levels are covered by truncated tombstones.
|
||||
if (ts_sz) {
|
||||
assert(tombstone.ts_.size() == ts_sz);
|
||||
smallest_candidate = InternalKey(*lower_bound, tombstone.seq_,
|
||||
kTypeRangeDeletion, tombstone.ts_);
|
||||
} else {
|
||||
smallest_candidate =
|
||||
InternalKey(*lower_bound, tombstone.seq_, kTypeRangeDeletion);
|
||||
}
|
||||
} else {
|
||||
// If lower_bound was chosen by the smallest data key in the file,
|
||||
// choose lowest seqnum so this file's smallest internal key comes
|
||||
// after the previous file's largest. The fake seqnum is OK because
|
||||
// the read path's file-picking code only considers user key.
|
||||
smallest_candidate = InternalKey(*lower_bound, 0, kTypeRangeDeletion);
|
||||
}
|
||||
if (lower_bound &&
|
||||
icmp.Compare(tombstone_start.Encode(), *lower_bound) < 0) {
|
||||
tombstone_start.DecodeFrom(*lower_bound);
|
||||
}
|
||||
InternalKey tombstone_end = tombstone.SerializeEndKey();
|
||||
InternalKey largest_candidate{tombstone_end};
|
||||
if (upper_bound != nullptr &&
|
||||
ucmp->CompareWithoutTimestamp(*upper_bound,
|
||||
largest_candidate.user_key()) <= 0) {
|
||||
// Pretend the largest key has the same user key as upper_bound (the
|
||||
// min key in the following table or subcompaction) in order for files
|
||||
// to appear key-space partitioned.
|
||||
//
|
||||
// Choose highest seqnum so this file's largest internal key comes
|
||||
// before the next file's/subcompaction's smallest. The fake seqnum is
|
||||
// OK because the read path's file-picking code only considers the
|
||||
// user key portion.
|
||||
//
|
||||
// Note Seek() also creates InternalKey with (user_key,
|
||||
// kMaxSequenceNumber), but with kTypeDeletion (0x7) instead of
|
||||
// kTypeRangeDeletion (0xF), so the range tombstone comes before the
|
||||
// Seek() key in InternalKey's ordering. So Seek() will look in the
|
||||
// next file for the user key
|
||||
if (ts_sz) {
|
||||
static constexpr char kTsMax[] = "\xff\xff\xff\xff\xff\xff\xff\xff\xff";
|
||||
if (ts_sz <= strlen(kTsMax)) {
|
||||
largest_candidate =
|
||||
InternalKey(*upper_bound, kMaxSequenceNumber, kTypeRangeDeletion,
|
||||
Slice(kTsMax, ts_sz));
|
||||
} else {
|
||||
largest_candidate =
|
||||
InternalKey(*upper_bound, kMaxSequenceNumber, kTypeRangeDeletion,
|
||||
std::string(ts_sz, '\xff'));
|
||||
}
|
||||
} else {
|
||||
largest_candidate =
|
||||
InternalKey(*upper_bound, kMaxSequenceNumber, kTypeRangeDeletion);
|
||||
}
|
||||
if (upper_bound && icmp.Compare(*upper_bound, tombstone_end.Encode()) < 0) {
|
||||
tombstone_end.DecodeFrom(*upper_bound);
|
||||
}
|
||||
meta.UpdateBoundariesForRange(smallest_candidate, largest_candidate,
|
||||
assert(icmp.Compare(tombstone_start, tombstone_end) <= 0);
|
||||
meta.UpdateBoundariesForRange(tombstone_start, tombstone_end,
|
||||
tombstone.seq_, icmp);
|
||||
if (!bottommost_level) {
|
||||
bool start_user_key_changed =
|
||||
@@ -618,17 +704,8 @@ Status CompactionOutputs::AddRangeDels(
|
||||
ucmp->CompareWithoutTimestamp(last_tombstone_start_user_key,
|
||||
it->start_key()) < 0;
|
||||
last_tombstone_start_user_key = it->start_key();
|
||||
// Range tombstones are truncated at file boundaries
|
||||
if (icmp.Compare(tombstone_start, meta.smallest) < 0) {
|
||||
tombstone_start = meta.smallest;
|
||||
}
|
||||
if (icmp.Compare(tombstone_end, meta.largest) > 0) {
|
||||
tombstone_end = meta.largest;
|
||||
}
|
||||
// this assertion validates invariant (2) in the comment below.
|
||||
assert(icmp.Compare(tombstone_start, tombstone_end) <= 0);
|
||||
if (start_user_key_changed) {
|
||||
// if tombstone_start >= tombstone_end, then either no key range is
|
||||
// If tombstone_start >= tombstone_end, then either no key range is
|
||||
// covered, or that they have the same user key. If they have the same
|
||||
// user key, then the internal key range should only be within this
|
||||
// level, and no keys from older levels is covered.
|
||||
@@ -638,7 +715,7 @@ Status CompactionOutputs::AddRangeDels(
|
||||
approx_opts.files_size_error_margin = 0.1;
|
||||
auto approximate_covered_size =
|
||||
compaction_->input_version()->version_set()->ApproximateSize(
|
||||
approx_opts, compaction_->input_version(),
|
||||
approx_opts, read_options, compaction_->input_version(),
|
||||
tombstone_start.Encode(), tombstone_end.Encode(),
|
||||
compaction_->output_level() + 1 /* start_level */,
|
||||
-1 /* end_level */, kCompaction);
|
||||
@@ -646,138 +723,6 @@ Status CompactionOutputs::AddRangeDels(
|
||||
}
|
||||
}
|
||||
}
|
||||
// TODO: show invariants that ensure all necessary range tombstones are
|
||||
// added
|
||||
// and that file boundaries ensure no coverage is lost.
|
||||
// Each range tombstone with internal key range [tombstone_start,
|
||||
// tombstone_end] is being added to the current compaction output file here.
|
||||
// The range tombstone is going to be truncated at range [meta.smallest,
|
||||
// meta.largest] during reading/scanning. We should maintain invariants
|
||||
// (1) meta.smallest <= meta.largest and,
|
||||
// (2) [tombstone_start, tombstone_end] and [meta.smallest, meta.largest]
|
||||
// overlaps, as there is no point adding range tombstone with a range
|
||||
// outside the file's range.
|
||||
// Since `tombstone_end` is always some user_key@kMaxSeqno, it is okay to
|
||||
// use either open or closed range. Using closed range here to make
|
||||
// reasoning easier, and it is more consistent with an ongoing work that
|
||||
// tries to simplify this method.
|
||||
//
|
||||
// There are two cases:
|
||||
// Case 1. Output file has no point key:
|
||||
// First we show this case only happens when the entire compaction output
|
||||
// is range tombstone only. This is true if CompactionIterator does not
|
||||
// emit any point key. Suppose CompactionIterator emits some point key.
|
||||
// Based on the assumption that CompactionOutputs::ShouldStopBefore()
|
||||
// always return false for the first point key, the first compaction
|
||||
// output file always contains a point key. Each new compaction output
|
||||
// file is created if there is a point key for which ShouldStopBefore()
|
||||
// returns true, and the point key would be added to the new compaction
|
||||
// output file. So each new compaction file always contains a point key.
|
||||
// So Case 1 only happens when CompactionIterator does not emit any
|
||||
// point key.
|
||||
//
|
||||
// To show (1) meta.smallest <= meta.largest:
|
||||
// Since the compaction output is range tombstone only, `lower_bound` and
|
||||
// `upper_bound` are either null or comp_start/end_user_key respectively.
|
||||
// According to how UpdateBoundariesForRange() is implemented, it blindly
|
||||
// updates meta.smallest and meta.largest to smallest_candidate and
|
||||
// largest_candidate the first time it is called. Subsequently, it
|
||||
// compares input parameter with meta.smallest and meta.largest and only
|
||||
// updates them when input is smaller/larger. So we only need to show
|
||||
// smallest_candidate <= largest_candidate the first time
|
||||
// UpdateBoundariesForRange() is called. Here we show something stronger
|
||||
// that smallest_candidate.user_key < largest_candidate.user_key always
|
||||
// hold for Case 1.
|
||||
// We assume comp_start_user_key < comp_end_user_key, if provided. We
|
||||
// assume that tombstone_start < tombstone_end. This assumption is based
|
||||
// on that each fragment in FragmentedTombstoneList has
|
||||
// start_key < end_key (user_key) and that
|
||||
// FragmentedTombstoneIterator::Tombstone() returns the pair
|
||||
// (start_key@tombstone_seqno with op_type kTypeRangeDeletion, end_key).
|
||||
// The logic in this loop sets smallest_candidate to
|
||||
// max(tombstone_start.user_key, comp_start_user_key)@tombstone.seq_ with
|
||||
// op_type kTypeRangeDeletion, largest_candidate to
|
||||
// min(tombstone_end.user_key, comp_end_user_key)@kMaxSequenceNumber with
|
||||
// op_type kTypeRangeDeletion. When a bound is null, there is no
|
||||
// truncation on that end. To show that smallest_candidate.user_key <
|
||||
// largest_candidate.user_key, it suffices to show
|
||||
// tombstone_start.user_key < comp_end_user_key (if not null) AND
|
||||
// comp_start_user_key (if not null) < tombstone_end.user_key.
|
||||
// Since the file has no point key, `has_overlapping_endpoints` is false.
|
||||
// In the first sanity check of this for-loop, we compare
|
||||
// tombstone_start.user_key against upper_bound = comp_end_user_key,
|
||||
// and only proceed if tombstone_start.user_key < comp_end_user_key.
|
||||
// We assume FragmentedTombstoneIterator::Seek(k) lands
|
||||
// on a tombstone with end_key > k. So the call it->Seek(*lower_bound)
|
||||
// above implies compact_start_user_key < tombstone_end.user_key.
|
||||
//
|
||||
// To show (2) [tombstone_start, tombstone_end] and [meta.smallest,
|
||||
// meta.largest] overlaps (after the call to UpdateBoundariesForRange()):
|
||||
// In the proof for (1) we have shown that
|
||||
// smallest_candidate <= largest_candidate. Since tombstone_start <=
|
||||
// smallest_candidate <= largest_candidate <= tombstone_end, for (2) to
|
||||
// hold, it suffices to show that [smallest_candidate, largest_candidate]
|
||||
// overlaps with [meta.smallest, meta.largest]. too.
|
||||
// Given meta.smallest <= meta.largest shown above, we need to show
|
||||
// that it is impossible to have largest_candidate < meta.smallest or
|
||||
// meta.largest < smallest_candidate. If the above
|
||||
// meta.UpdateBoundariesForRange(smallest_candidate, largest_candidate)
|
||||
// updates meta.largest or meta.smallest, then the two ranges overlap.
|
||||
// So we assume meta.UpdateBoundariesForRange(smallest_candidate,
|
||||
// largest_candidate) did not update meta.smallest nor meta.largest, which
|
||||
// means meta.smallest < smallest_candidate and largest_candidate <
|
||||
// meta.largest.
|
||||
//
|
||||
// Case 2. Output file has >= 1 point key. This means meta.smallest and
|
||||
// meta.largest are not empty when AddRangeDels() is called.
|
||||
// To show (1) meta.smallest <= meta.largest:
|
||||
// Assume meta.smallest <= meta.largest when AddRangeDels() is called,
|
||||
// this follow from how UpdateBoundariesForRange() is implemented where it
|
||||
// takes min or max to update meta.smallest or meta.largest.
|
||||
//
|
||||
// To show (2) [tombstone_start, tombstone_end] and [meta.smallest,
|
||||
// meta.largest] overlaps (after the call to UpdateBoundariesForRange()):
|
||||
// When smallest_candidate <= largest_candidate, the proof in Case 1
|
||||
// applies, so we only need to show (2) holds when smallest_candidate >
|
||||
// largest_candidate. When both bounds are either null or from
|
||||
// subcompaction boundary, the proof in Case 1 applies, so we only need to
|
||||
// show (2) holds when at least one bound is from a point key (either
|
||||
// meta.smallest for lower bound or next_table_min_key for upper bound).
|
||||
//
|
||||
// Suppose lower bound is meta.smallest.user_key. The call
|
||||
// it->Seek(*lower_bound) implies tombstone_end.user_key >
|
||||
// meta.smallest.user_key. We have smallest_candidate.user_key =
|
||||
// max(tombstone_start.user_key, meta.smallest.user_key). For
|
||||
// smallest_candidate to be > largest_candidate, we need
|
||||
// largest_candidate.user_key = upper_bound = smallest_candidate.user_key,
|
||||
// where tombstone_end is truncated to largest_candidate.
|
||||
// Subcase 1:
|
||||
// Suppose largest_candidate.user_key = comp_end_user_key (there is no
|
||||
// next point key). Subcompaction ensures any point key from this
|
||||
// subcompaction has a user_key < comp_end_user_key, so 1)
|
||||
// meta.smallest.user_key < comp_end_user_key, 2)
|
||||
// `has_overlapping_endpoints` is false, and the first if condition in
|
||||
// this for-loop ensures tombstone_start.user_key < comp_end_user_key. So
|
||||
// smallest_candidate.user_key < largest_candidate.user_key. This case
|
||||
// cannot happen when smallest > largest_candidate.
|
||||
// Subcase 2:
|
||||
// Suppose largest_candidate.user_key = next_table_min_key.user_key.
|
||||
// The first if condition in this for-loop together with
|
||||
// smallest_candidate.user_key = next_table_min_key.user_key =
|
||||
// upper_bound implies `has_overlapping_endpoints` is true (so meta
|
||||
// largest.user_key = upper_bound) and
|
||||
// tombstone.seq_ < meta.largest.seqno. So
|
||||
// tombstone_start < meta.largest < tombstone_end.
|
||||
//
|
||||
// Suppose lower bound is comp_start_user_key and upper_bound is
|
||||
// next_table_min_key. The call it->Seek(*lower_bound) implies we have
|
||||
// tombstone_end_key.user_key > comp_start_user_key. So
|
||||
// tombstone_end_key.user_key > smallest_candidate.user_key. For
|
||||
// smallest_candidate to be > largest_candidate, we need
|
||||
// tombstone_start.user_key = largest_candidate.user_key = upper_bound =
|
||||
// next_table_min_key.user_key. This means `has_overlapping_endpoints` is
|
||||
// true (so meta.largest.user_key = upper_bound) and tombstone.seq_ <
|
||||
// meta.largest.seqno. So tombstone_start < meta.largest < tombstone_end.
|
||||
}
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
@@ -167,9 +167,15 @@ class CompactionOutputs {
|
||||
current_output_file_size_ = 0;
|
||||
}
|
||||
|
||||
// Add range-dels from the aggregator to the current output file
|
||||
// Add range deletions from the range_del_agg_ to the current output file.
|
||||
// Input parameters, `range_tombstone_lower_bound_` and current output's
|
||||
// metadata determine the bounds on range deletions to add. Updates output
|
||||
// file metadata boundary if extended by range tombstones.
|
||||
//
|
||||
// @param comp_start_user_key and comp_end_user_key include timestamp if
|
||||
// user-defined timestamp is enabled.
|
||||
// user-defined timestamp is enabled. Their timestamp should be max timestamp.
|
||||
// @param next_table_min_key internal key lower bound for the next compaction
|
||||
// output.
|
||||
// @param full_history_ts_low used for range tombstone garbage collection.
|
||||
Status AddRangeDels(const Slice* comp_start_user_key,
|
||||
const Slice* comp_end_user_key,
|
||||
@@ -314,6 +320,7 @@ class CompactionOutputs {
|
||||
std::unique_ptr<SstPartitioner> partitioner_;
|
||||
|
||||
// A flag determines if this subcompaction has been split by the cursor
|
||||
// for RoundRobin compaction
|
||||
bool is_split_ = false;
|
||||
|
||||
// We also maintain the output split key for each subcompaction to avoid
|
||||
@@ -345,6 +352,10 @@ class CompactionOutputs {
|
||||
// for the current output file, how many file boundaries has it crossed,
|
||||
// basically number of files overlapped * 2
|
||||
size_t grandparent_boundary_switched_num_ = 0;
|
||||
|
||||
// The smallest key of the current output file, this is set when current
|
||||
// output file's smallest key is a range tombstone start key.
|
||||
InternalKey range_tombstone_lower_bound_;
|
||||
};
|
||||
|
||||
// helper struct to concatenate the last level and penultimate level outputs
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
#include "file/filename.h"
|
||||
#include "logging/log_buffer.h"
|
||||
#include "logging/logging.h"
|
||||
#include "monitoring/statistics.h"
|
||||
#include "monitoring/statistics_impl.h"
|
||||
#include "test_util/sync_point.h"
|
||||
#include "util/random.h"
|
||||
#include "util/string_util.h"
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
#include "db/column_family.h"
|
||||
#include "logging/log_buffer.h"
|
||||
#include "logging/logging.h"
|
||||
#include "options/options_helper.h"
|
||||
#include "util/string_util.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
@@ -284,31 +285,36 @@ Compaction* FIFOCompactionPicker::PickSizeCompaction(
|
||||
return c;
|
||||
}
|
||||
|
||||
Compaction* FIFOCompactionPicker::PickCompactionToWarm(
|
||||
Compaction* FIFOCompactionPicker::PickTemperatureChangeCompaction(
|
||||
const std::string& cf_name, const MutableCFOptions& mutable_cf_options,
|
||||
const MutableDBOptions& mutable_db_options, VersionStorageInfo* vstorage,
|
||||
LogBuffer* log_buffer) {
|
||||
if (mutable_cf_options.compaction_options_fifo.age_for_warm == 0) {
|
||||
const std::vector<FileTemperatureAge>& ages =
|
||||
mutable_cf_options.compaction_options_fifo
|
||||
.file_temperature_age_thresholds;
|
||||
if (ages.empty()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// PickCompactionToWarm is only triggered if there is no non-L0 files.
|
||||
for (int level = 1; level < vstorage->num_levels(); ++level) {
|
||||
if (GetTotalFilesSize(vstorage->LevelFiles(level)) > 0) {
|
||||
return nullptr;
|
||||
}
|
||||
// Does not apply to multi-level FIFO.
|
||||
if (vstorage->num_levels() > 1) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const int kLevel0 = 0;
|
||||
const std::vector<FileMetaData*>& level_files = vstorage->LevelFiles(kLevel0);
|
||||
if (level_files.empty()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int64_t _current_time;
|
||||
auto status = ioptions_.clock->GetCurrentTime(&_current_time);
|
||||
if (!status.ok()) {
|
||||
ROCKS_LOG_BUFFER(log_buffer,
|
||||
"[%s] FIFO compaction: Couldn't get current time: %s. "
|
||||
"Not doing compactions based on warm threshold. ",
|
||||
cf_name.c_str(), status.ToString().c_str());
|
||||
ROCKS_LOG_BUFFER(
|
||||
log_buffer,
|
||||
"[%s] FIFO compaction: Couldn't get current time: %s. "
|
||||
"Not doing compactions based on file temperature-age threshold. ",
|
||||
cf_name.c_str(), status.ToString().c_str());
|
||||
return nullptr;
|
||||
}
|
||||
const uint64_t current_time = static_cast<uint64_t>(_current_time);
|
||||
@@ -327,56 +333,77 @@ Compaction* FIFOCompactionPicker::PickCompactionToWarm(
|
||||
inputs[0].level = 0;
|
||||
|
||||
// avoid underflow
|
||||
if (current_time > mutable_cf_options.compaction_options_fifo.age_for_warm) {
|
||||
uint64_t create_time_threshold =
|
||||
current_time - mutable_cf_options.compaction_options_fifo.age_for_warm;
|
||||
uint64_t min_age = ages[0].age;
|
||||
// kLastTemperature means target temperature is to be determined.
|
||||
Temperature compaction_target_temp = Temperature::kLastTemperature;
|
||||
if (current_time > min_age) {
|
||||
uint64_t create_time_threshold = current_time - min_age;
|
||||
uint64_t compaction_size = 0;
|
||||
// We will ideally identify a file qualifying for warm tier by knowing
|
||||
// the timestamp for the youngest entry in the file. However, right now
|
||||
// we don't have the information. We infer it by looking at timestamp
|
||||
// of the next file's (which is just younger) oldest entry's timestamp.
|
||||
FileMetaData* prev_file = nullptr;
|
||||
for (auto ritr = level_files.rbegin(); ritr != level_files.rend(); ++ritr) {
|
||||
FileMetaData* f = *ritr;
|
||||
assert(f);
|
||||
if (f->being_compacted) {
|
||||
// Right now this probably won't happen as we never try to schedule
|
||||
// two compactions in parallel, so here we just simply don't schedule
|
||||
// anything.
|
||||
// We will ideally identify a file qualifying for temperature change by
|
||||
// knowing the timestamp for the youngest entry in the file. However, right
|
||||
// now we don't have the information. We infer it by looking at timestamp of
|
||||
// the previous file's (which is just younger) oldest entry's timestamp.
|
||||
Temperature cur_target_temp;
|
||||
// avoid index underflow
|
||||
assert(level_files.size() >= 1);
|
||||
for (size_t index = level_files.size() - 1; index >= 1; --index) {
|
||||
// Try to add cur_file to compaction inputs.
|
||||
FileMetaData* cur_file = level_files[index];
|
||||
// prev_file is just younger than cur_file
|
||||
FileMetaData* prev_file = level_files[index - 1];
|
||||
if (cur_file->being_compacted) {
|
||||
// Should not happen since we check for
|
||||
// `level0_compactions_in_progress_` above. Here we simply just don't
|
||||
// schedule anything.
|
||||
return nullptr;
|
||||
}
|
||||
uint64_t oldest_ancester_time = f->TryGetOldestAncesterTime();
|
||||
if (oldest_ancester_time == kUnknownOldestAncesterTime) {
|
||||
uint64_t oldest_ancestor_time = prev_file->TryGetOldestAncesterTime();
|
||||
if (oldest_ancestor_time == kUnknownOldestAncesterTime) {
|
||||
// Older files might not have enough information. It is possible to
|
||||
// handle these files by looking at newer files, but maintaining the
|
||||
// logic isn't worth it.
|
||||
break;
|
||||
}
|
||||
if (oldest_ancester_time > create_time_threshold) {
|
||||
// The previous file (which has slightly older data) doesn't qualify
|
||||
// for warm tier.
|
||||
if (oldest_ancestor_time > create_time_threshold) {
|
||||
// cur_file is too fresh
|
||||
break;
|
||||
}
|
||||
if (prev_file != nullptr) {
|
||||
compaction_size += prev_file->fd.GetFileSize();
|
||||
if (compaction_size > mutable_cf_options.max_compaction_bytes) {
|
||||
cur_target_temp = ages[0].temperature;
|
||||
for (size_t i = 1; i < ages.size(); ++i) {
|
||||
if (current_time >= ages[i].age &&
|
||||
oldest_ancestor_time <= current_time - ages[i].age) {
|
||||
cur_target_temp = ages[i].temperature;
|
||||
}
|
||||
}
|
||||
if (cur_file->temperature == cur_target_temp) {
|
||||
if (inputs[0].empty()) {
|
||||
continue;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
inputs[0].files.push_back(prev_file);
|
||||
ROCKS_LOG_BUFFER(log_buffer,
|
||||
"[%s] FIFO compaction: picking file %" PRIu64
|
||||
" with next file's oldest time %" PRIu64 " for warm",
|
||||
cf_name.c_str(), prev_file->fd.GetNumber(),
|
||||
oldest_ancester_time);
|
||||
}
|
||||
if (f->temperature == Temperature::kUnknown ||
|
||||
f->temperature == Temperature::kHot) {
|
||||
prev_file = f;
|
||||
} else if (!inputs[0].files.empty()) {
|
||||
// A warm file newer than files picked.
|
||||
|
||||
// cur_file needs to change temperature
|
||||
if (compaction_target_temp == Temperature::kLastTemperature) {
|
||||
assert(inputs[0].empty());
|
||||
compaction_target_temp = cur_target_temp;
|
||||
} else if (cur_target_temp != compaction_target_temp) {
|
||||
assert(!inputs[0].empty());
|
||||
break;
|
||||
}
|
||||
if (inputs[0].empty() || compaction_size + cur_file->fd.GetFileSize() <=
|
||||
mutable_cf_options.max_compaction_bytes) {
|
||||
inputs[0].files.push_back(cur_file);
|
||||
compaction_size += cur_file->fd.GetFileSize();
|
||||
ROCKS_LOG_BUFFER(
|
||||
log_buffer,
|
||||
"[%s] FIFO compaction: picking file %" PRIu64
|
||||
" with next file's oldest time %" PRIu64 " for temperature %s.",
|
||||
cf_name.c_str(), cur_file->fd.GetNumber(), oldest_ancestor_time,
|
||||
temperature_to_string[cur_target_temp].c_str());
|
||||
}
|
||||
if (compaction_size > mutable_cf_options.max_compaction_bytes) {
|
||||
break;
|
||||
} else {
|
||||
assert(prev_file == nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -390,7 +417,7 @@ Compaction* FIFOCompactionPicker::PickCompactionToWarm(
|
||||
std::move(inputs), 0, 0 /* output file size limit */,
|
||||
0 /* max compaction bytes, not applicable */, 0 /* output path ID */,
|
||||
mutable_cf_options.compression, mutable_cf_options.compression_opts,
|
||||
Temperature::kWarm,
|
||||
compaction_target_temp,
|
||||
/* max_subcompactions */ 0, {}, /* is manual */ false, /* trim_ts */ "",
|
||||
vstorage->CompactionScore(0),
|
||||
/* is deletion compaction */ false, /* l0_files_might_overlap */ true,
|
||||
@@ -412,8 +439,8 @@ Compaction* FIFOCompactionPicker::PickCompaction(
|
||||
vstorage, log_buffer);
|
||||
}
|
||||
if (c == nullptr) {
|
||||
c = PickCompactionToWarm(cf_name, mutable_cf_options, mutable_db_options,
|
||||
vstorage, log_buffer);
|
||||
c = PickTemperatureChangeCompaction(
|
||||
cf_name, mutable_cf_options, mutable_db_options, vstorage, log_buffer);
|
||||
}
|
||||
RegisterCompaction(c);
|
||||
return c;
|
||||
|
||||
@@ -52,10 +52,9 @@ class FIFOCompactionPicker : public CompactionPicker {
|
||||
VersionStorageInfo* version,
|
||||
LogBuffer* log_buffer);
|
||||
|
||||
Compaction* PickCompactionToWarm(const std::string& cf_name,
|
||||
const MutableCFOptions& mutable_cf_options,
|
||||
const MutableDBOptions& mutable_db_options,
|
||||
VersionStorageInfo* version,
|
||||
LogBuffer* log_buffer);
|
||||
Compaction* PickTemperatureChangeCompaction(
|
||||
const std::string& cf_name, const MutableCFOptions& mutable_cf_options,
|
||||
const MutableDBOptions& mutable_db_options, VersionStorageInfo* vstorage,
|
||||
LogBuffer* log_buffer);
|
||||
};
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
@@ -83,7 +83,7 @@ class LevelCompactionBuilder {
|
||||
|
||||
Compaction* GetCompaction();
|
||||
|
||||
// For the specfied level, pick a file that we want to compact.
|
||||
// From `start_level_`, pick files to compact to `output_level_`.
|
||||
// Returns false if there is no file to compact.
|
||||
// If it returns true, inputs->files.size() will be exactly one for
|
||||
// all compaction priorities except round-robin. For round-robin,
|
||||
@@ -107,8 +107,9 @@ class LevelCompactionBuilder {
|
||||
bool PickIntraL0Compaction();
|
||||
|
||||
// Return true if TrivialMove is extended. `start_index` is the index of
|
||||
// the intiial file picked, which should already be in `start_level_inputs_`.
|
||||
bool TryExtendNonL0TrivialMove(int start_index);
|
||||
// the initial file picked, which should already be in `start_level_inputs_`.
|
||||
bool TryExtendNonL0TrivialMove(int start_index,
|
||||
bool only_expand_right = false);
|
||||
|
||||
// Picks a file from level_files to compact.
|
||||
// level_files is a vector of (level, file metadata) in ascending order of
|
||||
@@ -355,7 +356,8 @@ void LevelCompactionBuilder::SetupOtherFilesWithRoundRobinExpansion() {
|
||||
vstorage_->GetOverlappingInputs(output_level_, &smallest, &largest,
|
||||
&output_level_inputs.files);
|
||||
if (output_level_inputs.empty()) {
|
||||
if (TryExtendNonL0TrivialMove((int)start_index)) {
|
||||
if (TryExtendNonL0TrivialMove((int)start_index,
|
||||
true /* only_expand_right */)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -501,6 +503,16 @@ Compaction* LevelCompactionBuilder::PickCompaction() {
|
||||
}
|
||||
|
||||
Compaction* LevelCompactionBuilder::GetCompaction() {
|
||||
// TryPickL0TrivialMove() does not apply to the case when compacting L0 to an
|
||||
// empty output level. So L0 files is picked in PickFileToCompact() by
|
||||
// compaction score. We may still be able to do trivial move when this file
|
||||
// does not overlap with other L0s. This happens when
|
||||
// compaction_inputs_[0].size() == 1 since SetupOtherL0FilesIfNeeded() did not
|
||||
// pull in more L0s.
|
||||
assert(!compaction_inputs_.empty());
|
||||
bool l0_files_might_overlap =
|
||||
start_level_ == 0 && !is_l0_trivial_move_ &&
|
||||
(compaction_inputs_.size() > 1 || compaction_inputs_[0].size() > 1);
|
||||
auto c = new Compaction(
|
||||
vstorage_, ioptions_, mutable_cf_options_, mutable_db_options_,
|
||||
std::move(compaction_inputs_), output_level_,
|
||||
@@ -515,8 +527,7 @@ Compaction* LevelCompactionBuilder::GetCompaction() {
|
||||
Temperature::kUnknown,
|
||||
/* max_subcompactions */ 0, std::move(grandparents_), is_manual_,
|
||||
/* trim_ts */ "", start_level_score_, false /* deletion_compaction */,
|
||||
/* l0_files_might_overlap */ start_level_ == 0 && !is_l0_trivial_move_,
|
||||
compaction_reason_);
|
||||
l0_files_might_overlap, compaction_reason_);
|
||||
|
||||
// If it's level 0 compaction, make sure we don't execute any other level 0
|
||||
// compactions in parallel
|
||||
@@ -653,7 +664,8 @@ bool LevelCompactionBuilder::TryPickL0TrivialMove() {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool LevelCompactionBuilder::TryExtendNonL0TrivialMove(int start_index) {
|
||||
bool LevelCompactionBuilder::TryExtendNonL0TrivialMove(int start_index,
|
||||
bool only_expand_right) {
|
||||
if (start_level_inputs_.size() == 1 &&
|
||||
(ioptions_.db_paths.empty() || ioptions_.db_paths.size() == 1) &&
|
||||
(mutable_cf_options_.compression_per_level.empty())) {
|
||||
@@ -670,6 +682,7 @@ bool LevelCompactionBuilder::TryExtendNonL0TrivialMove(int start_index) {
|
||||
size_t total_size = initial_file->fd.GetFileSize();
|
||||
CompactionInputFiles output_level_inputs;
|
||||
output_level_inputs.level = output_level_;
|
||||
// Expand towards right
|
||||
for (int i = start_index + 1;
|
||||
i < static_cast<int>(level_files.size()) &&
|
||||
start_level_inputs_.size() < kMaxMultiTrivialMove;
|
||||
@@ -702,6 +715,37 @@ bool LevelCompactionBuilder::TryExtendNonL0TrivialMove(int start_index) {
|
||||
}
|
||||
start_level_inputs_.files.push_back(next_file);
|
||||
}
|
||||
// Expand towards left
|
||||
if (!only_expand_right) {
|
||||
for (int i = start_index - 1;
|
||||
i >= 0 && start_level_inputs_.size() < kMaxMultiTrivialMove; i--) {
|
||||
FileMetaData* next_file = level_files[i];
|
||||
if (next_file->being_compacted) {
|
||||
break;
|
||||
}
|
||||
vstorage_->GetOverlappingInputs(output_level_, &(next_file->smallest),
|
||||
&(initial_file->largest),
|
||||
&output_level_inputs.files);
|
||||
if (!output_level_inputs.empty()) {
|
||||
break;
|
||||
}
|
||||
if (i > 0 && compaction_picker_->icmp()
|
||||
->user_comparator()
|
||||
->CompareWithoutTimestamp(
|
||||
next_file->smallest.user_key(),
|
||||
level_files[i - 1]->largest.user_key()) == 0) {
|
||||
// Not a clean up after adding the next file. Skip.
|
||||
break;
|
||||
}
|
||||
total_size += next_file->fd.GetFileSize();
|
||||
if (total_size > mutable_cf_options_.max_compaction_bytes) {
|
||||
break;
|
||||
}
|
||||
// keep `files` sorted in increasing order by key range
|
||||
start_level_inputs_.files.insert(start_level_inputs_.files.begin(),
|
||||
next_file);
|
||||
}
|
||||
}
|
||||
return start_level_inputs_.size() > 1;
|
||||
}
|
||||
return false;
|
||||
@@ -785,7 +829,10 @@ bool LevelCompactionBuilder::PickFileToCompact() {
|
||||
vstorage_->GetOverlappingInputs(output_level_, &smallest, &largest,
|
||||
&output_level_inputs.files);
|
||||
if (output_level_inputs.empty()) {
|
||||
if (TryExtendNonL0TrivialMove(index)) {
|
||||
if (start_level_ > 0 &&
|
||||
TryExtendNonL0TrivialMove(index,
|
||||
ioptions_.compaction_pri ==
|
||||
kRoundRobin /* only_expand_right */)) {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -148,7 +148,7 @@ class CompactionPickerTestBase : public testing::Test {
|
||||
smallest_seq, largest_seq, marked_for_compact, temperature,
|
||||
kInvalidBlobFileNumber, kUnknownOldestAncesterTime,
|
||||
kUnknownFileCreationTime, epoch_number, kUnknownFileChecksum,
|
||||
kUnknownFileChecksumFuncName, kNullUniqueId64x2, 0);
|
||||
kUnknownFileChecksumFuncName, kNullUniqueId64x2, 0, 0);
|
||||
f->compensated_file_size =
|
||||
(compensated_file_size != 0) ? compensated_file_size : file_size;
|
||||
f->oldest_ancester_time = oldest_ancestor_time;
|
||||
@@ -1005,29 +1005,28 @@ TEST_F(CompactionPickerTest, NeedsCompactionFIFO) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(CompactionPickerTest, FIFOToWarm1) {
|
||||
TEST_F(CompactionPickerTest, FIFOToCold1) {
|
||||
NewVersionStorage(1, kCompactionStyleFIFO);
|
||||
const uint64_t kFileSize = 100000;
|
||||
const uint64_t kMaxSize = kFileSize * 100000;
|
||||
uint64_t kWarmThreshold = 2000;
|
||||
uint64_t kColdThreshold = 2000;
|
||||
|
||||
fifo_options_.max_table_files_size = kMaxSize;
|
||||
fifo_options_.age_for_warm = kWarmThreshold;
|
||||
fifo_options_.file_temperature_age_thresholds = {
|
||||
{Temperature::kCold, kColdThreshold}};
|
||||
mutable_cf_options_.compaction_options_fifo = fifo_options_;
|
||||
mutable_cf_options_.level0_file_num_compaction_trigger = 2;
|
||||
mutable_cf_options_.level0_file_num_compaction_trigger = 100;
|
||||
mutable_cf_options_.max_compaction_bytes = kFileSize * 100;
|
||||
FIFOCompactionPicker fifo_compaction_picker(ioptions_, &icmp_);
|
||||
|
||||
int64_t current_time = 0;
|
||||
ASSERT_OK(Env::Default()->GetCurrentTime(¤t_time));
|
||||
uint64_t threshold_time =
|
||||
static_cast<uint64_t>(current_time) - kWarmThreshold;
|
||||
Add(0, 6U, "240", "290", 2 * kFileSize, 0, 2900, 3000, 0, true,
|
||||
Temperature::kUnknown, static_cast<uint64_t>(current_time) - 100);
|
||||
Add(0, 5U, "240", "290", 2 * kFileSize, 0, 2700, 2800, 0, true,
|
||||
Temperature::kUnknown, threshold_time + 100);
|
||||
Add(0, 4U, "260", "300", 1 * kFileSize, 0, 2500, 2600, 0, true,
|
||||
Temperature::kUnknown, threshold_time - 2000);
|
||||
static_cast<uint64_t>(current_time) - kColdThreshold;
|
||||
Add(0 /* level */, 4U /* file_number */, "260", "300", 1 * kFileSize, 0, 2500,
|
||||
2600, 0, true, Temperature::kUnknown,
|
||||
threshold_time - 2000 /* oldest_ancestor_time */);
|
||||
// Qualifies for compaction to kCold.
|
||||
Add(0, 3U, "200", "300", 4 * kFileSize, 0, 2300, 2400, 0, true,
|
||||
Temperature::kUnknown, threshold_time - 3000);
|
||||
UpdateVersionStorageInfo();
|
||||
@@ -1037,33 +1036,36 @@ TEST_F(CompactionPickerTest, FIFOToWarm1) {
|
||||
cf_name_, mutable_cf_options_, mutable_db_options_, vstorage_.get(),
|
||||
&log_buffer_));
|
||||
ASSERT_TRUE(compaction.get() != nullptr);
|
||||
ASSERT_EQ(compaction->compaction_reason(),
|
||||
CompactionReason::kChangeTemperature);
|
||||
ASSERT_EQ(compaction->output_temperature(), Temperature::kCold);
|
||||
ASSERT_EQ(1U, compaction->num_input_files(0));
|
||||
ASSERT_EQ(3U, compaction->input(0, 0)->fd.GetNumber());
|
||||
}
|
||||
|
||||
TEST_F(CompactionPickerTest, FIFOToWarm2) {
|
||||
TEST_F(CompactionPickerTest, FIFOToCold2) {
|
||||
NewVersionStorage(1, kCompactionStyleFIFO);
|
||||
const uint64_t kFileSize = 100000;
|
||||
const uint64_t kMaxSize = kFileSize * 100000;
|
||||
uint64_t kWarmThreshold = 2000;
|
||||
uint64_t kColdThreshold = 2000;
|
||||
|
||||
fifo_options_.max_table_files_size = kMaxSize;
|
||||
fifo_options_.age_for_warm = kWarmThreshold;
|
||||
fifo_options_.file_temperature_age_thresholds = {
|
||||
{Temperature::kCold, kColdThreshold}};
|
||||
mutable_cf_options_.compaction_options_fifo = fifo_options_;
|
||||
mutable_cf_options_.level0_file_num_compaction_trigger = 2;
|
||||
mutable_cf_options_.level0_file_num_compaction_trigger = 100;
|
||||
mutable_cf_options_.max_compaction_bytes = kFileSize * 100;
|
||||
FIFOCompactionPicker fifo_compaction_picker(ioptions_, &icmp_);
|
||||
|
||||
int64_t current_time = 0;
|
||||
ASSERT_OK(Env::Default()->GetCurrentTime(¤t_time));
|
||||
uint64_t threshold_time =
|
||||
static_cast<uint64_t>(current_time) - kWarmThreshold;
|
||||
static_cast<uint64_t>(current_time) - kColdThreshold;
|
||||
Add(0, 6U, "240", "290", 2 * kFileSize, 0, 2900, 3000, 0, true,
|
||||
Temperature::kUnknown, static_cast<uint64_t>(current_time) - 100);
|
||||
Add(0, 5U, "240", "290", 2 * kFileSize, 0, 2700, 2800, 0, true,
|
||||
Temperature::kUnknown, threshold_time + 100);
|
||||
Add(0, 4U, "260", "300", 1 * kFileSize, 0, 2500, 2600, 0, true,
|
||||
Temperature::kUnknown, threshold_time - 2000);
|
||||
Temperature::kUnknown, threshold_time);
|
||||
// The following two files qualify for compaction to kCold.
|
||||
Add(0, 3U, "200", "300", 4 * kFileSize, 0, 2300, 2400, 0, true,
|
||||
Temperature::kUnknown, threshold_time - 3000);
|
||||
Add(0, 2U, "200", "300", 4 * kFileSize, 0, 2100, 2200, 0, true,
|
||||
@@ -1075,34 +1077,40 @@ TEST_F(CompactionPickerTest, FIFOToWarm2) {
|
||||
cf_name_, mutable_cf_options_, mutable_db_options_, vstorage_.get(),
|
||||
&log_buffer_));
|
||||
ASSERT_TRUE(compaction.get() != nullptr);
|
||||
ASSERT_EQ(compaction->compaction_reason(),
|
||||
CompactionReason::kChangeTemperature);
|
||||
ASSERT_EQ(compaction->output_temperature(), Temperature::kCold);
|
||||
ASSERT_EQ(2U, compaction->num_input_files(0));
|
||||
ASSERT_EQ(2U, compaction->input(0, 0)->fd.GetNumber());
|
||||
ASSERT_EQ(3U, compaction->input(0, 1)->fd.GetNumber());
|
||||
}
|
||||
|
||||
TEST_F(CompactionPickerTest, FIFOToWarmMaxSize) {
|
||||
TEST_F(CompactionPickerTest, FIFOToColdMaxCompactionSize) {
|
||||
NewVersionStorage(1, kCompactionStyleFIFO);
|
||||
const uint64_t kFileSize = 100000;
|
||||
const uint64_t kMaxSize = kFileSize * 100000;
|
||||
uint64_t kWarmThreshold = 2000;
|
||||
uint64_t kColdThreshold = 2000;
|
||||
|
||||
fifo_options_.max_table_files_size = kMaxSize;
|
||||
fifo_options_.age_for_warm = kWarmThreshold;
|
||||
fifo_options_.file_temperature_age_thresholds = {
|
||||
{Temperature::kCold, kColdThreshold}};
|
||||
mutable_cf_options_.compaction_options_fifo = fifo_options_;
|
||||
mutable_cf_options_.level0_file_num_compaction_trigger = 2;
|
||||
mutable_cf_options_.level0_file_num_compaction_trigger = 100;
|
||||
mutable_cf_options_.max_compaction_bytes = kFileSize * 9;
|
||||
FIFOCompactionPicker fifo_compaction_picker(ioptions_, &icmp_);
|
||||
|
||||
int64_t current_time = 0;
|
||||
ASSERT_OK(Env::Default()->GetCurrentTime(¤t_time));
|
||||
uint64_t threshold_time =
|
||||
static_cast<uint64_t>(current_time) - kWarmThreshold;
|
||||
static_cast<uint64_t>(current_time) - kColdThreshold;
|
||||
Add(0, 6U, "240", "290", 2 * kFileSize, 0, 2900, 3000, 0, true,
|
||||
Temperature::kUnknown, static_cast<uint64_t>(current_time) - 100);
|
||||
Add(0, 5U, "240", "290", 2 * kFileSize, 0, 2700, 2800, 0, true,
|
||||
Temperature::kUnknown, threshold_time + 100);
|
||||
Add(0, 4U, "260", "300", 1 * kFileSize, 0, 2500, 2600, 0, true,
|
||||
Temperature::kUnknown, threshold_time - 2000);
|
||||
// The following two files qualify for compaction to kCold.
|
||||
// But only the last two should be included to respect `max_compaction_bytes`.
|
||||
Add(0, 3U, "200", "300", 4 * kFileSize, 0, 2300, 2400, 0, true,
|
||||
Temperature::kUnknown, threshold_time - 3000);
|
||||
Add(0, 2U, "200", "300", 4 * kFileSize, 0, 2100, 2200, 0, true,
|
||||
@@ -1116,40 +1124,45 @@ TEST_F(CompactionPickerTest, FIFOToWarmMaxSize) {
|
||||
cf_name_, mutable_cf_options_, mutable_db_options_, vstorage_.get(),
|
||||
&log_buffer_));
|
||||
ASSERT_TRUE(compaction.get() != nullptr);
|
||||
ASSERT_EQ(compaction->compaction_reason(),
|
||||
CompactionReason::kChangeTemperature);
|
||||
ASSERT_EQ(compaction->output_temperature(), Temperature::kCold);
|
||||
ASSERT_EQ(2U, compaction->num_input_files(0));
|
||||
ASSERT_EQ(1U, compaction->input(0, 0)->fd.GetNumber());
|
||||
ASSERT_EQ(2U, compaction->input(0, 1)->fd.GetNumber());
|
||||
}
|
||||
|
||||
TEST_F(CompactionPickerTest, FIFOToWarmWithExistingWarm) {
|
||||
TEST_F(CompactionPickerTest, FIFOToColdWithExistingCold) {
|
||||
NewVersionStorage(1, kCompactionStyleFIFO);
|
||||
const uint64_t kFileSize = 100000;
|
||||
const uint64_t kMaxSize = kFileSize * 100000;
|
||||
uint64_t kWarmThreshold = 2000;
|
||||
uint64_t kColdThreshold = 2000;
|
||||
|
||||
fifo_options_.max_table_files_size = kMaxSize;
|
||||
fifo_options_.age_for_warm = kWarmThreshold;
|
||||
fifo_options_.file_temperature_age_thresholds = {
|
||||
{Temperature::kCold, kColdThreshold}};
|
||||
mutable_cf_options_.compaction_options_fifo = fifo_options_;
|
||||
mutable_cf_options_.level0_file_num_compaction_trigger = 2;
|
||||
mutable_cf_options_.level0_file_num_compaction_trigger = 100;
|
||||
mutable_cf_options_.max_compaction_bytes = kFileSize * 100;
|
||||
FIFOCompactionPicker fifo_compaction_picker(ioptions_, &icmp_);
|
||||
|
||||
int64_t current_time = 0;
|
||||
ASSERT_OK(Env::Default()->GetCurrentTime(¤t_time));
|
||||
uint64_t threshold_time =
|
||||
static_cast<uint64_t>(current_time) - kWarmThreshold;
|
||||
static_cast<uint64_t>(current_time) - kColdThreshold;
|
||||
Add(0, 6U, "240", "290", 2 * kFileSize, 0, 2900, 3000, 0, true,
|
||||
Temperature::kUnknown, static_cast<uint64_t>(current_time) - 100);
|
||||
Add(0, 5U, "240", "290", 2 * kFileSize, 0, 2700, 2800, 0, true,
|
||||
Temperature::kUnknown, threshold_time + 100);
|
||||
Add(0, 4U, "260", "300", 1 * kFileSize, 0, 2500, 2600, 0, true,
|
||||
Temperature::kUnknown, threshold_time - 2000);
|
||||
// The following two files qualify for compaction to kCold.
|
||||
Add(0, 3U, "200", "300", 4 * kFileSize, 0, 2300, 2400, 0, true,
|
||||
Temperature::kUnknown, threshold_time - 3000);
|
||||
Add(0, 2U, "200", "300", 4 * kFileSize, 0, 2100, 2200, 0, true,
|
||||
Temperature::kUnknown, threshold_time - 4000);
|
||||
Add(0, 1U, "200", "300", 4 * kFileSize, 0, 2000, 2100, 0, true,
|
||||
Temperature::kWarm, threshold_time - 5000);
|
||||
Temperature::kCold, threshold_time - 5000);
|
||||
UpdateVersionStorageInfo();
|
||||
|
||||
ASSERT_EQ(fifo_compaction_picker.NeedsCompaction(vstorage_.get()), true);
|
||||
@@ -1157,28 +1170,32 @@ TEST_F(CompactionPickerTest, FIFOToWarmWithExistingWarm) {
|
||||
cf_name_, mutable_cf_options_, mutable_db_options_, vstorage_.get(),
|
||||
&log_buffer_));
|
||||
ASSERT_TRUE(compaction.get() != nullptr);
|
||||
ASSERT_EQ(2U, compaction->num_input_files(0));
|
||||
ASSERT_EQ(compaction->compaction_reason(),
|
||||
CompactionReason::kChangeTemperature);
|
||||
ASSERT_EQ(compaction->output_temperature(), Temperature::kCold);
|
||||
ASSERT_EQ(2U, compaction->input(0, 0)->fd.GetNumber());
|
||||
ASSERT_EQ(2U, compaction->num_input_files(0));
|
||||
ASSERT_EQ(3U, compaction->input(0, 1)->fd.GetNumber());
|
||||
}
|
||||
|
||||
TEST_F(CompactionPickerTest, FIFOToWarmWithOngoing) {
|
||||
TEST_F(CompactionPickerTest, FIFOToColdWithHotBetweenCold) {
|
||||
NewVersionStorage(1, kCompactionStyleFIFO);
|
||||
const uint64_t kFileSize = 100000;
|
||||
const uint64_t kMaxSize = kFileSize * 100000;
|
||||
uint64_t kWarmThreshold = 2000;
|
||||
uint64_t kColdThreshold = 2000;
|
||||
|
||||
fifo_options_.max_table_files_size = kMaxSize;
|
||||
fifo_options_.age_for_warm = kWarmThreshold;
|
||||
fifo_options_.file_temperature_age_thresholds = {
|
||||
{Temperature::kCold, kColdThreshold}};
|
||||
mutable_cf_options_.compaction_options_fifo = fifo_options_;
|
||||
mutable_cf_options_.level0_file_num_compaction_trigger = 2;
|
||||
mutable_cf_options_.level0_file_num_compaction_trigger = 100;
|
||||
mutable_cf_options_.max_compaction_bytes = kFileSize * 100;
|
||||
FIFOCompactionPicker fifo_compaction_picker(ioptions_, &icmp_);
|
||||
|
||||
int64_t current_time = 0;
|
||||
ASSERT_OK(Env::Default()->GetCurrentTime(¤t_time));
|
||||
uint64_t threshold_time =
|
||||
static_cast<uint64_t>(current_time) - kWarmThreshold;
|
||||
static_cast<uint64_t>(current_time) - kColdThreshold;
|
||||
Add(0, 6U, "240", "290", 2 * kFileSize, 0, 2900, 3000, 0, true,
|
||||
Temperature::kUnknown, static_cast<uint64_t>(current_time) - 100);
|
||||
Add(0, 5U, "240", "290", 2 * kFileSize, 0, 2700, 2800, 0, true,
|
||||
@@ -1186,63 +1203,77 @@ TEST_F(CompactionPickerTest, FIFOToWarmWithOngoing) {
|
||||
Add(0, 4U, "260", "300", 1 * kFileSize, 0, 2500, 2600, 0, true,
|
||||
Temperature::kUnknown, threshold_time - 2000);
|
||||
Add(0, 3U, "200", "300", 4 * kFileSize, 0, 2300, 2400, 0, true,
|
||||
Temperature::kUnknown, threshold_time - 3000);
|
||||
Temperature::kCold, threshold_time - 3000);
|
||||
// Qualifies for compaction to kCold.
|
||||
Add(0, 2U, "200", "300", 4 * kFileSize, 0, 2100, 2200, 0, true,
|
||||
Temperature::kUnknown, threshold_time - 4000);
|
||||
Add(0, 1U, "200", "300", 4 * kFileSize, 0, 2000, 2100, 0, true,
|
||||
Temperature::kWarm, threshold_time - 5000);
|
||||
file_map_[2].first->being_compacted = true;
|
||||
Temperature::kCold, threshold_time - 5000);
|
||||
UpdateVersionStorageInfo();
|
||||
|
||||
ASSERT_EQ(fifo_compaction_picker.NeedsCompaction(vstorage_.get()), true);
|
||||
std::unique_ptr<Compaction> compaction(fifo_compaction_picker.PickCompaction(
|
||||
cf_name_, mutable_cf_options_, mutable_db_options_, vstorage_.get(),
|
||||
&log_buffer_));
|
||||
// Stop if a file is being compacted
|
||||
ASSERT_TRUE(compaction.get() == nullptr);
|
||||
}
|
||||
|
||||
TEST_F(CompactionPickerTest, FIFOToWarmWithHotBetweenWarms) {
|
||||
NewVersionStorage(1, kCompactionStyleFIFO);
|
||||
const uint64_t kFileSize = 100000;
|
||||
const uint64_t kMaxSize = kFileSize * 100000;
|
||||
uint64_t kWarmThreshold = 2000;
|
||||
|
||||
fifo_options_.max_table_files_size = kMaxSize;
|
||||
fifo_options_.age_for_warm = kWarmThreshold;
|
||||
mutable_cf_options_.compaction_options_fifo = fifo_options_;
|
||||
mutable_cf_options_.level0_file_num_compaction_trigger = 2;
|
||||
mutable_cf_options_.max_compaction_bytes = kFileSize * 100;
|
||||
FIFOCompactionPicker fifo_compaction_picker(ioptions_, &icmp_);
|
||||
|
||||
int64_t current_time = 0;
|
||||
ASSERT_OK(Env::Default()->GetCurrentTime(¤t_time));
|
||||
uint64_t threshold_time =
|
||||
static_cast<uint64_t>(current_time) - kWarmThreshold;
|
||||
Add(0, 6U, "240", "290", 2 * kFileSize, 0, 2900, 3000, 0, true,
|
||||
Temperature::kUnknown, static_cast<uint64_t>(current_time) - 100);
|
||||
Add(0, 5U, "240", "290", 2 * kFileSize, 0, 2700, 2800, 0, true,
|
||||
Temperature::kUnknown, threshold_time + 100);
|
||||
Add(0, 4U, "260", "300", 1 * kFileSize, 0, 2500, 2600, 0, true,
|
||||
Temperature::kUnknown, threshold_time - 2000);
|
||||
Add(0, 3U, "200", "300", 4 * kFileSize, 0, 2300, 2400, 0, true,
|
||||
Temperature::kWarm, threshold_time - 3000);
|
||||
Add(0, 2U, "200", "300", 4 * kFileSize, 0, 2100, 2200, 0, true,
|
||||
Temperature::kUnknown, threshold_time - 4000);
|
||||
Add(0, 1U, "200", "300", 4 * kFileSize, 0, 2000, 2100, 0, true,
|
||||
Temperature::kWarm, threshold_time - 5000);
|
||||
UpdateVersionStorageInfo();
|
||||
|
||||
ASSERT_EQ(fifo_compaction_picker.NeedsCompaction(vstorage_.get()), true);
|
||||
std::unique_ptr<Compaction> compaction(fifo_compaction_picker.PickCompaction(
|
||||
cf_name_, mutable_cf_options_, mutable_db_options_, vstorage_.get(),
|
||||
&log_buffer_));
|
||||
// Stop if a file is being compacted
|
||||
ASSERT_TRUE(compaction.get() != nullptr);
|
||||
ASSERT_EQ(compaction->compaction_reason(),
|
||||
CompactionReason::kChangeTemperature);
|
||||
ASSERT_EQ(compaction->output_temperature(), Temperature::kCold);
|
||||
ASSERT_EQ(1U, compaction->num_input_files(0));
|
||||
ASSERT_EQ(2U, compaction->input(0, 0)->fd.GetNumber());
|
||||
}
|
||||
|
||||
TEST_F(CompactionPickerTest, FIFOToColdAndWarm) {
|
||||
NewVersionStorage(1, kCompactionStyleFIFO);
|
||||
const uint64_t kFileSize = 100000;
|
||||
const uint64_t kMaxSize = kFileSize * 100000;
|
||||
uint64_t kWarmThreshold = 10000;
|
||||
uint64_t kHotThreshold = 2000;
|
||||
|
||||
fifo_options_.max_table_files_size = kMaxSize;
|
||||
// Test that multiple threshold works.
|
||||
fifo_options_.file_temperature_age_thresholds = {
|
||||
{Temperature::kHot, kHotThreshold}, {Temperature::kWarm, kWarmThreshold}};
|
||||
mutable_cf_options_.compaction_options_fifo = fifo_options_;
|
||||
mutable_cf_options_.level0_file_num_compaction_trigger = 100;
|
||||
mutable_cf_options_.max_compaction_bytes = kFileSize * 100;
|
||||
FIFOCompactionPicker fifo_compaction_picker(ioptions_, &icmp_);
|
||||
|
||||
int64_t current_time = 0;
|
||||
ASSERT_OK(Env::Default()->GetCurrentTime(¤t_time));
|
||||
uint64_t hot_threshold_time =
|
||||
static_cast<uint64_t>(current_time) - kHotThreshold;
|
||||
uint64_t warm_threshold_time =
|
||||
static_cast<uint64_t>(current_time) - kWarmThreshold;
|
||||
Add(0, 6U, "240", "290", 2 * kFileSize, 0, 2900, 3000, 0, true,
|
||||
Temperature::kUnknown, static_cast<uint64_t>(current_time) - 100);
|
||||
Add(0, 5U, "240", "290", 2 * kFileSize, 0, 2700, 2800, 0, true,
|
||||
Temperature::kUnknown, hot_threshold_time + 100);
|
||||
Add(0, 4U, "260", "300", 1 * kFileSize, 0, 2500, 2600, 0, true,
|
||||
Temperature::kUnknown, hot_threshold_time - 200);
|
||||
// Qualifies for Hot
|
||||
Add(0, 3U, "200", "300", 4 * kFileSize, 0, 2300, 2400, 0, true,
|
||||
Temperature::kUnknown, warm_threshold_time - 100);
|
||||
// Qualifies for Warm
|
||||
Add(0, 2U, "200", "300", 4 * kFileSize, 0, 2100, 2200, 0, true,
|
||||
Temperature::kUnknown, warm_threshold_time - 4000);
|
||||
Add(0, 1U, "200", "300", 4 * kFileSize, 0, 2000, 2100, 0, true,
|
||||
Temperature::kUnknown, warm_threshold_time - 5000);
|
||||
UpdateVersionStorageInfo();
|
||||
|
||||
ASSERT_EQ(fifo_compaction_picker.NeedsCompaction(vstorage_.get()), true);
|
||||
std::unique_ptr<Compaction> compaction(fifo_compaction_picker.PickCompaction(
|
||||
cf_name_, mutable_cf_options_, mutable_db_options_, vstorage_.get(),
|
||||
&log_buffer_));
|
||||
ASSERT_TRUE(compaction.get() != nullptr);
|
||||
ASSERT_EQ(compaction->compaction_reason(),
|
||||
CompactionReason::kChangeTemperature);
|
||||
// Assumes compaction picker picks older files first.
|
||||
ASSERT_EQ(compaction->output_temperature(), Temperature::kWarm);
|
||||
ASSERT_EQ(2U, compaction->num_input_files(0));
|
||||
ASSERT_EQ(1U, compaction->input(0, 0)->fd.GetNumber());
|
||||
ASSERT_EQ(2U, compaction->input(0, 1)->fd.GetNumber());
|
||||
}
|
||||
|
||||
TEST_F(CompactionPickerTest, CompactionPriMinOverlapping1) {
|
||||
NewVersionStorage(6, kCompactionStyleLevel);
|
||||
@@ -2520,6 +2551,61 @@ TEST_F(CompactionPickerTest, L0TrivialMoveWholeL0) {
|
||||
ASSERT_TRUE(compaction->IsTrivialMove());
|
||||
}
|
||||
|
||||
TEST_F(CompactionPickerTest, NonL0TrivialMoveExtendBothDirection) {
|
||||
mutable_cf_options_.max_bytes_for_level_base = 5000;
|
||||
mutable_cf_options_.level0_file_num_compaction_trigger = 4;
|
||||
mutable_cf_options_.max_compaction_bytes = 10000000u;
|
||||
ioptions_.level_compaction_dynamic_level_bytes = false;
|
||||
NewVersionStorage(6, kCompactionStyleLevel);
|
||||
|
||||
Add(1, 1U, "300", "350", 3000U, 0, 710, 800, 3000U);
|
||||
Add(1, 2U, "600", "651", 3001U, 0, 610, 700, 3001U);
|
||||
Add(1, 3U, "700", "750", 3000U, 0, 500, 550, 3000U);
|
||||
Add(2, 4U, "800", "850", 4000U, 0, 150, 200, 4000U);
|
||||
|
||||
UpdateVersionStorageInfo();
|
||||
// File #2 should be picked first, and expand both directions to include
|
||||
// files #1 and #3.
|
||||
std::unique_ptr<Compaction> compaction(level_compaction_picker.PickCompaction(
|
||||
cf_name_, mutable_cf_options_, mutable_db_options_, vstorage_.get(),
|
||||
&log_buffer_));
|
||||
ASSERT_TRUE(compaction.get() != nullptr);
|
||||
ASSERT_EQ(1, compaction->num_input_levels());
|
||||
ASSERT_EQ(3, compaction->num_input_files(0));
|
||||
ASSERT_EQ(1, compaction->input(0, 0)->fd.GetNumber());
|
||||
ASSERT_EQ(2, compaction->input(0, 1)->fd.GetNumber());
|
||||
ASSERT_EQ(3, compaction->input(0, 2)->fd.GetNumber());
|
||||
ASSERT_TRUE(compaction->IsTrivialMove());
|
||||
}
|
||||
|
||||
TEST_F(CompactionPickerTest, L0TrivialMoveToEmptyLevel) {
|
||||
mutable_cf_options_.max_bytes_for_level_base = 5000;
|
||||
mutable_cf_options_.level0_file_num_compaction_trigger = 4;
|
||||
mutable_cf_options_.max_compaction_bytes = 10000000u;
|
||||
ioptions_.level_compaction_dynamic_level_bytes = false;
|
||||
NewVersionStorage(6, kCompactionStyleLevel);
|
||||
|
||||
// File 2 will be picked first, which by itself is trivial movable.
|
||||
// There was a bug before where compaction also picks file 3 and 4,
|
||||
// (and then file 1 since it overlaps with the key range),
|
||||
// which makes the compaction not trivial movable.
|
||||
Add(0, 1U, "450", "599", 3000U, 0, 710, 800, 3000U);
|
||||
Add(0, 2U, "600", "651", 3001U, 0, 610, 700, 3001U);
|
||||
Add(0, 3U, "300", "350", 3000U, 0, 500, 550, 3000U);
|
||||
Add(0, 4U, "500", "550", 2999U, 0, 300, 350, 2999U);
|
||||
|
||||
UpdateVersionStorageInfo();
|
||||
|
||||
std::unique_ptr<Compaction> compaction(level_compaction_picker.PickCompaction(
|
||||
cf_name_, mutable_cf_options_, mutable_db_options_, vstorage_.get(),
|
||||
&log_buffer_));
|
||||
ASSERT_TRUE(compaction.get() != nullptr);
|
||||
ASSERT_EQ(1, compaction->num_input_levels());
|
||||
ASSERT_EQ(1, compaction->num_input_files(0));
|
||||
ASSERT_EQ(2, compaction->input(0, 0)->fd.GetNumber());
|
||||
ASSERT_TRUE(compaction->IsTrivialMove());
|
||||
}
|
||||
|
||||
TEST_F(CompactionPickerTest, IsTrivialMoveOffSstPartitioned) {
|
||||
mutable_cf_options_.max_bytes_for_level_base = 10000u;
|
||||
mutable_cf_options_.max_compaction_bytes = 10001u;
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
#include "file/filename.h"
|
||||
#include "logging/log_buffer.h"
|
||||
#include "logging/logging.h"
|
||||
#include "monitoring/statistics.h"
|
||||
#include "monitoring/statistics_impl.h"
|
||||
#include "test_util/sync_point.h"
|
||||
#include "util/random.h"
|
||||
#include "util/string_util.h"
|
||||
|
||||
@@ -928,7 +928,7 @@ TEST_F(CompactionServiceTest, TablePropertiesCollector) {
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact(true));
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
|
||||
ASSERT_OK(db_->GetPropertiesOfAllTables(&fname_to_props));
|
||||
|
||||
@@ -952,4 +952,3 @@ int main(int argc, char** argv) {
|
||||
RegisterCustomObjects(argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
|
||||
|
||||
@@ -78,7 +78,6 @@ Status SstPartitionerFactory::CreateFromString(
|
||||
std::call_once(once, [&]() {
|
||||
RegisterSstPartitionerFactories(*(ObjectLibrary::Default().get()), "");
|
||||
});
|
||||
return LoadSharedObject<SstPartitionerFactory>(options, value, nullptr,
|
||||
result);
|
||||
return LoadSharedObject<SstPartitionerFactory>(options, value, result);
|
||||
}
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
@@ -84,6 +84,11 @@ class SubcompactionState {
|
||||
// Assign range dels aggregator, for each range_del, it can only be assigned
|
||||
// to one output level, for per_key_placement, it's going to be the
|
||||
// penultimate level.
|
||||
// TODO: This does not work for per_key_placement + user-defined timestamp +
|
||||
// DeleteRange() combo. If user-defined timestamp is enabled,
|
||||
// it is possible for a range tombstone to belong to bottommost level (
|
||||
// seqno < earliest snapshot) without being dropped (garbage collection
|
||||
// for user-defined timestamp).
|
||||
void AssignRangeDelAggregator(
|
||||
std::unique_ptr<CompactionRangeDelAggregator>&& range_del_agg) {
|
||||
if (compaction->SupportsPerKeyPlacement()) {
|
||||
|
||||
@@ -209,7 +209,7 @@ TEST_P(TieredCompactionTest, SequenceBasedTieredStorageUniversal) {
|
||||
seq_history.emplace_back(dbfull()->GetLatestSequenceNumber());
|
||||
expect_stats[0].Add(kBasicFlushStats);
|
||||
}
|
||||
ASSERT_OK(dbfull()->WaitForCompact(true));
|
||||
ASSERT_OK(dbfull()->WaitForCompact());
|
||||
|
||||
// the penultimate level file temperature is not cold, all data are output to
|
||||
// the penultimate level.
|
||||
@@ -374,7 +374,7 @@ TEST_P(TieredCompactionTest, RangeBasedTieredStorageUniversal) {
|
||||
ASSERT_OK(Flush());
|
||||
expect_stats[0].Add(kBasicFlushStats);
|
||||
}
|
||||
ASSERT_OK(dbfull()->WaitForCompact(true));
|
||||
ASSERT_OK(dbfull()->WaitForCompact());
|
||||
ASSERT_EQ("0,0,0,0,0,1,1", FilesPerLevel());
|
||||
ASSERT_GT(GetSstSizeHelper(Temperature::kUnknown), 0);
|
||||
ASSERT_GT(GetSstSizeHelper(Temperature::kCold), 0);
|
||||
@@ -445,8 +445,8 @@ TEST_P(TieredCompactionTest, RangeBasedTieredStorageUniversal) {
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
ASSERT_OK(dbfull()->WaitForCompact(
|
||||
true)); // make sure the compaction is able to finish
|
||||
// make sure the compaction is able to finish
|
||||
ASSERT_OK(dbfull()->WaitForCompact());
|
||||
ASSERT_EQ("0,0,0,0,0,1,1", FilesPerLevel());
|
||||
ASSERT_GT(GetSstSizeHelper(Temperature::kUnknown), 0);
|
||||
ASSERT_GT(GetSstSizeHelper(Temperature::kCold), 0);
|
||||
@@ -911,7 +911,7 @@ TEST_P(TieredCompactionTest, SequenceBasedTieredStorageLevel) {
|
||||
ASSERT_OK(Flush());
|
||||
expect_stats[0].Add(kBasicFlushStats);
|
||||
}
|
||||
ASSERT_OK(dbfull()->WaitForCompact(true));
|
||||
ASSERT_OK(dbfull()->WaitForCompact());
|
||||
|
||||
// non last level is hot
|
||||
ASSERT_EQ("0,1", FilesPerLevel());
|
||||
@@ -954,7 +954,7 @@ TEST_P(TieredCompactionTest, SequenceBasedTieredStorageLevel) {
|
||||
ASSERT_OK(Flush());
|
||||
seq_history.emplace_back(dbfull()->GetLatestSequenceNumber());
|
||||
}
|
||||
ASSERT_OK(dbfull()->WaitForCompact(true));
|
||||
ASSERT_OK(dbfull()->WaitForCompact());
|
||||
ASSERT_EQ("0,1,0,0,0,0,1", FilesPerLevel());
|
||||
ASSERT_GT(GetSstSizeHelper(Temperature::kUnknown), 0);
|
||||
ASSERT_GT(GetSstSizeHelper(Temperature::kCold), 0);
|
||||
@@ -1005,7 +1005,7 @@ TEST_P(TieredCompactionTest, SequenceBasedTieredStorageLevel) {
|
||||
ASSERT_OK(Flush());
|
||||
seq_history.emplace_back(dbfull()->GetLatestSequenceNumber());
|
||||
}
|
||||
ASSERT_OK(dbfull()->WaitForCompact(true));
|
||||
ASSERT_OK(dbfull()->WaitForCompact());
|
||||
|
||||
latest_cold_seq = seq_history[0];
|
||||
ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr));
|
||||
@@ -1111,6 +1111,7 @@ TEST_P(TieredCompactionTest, RangeBasedTieredStorageLevel) {
|
||||
options.num_levels = kNumLevels;
|
||||
options.statistics = CreateDBStatistics();
|
||||
options.max_subcompactions = 10;
|
||||
options.preclude_last_level_data_seconds = 10000;
|
||||
DestroyAndReopen(options);
|
||||
auto cmp = options.comparator;
|
||||
|
||||
@@ -1134,7 +1135,7 @@ TEST_P(TieredCompactionTest, RangeBasedTieredStorageLevel) {
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
ASSERT_OK(dbfull()->WaitForCompact(true));
|
||||
ASSERT_OK(dbfull()->WaitForCompact());
|
||||
ASSERT_EQ("0,0,0,0,0,1,1", FilesPerLevel());
|
||||
ASSERT_GT(GetSstSizeHelper(Temperature::kUnknown), 0);
|
||||
ASSERT_GT(GetSstSizeHelper(Temperature::kCold), 0);
|
||||
@@ -1264,7 +1265,7 @@ TEST_F(PrecludeLastLevelTest, MigrationFromPreserveTimeManualCompaction) {
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
ASSERT_OK(dbfull()->WaitForCompact(true));
|
||||
ASSERT_OK(dbfull()->WaitForCompact());
|
||||
|
||||
// all data is pushed to the last level
|
||||
ASSERT_EQ("0,0,0,0,0,0,1", FilesPerLevel());
|
||||
@@ -1326,7 +1327,7 @@ TEST_F(PrecludeLastLevelTest, MigrationFromPreserveTimeAutoCompaction) {
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
ASSERT_OK(dbfull()->WaitForCompact(true));
|
||||
ASSERT_OK(dbfull()->WaitForCompact());
|
||||
|
||||
// all data is pushed to the last level
|
||||
ASSERT_EQ("0,0,0,0,0,0,1", FilesPerLevel());
|
||||
@@ -1359,7 +1360,7 @@ TEST_F(PrecludeLastLevelTest, MigrationFromPreserveTimeAutoCompaction) {
|
||||
});
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(dbfull()->WaitForCompact(true));
|
||||
ASSERT_OK(dbfull()->WaitForCompact());
|
||||
}
|
||||
|
||||
// all data is moved up to the penultimate level
|
||||
@@ -1402,7 +1403,7 @@ TEST_F(PrecludeLastLevelTest, MigrationFromPreserveTimePartial) {
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
ASSERT_OK(dbfull()->WaitForCompact(true));
|
||||
ASSERT_OK(dbfull()->WaitForCompact());
|
||||
|
||||
// all data is pushed to the last level
|
||||
ASSERT_EQ("0,0,0,0,0,0,1", FilesPerLevel());
|
||||
@@ -1529,7 +1530,7 @@ TEST_F(PrecludeLastLevelTest, LastLevelOnlyCompactionPartial) {
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
ASSERT_OK(dbfull()->WaitForCompact(true));
|
||||
ASSERT_OK(dbfull()->WaitForCompact());
|
||||
|
||||
// all data is pushed to the last level
|
||||
ASSERT_EQ("0,0,0,0,0,0,1", FilesPerLevel());
|
||||
@@ -1608,7 +1609,7 @@ TEST_P(PrecludeLastLevelTestWithParms, LastLevelOnlyCompactionNoPreclude) {
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
ASSERT_OK(dbfull()->WaitForCompact(true));
|
||||
ASSERT_OK(dbfull()->WaitForCompact());
|
||||
|
||||
// all data is pushed to the last level
|
||||
ASSERT_EQ("0,0,0,0,0,0,1", FilesPerLevel());
|
||||
@@ -1704,7 +1705,7 @@ TEST_P(PrecludeLastLevelTestWithParms, LastLevelOnlyCompactionNoPreclude) {
|
||||
|
||||
manual_compaction_thread.join();
|
||||
|
||||
ASSERT_OK(dbfull()->WaitForCompact(true));
|
||||
ASSERT_OK(dbfull()->WaitForCompact());
|
||||
|
||||
if (enable_preclude_last_level) {
|
||||
ASSERT_NE("0,0,0,0,0,1,1", FilesPerLevel());
|
||||
@@ -1840,7 +1841,7 @@ TEST_P(PrecludeLastLevelTestWithParms, PeriodicCompactionToPenultimateLevel) {
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
ASSERT_OK(dbfull()->WaitForCompact(true));
|
||||
ASSERT_OK(dbfull()->WaitForCompact());
|
||||
|
||||
stop_token.reset();
|
||||
|
||||
@@ -1939,7 +1940,7 @@ TEST_F(PrecludeLastLevelTest, PartialPenultimateLevelCompaction) {
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
|
||||
ASSERT_OK(dbfull()->WaitForCompact(true));
|
||||
ASSERT_OK(dbfull()->WaitForCompact());
|
||||
|
||||
// L5: [0,19] [20,39] [40,299]
|
||||
// L6: [0, 299]
|
||||
@@ -2105,7 +2106,7 @@ TEST_F(PrecludeLastLevelTest, RangeDelsCauseFileEndpointsToOverlap) {
|
||||
Slice begin_key(begin_key_buf), end_key(end_key_buf);
|
||||
ASSERT_OK(db_->SuggestCompactRange(db_->DefaultColumnFamily(), &begin_key,
|
||||
&end_key));
|
||||
ASSERT_OK(dbfull()->WaitForCompact(true));
|
||||
ASSERT_OK(dbfull()->WaitForCompact());
|
||||
ASSERT_EQ("0,0,0,0,0,3,3", FilesPerLevel());
|
||||
ASSERT_EQ(1, per_key_comp_num);
|
||||
verify_db();
|
||||
@@ -2115,7 +2116,7 @@ TEST_F(PrecludeLastLevelTest, RangeDelsCauseFileEndpointsToOverlap) {
|
||||
db_->ReleaseSnapshot(snap2);
|
||||
ASSERT_OK(db_->SuggestCompactRange(db_->DefaultColumnFamily(), &begin_key,
|
||||
&end_key));
|
||||
ASSERT_OK(dbfull()->WaitForCompact(true));
|
||||
ASSERT_OK(dbfull()->WaitForCompact());
|
||||
ASSERT_EQ("0,0,0,0,0,3,3", FilesPerLevel());
|
||||
ASSERT_EQ(2, per_key_comp_num);
|
||||
verify_db();
|
||||
@@ -2125,7 +2126,7 @@ TEST_F(PrecludeLastLevelTest, RangeDelsCauseFileEndpointsToOverlap) {
|
||||
db_->ReleaseSnapshot(snap1);
|
||||
ASSERT_OK(db_->SuggestCompactRange(db_->DefaultColumnFamily(), &begin_key,
|
||||
&end_key));
|
||||
ASSERT_OK(dbfull()->WaitForCompact(true));
|
||||
ASSERT_OK(dbfull()->WaitForCompact());
|
||||
ASSERT_EQ("0,0,0,0,0,2,3", FilesPerLevel());
|
||||
ASSERT_EQ(3, per_key_comp_num);
|
||||
verify_db();
|
||||
|
||||
+8
-6
@@ -33,7 +33,9 @@ Status DeleteFilesInRanges(DB* db, ColumnFamilyHandle* column_family,
|
||||
Status VerifySstFileChecksum(const Options& options,
|
||||
const EnvOptions& env_options,
|
||||
const std::string& file_path) {
|
||||
return VerifySstFileChecksum(options, env_options, ReadOptions(), file_path);
|
||||
// TODO: plumb Env::IOActivity
|
||||
const ReadOptions read_options;
|
||||
return VerifySstFileChecksum(options, env_options, read_options, file_path);
|
||||
}
|
||||
Status VerifySstFileChecksum(const Options& options,
|
||||
const EnvOptions& env_options,
|
||||
@@ -56,13 +58,14 @@ Status VerifySstFileChecksum(const Options& options,
|
||||
std::unique_ptr<RandomAccessFileReader> file_reader(
|
||||
new RandomAccessFileReader(
|
||||
std::move(file), file_path, ioptions.clock, nullptr /* io_tracer */,
|
||||
nullptr /* stats */, 0 /* hist_type */, nullptr /* file_read_hist */,
|
||||
ioptions.rate_limiter.get()));
|
||||
ioptions.stats /* stats */,
|
||||
Histograms::SST_READ_MICROS /* hist_type */,
|
||||
nullptr /* file_read_hist */, ioptions.rate_limiter.get()));
|
||||
const bool kImmortal = true;
|
||||
auto reader_options = TableReaderOptions(
|
||||
ioptions, options.prefix_extractor, env_options, internal_comparator,
|
||||
false /* skip_filters */, !kImmortal, false /* force_direct_prefetch */,
|
||||
-1 /* level */);
|
||||
options.block_protection_bytes_per_key, false /* skip_filters */,
|
||||
!kImmortal, false /* force_direct_prefetch */, -1 /* level */);
|
||||
reader_options.largest_seqno = largest_seqno;
|
||||
s = ioptions.table_factory->NewTableReader(
|
||||
reader_options, std::move(file_reader), file_size, &table_reader,
|
||||
@@ -76,4 +79,3 @@ Status VerifySstFileChecksum(const Options& options,
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
|
||||
@@ -762,9 +762,11 @@ TEST_F(CorruptionTest, RangeDeletionCorrupted) {
|
||||
fs->GetFileSize(filename, file_opts.io_options, &file_size, nullptr));
|
||||
|
||||
BlockHandle range_del_handle;
|
||||
ASSERT_OK(FindMetaBlockInFile(
|
||||
file_reader.get(), file_size, kBlockBasedTableMagicNumber,
|
||||
ImmutableOptions(options_), kRangeDelBlockName, &range_del_handle));
|
||||
const ReadOptions read_options;
|
||||
ASSERT_OK(FindMetaBlockInFile(file_reader.get(), file_size,
|
||||
kBlockBasedTableMagicNumber,
|
||||
ImmutableOptions(options_), read_options,
|
||||
kRangeDelBlockName, &range_del_handle));
|
||||
|
||||
ASSERT_OK(TryReopen());
|
||||
ASSERT_OK(test::CorruptFile(env_.get(), filename,
|
||||
@@ -1666,4 +1668,3 @@ int main(int argc, char** argv) {
|
||||
RegisterCustomObjects(argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
|
||||
|
||||
+150
-4
@@ -30,6 +30,9 @@
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
static bool enable_io_uring = true;
|
||||
extern "C" bool RocksDbIOUringEnable() { return enable_io_uring; }
|
||||
|
||||
class DBBasicTest : public DBTestBase {
|
||||
public:
|
||||
DBBasicTest() : DBTestBase("db_basic_test", /*env_do_fsync=*/false) {}
|
||||
@@ -2173,6 +2176,7 @@ class DBMultiGetAsyncIOTest : public DBBasicTest,
|
||||
options_.disable_auto_compactions = true;
|
||||
options_.statistics = statistics_;
|
||||
options_.table_factory.reset(NewBlockBasedTableFactory(bbto));
|
||||
options_.env = Env::Default();
|
||||
Reopen(options_);
|
||||
int num_keys = 0;
|
||||
|
||||
@@ -2239,6 +2243,20 @@ class DBMultiGetAsyncIOTest : public DBBasicTest,
|
||||
const std::shared_ptr<Statistics>& statistics() { return statistics_; }
|
||||
|
||||
protected:
|
||||
void PrepareDBForTest() {
|
||||
#ifdef ROCKSDB_IOURING_PRESENT
|
||||
Reopen(options_);
|
||||
#else // ROCKSDB_IOURING_PRESENT
|
||||
// Warm up the block cache so we don't need to use the IO uring
|
||||
Iterator* iter = dbfull()->NewIterator(ReadOptions());
|
||||
for (iter->SeekToFirst(); iter->Valid() && iter->status().ok();
|
||||
iter->Next())
|
||||
;
|
||||
EXPECT_OK(iter->status());
|
||||
delete iter;
|
||||
#endif // ROCKSDB_IOURING_PRESENT
|
||||
}
|
||||
|
||||
void ReopenDB() { Reopen(options_); }
|
||||
|
||||
private:
|
||||
@@ -2253,6 +2271,8 @@ TEST_P(DBMultiGetAsyncIOTest, GetFromL0) {
|
||||
std::vector<PinnableSlice> values(key_strs.size());
|
||||
std::vector<Status> statuses(key_strs.size());
|
||||
|
||||
PrepareDBForTest();
|
||||
|
||||
ReadOptions ro;
|
||||
ro.async_io = true;
|
||||
ro.optimize_multiget_for_io = GetParam();
|
||||
@@ -2271,6 +2291,7 @@ TEST_P(DBMultiGetAsyncIOTest, GetFromL0) {
|
||||
statistics()->histogramData(MULTIGET_IO_BATCH_SIZE, &multiget_io_batch_size);
|
||||
|
||||
// With async IO, lookups will happen in parallel for each key
|
||||
#ifdef ROCKSDB_IOURING_PRESENT
|
||||
if (GetParam()) {
|
||||
ASSERT_EQ(multiget_io_batch_size.count, 1);
|
||||
ASSERT_EQ(multiget_io_batch_size.max, 3);
|
||||
@@ -2280,6 +2301,9 @@ TEST_P(DBMultiGetAsyncIOTest, GetFromL0) {
|
||||
// L0 file
|
||||
ASSERT_EQ(multiget_io_batch_size.count, 3);
|
||||
}
|
||||
#else // ROCKSDB_IOURING_PRESENT
|
||||
ASSERT_EQ(statistics()->getTickerCount(MULTIGET_COROUTINE_COUNT), 0);
|
||||
#endif // ROCKSDB_IOURING_PRESENT
|
||||
}
|
||||
|
||||
TEST_P(DBMultiGetAsyncIOTest, GetFromL1) {
|
||||
@@ -2297,6 +2321,8 @@ TEST_P(DBMultiGetAsyncIOTest, GetFromL1) {
|
||||
values.resize(keys.size());
|
||||
statuses.resize(keys.size());
|
||||
|
||||
PrepareDBForTest();
|
||||
|
||||
ReadOptions ro;
|
||||
ro.async_io = true;
|
||||
ro.optimize_multiget_for_io = GetParam();
|
||||
@@ -2314,12 +2340,17 @@ TEST_P(DBMultiGetAsyncIOTest, GetFromL1) {
|
||||
|
||||
statistics()->histogramData(MULTIGET_IO_BATCH_SIZE, &multiget_io_batch_size);
|
||||
|
||||
#ifdef ROCKSDB_IOURING_PRESENT
|
||||
// A batch of 3 async IOs is expected, one for each overlapping file in L1
|
||||
ASSERT_EQ(multiget_io_batch_size.count, 1);
|
||||
ASSERT_EQ(multiget_io_batch_size.max, 3);
|
||||
ASSERT_EQ(statistics()->getTickerCount(MULTIGET_COROUTINE_COUNT), 3);
|
||||
#else // ROCKSDB_IOURING_PRESENT
|
||||
ASSERT_EQ(statistics()->getTickerCount(MULTIGET_COROUTINE_COUNT), 0);
|
||||
#endif // ROCKSDB_IOURING_PRESENT
|
||||
}
|
||||
|
||||
#ifdef ROCKSDB_IOURING_PRESENT
|
||||
TEST_P(DBMultiGetAsyncIOTest, GetFromL1Error) {
|
||||
std::vector<std::string> key_strs;
|
||||
std::vector<Slice> keys;
|
||||
@@ -2335,9 +2366,9 @@ TEST_P(DBMultiGetAsyncIOTest, GetFromL1Error) {
|
||||
values.resize(keys.size());
|
||||
statuses.resize(keys.size());
|
||||
|
||||
int count = 0;
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"TableCache::GetTableReader:BeforeOpenFile", [&](void* status) {
|
||||
static int count = 0;
|
||||
count++;
|
||||
// Fail the last table reader open, which is the 6th SST file
|
||||
// since 3 overlapping L0 files + 3 L1 files containing the keys
|
||||
@@ -2360,7 +2391,7 @@ TEST_P(DBMultiGetAsyncIOTest, GetFromL1Error) {
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
ReopenDB();
|
||||
PrepareDBForTest();
|
||||
|
||||
ReadOptions ro;
|
||||
ro.async_io = true;
|
||||
@@ -2382,6 +2413,7 @@ TEST_P(DBMultiGetAsyncIOTest, GetFromL1Error) {
|
||||
ASSERT_EQ(multiget_io_batch_size.max, 2);
|
||||
ASSERT_EQ(statistics()->getTickerCount(MULTIGET_COROUTINE_COUNT), 2);
|
||||
}
|
||||
#endif // ROCKSDB_IOURING_PRESENT
|
||||
|
||||
TEST_P(DBMultiGetAsyncIOTest, LastKeyInFile) {
|
||||
std::vector<std::string> key_strs;
|
||||
@@ -2399,6 +2431,8 @@ TEST_P(DBMultiGetAsyncIOTest, LastKeyInFile) {
|
||||
values.resize(keys.size());
|
||||
statuses.resize(keys.size());
|
||||
|
||||
PrepareDBForTest();
|
||||
|
||||
ReadOptions ro;
|
||||
ro.async_io = true;
|
||||
ro.optimize_multiget_for_io = GetParam();
|
||||
@@ -2412,6 +2446,7 @@ TEST_P(DBMultiGetAsyncIOTest, LastKeyInFile) {
|
||||
ASSERT_EQ(values[1], "val_l1_" + std::to_string(54));
|
||||
ASSERT_EQ(values[2], "val_l1_" + std::to_string(102));
|
||||
|
||||
#ifdef ROCKSDB_IOURING_PRESENT
|
||||
HistogramData multiget_io_batch_size;
|
||||
|
||||
statistics()->histogramData(MULTIGET_IO_BATCH_SIZE, &multiget_io_batch_size);
|
||||
@@ -2422,6 +2457,7 @@ TEST_P(DBMultiGetAsyncIOTest, LastKeyInFile) {
|
||||
// will lookup 2 files in parallel and issue 2 async reads
|
||||
ASSERT_EQ(multiget_io_batch_size.count, 2);
|
||||
ASSERT_EQ(multiget_io_batch_size.max, 2);
|
||||
#endif // ROCKSDB_IOURING_PRESENT
|
||||
}
|
||||
|
||||
TEST_P(DBMultiGetAsyncIOTest, GetFromL1AndL2) {
|
||||
@@ -2440,6 +2476,8 @@ TEST_P(DBMultiGetAsyncIOTest, GetFromL1AndL2) {
|
||||
values.resize(keys.size());
|
||||
statuses.resize(keys.size());
|
||||
|
||||
PrepareDBForTest();
|
||||
|
||||
ReadOptions ro;
|
||||
ro.async_io = true;
|
||||
ro.optimize_multiget_for_io = GetParam();
|
||||
@@ -2453,6 +2491,7 @@ TEST_P(DBMultiGetAsyncIOTest, GetFromL1AndL2) {
|
||||
ASSERT_EQ(values[1], "val_l2_" + std::to_string(56));
|
||||
ASSERT_EQ(values[2], "val_l1_" + std::to_string(102));
|
||||
|
||||
#ifdef ROCKSDB_IOURING_PRESENT
|
||||
HistogramData multiget_io_batch_size;
|
||||
|
||||
statistics()->histogramData(MULTIGET_IO_BATCH_SIZE, &multiget_io_batch_size);
|
||||
@@ -2462,6 +2501,7 @@ TEST_P(DBMultiGetAsyncIOTest, GetFromL1AndL2) {
|
||||
// Otherwise, the L2 lookup will happen after L1.
|
||||
ASSERT_EQ(multiget_io_batch_size.count, GetParam() ? 1 : 2);
|
||||
ASSERT_EQ(multiget_io_batch_size.max, GetParam() ? 3 : 2);
|
||||
#endif // ROCKSDB_IOURING_PRESENT
|
||||
}
|
||||
|
||||
TEST_P(DBMultiGetAsyncIOTest, GetFromL2WithRangeOverlapL0L1) {
|
||||
@@ -2478,6 +2518,8 @@ TEST_P(DBMultiGetAsyncIOTest, GetFromL2WithRangeOverlapL0L1) {
|
||||
values.resize(keys.size());
|
||||
statuses.resize(keys.size());
|
||||
|
||||
PrepareDBForTest();
|
||||
|
||||
ReadOptions ro;
|
||||
ro.async_io = true;
|
||||
ro.optimize_multiget_for_io = GetParam();
|
||||
@@ -2489,10 +2531,15 @@ TEST_P(DBMultiGetAsyncIOTest, GetFromL2WithRangeOverlapL0L1) {
|
||||
ASSERT_EQ(values[0], "val_l2_" + std::to_string(19));
|
||||
ASSERT_EQ(values[1], "val_l2_" + std::to_string(26));
|
||||
|
||||
#ifdef ROCKSDB_IOURING_PRESENT
|
||||
// Bloom filters in L0/L1 will avoid the coroutine calls in those levels
|
||||
ASSERT_EQ(statistics()->getTickerCount(MULTIGET_COROUTINE_COUNT), 2);
|
||||
#else // ROCKSDB_IOURING_PRESENT
|
||||
ASSERT_EQ(statistics()->getTickerCount(MULTIGET_COROUTINE_COUNT), 0);
|
||||
#endif // ROCKSDB_IOURING_PRESENT
|
||||
}
|
||||
|
||||
#ifdef ROCKSDB_IOURING_PRESENT
|
||||
TEST_P(DBMultiGetAsyncIOTest, GetFromL2WithRangeDelInL1) {
|
||||
std::vector<std::string> key_strs;
|
||||
std::vector<Slice> keys;
|
||||
@@ -2507,6 +2554,8 @@ TEST_P(DBMultiGetAsyncIOTest, GetFromL2WithRangeDelInL1) {
|
||||
values.resize(keys.size());
|
||||
statuses.resize(keys.size());
|
||||
|
||||
PrepareDBForTest();
|
||||
|
||||
ReadOptions ro;
|
||||
ro.async_io = true;
|
||||
ro.optimize_multiget_for_io = GetParam();
|
||||
@@ -2536,6 +2585,8 @@ TEST_P(DBMultiGetAsyncIOTest, GetFromL1AndL2WithRangeDelInL1) {
|
||||
values.resize(keys.size());
|
||||
statuses.resize(keys.size());
|
||||
|
||||
PrepareDBForTest();
|
||||
|
||||
ReadOptions ro;
|
||||
ro.async_io = true;
|
||||
ro.optimize_multiget_for_io = GetParam();
|
||||
@@ -2550,6 +2601,44 @@ TEST_P(DBMultiGetAsyncIOTest, GetFromL1AndL2WithRangeDelInL1) {
|
||||
// Bloom filters in L0/L1 will avoid the coroutine calls in those levels
|
||||
ASSERT_EQ(statistics()->getTickerCount(MULTIGET_COROUTINE_COUNT), 3);
|
||||
}
|
||||
#endif // ROCKSDB_IOURING_PRESENT
|
||||
|
||||
TEST_P(DBMultiGetAsyncIOTest, GetNoIOUring) {
|
||||
std::vector<std::string> key_strs;
|
||||
std::vector<Slice> keys;
|
||||
std::vector<PinnableSlice> values;
|
||||
std::vector<Status> statuses;
|
||||
|
||||
key_strs.push_back(Key(33));
|
||||
key_strs.push_back(Key(54));
|
||||
key_strs.push_back(Key(102));
|
||||
keys.push_back(key_strs[0]);
|
||||
keys.push_back(key_strs[1]);
|
||||
keys.push_back(key_strs[2]);
|
||||
values.resize(keys.size());
|
||||
statuses.resize(keys.size());
|
||||
|
||||
enable_io_uring = false;
|
||||
ReopenDB();
|
||||
|
||||
ReadOptions ro;
|
||||
ro.async_io = true;
|
||||
ro.optimize_multiget_for_io = GetParam();
|
||||
dbfull()->MultiGet(ro, dbfull()->DefaultColumnFamily(), keys.size(),
|
||||
keys.data(), values.data(), statuses.data());
|
||||
ASSERT_EQ(values.size(), 3);
|
||||
ASSERT_EQ(statuses[0], Status::OK());
|
||||
ASSERT_EQ(statuses[1], Status::OK());
|
||||
ASSERT_EQ(statuses[2], Status::OK());
|
||||
|
||||
HistogramData async_read_bytes;
|
||||
|
||||
statistics()->histogramData(ASYNC_READ_BYTES, &async_read_bytes);
|
||||
|
||||
// A batch of 3 async IOs is expected, one for each overlapping file in L1
|
||||
ASSERT_EQ(async_read_bytes.count, 0);
|
||||
ASSERT_EQ(statistics()->getTickerCount(MULTIGET_COROUTINE_COUNT), 0);
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(DBMultiGetAsyncIOTest, DBMultiGetAsyncIOTest,
|
||||
testing::Bool());
|
||||
@@ -3540,11 +3629,11 @@ class DBBasicTestMultiGet : public DBTestBase {
|
||||
|
||||
Handle* Lookup(const Slice& key, const CacheItemHelper* helper,
|
||||
CreateContext* create_context,
|
||||
Priority priority = Priority::LOW, bool wait = true,
|
||||
Priority priority = Priority::LOW,
|
||||
Statistics* stats = nullptr) override {
|
||||
num_lookups_++;
|
||||
Handle* handle =
|
||||
target_->Lookup(key, helper, create_context, priority, wait, stats);
|
||||
target_->Lookup(key, helper, create_context, priority, stats);
|
||||
if (handle != nullptr) {
|
||||
num_found_++;
|
||||
}
|
||||
@@ -4405,6 +4494,63 @@ TEST_F(DBBasicTest, VerifyFileChecksums) {
|
||||
ASSERT_TRUE(db_->VerifyFileChecksums(ReadOptions()).IsInvalidArgument());
|
||||
}
|
||||
|
||||
TEST_F(DBBasicTest, VerifyFileChecksumsReadahead) {
|
||||
Options options = GetDefaultOptions();
|
||||
options.create_if_missing = true;
|
||||
options.env = env_;
|
||||
options.file_checksum_gen_factory = GetFileChecksumGenCrc32cFactory();
|
||||
DestroyAndReopen(options);
|
||||
|
||||
Random rnd(301);
|
||||
int alignment = 256 * 1024;
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
ASSERT_OK(Put("key" + std::to_string(i), rnd.RandomString(alignment)));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
std::vector<std::string> filenames;
|
||||
int sst_cnt = 0;
|
||||
std::string sst_name;
|
||||
uint64_t sst_size;
|
||||
uint64_t number;
|
||||
FileType type;
|
||||
ASSERT_OK(env_->GetChildren(dbname_, &filenames));
|
||||
for (auto name : filenames) {
|
||||
if (ParseFileName(name, &number, &type)) {
|
||||
if (type == kTableFile) {
|
||||
sst_cnt++;
|
||||
sst_name = name;
|
||||
}
|
||||
}
|
||||
}
|
||||
ASSERT_EQ(sst_cnt, 1);
|
||||
ASSERT_OK(env_->GetFileSize(dbname_ + '/' + sst_name, &sst_size));
|
||||
|
||||
bool last_read = false;
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"GenerateOneFileChecksum::Chunk:0", [&](void* /*arg*/) {
|
||||
if (env_->random_read_bytes_counter_.load() == sst_size) {
|
||||
EXPECT_FALSE(last_read);
|
||||
last_read = true;
|
||||
} else {
|
||||
ASSERT_EQ(env_->random_read_bytes_counter_.load() & (alignment - 1),
|
||||
0);
|
||||
}
|
||||
});
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
|
||||
env_->count_random_reads_ = true;
|
||||
env_->random_read_bytes_counter_ = 0;
|
||||
env_->random_read_counter_.Reset();
|
||||
|
||||
ReadOptions ro;
|
||||
ro.readahead_size = alignment;
|
||||
ASSERT_OK(db_->VerifyFileChecksums(ro));
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
|
||||
ASSERT_TRUE(last_read);
|
||||
ASSERT_EQ(env_->random_read_counter_.Read(),
|
||||
(sst_size + alignment - 1) / (alignment));
|
||||
}
|
||||
|
||||
// TODO: re-enable after we provide finer-grained control for WAL tracking to
|
||||
// meet the needs of different use cases, durability levels and recovery modes.
|
||||
TEST_F(DBBasicTest, DISABLED_ManualWalSync) {
|
||||
|
||||
@@ -287,9 +287,11 @@ class PersistentCacheFromCache : public PersistentCache {
|
||||
};
|
||||
|
||||
class ReadOnlyCacheWrapper : public CacheWrapper {
|
||||
public:
|
||||
using CacheWrapper::CacheWrapper;
|
||||
|
||||
using Cache::Insert;
|
||||
const char* Name() const override { return "ReadOnlyCacheWrapper"; }
|
||||
|
||||
Status Insert(const Slice& /*key*/, Cache::ObjectPtr /*value*/,
|
||||
const CacheItemHelper* /*helper*/, size_t /*charge*/,
|
||||
Handle** /*handle*/, Priority /*priority*/) override {
|
||||
@@ -618,9 +620,9 @@ class MockCache : public LRUCache {
|
||||
static uint32_t low_pri_insert_count;
|
||||
|
||||
MockCache()
|
||||
: LRUCache((size_t)1 << 25 /*capacity*/, 0 /*num_shard_bits*/,
|
||||
false /*strict_capacity_limit*/, 0.0 /*high_pri_pool_ratio*/,
|
||||
0.0 /*low_pri_pool_ratio*/) {}
|
||||
: LRUCache(LRUCacheOptions(
|
||||
size_t{1} << 25 /*capacity*/, 0 /*num_shard_bits*/,
|
||||
false /*strict_capacity_limit*/, 0.0 /*high_pri_pool_ratio*/)) {}
|
||||
|
||||
using ShardedCache::Insert;
|
||||
|
||||
@@ -711,10 +713,11 @@ class LookupLiarCache : public CacheWrapper {
|
||||
explicit LookupLiarCache(std::shared_ptr<Cache> target)
|
||||
: CacheWrapper(std::move(target)) {}
|
||||
|
||||
using Cache::Lookup;
|
||||
const char* Name() const override { return "LookupLiarCache"; }
|
||||
|
||||
Handle* Lookup(const Slice& key, const CacheItemHelper* helper = nullptr,
|
||||
CreateContext* create_context = nullptr,
|
||||
Priority priority = Priority::LOW, bool wait = true,
|
||||
Priority priority = Priority::LOW,
|
||||
Statistics* stats = nullptr) override {
|
||||
if (nth_lookup_not_found_ == 1) {
|
||||
nth_lookup_not_found_ = 0;
|
||||
@@ -723,8 +726,7 @@ class LookupLiarCache : public CacheWrapper {
|
||||
if (nth_lookup_not_found_ > 1) {
|
||||
--nth_lookup_not_found_;
|
||||
}
|
||||
return CacheWrapper::Lookup(key, helper, create_context, priority, wait,
|
||||
stats);
|
||||
return CacheWrapper::Lookup(key, helper, create_context, priority, stats);
|
||||
}
|
||||
|
||||
// 1 == next lookup, 2 == after next, etc.
|
||||
@@ -977,12 +979,14 @@ TEST_F(DBBlockCacheTest, CacheEntryRoleStats) {
|
||||
const size_t capacity = size_t{1} << 25;
|
||||
int iterations_tested = 0;
|
||||
for (bool partition : {false, true}) {
|
||||
SCOPED_TRACE("Partition? " + std::to_string(partition));
|
||||
for (std::shared_ptr<Cache> cache :
|
||||
{NewLRUCache(capacity),
|
||||
HyperClockCacheOptions(
|
||||
capacity,
|
||||
BlockBasedTableOptions().block_size /*estimated_value_size*/)
|
||||
.MakeSharedCache()}) {
|
||||
SCOPED_TRACE(std::string("Cache: ") + cache->Name());
|
||||
++iterations_tested;
|
||||
|
||||
Options options = CurrentOptions();
|
||||
@@ -1276,6 +1280,7 @@ TEST_F(DBBlockCacheTest, HyperClockCacheReportProblems) {
|
||||
HyperClockCacheOptions hcc_opts{capacity, value_size_est};
|
||||
hcc_opts.num_shard_bits = 2; // 4 shards
|
||||
hcc_opts.metadata_charge_policy = kDontChargeCacheMetadata;
|
||||
hcc_opts.hash_seed = 0; // deterministic hashing
|
||||
std::shared_ptr<Cache> cache = hcc_opts.MakeSharedCache();
|
||||
std::shared_ptr<CountingLogger> logger = std::make_shared<CountingLogger>();
|
||||
|
||||
|
||||
+260
-220
@@ -43,6 +43,16 @@ const std::string kStandard128Ribbon =
|
||||
test::Standard128RibbonFilterPolicy::kClassName();
|
||||
const std::string kAutoBloom = BloomFilterPolicy::kClassName();
|
||||
const std::string kAutoRibbon = RibbonFilterPolicy::kClassName();
|
||||
|
||||
template <typename T>
|
||||
T Pop(T& var) {
|
||||
auto rv = var;
|
||||
var = 0;
|
||||
return rv;
|
||||
}
|
||||
PerfContextByLevel& GetLevelPerfContext(uint32_t level) {
|
||||
return (*(get_perf_context()->level_to_perf_context))[level];
|
||||
}
|
||||
} // anonymous namespace
|
||||
|
||||
// DB tests related to bloom filter.
|
||||
@@ -209,57 +219,43 @@ TEST_F(DBBloomFilterTest, GetFilterByPrefixBloomCustomPrefixExtractor) {
|
||||
ASSERT_OK(dbfull()->Flush(fo));
|
||||
|
||||
ASSERT_EQ("foo", Get("barbarbar"));
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 0);
|
||||
ASSERT_EQ(
|
||||
0,
|
||||
(*(get_perf_context()->level_to_perf_context))[0].bloom_filter_useful);
|
||||
EXPECT_EQ(PopTicker(options, BLOOM_FILTER_PREFIX_USEFUL), 0);
|
||||
EXPECT_EQ(Pop(GetLevelPerfContext(0).bloom_filter_useful), 0);
|
||||
|
||||
ASSERT_EQ("foo2", Get("barbarbar2"));
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 0);
|
||||
ASSERT_EQ(
|
||||
0,
|
||||
(*(get_perf_context()->level_to_perf_context))[0].bloom_filter_useful);
|
||||
EXPECT_EQ(PopTicker(options, BLOOM_FILTER_PREFIX_USEFUL), 0);
|
||||
EXPECT_EQ(Pop(GetLevelPerfContext(0).bloom_filter_useful), 0);
|
||||
|
||||
ASSERT_EQ("NOT_FOUND", Get("barbarbar3"));
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 0);
|
||||
ASSERT_EQ(
|
||||
0,
|
||||
(*(get_perf_context()->level_to_perf_context))[0].bloom_filter_useful);
|
||||
EXPECT_EQ(PopTicker(options, BLOOM_FILTER_PREFIX_USEFUL), 0);
|
||||
EXPECT_EQ(Pop(GetLevelPerfContext(0).bloom_filter_useful), 0);
|
||||
|
||||
ASSERT_EQ("NOT_FOUND", Get("barfoofoo"));
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 1);
|
||||
ASSERT_EQ(
|
||||
1,
|
||||
(*(get_perf_context()->level_to_perf_context))[0].bloom_filter_useful);
|
||||
EXPECT_EQ(PopTicker(options, BLOOM_FILTER_PREFIX_USEFUL), 1);
|
||||
EXPECT_EQ(Pop(GetLevelPerfContext(0).bloom_filter_useful), 1);
|
||||
|
||||
ASSERT_EQ("NOT_FOUND", Get("foobarbar"));
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 2);
|
||||
ASSERT_EQ(
|
||||
2,
|
||||
(*(get_perf_context()->level_to_perf_context))[0].bloom_filter_useful);
|
||||
EXPECT_EQ(PopTicker(options, BLOOM_FILTER_PREFIX_USEFUL), 1);
|
||||
EXPECT_EQ(Pop(GetLevelPerfContext(0).bloom_filter_useful), 1);
|
||||
|
||||
ro.total_order_seek = true;
|
||||
// NOTE: total_order_seek no longer affects Get()
|
||||
ASSERT_EQ("NOT_FOUND", Get("foobarbar"));
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 3);
|
||||
ASSERT_EQ(
|
||||
3,
|
||||
(*(get_perf_context()->level_to_perf_context))[0].bloom_filter_useful);
|
||||
EXPECT_EQ(PopTicker(options, BLOOM_FILTER_PREFIX_USEFUL), 1);
|
||||
EXPECT_EQ(Pop(GetLevelPerfContext(0).bloom_filter_useful), 1);
|
||||
|
||||
// No bloom on extractor changed
|
||||
ASSERT_OK(db_->SetOptions({{"prefix_extractor", "capped:10"}}));
|
||||
ASSERT_EQ("NOT_FOUND", Get("foobarbar"));
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 3);
|
||||
ASSERT_EQ(
|
||||
3,
|
||||
(*(get_perf_context()->level_to_perf_context))[0].bloom_filter_useful);
|
||||
EXPECT_EQ(PopTicker(options, BLOOM_FILTER_PREFIX_USEFUL), 0);
|
||||
EXPECT_EQ(Pop(GetLevelPerfContext(0).bloom_filter_useful), 0);
|
||||
|
||||
// No bloom on extractor changed, after re-open
|
||||
options.prefix_extractor.reset(NewCappedPrefixTransform(10));
|
||||
Reopen(options);
|
||||
ASSERT_EQ("NOT_FOUND", Get("foobarbar"));
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 3);
|
||||
ASSERT_EQ(
|
||||
3,
|
||||
(*(get_perf_context()->level_to_perf_context))[0].bloom_filter_useful);
|
||||
EXPECT_EQ(PopTicker(options, BLOOM_FILTER_PREFIX_USEFUL), 0);
|
||||
EXPECT_EQ(Pop(GetLevelPerfContext(0).bloom_filter_useful), 0);
|
||||
|
||||
get_perf_context()->Reset();
|
||||
}
|
||||
@@ -294,33 +290,32 @@ TEST_F(DBBloomFilterTest, GetFilterByPrefixBloom) {
|
||||
ASSERT_OK(dbfull()->Flush(fo));
|
||||
|
||||
ASSERT_EQ("foo", Get("barbarbar"));
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 0);
|
||||
EXPECT_EQ(PopTicker(options, BLOOM_FILTER_PREFIX_USEFUL), 0);
|
||||
ASSERT_EQ("foo2", Get("barbarbar2"));
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 0);
|
||||
EXPECT_EQ(PopTicker(options, BLOOM_FILTER_PREFIX_USEFUL), 0);
|
||||
ASSERT_EQ("NOT_FOUND", Get("barbarbar3"));
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 0);
|
||||
EXPECT_EQ(PopTicker(options, BLOOM_FILTER_PREFIX_USEFUL), 0);
|
||||
EXPECT_EQ(Pop(GetLevelPerfContext(0).bloom_filter_useful), 0);
|
||||
|
||||
ASSERT_EQ("NOT_FOUND", Get("barfoofoo"));
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 1);
|
||||
EXPECT_EQ(PopTicker(options, BLOOM_FILTER_PREFIX_USEFUL), 1);
|
||||
EXPECT_EQ(Pop(GetLevelPerfContext(0).bloom_filter_useful), 1);
|
||||
|
||||
ASSERT_EQ("NOT_FOUND", Get("foobarbar"));
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 2);
|
||||
EXPECT_EQ(PopTicker(options, BLOOM_FILTER_PREFIX_USEFUL), 1);
|
||||
EXPECT_EQ(Pop(GetLevelPerfContext(0).bloom_filter_useful), 1);
|
||||
|
||||
ro.total_order_seek = true;
|
||||
// NOTE: total_order_seek no longer affects Get()
|
||||
ASSERT_EQ("NOT_FOUND", Get("foobarbar"));
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 3);
|
||||
ASSERT_EQ(
|
||||
3,
|
||||
(*(get_perf_context()->level_to_perf_context))[0].bloom_filter_useful);
|
||||
EXPECT_EQ(PopTicker(options, BLOOM_FILTER_PREFIX_USEFUL), 1);
|
||||
EXPECT_EQ(Pop(GetLevelPerfContext(0).bloom_filter_useful), 1);
|
||||
|
||||
// No bloom on extractor changed
|
||||
ASSERT_OK(db_->SetOptions({{"prefix_extractor", "capped:10"}}));
|
||||
ASSERT_EQ("NOT_FOUND", Get("foobarbar"));
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 3);
|
||||
ASSERT_EQ(
|
||||
3,
|
||||
(*(get_perf_context()->level_to_perf_context))[0].bloom_filter_useful);
|
||||
EXPECT_EQ(PopTicker(options, BLOOM_FILTER_PREFIX_USEFUL), 0);
|
||||
EXPECT_EQ(Pop(GetLevelPerfContext(0).bloom_filter_useful), 0);
|
||||
|
||||
get_perf_context()->Reset();
|
||||
}
|
||||
@@ -357,12 +352,17 @@ TEST_F(DBBloomFilterTest, WholeKeyFilterProp) {
|
||||
ASSERT_OK(dbfull()->Flush(fo));
|
||||
|
||||
Reopen(options);
|
||||
EXPECT_EQ(PopTicker(options, BLOOM_FILTER_PREFIX_USEFUL), 0);
|
||||
EXPECT_EQ(PopTicker(options, BLOOM_FILTER_USEFUL), 0);
|
||||
ASSERT_EQ("NOT_FOUND", Get("foo"));
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 0);
|
||||
EXPECT_EQ(PopTicker(options, BLOOM_FILTER_PREFIX_USEFUL), 0);
|
||||
EXPECT_EQ(PopTicker(options, BLOOM_FILTER_USEFUL), 0);
|
||||
ASSERT_EQ("NOT_FOUND", Get("bar"));
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 1);
|
||||
EXPECT_EQ(PopTicker(options, BLOOM_FILTER_PREFIX_USEFUL), 1);
|
||||
EXPECT_EQ(PopTicker(options, BLOOM_FILTER_USEFUL), 0);
|
||||
ASSERT_EQ("foo", Get("foobar"));
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 1);
|
||||
EXPECT_EQ(PopTicker(options, BLOOM_FILTER_PREFIX_USEFUL), 0);
|
||||
EXPECT_EQ(PopTicker(options, BLOOM_FILTER_USEFUL), 0);
|
||||
|
||||
// Reopen with whole key filtering enabled and prefix extractor
|
||||
// NULL. Bloom filter should be off for both of whole key and
|
||||
@@ -372,13 +372,17 @@ TEST_F(DBBloomFilterTest, WholeKeyFilterProp) {
|
||||
options.prefix_extractor.reset();
|
||||
Reopen(options);
|
||||
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 1);
|
||||
EXPECT_EQ(PopTicker(options, BLOOM_FILTER_PREFIX_USEFUL), 0);
|
||||
EXPECT_EQ(PopTicker(options, BLOOM_FILTER_USEFUL), 0);
|
||||
ASSERT_EQ("NOT_FOUND", Get("foo"));
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 1);
|
||||
EXPECT_EQ(PopTicker(options, BLOOM_FILTER_PREFIX_USEFUL), 0);
|
||||
EXPECT_EQ(PopTicker(options, BLOOM_FILTER_USEFUL), 0);
|
||||
ASSERT_EQ("NOT_FOUND", Get("bar"));
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 1);
|
||||
EXPECT_EQ(PopTicker(options, BLOOM_FILTER_PREFIX_USEFUL), 0);
|
||||
EXPECT_EQ(PopTicker(options, BLOOM_FILTER_USEFUL), 0);
|
||||
ASSERT_EQ("foo", Get("foobar"));
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 1);
|
||||
EXPECT_EQ(PopTicker(options, BLOOM_FILTER_PREFIX_USEFUL), 0);
|
||||
EXPECT_EQ(PopTicker(options, BLOOM_FILTER_USEFUL), 0);
|
||||
// Write DB with only full key filtering.
|
||||
ASSERT_OK(dbfull()->Put(wo, "foobar", "foo"));
|
||||
// Needs insert some keys to make sure files are not filtered out by key
|
||||
@@ -394,13 +398,17 @@ TEST_F(DBBloomFilterTest, WholeKeyFilterProp) {
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(bbto));
|
||||
Reopen(options);
|
||||
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 1);
|
||||
EXPECT_EQ(PopTicker(options, BLOOM_FILTER_PREFIX_USEFUL), 0);
|
||||
EXPECT_EQ(PopTicker(options, BLOOM_FILTER_USEFUL), 0);
|
||||
ASSERT_EQ("NOT_FOUND", Get("foo"));
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 1);
|
||||
EXPECT_EQ(PopTicker(options, BLOOM_FILTER_PREFIX_USEFUL), 0);
|
||||
EXPECT_EQ(PopTicker(options, BLOOM_FILTER_USEFUL), 0);
|
||||
ASSERT_EQ("NOT_FOUND", Get("bar"));
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 1);
|
||||
EXPECT_EQ(PopTicker(options, BLOOM_FILTER_PREFIX_USEFUL), 0);
|
||||
EXPECT_EQ(PopTicker(options, BLOOM_FILTER_USEFUL), 0);
|
||||
ASSERT_EQ("foo", Get("foobar"));
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 1);
|
||||
EXPECT_EQ(PopTicker(options, BLOOM_FILTER_PREFIX_USEFUL), 0);
|
||||
EXPECT_EQ(PopTicker(options, BLOOM_FILTER_USEFUL), 0);
|
||||
|
||||
// Try to create a DB with mixed files:
|
||||
ASSERT_OK(dbfull()->Put(wo, "foobar", "foo"));
|
||||
@@ -424,61 +432,81 @@ TEST_F(DBBloomFilterTest, WholeKeyFilterProp) {
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
// Now we have two files:
|
||||
// File 1: An older file with prefix bloom.
|
||||
// File 1: An older file with prefix bloom (disabled)
|
||||
// File 2: A newer file with whole bloom filter.
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 1);
|
||||
EXPECT_EQ(PopTicker(options, BLOOM_FILTER_PREFIX_USEFUL), 0);
|
||||
EXPECT_EQ(PopTicker(options, BLOOM_FILTER_USEFUL), 0);
|
||||
ASSERT_EQ("NOT_FOUND", Get("foo"));
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 2);
|
||||
EXPECT_EQ(PopTicker(options, BLOOM_FILTER_PREFIX_USEFUL), 0);
|
||||
EXPECT_EQ(PopTicker(options, BLOOM_FILTER_USEFUL), 1);
|
||||
ASSERT_EQ("NOT_FOUND", Get("bar"));
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 3);
|
||||
EXPECT_EQ(PopTicker(options, BLOOM_FILTER_PREFIX_USEFUL), 0);
|
||||
EXPECT_EQ(PopTicker(options, BLOOM_FILTER_USEFUL), 1);
|
||||
ASSERT_EQ("foo", Get("foobar"));
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 4);
|
||||
EXPECT_EQ(PopTicker(options, BLOOM_FILTER_PREFIX_USEFUL), 0);
|
||||
EXPECT_EQ(PopTicker(options, BLOOM_FILTER_USEFUL), 1);
|
||||
ASSERT_EQ("bar", Get("barfoo"));
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 4);
|
||||
EXPECT_EQ(PopTicker(options, BLOOM_FILTER_PREFIX_USEFUL), 0);
|
||||
EXPECT_EQ(PopTicker(options, BLOOM_FILTER_USEFUL), 0);
|
||||
|
||||
// Reopen with the same setting: only whole key is used
|
||||
Reopen(options);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 4);
|
||||
EXPECT_EQ(PopTicker(options, BLOOM_FILTER_PREFIX_USEFUL), 0);
|
||||
EXPECT_EQ(PopTicker(options, BLOOM_FILTER_USEFUL), 0);
|
||||
ASSERT_EQ("NOT_FOUND", Get("foo"));
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 5);
|
||||
EXPECT_EQ(PopTicker(options, BLOOM_FILTER_PREFIX_USEFUL), 0);
|
||||
EXPECT_EQ(PopTicker(options, BLOOM_FILTER_USEFUL), 1);
|
||||
ASSERT_EQ("NOT_FOUND", Get("bar"));
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 6);
|
||||
EXPECT_EQ(PopTicker(options, BLOOM_FILTER_PREFIX_USEFUL), 0);
|
||||
EXPECT_EQ(PopTicker(options, BLOOM_FILTER_USEFUL), 1);
|
||||
ASSERT_EQ("foo", Get("foobar"));
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 7);
|
||||
EXPECT_EQ(PopTicker(options, BLOOM_FILTER_PREFIX_USEFUL), 0);
|
||||
EXPECT_EQ(PopTicker(options, BLOOM_FILTER_USEFUL), 1);
|
||||
ASSERT_EQ("bar", Get("barfoo"));
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 7);
|
||||
EXPECT_EQ(PopTicker(options, BLOOM_FILTER_PREFIX_USEFUL), 0);
|
||||
EXPECT_EQ(PopTicker(options, BLOOM_FILTER_USEFUL), 0);
|
||||
|
||||
// Restart with both filters are allowed
|
||||
options.prefix_extractor.reset(NewFixedPrefixTransform(3));
|
||||
bbto.whole_key_filtering = true;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(bbto));
|
||||
Reopen(options);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 7);
|
||||
EXPECT_EQ(PopTicker(options, BLOOM_FILTER_PREFIX_USEFUL), 0);
|
||||
EXPECT_EQ(PopTicker(options, BLOOM_FILTER_USEFUL), 0);
|
||||
// File 1 will has it filtered out.
|
||||
// File 2 will not, as prefix `foo` exists in the file.
|
||||
ASSERT_EQ("NOT_FOUND", Get("foo"));
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 8);
|
||||
EXPECT_EQ(PopTicker(options, BLOOM_FILTER_PREFIX_USEFUL), 0);
|
||||
EXPECT_EQ(PopTicker(options, BLOOM_FILTER_USEFUL), 1);
|
||||
ASSERT_EQ("NOT_FOUND", Get("bar"));
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 10);
|
||||
EXPECT_EQ(PopTicker(options, BLOOM_FILTER_PREFIX_USEFUL), 1);
|
||||
EXPECT_EQ(PopTicker(options, BLOOM_FILTER_USEFUL), 1);
|
||||
ASSERT_EQ("foo", Get("foobar"));
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 11);
|
||||
EXPECT_EQ(PopTicker(options, BLOOM_FILTER_PREFIX_USEFUL), 0);
|
||||
EXPECT_EQ(PopTicker(options, BLOOM_FILTER_USEFUL), 1);
|
||||
ASSERT_EQ("bar", Get("barfoo"));
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 11);
|
||||
EXPECT_EQ(PopTicker(options, BLOOM_FILTER_PREFIX_USEFUL), 0);
|
||||
EXPECT_EQ(PopTicker(options, BLOOM_FILTER_USEFUL), 0);
|
||||
|
||||
// Restart with only prefix bloom is allowed.
|
||||
options.prefix_extractor.reset(NewFixedPrefixTransform(3));
|
||||
bbto.whole_key_filtering = false;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(bbto));
|
||||
Reopen(options);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 11);
|
||||
EXPECT_EQ(PopTicker(options, BLOOM_FILTER_PREFIX_USEFUL), 0);
|
||||
EXPECT_EQ(PopTicker(options, BLOOM_FILTER_USEFUL), 0);
|
||||
ASSERT_EQ("NOT_FOUND", Get("foo"));
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 11);
|
||||
EXPECT_EQ(PopTicker(options, BLOOM_FILTER_PREFIX_USEFUL), 0);
|
||||
EXPECT_EQ(PopTicker(options, BLOOM_FILTER_USEFUL), 0);
|
||||
ASSERT_EQ("NOT_FOUND", Get("bar"));
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 12);
|
||||
EXPECT_EQ(PopTicker(options, BLOOM_FILTER_PREFIX_USEFUL), 1);
|
||||
EXPECT_EQ(PopTicker(options, BLOOM_FILTER_USEFUL), 0);
|
||||
ASSERT_EQ("foo", Get("foobar"));
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 12);
|
||||
EXPECT_EQ(PopTicker(options, BLOOM_FILTER_PREFIX_USEFUL), 0);
|
||||
EXPECT_EQ(PopTicker(options, BLOOM_FILTER_USEFUL), 0);
|
||||
ASSERT_EQ("bar", Get("barfoo"));
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 12);
|
||||
EXPECT_EQ(PopTicker(options, BLOOM_FILTER_PREFIX_USEFUL), 0);
|
||||
EXPECT_EQ(PopTicker(options, BLOOM_FILTER_USEFUL), 0);
|
||||
uint64_t bloom_filter_useful_all_levels = 0;
|
||||
for (auto& kv : (*(get_perf_context()->level_to_perf_context))) {
|
||||
if (kv.second.bloom_filter_useful > 0) {
|
||||
@@ -696,11 +724,8 @@ TEST_P(DBBloomFilterTestWithParam, SkipFilterOnEssentiallyZeroBpk) {
|
||||
PutAndGetFn();
|
||||
|
||||
// Verify filter is accessed (and constructed)
|
||||
EXPECT_EQ(TestGetAndResetTickerCount(options, BLOOM_FILTER_FULL_POSITIVE),
|
||||
maxKey * 2);
|
||||
EXPECT_EQ(
|
||||
TestGetAndResetTickerCount(options, BLOOM_FILTER_FULL_TRUE_POSITIVE),
|
||||
maxKey);
|
||||
EXPECT_EQ(PopTicker(options, BLOOM_FILTER_FULL_POSITIVE), maxKey * 2);
|
||||
EXPECT_EQ(PopTicker(options, BLOOM_FILTER_FULL_TRUE_POSITIVE), maxKey);
|
||||
props.clear();
|
||||
ASSERT_TRUE(db_->GetMapProperty(kAggTableProps, &props));
|
||||
EXPECT_NE(props["filter_size"], "0");
|
||||
@@ -715,11 +740,8 @@ TEST_P(DBBloomFilterTestWithParam, SkipFilterOnEssentiallyZeroBpk) {
|
||||
GetFn();
|
||||
|
||||
// Verify filter is accessed
|
||||
EXPECT_EQ(TestGetAndResetTickerCount(options, BLOOM_FILTER_FULL_POSITIVE),
|
||||
maxKey * 2);
|
||||
EXPECT_EQ(
|
||||
TestGetAndResetTickerCount(options, BLOOM_FILTER_FULL_TRUE_POSITIVE),
|
||||
maxKey);
|
||||
EXPECT_EQ(PopTicker(options, BLOOM_FILTER_FULL_POSITIVE), maxKey * 2);
|
||||
EXPECT_EQ(PopTicker(options, BLOOM_FILTER_FULL_TRUE_POSITIVE), maxKey);
|
||||
|
||||
// But new filters are not generated (configuration details unknown)
|
||||
DestroyAndReopen(options);
|
||||
@@ -783,9 +805,7 @@ TEST_F(DBBloomFilterTest, BloomFilterRate) {
|
||||
ASSERT_EQ("NOT_FOUND", Get(1, Key(i + 33333)));
|
||||
}
|
||||
ASSERT_GE(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), maxKey * 0.98);
|
||||
ASSERT_GE(
|
||||
(*(get_perf_context()->level_to_perf_context))[0].bloom_filter_useful,
|
||||
maxKey * 0.98);
|
||||
ASSERT_GE(GetLevelPerfContext(0).bloom_filter_useful, maxKey * 0.98);
|
||||
get_perf_context()->Reset();
|
||||
}
|
||||
}
|
||||
@@ -864,9 +884,8 @@ TEST_F(DBBloomFilterTest, BloomFilterCompatibility) {
|
||||
ASSERT_EQ("val", Get(prefix + "Z")); // Filter positive
|
||||
// Filter negative, with high probability
|
||||
ASSERT_EQ("NOT_FOUND", Get(prefix + "Q"));
|
||||
EXPECT_EQ(TestGetAndResetTickerCount(options, BLOOM_FILTER_FULL_POSITIVE),
|
||||
2);
|
||||
EXPECT_EQ(TestGetAndResetTickerCount(options, BLOOM_FILTER_USEFUL), 1);
|
||||
EXPECT_EQ(PopTicker(options, BLOOM_FILTER_FULL_POSITIVE), 2);
|
||||
EXPECT_EQ(PopTicker(options, BLOOM_FILTER_USEFUL), 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1705,7 +1724,7 @@ TEST_F(DBBloomFilterTest, ContextCustomFilterPolicy) {
|
||||
}
|
||||
// Since we have two tables / two filters, we might have Bloom checks on
|
||||
// our queries, but no more than one "useful" per query on a found key.
|
||||
EXPECT_LE(TestGetAndResetTickerCount(options, BLOOM_FILTER_USEFUL), maxKey);
|
||||
EXPECT_LE(PopTicker(options, BLOOM_FILTER_USEFUL), maxKey);
|
||||
|
||||
// Check that we have two filters, each about
|
||||
// fifo: 0.12% FP rate (15 bits per key)
|
||||
@@ -1714,8 +1733,7 @@ TEST_F(DBBloomFilterTest, ContextCustomFilterPolicy) {
|
||||
ASSERT_EQ("NOT_FOUND", Get(1, Key(i + 33333)));
|
||||
}
|
||||
{
|
||||
auto useful_count =
|
||||
TestGetAndResetTickerCount(options, BLOOM_FILTER_USEFUL);
|
||||
auto useful_count = PopTicker(options, BLOOM_FILTER_USEFUL);
|
||||
EXPECT_GE(useful_count, maxKey * 2 * (fifo ? 0.9980 : 0.975));
|
||||
EXPECT_LE(useful_count, maxKey * 2 * (fifo ? 0.9995 : 0.98));
|
||||
}
|
||||
@@ -1732,8 +1750,7 @@ TEST_F(DBBloomFilterTest, ContextCustomFilterPolicy) {
|
||||
ASSERT_EQ("NOT_FOUND", Get(1, Key(i + 33333)));
|
||||
}
|
||||
{
|
||||
auto useful_count =
|
||||
TestGetAndResetTickerCount(options, BLOOM_FILTER_USEFUL);
|
||||
auto useful_count = PopTicker(options, BLOOM_FILTER_USEFUL);
|
||||
EXPECT_GE(useful_count, maxKey * 0.90);
|
||||
EXPECT_LE(useful_count, maxKey * 0.91);
|
||||
}
|
||||
@@ -2027,13 +2044,14 @@ TEST_P(DBBloomFilterTestVaryPrefixAndFormatVer, PartitionedMultiGet) {
|
||||
std::array<Status, Q> statuses;
|
||||
std::array<PinnableSlice, Q> values;
|
||||
|
||||
TestGetAndResetTickerCount(options, BLOCK_CACHE_FILTER_HIT);
|
||||
TestGetAndResetTickerCount(options, BLOCK_CACHE_FILTER_MISS);
|
||||
TestGetAndResetTickerCount(options, BLOOM_FILTER_PREFIX_USEFUL);
|
||||
TestGetAndResetTickerCount(options, BLOOM_FILTER_USEFUL);
|
||||
TestGetAndResetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED);
|
||||
TestGetAndResetTickerCount(options, BLOOM_FILTER_FULL_POSITIVE);
|
||||
TestGetAndResetTickerCount(options, BLOOM_FILTER_FULL_TRUE_POSITIVE);
|
||||
PopTicker(options, BLOCK_CACHE_FILTER_HIT);
|
||||
PopTicker(options, BLOCK_CACHE_FILTER_MISS);
|
||||
PopTicker(options, BLOOM_FILTER_PREFIX_USEFUL);
|
||||
PopTicker(options, BLOOM_FILTER_USEFUL);
|
||||
PopTicker(options, BLOOM_FILTER_PREFIX_CHECKED);
|
||||
PopTicker(options, BLOOM_FILTER_FULL_POSITIVE);
|
||||
PopTicker(options, BLOOM_FILTER_FULL_TRUE_POSITIVE);
|
||||
PopTicker(options, BLOOM_FILTER_PREFIX_TRUE_POSITIVE);
|
||||
|
||||
// Check that initial clump of keys only loads one partition filter from
|
||||
// block cache.
|
||||
@@ -2063,26 +2081,22 @@ TEST_P(DBBloomFilterTestVaryPrefixAndFormatVer, PartitionedMultiGet) {
|
||||
}
|
||||
|
||||
// Confirm correct Bloom stats (no FPs)
|
||||
uint64_t filter_useful = TestGetAndResetTickerCount(
|
||||
options,
|
||||
use_prefix_ ? BLOOM_FILTER_PREFIX_USEFUL : BLOOM_FILTER_USEFUL);
|
||||
uint64_t filter_useful =
|
||||
PopTicker(options, use_prefix_ ? BLOOM_FILTER_PREFIX_USEFUL
|
||||
: BLOOM_FILTER_USEFUL);
|
||||
uint64_t filter_checked =
|
||||
TestGetAndResetTickerCount(options, use_prefix_
|
||||
? BLOOM_FILTER_PREFIX_CHECKED
|
||||
: BLOOM_FILTER_FULL_POSITIVE) +
|
||||
PopTicker(options, use_prefix_ ? BLOOM_FILTER_PREFIX_CHECKED
|
||||
: BLOOM_FILTER_FULL_POSITIVE) +
|
||||
(use_prefix_ ? 0 : filter_useful);
|
||||
EXPECT_EQ(filter_useful, number_not_found);
|
||||
EXPECT_EQ(filter_checked, Q);
|
||||
if (!use_prefix_) {
|
||||
EXPECT_EQ(
|
||||
TestGetAndResetTickerCount(options, BLOOM_FILTER_FULL_TRUE_POSITIVE),
|
||||
Q - number_not_found);
|
||||
}
|
||||
EXPECT_EQ(PopTicker(options, use_prefix_ ? BLOOM_FILTER_PREFIX_TRUE_POSITIVE
|
||||
: BLOOM_FILTER_FULL_TRUE_POSITIVE),
|
||||
Q - number_not_found);
|
||||
|
||||
// Confirm no duplicate loading same filter partition
|
||||
uint64_t filter_accesses =
|
||||
TestGetAndResetTickerCount(options, BLOCK_CACHE_FILTER_HIT) +
|
||||
TestGetAndResetTickerCount(options, BLOCK_CACHE_FILTER_MISS);
|
||||
uint64_t filter_accesses = PopTicker(options, BLOCK_CACHE_FILTER_HIT) +
|
||||
PopTicker(options, BLOCK_CACHE_FILTER_MISS);
|
||||
if (stride == 1) {
|
||||
EXPECT_EQ(filter_accesses, 1);
|
||||
} else {
|
||||
@@ -2118,26 +2132,22 @@ TEST_P(DBBloomFilterTestVaryPrefixAndFormatVer, PartitionedMultiGet) {
|
||||
}
|
||||
|
||||
// Confirm correct Bloom stats (might see some FPs)
|
||||
uint64_t filter_useful = TestGetAndResetTickerCount(
|
||||
options,
|
||||
use_prefix_ ? BLOOM_FILTER_PREFIX_USEFUL : BLOOM_FILTER_USEFUL);
|
||||
uint64_t filter_useful =
|
||||
PopTicker(options, use_prefix_ ? BLOOM_FILTER_PREFIX_USEFUL
|
||||
: BLOOM_FILTER_USEFUL);
|
||||
uint64_t filter_checked =
|
||||
TestGetAndResetTickerCount(options, use_prefix_
|
||||
? BLOOM_FILTER_PREFIX_CHECKED
|
||||
: BLOOM_FILTER_FULL_POSITIVE) +
|
||||
PopTicker(options, use_prefix_ ? BLOOM_FILTER_PREFIX_CHECKED
|
||||
: BLOOM_FILTER_FULL_POSITIVE) +
|
||||
(use_prefix_ ? 0 : filter_useful);
|
||||
EXPECT_GE(filter_useful, number_not_found - 2); // possible FP
|
||||
EXPECT_EQ(filter_checked, Q);
|
||||
if (!use_prefix_) {
|
||||
EXPECT_EQ(
|
||||
TestGetAndResetTickerCount(options, BLOOM_FILTER_FULL_TRUE_POSITIVE),
|
||||
Q - number_not_found);
|
||||
}
|
||||
EXPECT_EQ(PopTicker(options, use_prefix_ ? BLOOM_FILTER_PREFIX_TRUE_POSITIVE
|
||||
: BLOOM_FILTER_FULL_TRUE_POSITIVE),
|
||||
Q - number_not_found);
|
||||
|
||||
// Confirm no duplicate loading of same filter partition
|
||||
uint64_t filter_accesses =
|
||||
TestGetAndResetTickerCount(options, BLOCK_CACHE_FILTER_HIT) +
|
||||
TestGetAndResetTickerCount(options, BLOCK_CACHE_FILTER_MISS);
|
||||
uint64_t filter_accesses = PopTicker(options, BLOCK_CACHE_FILTER_HIT) +
|
||||
PopTicker(options, BLOCK_CACHE_FILTER_MISS);
|
||||
if (filter_accesses == 2) {
|
||||
// Spanned across partitions.
|
||||
++found_spanning;
|
||||
@@ -2437,9 +2447,11 @@ TEST_F(DBBloomFilterTest, PrefixScan) {
|
||||
}
|
||||
|
||||
TEST_F(DBBloomFilterTest, OptimizeFiltersForHits) {
|
||||
const int kNumKeysPerFlush = 1000;
|
||||
|
||||
Options options = CurrentOptions();
|
||||
options.write_buffer_size = 64 * 1024;
|
||||
options.arena_block_size = 4 * 1024;
|
||||
options.memtable_factory.reset(
|
||||
test::NewSpecialSkipListFactory(kNumKeysPerFlush));
|
||||
options.target_file_size_base = 64 * 1024;
|
||||
options.level0_file_num_compaction_trigger = 2;
|
||||
options.level0_slowdown_writes_trigger = 2;
|
||||
@@ -2475,8 +2487,13 @@ TEST_F(DBBloomFilterTest, OptimizeFiltersForHits) {
|
||||
int num_inserted = 0;
|
||||
for (int key : keys) {
|
||||
ASSERT_OK(Put(1, Key(key), "val"));
|
||||
if (++num_inserted % 1000 == 0) {
|
||||
ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable());
|
||||
num_inserted++;
|
||||
// The write after each `kNumKeysPerFlush` keys triggers a flush. Always
|
||||
// wait for that flush and any follow-on compactions for deterministic LSM
|
||||
// shape.
|
||||
if (num_inserted > kNumKeysPerFlush &&
|
||||
num_inserted % kNumKeysPerFlush == 1) {
|
||||
ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable(handles_[1]));
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
}
|
||||
}
|
||||
@@ -2629,6 +2646,9 @@ int CountIter(std::unique_ptr<Iterator>& iter, const Slice& key) {
|
||||
int count = 0;
|
||||
for (iter->Seek(key); iter->Valid(); iter->Next()) {
|
||||
count++;
|
||||
// Access key & value as if we were using them
|
||||
(void)iter->key();
|
||||
(void)iter->value();
|
||||
}
|
||||
EXPECT_OK(iter->status());
|
||||
return count;
|
||||
@@ -2668,6 +2688,12 @@ TEST_F(DBBloomFilterTest, DynamicBloomFilterUpperBound) {
|
||||
read_options.iterate_upper_bound = &upper_bound;
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options));
|
||||
ASSERT_EQ(CountIter(iter, "abcd0000"), 4);
|
||||
ASSERT_EQ(TestGetTickerCount(options, NON_LAST_LEVEL_SEEK_FILTER_MATCH),
|
||||
1);
|
||||
ASSERT_EQ(TestGetTickerCount(options, NON_LAST_LEVEL_SEEK_FILTERED), 0);
|
||||
ASSERT_EQ(TestGetTickerCount(
|
||||
options, NON_LAST_LEVEL_SEEK_DATA_USEFUL_FILTER_MATCH),
|
||||
1);
|
||||
}
|
||||
{
|
||||
Slice upper_bound("abcdzzzz");
|
||||
@@ -2676,8 +2702,9 @@ TEST_F(DBBloomFilterTest, DynamicBloomFilterUpperBound) {
|
||||
read_options.iterate_upper_bound = &upper_bound;
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options));
|
||||
ASSERT_EQ(CountIter(iter, "abcd0000"), 4);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED), 2);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_USEFUL), 0);
|
||||
ASSERT_EQ(TestGetTickerCount(options, NON_LAST_LEVEL_SEEK_FILTER_MATCH),
|
||||
2);
|
||||
ASSERT_EQ(TestGetTickerCount(options, NON_LAST_LEVEL_SEEK_FILTERED), 0);
|
||||
}
|
||||
ASSERT_OK(dbfull()->SetOptions({{"prefix_extractor", "fixed:5"}}));
|
||||
ASSERT_EQ(dbfull()->GetOptions().prefix_extractor->AsString(),
|
||||
@@ -2691,8 +2718,9 @@ TEST_F(DBBloomFilterTest, DynamicBloomFilterUpperBound) {
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options));
|
||||
ASSERT_EQ(CountIter(iter, "abcdxx00"), 4);
|
||||
// should check bloom filter since upper bound meets requirement
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED), 3);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_USEFUL), 0);
|
||||
ASSERT_EQ(TestGetTickerCount(options, NON_LAST_LEVEL_SEEK_FILTER_MATCH),
|
||||
3);
|
||||
ASSERT_EQ(TestGetTickerCount(options, NON_LAST_LEVEL_SEEK_FILTERED), 0);
|
||||
}
|
||||
{
|
||||
// [abcdxx01, abcey) is not valid bound since upper bound is too long for
|
||||
@@ -2704,8 +2732,9 @@ TEST_F(DBBloomFilterTest, DynamicBloomFilterUpperBound) {
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options));
|
||||
ASSERT_EQ(CountIter(iter, "abcdxx01"), 4);
|
||||
// should skip bloom filter since upper bound is too long
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED), 3);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_USEFUL), 0);
|
||||
ASSERT_EQ(TestGetTickerCount(options, NON_LAST_LEVEL_SEEK_FILTER_MATCH),
|
||||
3);
|
||||
ASSERT_EQ(TestGetTickerCount(options, NON_LAST_LEVEL_SEEK_FILTERED), 0);
|
||||
}
|
||||
{
|
||||
// [abcdxx02, abcdy) is a valid bound since the prefix is the same
|
||||
@@ -2717,8 +2746,9 @@ TEST_F(DBBloomFilterTest, DynamicBloomFilterUpperBound) {
|
||||
ASSERT_EQ(CountIter(iter, "abcdxx02"), 4);
|
||||
// should check bloom filter since upper bound matches transformed seek
|
||||
// key
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED), 4);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_USEFUL), 0);
|
||||
ASSERT_EQ(TestGetTickerCount(options, NON_LAST_LEVEL_SEEK_FILTER_MATCH),
|
||||
4);
|
||||
ASSERT_EQ(TestGetTickerCount(options, NON_LAST_LEVEL_SEEK_FILTERED), 0);
|
||||
}
|
||||
{
|
||||
// [aaaaaaaa, abce) is not a valid bound since 1) they don't share the
|
||||
@@ -2730,8 +2760,9 @@ TEST_F(DBBloomFilterTest, DynamicBloomFilterUpperBound) {
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options));
|
||||
ASSERT_EQ(CountIter(iter, "aaaaaaaa"), 0);
|
||||
// should skip bloom filter since mismatch is found
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED), 4);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_USEFUL), 0);
|
||||
ASSERT_EQ(TestGetTickerCount(options, NON_LAST_LEVEL_SEEK_FILTER_MATCH),
|
||||
4);
|
||||
ASSERT_EQ(TestGetTickerCount(options, NON_LAST_LEVEL_SEEK_FILTERED), 0);
|
||||
}
|
||||
ASSERT_OK(dbfull()->SetOptions({{"prefix_extractor", "fixed:3"}}));
|
||||
{
|
||||
@@ -2743,8 +2774,9 @@ TEST_F(DBBloomFilterTest, DynamicBloomFilterUpperBound) {
|
||||
read_options.iterate_upper_bound = &upper_bound;
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options));
|
||||
ASSERT_EQ(CountIter(iter, "abc"), 4);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED), 4);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_USEFUL), 0);
|
||||
ASSERT_EQ(TestGetTickerCount(options, NON_LAST_LEVEL_SEEK_FILTER_MATCH),
|
||||
4);
|
||||
ASSERT_EQ(TestGetTickerCount(options, NON_LAST_LEVEL_SEEK_FILTERED), 0);
|
||||
}
|
||||
// Same with re-open
|
||||
options.prefix_extractor.reset(NewFixedPrefixTransform(3));
|
||||
@@ -2756,8 +2788,9 @@ TEST_F(DBBloomFilterTest, DynamicBloomFilterUpperBound) {
|
||||
read_options.iterate_upper_bound = &upper_bound;
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options));
|
||||
ASSERT_EQ(CountIter(iter, "abc"), 4);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED), 4);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_USEFUL), 0);
|
||||
ASSERT_EQ(TestGetTickerCount(options, NON_LAST_LEVEL_SEEK_FILTER_MATCH),
|
||||
4);
|
||||
ASSERT_EQ(TestGetTickerCount(options, NON_LAST_LEVEL_SEEK_FILTERED), 0);
|
||||
}
|
||||
// Set back to capped:4 and verify BF is always read
|
||||
options.prefix_extractor.reset(NewCappedPrefixTransform(4));
|
||||
@@ -2769,8 +2802,9 @@ TEST_F(DBBloomFilterTest, DynamicBloomFilterUpperBound) {
|
||||
read_options.iterate_upper_bound = &upper_bound;
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options));
|
||||
ASSERT_EQ(CountIter(iter, "abc"), 0);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED), 5);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_USEFUL), 1);
|
||||
ASSERT_EQ(TestGetTickerCount(options, NON_LAST_LEVEL_SEEK_FILTER_MATCH),
|
||||
4);
|
||||
ASSERT_EQ(TestGetTickerCount(options, NON_LAST_LEVEL_SEEK_FILTERED), 1);
|
||||
}
|
||||
// Same if there's a problem initally loading prefix transform
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
@@ -2785,8 +2819,9 @@ TEST_F(DBBloomFilterTest, DynamicBloomFilterUpperBound) {
|
||||
read_options.iterate_upper_bound = &upper_bound;
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options));
|
||||
ASSERT_EQ(CountIter(iter, "abc"), 0);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED), 6);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_USEFUL), 2);
|
||||
ASSERT_EQ(TestGetTickerCount(options, NON_LAST_LEVEL_SEEK_FILTER_MATCH),
|
||||
4);
|
||||
ASSERT_EQ(TestGetTickerCount(options, NON_LAST_LEVEL_SEEK_FILTERED), 2);
|
||||
}
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
@@ -2821,7 +2856,8 @@ TEST_F(DBBloomFilterTest, DynamicBloomFilterMultipleSST) {
|
||||
dbfull()->Flush(FlushOptions());
|
||||
std::unique_ptr<Iterator> iter_old(db_->NewIterator(read_options));
|
||||
ASSERT_EQ(CountIter(iter_old, "foo"), 4);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED), 1);
|
||||
EXPECT_EQ(PopTicker(options, NON_LAST_LEVEL_SEEK_FILTERED), 0);
|
||||
EXPECT_EQ(PopTicker(options, NON_LAST_LEVEL_SEEK_FILTER_MATCH), 1);
|
||||
|
||||
ASSERT_OK(dbfull()->SetOptions({{"prefix_extractor", "capped:3"}}));
|
||||
ASSERT_EQ(dbfull()->GetOptions().prefix_extractor->AsString(),
|
||||
@@ -2829,10 +2865,11 @@ TEST_F(DBBloomFilterTest, DynamicBloomFilterMultipleSST) {
|
||||
read_options.iterate_upper_bound = &upper_bound;
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options));
|
||||
ASSERT_EQ(CountIter(iter, "foo"), 2);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED), 2);
|
||||
EXPECT_EQ(PopTicker(options, NON_LAST_LEVEL_SEEK_FILTERED), 0);
|
||||
EXPECT_EQ(PopTicker(options, NON_LAST_LEVEL_SEEK_FILTER_MATCH), 1);
|
||||
ASSERT_EQ(CountIter(iter, "gpk"), 0);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED), 2);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_USEFUL), 0);
|
||||
EXPECT_EQ(PopTicker(options, NON_LAST_LEVEL_SEEK_FILTERED), 0);
|
||||
EXPECT_EQ(PopTicker(options, NON_LAST_LEVEL_SEEK_FILTER_MATCH), 0);
|
||||
|
||||
// second SST with capped:3 BF
|
||||
ASSERT_OK(Put("foo3", "bar3"));
|
||||
@@ -2844,13 +2881,13 @@ TEST_F(DBBloomFilterTest, DynamicBloomFilterMultipleSST) {
|
||||
// BF is cappped:3 now
|
||||
std::unique_ptr<Iterator> iter_tmp(db_->NewIterator(read_options));
|
||||
ASSERT_EQ(CountIter(iter_tmp, "foo"), 4);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED), 4);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_USEFUL), 0);
|
||||
EXPECT_EQ(PopTicker(options, NON_LAST_LEVEL_SEEK_FILTERED), 0);
|
||||
EXPECT_EQ(PopTicker(options, NON_LAST_LEVEL_SEEK_FILTER_MATCH), 2);
|
||||
ASSERT_EQ(CountIter(iter_tmp, "gpk"), 0);
|
||||
// both counters are incremented because BF is "not changed" for 1 of the
|
||||
// 2 SST files, so filter is checked once and found no match.
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED), 5);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_USEFUL), 1);
|
||||
EXPECT_EQ(PopTicker(options, NON_LAST_LEVEL_SEEK_FILTERED), 1);
|
||||
EXPECT_EQ(PopTicker(options, NON_LAST_LEVEL_SEEK_FILTER_MATCH), 0);
|
||||
}
|
||||
|
||||
ASSERT_OK(dbfull()->SetOptions({{"prefix_extractor", "fixed:2"}}));
|
||||
@@ -2867,33 +2904,34 @@ TEST_F(DBBloomFilterTest, DynamicBloomFilterMultipleSST) {
|
||||
std::unique_ptr<Iterator> iter_tmp(db_->NewIterator(read_options));
|
||||
ASSERT_EQ(CountIter(iter_tmp, "foo"), 9);
|
||||
// the first and last BF are checked
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED), 7);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_USEFUL), 1);
|
||||
EXPECT_EQ(PopTicker(options, NON_LAST_LEVEL_SEEK_FILTERED), 0);
|
||||
EXPECT_EQ(PopTicker(options, NON_LAST_LEVEL_SEEK_FILTER_MATCH), 2);
|
||||
ASSERT_EQ(CountIter(iter_tmp, "gpk"), 0);
|
||||
// only last BF is checked and not found
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED), 8);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_USEFUL), 2);
|
||||
EXPECT_EQ(PopTicker(options, NON_LAST_LEVEL_SEEK_FILTERED), 1);
|
||||
EXPECT_EQ(PopTicker(options, NON_LAST_LEVEL_SEEK_FILTER_MATCH), 0);
|
||||
}
|
||||
|
||||
// iter_old can only see the first SST, so checked plus 1
|
||||
// iter_old can only see the first SST
|
||||
ASSERT_EQ(CountIter(iter_old, "foo"), 4);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED), 9);
|
||||
// iter was created after the first setoptions call so only full filter
|
||||
// will check the filter
|
||||
EXPECT_EQ(PopTicker(options, NON_LAST_LEVEL_SEEK_FILTERED), 0);
|
||||
EXPECT_EQ(PopTicker(options, NON_LAST_LEVEL_SEEK_FILTER_MATCH), 1);
|
||||
// same with iter, but different prefix extractor
|
||||
ASSERT_EQ(CountIter(iter, "foo"), 2);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED), 10);
|
||||
EXPECT_EQ(PopTicker(options, NON_LAST_LEVEL_SEEK_FILTERED), 0);
|
||||
EXPECT_EQ(PopTicker(options, NON_LAST_LEVEL_SEEK_FILTER_MATCH), 1);
|
||||
|
||||
{
|
||||
// keys in all three SSTs are visible to iterator
|
||||
// The range of [foo, foz90000] is compatible with (fixed:1) and (fixed:2)
|
||||
// so +2 for checked counter
|
||||
std::unique_ptr<Iterator> iter_all(db_->NewIterator(read_options));
|
||||
ASSERT_EQ(CountIter(iter_all, "foo"), 9);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED), 12);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_USEFUL), 2);
|
||||
EXPECT_EQ(PopTicker(options, NON_LAST_LEVEL_SEEK_FILTERED), 0);
|
||||
EXPECT_EQ(PopTicker(options, NON_LAST_LEVEL_SEEK_FILTER_MATCH), 2);
|
||||
ASSERT_EQ(CountIter(iter_all, "gpk"), 0);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED), 13);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_USEFUL), 3);
|
||||
// FIXME? isn't seek key out of SST range?
|
||||
EXPECT_EQ(PopTicker(options, NON_LAST_LEVEL_SEEK_FILTERED), 1);
|
||||
EXPECT_EQ(PopTicker(options, NON_LAST_LEVEL_SEEK_FILTER_MATCH), 0);
|
||||
}
|
||||
ASSERT_OK(dbfull()->SetOptions({{"prefix_extractor", "capped:3"}}));
|
||||
ASSERT_EQ(dbfull()->GetOptions().prefix_extractor->AsString(),
|
||||
@@ -2903,11 +2941,12 @@ TEST_F(DBBloomFilterTest, DynamicBloomFilterMultipleSST) {
|
||||
ASSERT_EQ(CountIter(iter_all, "foo"), 6);
|
||||
// all three SST are checked because the current options has the same as
|
||||
// the remaining SST (capped:3)
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED), 16);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_USEFUL), 3);
|
||||
EXPECT_EQ(PopTicker(options, NON_LAST_LEVEL_SEEK_FILTERED), 0);
|
||||
EXPECT_EQ(PopTicker(options, NON_LAST_LEVEL_SEEK_FILTER_MATCH), 3);
|
||||
ASSERT_EQ(CountIter(iter_all, "gpk"), 0);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED), 17);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_USEFUL), 4);
|
||||
// FIXME? isn't seek key out of SST range?
|
||||
EXPECT_EQ(PopTicker(options, NON_LAST_LEVEL_SEEK_FILTERED), 1);
|
||||
EXPECT_EQ(PopTicker(options, NON_LAST_LEVEL_SEEK_FILTER_MATCH), 0);
|
||||
}
|
||||
// TODO(Zhongyi): Maybe also need to add Get calls to test point look up?
|
||||
}
|
||||
@@ -3009,13 +3048,13 @@ TEST_F(DBBloomFilterTest, DynamicBloomFilterOptions) {
|
||||
{
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options));
|
||||
ASSERT_EQ(CountIter(iter, "foo"), 12);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED), 3);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_USEFUL), 0);
|
||||
EXPECT_EQ(PopTicker(options, NON_LAST_LEVEL_SEEK_FILTERED), 0);
|
||||
EXPECT_EQ(PopTicker(options, NON_LAST_LEVEL_SEEK_FILTER_MATCH), 3);
|
||||
}
|
||||
std::unique_ptr<Iterator> iter_old(db_->NewIterator(read_options));
|
||||
ASSERT_EQ(CountIter(iter_old, "foo"), 12);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED), 6);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_USEFUL), 0);
|
||||
EXPECT_EQ(PopTicker(options, NON_LAST_LEVEL_SEEK_FILTERED), 0);
|
||||
EXPECT_EQ(PopTicker(options, NON_LAST_LEVEL_SEEK_FILTER_MATCH), 3);
|
||||
|
||||
ASSERT_OK(dbfull()->SetOptions({{"prefix_extractor", "capped:3"}}));
|
||||
ASSERT_EQ(dbfull()->GetOptions().prefix_extractor->AsString(),
|
||||
@@ -3024,17 +3063,18 @@ TEST_F(DBBloomFilterTest, DynamicBloomFilterOptions) {
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options));
|
||||
// "fp*" should be skipped
|
||||
ASSERT_EQ(CountIter(iter, "foo"), 9);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED), 6);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_USEFUL), 0);
|
||||
EXPECT_EQ(PopTicker(options, NON_LAST_LEVEL_SEEK_FILTERED), 0);
|
||||
EXPECT_EQ(PopTicker(options, NON_LAST_LEVEL_SEEK_FILTER_MATCH), 0);
|
||||
}
|
||||
|
||||
// iterator created before should not be affected and see all keys
|
||||
ASSERT_EQ(CountIter(iter_old, "foo"), 12);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED), 9);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_USEFUL), 0);
|
||||
EXPECT_EQ(PopTicker(options, NON_LAST_LEVEL_SEEK_FILTERED), 0);
|
||||
EXPECT_EQ(PopTicker(options, NON_LAST_LEVEL_SEEK_FILTER_MATCH), 3);
|
||||
ASSERT_EQ(CountIter(iter_old, "abc"), 0);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED), 12);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_USEFUL), 3);
|
||||
// FIXME? isn't seek key out of SST range?
|
||||
EXPECT_EQ(PopTicker(options, NON_LAST_LEVEL_SEEK_FILTERED), 3);
|
||||
EXPECT_EQ(PopTicker(options, NON_LAST_LEVEL_SEEK_FILTER_MATCH), 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3129,19 +3169,19 @@ class FixedSuffix4Transform : public SliceTransform {
|
||||
|
||||
std::pair<uint64_t, uint64_t> GetBloomStat(const Options& options, bool sst) {
|
||||
if (sst) {
|
||||
return {
|
||||
options.statistics->getAndResetTickerCount(BLOOM_FILTER_PREFIX_CHECKED),
|
||||
options.statistics->getAndResetTickerCount(BLOOM_FILTER_PREFIX_USEFUL)};
|
||||
return {options.statistics->getAndResetTickerCount(
|
||||
NON_LAST_LEVEL_SEEK_FILTER_MATCH),
|
||||
options.statistics->getAndResetTickerCount(
|
||||
NON_LAST_LEVEL_SEEK_FILTERED)};
|
||||
} else {
|
||||
auto hit = std::exchange(get_perf_context()->bloom_memtable_hit_count, 0);
|
||||
auto miss = std::exchange(get_perf_context()->bloom_memtable_miss_count, 0);
|
||||
return {hit + miss, miss};
|
||||
return {hit, miss};
|
||||
}
|
||||
}
|
||||
|
||||
std::pair<uint64_t, uint64_t> CheckedAndUseful(uint64_t checked,
|
||||
uint64_t useful) {
|
||||
return {checked, useful};
|
||||
std::pair<uint64_t, uint64_t> HitAndMiss(uint64_t hits, uint64_t misses) {
|
||||
return {hits, misses};
|
||||
}
|
||||
} // anonymous namespace
|
||||
|
||||
@@ -3179,27 +3219,27 @@ TEST_F(DBBloomFilterTest, WeirdPrefixExtractorWithFilter1) {
|
||||
if (flushed) { // TODO: support auto_prefix_mode in memtable?
|
||||
read_options.auto_prefix_mode = true;
|
||||
}
|
||||
EXPECT_EQ(GetBloomStat(options, flushed), CheckedAndUseful(0, 0));
|
||||
EXPECT_EQ(GetBloomStat(options, flushed), HitAndMiss(0, 0));
|
||||
{
|
||||
Slice ub("999aaaa");
|
||||
read_options.iterate_upper_bound = &ub;
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options));
|
||||
EXPECT_EQ(CountIter(iter, "aaaa"), 3);
|
||||
EXPECT_EQ(GetBloomStat(options, flushed), CheckedAndUseful(1, 0));
|
||||
EXPECT_EQ(GetBloomStat(options, flushed), HitAndMiss(1, 0));
|
||||
}
|
||||
{
|
||||
Slice ub("999abaa");
|
||||
read_options.iterate_upper_bound = &ub;
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options));
|
||||
EXPECT_EQ(CountIter(iter, "abaa"), 1);
|
||||
EXPECT_EQ(GetBloomStat(options, flushed), CheckedAndUseful(1, 0));
|
||||
EXPECT_EQ(GetBloomStat(options, flushed), HitAndMiss(1, 0));
|
||||
}
|
||||
{
|
||||
Slice ub("999acaa");
|
||||
read_options.iterate_upper_bound = &ub;
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options));
|
||||
EXPECT_EQ(CountIter(iter, "acaa"), 0);
|
||||
EXPECT_EQ(GetBloomStat(options, flushed), CheckedAndUseful(1, 1));
|
||||
EXPECT_EQ(GetBloomStat(options, flushed), HitAndMiss(0, 1));
|
||||
}
|
||||
{
|
||||
Slice ub("zzzz");
|
||||
@@ -3207,7 +3247,7 @@ TEST_F(DBBloomFilterTest, WeirdPrefixExtractorWithFilter1) {
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options));
|
||||
EXPECT_EQ(CountIter(iter, "baa"), 3);
|
||||
if (flushed) { // TODO: fix memtable case
|
||||
EXPECT_EQ(GetBloomStat(options, flushed), CheckedAndUseful(0, 0));
|
||||
EXPECT_EQ(GetBloomStat(options, flushed), HitAndMiss(0, 0));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3253,13 +3293,13 @@ TEST_F(DBBloomFilterTest, WeirdPrefixExtractorWithFilter2) {
|
||||
get_perf_context()->bloom_memtable_hit_count = 0;
|
||||
get_perf_context()->bloom_memtable_miss_count = 0;
|
||||
}
|
||||
EXPECT_EQ(GetBloomStat(options, flushed), CheckedAndUseful(0, 0));
|
||||
EXPECT_EQ(GetBloomStat(options, flushed), HitAndMiss(0, 0));
|
||||
{
|
||||
Slice ub("aaaa000");
|
||||
read_options.iterate_upper_bound = &ub;
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options));
|
||||
EXPECT_EQ(CountIter(iter, "aaaa999"), 3);
|
||||
EXPECT_EQ(GetBloomStat(options, flushed), CheckedAndUseful(1, 0));
|
||||
EXPECT_EQ(GetBloomStat(options, flushed), HitAndMiss(1, 0));
|
||||
}
|
||||
{
|
||||
// Note: prefix does work as upper bound
|
||||
@@ -3267,7 +3307,7 @@ TEST_F(DBBloomFilterTest, WeirdPrefixExtractorWithFilter2) {
|
||||
read_options.iterate_upper_bound = &ub;
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options));
|
||||
EXPECT_EQ(CountIter(iter, "aaaa999"), 3);
|
||||
EXPECT_EQ(GetBloomStat(options, flushed), CheckedAndUseful(1, 0));
|
||||
EXPECT_EQ(GetBloomStat(options, flushed), HitAndMiss(1, 0));
|
||||
}
|
||||
{
|
||||
// Note: prefix does not work here as seek key
|
||||
@@ -3275,28 +3315,28 @@ TEST_F(DBBloomFilterTest, WeirdPrefixExtractorWithFilter2) {
|
||||
read_options.iterate_upper_bound = &ub;
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options));
|
||||
EXPECT_EQ(CountIter(iter, "aaaa"), 0);
|
||||
EXPECT_EQ(GetBloomStat(options, flushed), CheckedAndUseful(1, 0));
|
||||
EXPECT_EQ(GetBloomStat(options, flushed), HitAndMiss(1, 0));
|
||||
}
|
||||
{
|
||||
Slice ub("aaba000");
|
||||
read_options.iterate_upper_bound = &ub;
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options));
|
||||
EXPECT_EQ(CountIter(iter, "aaba999"), 1);
|
||||
EXPECT_EQ(GetBloomStat(options, flushed), CheckedAndUseful(1, 0));
|
||||
EXPECT_EQ(GetBloomStat(options, flushed), HitAndMiss(1, 0));
|
||||
}
|
||||
{
|
||||
Slice ub("aaca000");
|
||||
read_options.iterate_upper_bound = &ub;
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options));
|
||||
EXPECT_EQ(CountIter(iter, "aaca999"), 0);
|
||||
EXPECT_EQ(GetBloomStat(options, flushed), CheckedAndUseful(1, 1));
|
||||
EXPECT_EQ(GetBloomStat(options, flushed), HitAndMiss(0, 1));
|
||||
}
|
||||
{
|
||||
Slice ub("aaaz");
|
||||
read_options.iterate_upper_bound = &ub;
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options));
|
||||
EXPECT_EQ(CountIter(iter, "zzz"), 5);
|
||||
EXPECT_EQ(GetBloomStat(options, flushed), CheckedAndUseful(0, 0));
|
||||
EXPECT_EQ(GetBloomStat(options, flushed), HitAndMiss(0, 0));
|
||||
}
|
||||
{
|
||||
// Note: prefix does work here as seek key, but only finds key equal
|
||||
@@ -3306,7 +3346,7 @@ TEST_F(DBBloomFilterTest, WeirdPrefixExtractorWithFilter2) {
|
||||
read_options.prefix_same_as_start = true;
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options));
|
||||
EXPECT_EQ(CountIter(iter, "qqqq"), 1);
|
||||
EXPECT_EQ(GetBloomStat(options, flushed), CheckedAndUseful(1, 0));
|
||||
EXPECT_EQ(GetBloomStat(options, flushed), HitAndMiss(1, 0));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3397,13 +3437,13 @@ TEST_F(DBBloomFilterTest, WeirdPrefixExtractorWithFilter3) {
|
||||
get_perf_context()->bloom_memtable_hit_count = 0;
|
||||
get_perf_context()->bloom_memtable_miss_count = 0;
|
||||
}
|
||||
EXPECT_EQ(GetBloomStat(options, flushed), CheckedAndUseful(0, 0));
|
||||
EXPECT_EQ(GetBloomStat(options, flushed), HitAndMiss(0, 0));
|
||||
{
|
||||
Slice ub("aaaa999");
|
||||
read_options.iterate_upper_bound = &ub;
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options));
|
||||
EXPECT_EQ(CountIter(iter, "aaaa000"), 3);
|
||||
EXPECT_EQ(GetBloomStat(options, flushed), CheckedAndUseful(1, 0));
|
||||
EXPECT_EQ(GetBloomStat(options, flushed), HitAndMiss(1, 0));
|
||||
}
|
||||
{
|
||||
// Note: prefix as seek key is not bloom-optimized
|
||||
@@ -3413,28 +3453,28 @@ TEST_F(DBBloomFilterTest, WeirdPrefixExtractorWithFilter3) {
|
||||
read_options.iterate_upper_bound = &ub;
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options));
|
||||
EXPECT_EQ(CountIter(iter, "aaaa"), 3);
|
||||
EXPECT_EQ(GetBloomStat(options, flushed), CheckedAndUseful(0, 0));
|
||||
EXPECT_EQ(GetBloomStat(options, flushed), HitAndMiss(0, 0));
|
||||
}
|
||||
{
|
||||
Slice ub("aaba9");
|
||||
read_options.iterate_upper_bound = &ub;
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options));
|
||||
EXPECT_EQ(CountIter(iter, "aaba0"), 1);
|
||||
EXPECT_EQ(GetBloomStat(options, flushed), CheckedAndUseful(1, 0));
|
||||
EXPECT_EQ(GetBloomStat(options, flushed), HitAndMiss(1, 0));
|
||||
}
|
||||
{
|
||||
Slice ub("aaca9");
|
||||
read_options.iterate_upper_bound = &ub;
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options));
|
||||
EXPECT_EQ(CountIter(iter, "aaca0"), 0);
|
||||
EXPECT_EQ(GetBloomStat(options, flushed), CheckedAndUseful(1, 1));
|
||||
EXPECT_EQ(GetBloomStat(options, flushed), HitAndMiss(0, 1));
|
||||
}
|
||||
{
|
||||
Slice ub("qqqq9");
|
||||
read_options.iterate_upper_bound = &ub;
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options));
|
||||
EXPECT_EQ(CountIter(iter, "qqqq0"), 1);
|
||||
EXPECT_EQ(GetBloomStat(options, flushed), CheckedAndUseful(1, 0));
|
||||
EXPECT_EQ(GetBloomStat(options, flushed), HitAndMiss(1, 0));
|
||||
}
|
||||
{
|
||||
// Note: prefix as seek key is not bloom-optimized
|
||||
@@ -3442,7 +3482,7 @@ TEST_F(DBBloomFilterTest, WeirdPrefixExtractorWithFilter3) {
|
||||
read_options.iterate_upper_bound = &ub;
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options));
|
||||
EXPECT_EQ(CountIter(iter, "qqqq"), weird_comparator ? 7 : 2);
|
||||
EXPECT_EQ(GetBloomStat(options, flushed), CheckedAndUseful(0, 0));
|
||||
EXPECT_EQ(GetBloomStat(options, flushed), HitAndMiss(0, 0));
|
||||
}
|
||||
{
|
||||
// Note: prefix as seek key is not bloom-optimized
|
||||
@@ -3450,14 +3490,14 @@ TEST_F(DBBloomFilterTest, WeirdPrefixExtractorWithFilter3) {
|
||||
read_options.iterate_upper_bound = &ub;
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options));
|
||||
EXPECT_EQ(CountIter(iter, "zzzz"), weird_comparator ? 8 : 1);
|
||||
EXPECT_EQ(GetBloomStat(options, flushed), CheckedAndUseful(0, 0));
|
||||
EXPECT_EQ(GetBloomStat(options, flushed), HitAndMiss(0, 0));
|
||||
}
|
||||
{
|
||||
Slice ub("zzzz9");
|
||||
read_options.iterate_upper_bound = &ub;
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options));
|
||||
EXPECT_EQ(CountIter(iter, "aab"), weird_comparator ? 6 : 5);
|
||||
EXPECT_EQ(GetBloomStat(options, flushed), CheckedAndUseful(0, 0));
|
||||
EXPECT_EQ(GetBloomStat(options, flushed), HitAndMiss(0, 0));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,142 @@
|
||||
// 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).
|
||||
|
||||
#include "db/db_test_util.h"
|
||||
#include "port/port.h"
|
||||
#include "util/random.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
class DBClipTest : public DBTestBase {
|
||||
public:
|
||||
DBClipTest() : DBTestBase("db_clip_test", /*env_do_fsync=*/true) {}
|
||||
};
|
||||
|
||||
TEST_F(DBClipTest, TestClipRange) {
|
||||
Options options = CurrentOptions();
|
||||
options.write_buffer_size = 10 * 1024 * 1024;
|
||||
options.max_bytes_for_level_multiplier = 2;
|
||||
options.num_levels = 3;
|
||||
options.max_background_compactions = 3;
|
||||
options.disable_auto_compactions = true;
|
||||
options.statistics = CreateDBStatistics();
|
||||
|
||||
DestroyAndReopen(options);
|
||||
int32_t value_size = 10 * 1024; // 10 KB
|
||||
|
||||
Random rnd(301);
|
||||
std::map<int32_t, std::string> values;
|
||||
|
||||
// file [0 => 100), [100 => 200), ... [900, 1000)
|
||||
for (auto i = 0; i < 10; i++) {
|
||||
for (auto j = 0; j < 100; j++) {
|
||||
auto k = i * 100 + j;
|
||||
values[k] = rnd.RandomString(value_size);
|
||||
ASSERT_OK(Put(Key(k), values[k]));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
ASSERT_EQ("10", FilesPerLevel(0));
|
||||
auto begin_key = Key(251), end_key = Key(751);
|
||||
ASSERT_OK(
|
||||
db_->ClipColumnFamily(db_->DefaultColumnFamily(), begin_key, end_key));
|
||||
|
||||
for (auto i = 0; i < 251; i++) {
|
||||
ReadOptions ropts;
|
||||
std::string result;
|
||||
auto s = db_->Get(ropts, Key(i), &result);
|
||||
ASSERT_TRUE(s.IsNotFound());
|
||||
}
|
||||
for (auto i = 251; i < 751; i++) {
|
||||
ASSERT_EQ(Get(Key(i)), values[i]);
|
||||
}
|
||||
for (auto i = 751; i < 1000; i++) {
|
||||
ReadOptions ropts;
|
||||
std::string result;
|
||||
auto s = db_->Get(ropts, Key(i), &result);
|
||||
ASSERT_TRUE(s.IsNotFound());
|
||||
}
|
||||
|
||||
std::vector<LiveFileMetaData> all_metadata;
|
||||
db_->GetLiveFilesMetaData(&all_metadata);
|
||||
for (auto& md : all_metadata) {
|
||||
// make sure clip_begin_key <= file_smallestkey <= file_largestkey <=
|
||||
// clip_end_key
|
||||
bool in_range = false;
|
||||
|
||||
if (options.comparator->Compare(begin_key, md.smallestkey) <= 0 &&
|
||||
options.comparator->Compare(end_key, md.largestkey) > 0) {
|
||||
in_range = true;
|
||||
}
|
||||
ASSERT_TRUE(in_range);
|
||||
}
|
||||
|
||||
CompactRangeOptions compact_options;
|
||||
compact_options.change_level = true;
|
||||
compact_options.target_level = 2;
|
||||
ASSERT_OK(db_->CompactRange(compact_options, nullptr, nullptr));
|
||||
ASSERT_EQ("0,0,3", FilesPerLevel(0));
|
||||
|
||||
for (auto i = 0; i < 10; i += 2) {
|
||||
for (auto j = 0; j < 100; j++) {
|
||||
auto k = i * 100 + j;
|
||||
ASSERT_OK(Put(Key(k), values[k]));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
ASSERT_EQ("5,0,3", FilesPerLevel(0));
|
||||
ASSERT_OK(dbfull()->TEST_CompactRange(0, nullptr, nullptr));
|
||||
ASSERT_EQ("0,5,3", FilesPerLevel(0));
|
||||
|
||||
for (auto i = 1; i < 10; i += 2) {
|
||||
for (auto j = 0; j < 100; j++) {
|
||||
auto k = i * 100 + j;
|
||||
ASSERT_OK(Put(Key(k), values[k]));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
ASSERT_EQ("5,5,3", FilesPerLevel(0));
|
||||
|
||||
auto begin_key_2 = Key(222), end_key_2 = Key(888);
|
||||
|
||||
ASSERT_OK(db_->ClipColumnFamily(db_->DefaultColumnFamily(), begin_key_2,
|
||||
end_key_2));
|
||||
|
||||
for (auto i = 0; i < 222; i++) {
|
||||
ReadOptions ropts;
|
||||
std::string result;
|
||||
auto s = db_->Get(ropts, Key(i), &result);
|
||||
ASSERT_TRUE(s.IsNotFound());
|
||||
}
|
||||
for (auto i = 222; i < 888; i++) {
|
||||
ASSERT_EQ(Get(Key(i)), values[i]);
|
||||
}
|
||||
for (auto i = 888; i < 1000; i++) {
|
||||
ReadOptions ropts;
|
||||
std::string result;
|
||||
auto s = db_->Get(ropts, Key(i), &result);
|
||||
ASSERT_TRUE(s.IsNotFound());
|
||||
}
|
||||
|
||||
std::vector<LiveFileMetaData> all_metadata_2;
|
||||
db_->GetLiveFilesMetaData(&all_metadata_2);
|
||||
for (auto& md : all_metadata_2) {
|
||||
// make sure clip_begin_key <= file_smallestkey <= file_largestkey <=
|
||||
// clip_end_key
|
||||
bool in_range = false;
|
||||
if (begin_key_2.compare(md.smallestkey) <= 0 &&
|
||||
end_key_2.compare(md.largestkey) > 0) {
|
||||
in_range = true;
|
||||
}
|
||||
ASSERT_TRUE(in_range);
|
||||
}
|
||||
}
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
ROCKSDB_NAMESPACE::port::InstallStackTraceHandler();
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
@@ -742,7 +742,7 @@ TEST_F(DBTestCompactionFilter, CompactionFilterContextCfId) {
|
||||
ASSERT_TRUE(filter->compaction_filter_created());
|
||||
}
|
||||
|
||||
// Compaction filters aplies to all records, regardless snapshots.
|
||||
// Compaction filters applies to all records, regardless snapshots.
|
||||
TEST_F(DBTestCompactionFilter, CompactionFilterIgnoreSnapshot) {
|
||||
std::string five = std::to_string(5);
|
||||
Options options = CurrentOptions();
|
||||
|
||||
+333
-27
@@ -136,11 +136,12 @@ class DBCompactionTestWithParam
|
||||
|
||||
class DBCompactionTestWithBottommostParam
|
||||
: public DBTestBase,
|
||||
public testing::WithParamInterface<BottommostLevelCompaction> {
|
||||
public testing::WithParamInterface<
|
||||
std::tuple<BottommostLevelCompaction, bool>> {
|
||||
public:
|
||||
DBCompactionTestWithBottommostParam()
|
||||
: DBTestBase("db_compaction_test", /*env_do_fsync=*/true) {
|
||||
bottommost_level_compaction_ = GetParam();
|
||||
bottommost_level_compaction_ = std::get<0>(GetParam());
|
||||
}
|
||||
|
||||
BottommostLevelCompaction bottommost_level_compaction_;
|
||||
@@ -1104,7 +1105,7 @@ TEST_F(DBCompactionTest, CompactionSstPartitionerNonTrivial) {
|
||||
ASSERT_OK(Put("bbbb1", "B"));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable());
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact(true));
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
|
||||
std::vector<LiveFileMetaData> files;
|
||||
dbfull()->GetLiveFilesMetaData(&files);
|
||||
@@ -3613,7 +3614,10 @@ TEST_F(DBCompactionTest, CancelCompactionWaitingOnConflict) {
|
||||
Random rnd(301);
|
||||
for (int i = 0; i < kNumSortedRuns; ++i) {
|
||||
int key_idx = 0;
|
||||
GenerateNewFile(&rnd, &key_idx, true /* nowait */);
|
||||
// We hold the compaction from happening, so when generating the last SST
|
||||
// file, we cannot wait. Otherwise, we'll hit a deadlock.
|
||||
GenerateNewFile(&rnd, &key_idx,
|
||||
(i == kNumSortedRuns - 1) ? true : false /* nowait */);
|
||||
}
|
||||
auto_compaction_sleeping_task.WaitUntilSleeping();
|
||||
|
||||
@@ -5071,7 +5075,11 @@ TEST_F(DBCompactionTest, CompactRangeShutdownWhileDelayed) {
|
||||
manual_compaction_thread.join();
|
||||
TEST_SYNC_POINT(
|
||||
"DBCompactionTest::CompactRangeShutdownWhileDelayed:PostManual");
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
if (i == 0) {
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
} else {
|
||||
ASSERT_NOK(dbfull()->TEST_WaitForCompact());
|
||||
}
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
}
|
||||
@@ -5506,8 +5514,9 @@ TEST_F(DBCompactionTest, CompactionLimiter) {
|
||||
|
||||
for (int n = 0; n < options.level0_file_num_compaction_trigger; n++) {
|
||||
for (unsigned int cf = 0; cf < cf_count; cf++) {
|
||||
// All L0s should overlap with each other
|
||||
for (int i = 0; i < kNumKeysPerFile; i++) {
|
||||
ASSERT_OK(Put(cf, Key(keyIndex++), ""));
|
||||
ASSERT_OK(Put(cf, Key(i), ""));
|
||||
}
|
||||
// put extra key to trigger flush
|
||||
ASSERT_OK(Put(cf, "", ""));
|
||||
@@ -6735,10 +6744,8 @@ class DBCompactionTestL0FilesMisorderCorruption : public DBCompactionTest {
|
||||
if (compaction_path_to_test == "FindIntraL0Compaction" ||
|
||||
compaction_path_to_test == "CompactRange") {
|
||||
fifo_options.allow_compaction = true;
|
||||
fifo_options.age_for_warm = 0;
|
||||
} else if (compaction_path_to_test == "CompactFile") {
|
||||
fifo_options.allow_compaction = false;
|
||||
fifo_options.age_for_warm = 0;
|
||||
}
|
||||
options_.compaction_options_fifo = fifo_options;
|
||||
}
|
||||
@@ -7335,10 +7342,63 @@ TEST_P(DBCompactionTestL0FilesMisorderCorruptionWithParam,
|
||||
Destroy(options_);
|
||||
}
|
||||
|
||||
TEST_F(DBCompactionTest, SingleLevelUniveresal) {
|
||||
// Tests that manual compaction works with single level universal compaction.
|
||||
Options options = CurrentOptions();
|
||||
options.compaction_style = kCompactionStyleUniversal;
|
||||
options.disable_auto_compactions = true;
|
||||
options.num_levels = 1;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
Random rnd(31);
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
for (int j = 0; j < 50; ++j) {
|
||||
ASSERT_OK(Put(Key(i * 100 + j), rnd.RandomString(50)));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
ASSERT_EQ(NumTableFilesAtLevel(0), 10);
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
||||
ASSERT_EQ(NumTableFilesAtLevel(0), 1);
|
||||
}
|
||||
|
||||
TEST_F(DBCompactionTest, SingleOverlappingNonL0BottommostManualCompaction) {
|
||||
// Tests that manual compact will rewrite bottommost level
|
||||
// when there is only a single non-L0 level that overlaps with
|
||||
// manual compaction range.
|
||||
constexpr int kSstNum = 10;
|
||||
Options options = CurrentOptions();
|
||||
options.disable_auto_compactions = true;
|
||||
options.num_levels = 7;
|
||||
for (auto b : {BottommostLevelCompaction::kForce,
|
||||
BottommostLevelCompaction::kForceOptimized}) {
|
||||
DestroyAndReopen(options);
|
||||
|
||||
// Generate some sst files on level 0 with sequence keys (no overlap)
|
||||
for (int i = 0; i < kSstNum; i++) {
|
||||
for (int j = 1; j < UCHAR_MAX; j++) {
|
||||
auto key = std::string(kSstNum, '\0');
|
||||
key[kSstNum - i] += static_cast<char>(j);
|
||||
ASSERT_OK(Put(key, std::string(i % 1000, 'A')));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
MoveFilesToLevel(4);
|
||||
ASSERT_EQ(NumTableFilesAtLevel(4), kSstNum);
|
||||
CompactRangeOptions cro;
|
||||
cro.bottommost_level_compaction = b;
|
||||
ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr));
|
||||
ASSERT_EQ(NumTableFilesAtLevel(4), 1);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(DBCompactionTestWithBottommostParam, SequenceKeysManualCompaction) {
|
||||
constexpr int kSstNum = 10;
|
||||
Options options = CurrentOptions();
|
||||
options.disable_auto_compactions = true;
|
||||
options.num_levels = 7;
|
||||
const bool dynamic_level = std::get<1>(GetParam());
|
||||
options.level_compaction_dynamic_level_bytes = dynamic_level;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
// Generate some sst files on level 0 with sequence keys (no overlap)
|
||||
@@ -7356,25 +7416,42 @@ TEST_P(DBCompactionTestWithBottommostParam, SequenceKeysManualCompaction) {
|
||||
|
||||
auto cro = CompactRangeOptions();
|
||||
cro.bottommost_level_compaction = bottommost_level_compaction_;
|
||||
bool trivial_moved = false;
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::BackgroundCompaction:TrivialMove",
|
||||
[&](void* /*arg*/) { trivial_moved = true; });
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
|
||||
// All bottommost_level_compaction options should allow l0 -> l1 trivial move.
|
||||
ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr));
|
||||
ASSERT_TRUE(trivial_moved);
|
||||
if (bottommost_level_compaction_ == BottommostLevelCompaction::kForce ||
|
||||
bottommost_level_compaction_ ==
|
||||
BottommostLevelCompaction::kForceOptimized) {
|
||||
// Real compaction to compact all sst files from level 0 to 1 file on level
|
||||
// 1
|
||||
ASSERT_EQ("0,1", FilesPerLevel(0));
|
||||
// bottommost level should go through intra-level compaction
|
||||
// and has only 1 file
|
||||
if (dynamic_level) {
|
||||
ASSERT_EQ("0,0,0,0,0,0,1", FilesPerLevel(0));
|
||||
} else {
|
||||
ASSERT_EQ("0,1", FilesPerLevel(0));
|
||||
}
|
||||
} else {
|
||||
// Just trivial move from level 0 -> 1
|
||||
ASSERT_EQ("0," + std::to_string(kSstNum), FilesPerLevel(0));
|
||||
// Just trivial move from level 0 -> 1/base
|
||||
if (dynamic_level) {
|
||||
ASSERT_EQ("0,0,0,0,0,0," + std::to_string(kSstNum), FilesPerLevel(0));
|
||||
} else {
|
||||
ASSERT_EQ("0," + std::to_string(kSstNum), FilesPerLevel(0));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(
|
||||
DBCompactionTestWithBottommostParam, DBCompactionTestWithBottommostParam,
|
||||
::testing::Values(BottommostLevelCompaction::kSkip,
|
||||
BottommostLevelCompaction::kIfHaveCompactionFilter,
|
||||
BottommostLevelCompaction::kForce,
|
||||
BottommostLevelCompaction::kForceOptimized));
|
||||
::testing::Combine(
|
||||
::testing::Values(BottommostLevelCompaction::kSkip,
|
||||
BottommostLevelCompaction::kIfHaveCompactionFilter,
|
||||
BottommostLevelCompaction::kForce,
|
||||
BottommostLevelCompaction::kForceOptimized),
|
||||
::testing::Bool()));
|
||||
|
||||
TEST_F(DBCompactionTest, UpdateLevelSubCompactionTest) {
|
||||
Options options = CurrentOptions();
|
||||
@@ -8518,7 +8595,7 @@ TEST_F(DBCompactionTest, CompactionWithChecksumHandoffManifest2) {
|
||||
Destroy(options);
|
||||
}
|
||||
|
||||
TEST_F(DBCompactionTest, FIFOWarm) {
|
||||
TEST_F(DBCompactionTest, FIFOChangeTemperature) {
|
||||
Options options = CurrentOptions();
|
||||
options.compaction_style = kCompactionStyleFIFO;
|
||||
options.num_levels = 1;
|
||||
@@ -8526,18 +8603,18 @@ TEST_F(DBCompactionTest, FIFOWarm) {
|
||||
options.level0_file_num_compaction_trigger = 2;
|
||||
options.create_if_missing = true;
|
||||
CompactionOptionsFIFO fifo_options;
|
||||
fifo_options.age_for_warm = 1000;
|
||||
fifo_options.file_temperature_age_thresholds = {{Temperature::kCold, 1000}};
|
||||
fifo_options.max_table_files_size = 100000000;
|
||||
options.compaction_options_fifo = fifo_options;
|
||||
env_->SetMockSleep();
|
||||
Reopen(options);
|
||||
|
||||
int total_warm = 0;
|
||||
int total_cold = 0;
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"NewWritableFile::FileOptions.temperature", [&](void* arg) {
|
||||
Temperature temperature = *(static_cast<Temperature*>(arg));
|
||||
if (temperature == Temperature::kWarm) {
|
||||
total_warm++;
|
||||
if (temperature == Temperature::kCold) {
|
||||
total_cold++;
|
||||
}
|
||||
});
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
|
||||
@@ -8574,9 +8651,9 @@ TEST_F(DBCompactionTest, FIFOWarm) {
|
||||
ASSERT_EQ(4, metadata.file_count);
|
||||
ASSERT_EQ(Temperature::kUnknown, metadata.levels[0].files[0].temperature);
|
||||
ASSERT_EQ(Temperature::kUnknown, metadata.levels[0].files[1].temperature);
|
||||
ASSERT_EQ(Temperature::kWarm, metadata.levels[0].files[2].temperature);
|
||||
ASSERT_EQ(Temperature::kWarm, metadata.levels[0].files[3].temperature);
|
||||
ASSERT_EQ(2, total_warm);
|
||||
ASSERT_EQ(Temperature::kCold, metadata.levels[0].files[2].temperature);
|
||||
ASSERT_EQ(Temperature::kCold, metadata.levels[0].files[3].temperature);
|
||||
ASSERT_EQ(2, total_cold);
|
||||
|
||||
Destroy(options);
|
||||
}
|
||||
@@ -8641,7 +8718,7 @@ TEST_F(DBCompactionTest, DisableMultiManualCompaction) {
|
||||
|
||||
sleeping_task_low.WakeUp();
|
||||
sleeping_task_low.WaitUntilDone();
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact(true));
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
}
|
||||
|
||||
TEST_F(DBCompactionTest, DisableJustStartedManualCompaction) {
|
||||
@@ -8773,7 +8850,7 @@ TEST_F(DBCompactionTest, DisableManualCompactionThreadQueueFull) {
|
||||
|
||||
sleeping_task_low.WakeUp();
|
||||
sleeping_task_low.WaitUntilDone();
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact(true));
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
ASSERT_EQ("0,1", FilesPerLevel(0));
|
||||
}
|
||||
|
||||
@@ -9108,6 +9185,235 @@ TEST_F(DBCompactionTest, BottommostFileCompactionAllowIngestBehind) {
|
||||
// ASSERT_OK(dbfull()->TEST_WaitForCompact(true /* wait_unscheduled */));
|
||||
}
|
||||
|
||||
TEST_F(DBCompactionTest, TurnOnLevelCompactionDynamicLevelBytes) {
|
||||
Options options = CurrentOptions();
|
||||
options.compaction_style = kCompactionStyleLevel;
|
||||
options.allow_ingest_behind = false;
|
||||
options.level_compaction_dynamic_level_bytes = false;
|
||||
options.num_levels = 6;
|
||||
options.compression = kNoCompression;
|
||||
options.max_bytes_for_level_base = 1 << 20;
|
||||
options.max_bytes_for_level_multiplier = 10;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
// put files in L0, L1 and L2
|
||||
WriteOptions write_opts;
|
||||
ASSERT_OK(db_->Put(write_opts, Key(1), "val1"));
|
||||
Random rnd(33);
|
||||
// Fill L2 with size larger than max_bytes_for_level_base,
|
||||
// so the level above it won't be drained.
|
||||
for (int i = 2; i <= (1 << 10); ++i) {
|
||||
ASSERT_OK(db_->Put(write_opts, Key(i), rnd.RandomString(2 << 10)));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
MoveFilesToLevel(2);
|
||||
ASSERT_OK(db_->Put(write_opts, Key(2), "val2"));
|
||||
ASSERT_OK(Flush());
|
||||
MoveFilesToLevel(2);
|
||||
ASSERT_OK(db_->Put(write_opts, Key(1), "new_val1"));
|
||||
ASSERT_OK(Flush());
|
||||
MoveFilesToLevel(1);
|
||||
ASSERT_OK(db_->Put(write_opts, Key(3), "val3"));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_EQ("1,1,2", FilesPerLevel());
|
||||
auto verify_db = [&]() {
|
||||
ASSERT_EQ(Get(Key(1)), "new_val1");
|
||||
ASSERT_EQ(Get(Key(2)), "val2");
|
||||
ASSERT_EQ(Get(Key(3)), "val3");
|
||||
};
|
||||
verify_db();
|
||||
|
||||
options.level_compaction_dynamic_level_bytes = true;
|
||||
Reopen(options);
|
||||
// except for L0, files should be pushed down as much as possible
|
||||
ASSERT_EQ("1,0,0,0,1,2", FilesPerLevel());
|
||||
verify_db();
|
||||
|
||||
// turning the options on and off should be safe
|
||||
options.level_compaction_dynamic_level_bytes = false;
|
||||
Reopen(options);
|
||||
MoveFilesToLevel(1);
|
||||
ASSERT_EQ("0,1,0,0,1,2", FilesPerLevel());
|
||||
verify_db();
|
||||
|
||||
// newly flushed file is also pushed down
|
||||
options.level_compaction_dynamic_level_bytes = true;
|
||||
Reopen(options);
|
||||
// Files in L1 should be trivially moved down during DB opening.
|
||||
// The file should be moved to L3, and then may be drained and compacted to
|
||||
// L4. So we just check L1 and L2 here.
|
||||
ASSERT_EQ(0, NumTableFilesAtLevel(1));
|
||||
ASSERT_EQ(0, NumTableFilesAtLevel(2));
|
||||
verify_db();
|
||||
}
|
||||
|
||||
TEST_F(DBCompactionTest, TurnOnLevelCompactionDynamicLevelBytesUCToLC) {
|
||||
// Basic test for migrating from UC to LC.
|
||||
// DB has non-empty L1 that should be pushed down to last level (L49).
|
||||
Options options = CurrentOptions();
|
||||
options.compaction_style = CompactionStyle::kCompactionStyleUniversal;
|
||||
options.allow_ingest_behind = false;
|
||||
options.level_compaction_dynamic_level_bytes = false;
|
||||
options.num_levels = 50;
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
|
||||
Random rnd(33);
|
||||
for (int f = 0; f < 10; ++f) {
|
||||
ASSERT_OK(Put(1, Key(f), rnd.RandomString(1000)));
|
||||
ASSERT_OK(Flush(1));
|
||||
}
|
||||
CompactRangeOptions compact_options;
|
||||
compact_options.change_level = true;
|
||||
compact_options.target_level = 1;
|
||||
ASSERT_OK(db_->CompactRange(compact_options, handles_[1], nullptr, nullptr));
|
||||
ASSERT_EQ("0,1", FilesPerLevel(1));
|
||||
|
||||
options.compaction_style = CompactionStyle::kCompactionStyleLevel;
|
||||
options.level_compaction_dynamic_level_bytes = true;
|
||||
ReopenWithColumnFamilies({"default", "pikachu"}, options);
|
||||
std::string expected_lsm = "";
|
||||
for (int i = 0; i < 49; ++i) {
|
||||
expected_lsm += "0,";
|
||||
}
|
||||
expected_lsm += "1";
|
||||
ASSERT_EQ(expected_lsm, FilesPerLevel(1));
|
||||
|
||||
// Tests that entries for trial move in MANIFEST should be valid
|
||||
ReopenWithColumnFamilies({"default", "pikachu"}, options);
|
||||
ASSERT_EQ(expected_lsm, FilesPerLevel(1));
|
||||
}
|
||||
|
||||
TEST_F(DBCompactionTest, DrainUnnecessaryLevelsAfterMultiplierChanged) {
|
||||
// When the level size multiplier increases such that fewer levels become
|
||||
// necessary, unnecessary levels should to be drained.
|
||||
const int kBaseLevelBytes = 256 << 10; // 256KB
|
||||
const int kFileBytes = 64 << 10; // 64KB
|
||||
const int kInitMultiplier = 2, kChangedMultiplier = 10;
|
||||
const int kNumFiles = 32;
|
||||
const int kNumLevels = 5;
|
||||
const int kValueBytes = 1 << 10; // 1KB
|
||||
|
||||
Options options = CurrentOptions();
|
||||
options.compression = kNoCompression;
|
||||
options.level_compaction_dynamic_level_bytes = true;
|
||||
options.max_bytes_for_level_base = kBaseLevelBytes;
|
||||
options.max_bytes_for_level_multiplier = kInitMultiplier;
|
||||
options.num_levels = kNumLevels;
|
||||
Reopen(options);
|
||||
|
||||
// Initially we setup the LSM to look roughly as follows:
|
||||
//
|
||||
// L0: empty
|
||||
// L1: 256KB
|
||||
// ...
|
||||
// L4: 1MB
|
||||
Random rnd(301);
|
||||
for (int file = 0; file < kNumFiles; ++file) {
|
||||
for (int i = 0; i < kFileBytes / kValueBytes; ++i) {
|
||||
ASSERT_OK(Put(Key(file * kFileBytes / kValueBytes + i),
|
||||
rnd.RandomString(kValueBytes)));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
|
||||
int init_num_nonempty = 0;
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
for (int level = 1; level < kNumLevels; ++level) {
|
||||
if (NumTableFilesAtLevel(level) > 0) {
|
||||
++init_num_nonempty;
|
||||
}
|
||||
}
|
||||
|
||||
// After increasing the multiplier and running compaction fewer levels are
|
||||
// needed to hold all the data. Unnecessary levels should be drained.
|
||||
ASSERT_OK(db_->SetOptions({{"max_bytes_for_level_multiplier",
|
||||
std::to_string(kChangedMultiplier)}}));
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
int final_num_nonempty = 0;
|
||||
for (int level = 1; level < kNumLevels; ++level) {
|
||||
if (NumTableFilesAtLevel(level) > 0) {
|
||||
++final_num_nonempty;
|
||||
}
|
||||
}
|
||||
ASSERT_GT(init_num_nonempty, final_num_nonempty);
|
||||
}
|
||||
|
||||
TEST_F(DBCompactionTest, DrainUnnecessaryLevelsAfterDBBecomesSmall) {
|
||||
// When the DB size is smaller, e.g., large chunk of data deleted by
|
||||
// DeleteRange(), unnecessary levels should to be drained.
|
||||
const int kBaseLevelBytes = 256 << 10; // 256KB
|
||||
const int kFileBytes = 64 << 10; // 64KB
|
||||
const int kMultiplier = 2;
|
||||
const int kNumFiles = 32;
|
||||
const int kNumLevels = 5;
|
||||
const int kValueBytes = 1 << 10; // 1KB
|
||||
const int kDeleteFileNum = 8;
|
||||
|
||||
Options options = CurrentOptions();
|
||||
options.compression = kNoCompression;
|
||||
options.level_compaction_dynamic_level_bytes = true;
|
||||
options.max_bytes_for_level_base = kBaseLevelBytes;
|
||||
options.max_bytes_for_level_multiplier = kMultiplier;
|
||||
options.num_levels = kNumLevels;
|
||||
Reopen(options);
|
||||
|
||||
// Initially we setup the LSM to look roughly as follows:
|
||||
//
|
||||
// L0: empty
|
||||
// L1: 256KB
|
||||
// ...
|
||||
// L4: 1MB
|
||||
Random rnd(301);
|
||||
for (int file = 0; file < kNumFiles; ++file) {
|
||||
for (int i = 0; i < kFileBytes / kValueBytes; ++i) {
|
||||
ASSERT_OK(Put(Key(file * kFileBytes / kValueBytes + i),
|
||||
rnd.RandomString(kValueBytes)));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
if (file == kDeleteFileNum) {
|
||||
// Ensure the DeleteRange() call below only delete data from last level
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
||||
ASSERT_EQ(NumTableFilesAtLevel(kNumLevels - 1), kDeleteFileNum + 1);
|
||||
}
|
||||
}
|
||||
|
||||
int init_num_nonempty = 0;
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
for (int level = 1; level < kNumLevels; ++level) {
|
||||
if (NumTableFilesAtLevel(level) > 0) {
|
||||
++init_num_nonempty;
|
||||
}
|
||||
}
|
||||
|
||||
// Disable auto compaction CompactRange() below
|
||||
ASSERT_OK(dbfull()->SetOptions({{"disable_auto_compactions", "true"}}));
|
||||
// Delete keys within first (kDeleteFileNum + 1) files' key ranges.
|
||||
// This should reduce DB size enough such that there is now
|
||||
// an unneeded level.
|
||||
std::string begin = Key(0);
|
||||
std::string end = Key(kDeleteFileNum * kFileBytes / kValueBytes);
|
||||
ASSERT_OK(
|
||||
db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), begin, end));
|
||||
Slice begin_slice = begin;
|
||||
Slice end_slice = end;
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), &begin_slice, &end_slice));
|
||||
int after_delete_range_nonempty = 0;
|
||||
for (int level = 1; level < kNumLevels; ++level) {
|
||||
if (NumTableFilesAtLevel(level) > 0) {
|
||||
++after_delete_range_nonempty;
|
||||
}
|
||||
}
|
||||
ASSERT_OK(dbfull()->SetOptions({{"disable_auto_compactions", "false"}}));
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
int final_num_nonempty = 0;
|
||||
for (int level = 1; level < kNumLevels; ++level) {
|
||||
if (NumTableFilesAtLevel(level) > 0) {
|
||||
++final_num_nonempty;
|
||||
}
|
||||
}
|
||||
ASSERT_GE(init_num_nonempty, after_delete_range_nonempty);
|
||||
ASSERT_GT(after_delete_range_nonempty, final_num_nonempty);
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
|
||||
@@ -34,11 +34,8 @@ Status DBImpl::FlushForGetLiveFiles() {
|
||||
// flush all dirty data to disk.
|
||||
Status status;
|
||||
if (immutable_db_options_.atomic_flush) {
|
||||
autovector<ColumnFamilyData*> cfds;
|
||||
SelectColumnFamiliesForAtomicFlush(&cfds);
|
||||
mutex_.Unlock();
|
||||
status =
|
||||
AtomicFlushMemTables(cfds, FlushOptions(), FlushReason::kGetLiveFiles);
|
||||
status = AtomicFlushMemTables(FlushOptions(), FlushReason::kGetLiveFiles);
|
||||
if (status.IsColumnFamilyDropped()) {
|
||||
status = Status::OK();
|
||||
}
|
||||
@@ -437,4 +434,3 @@ Status DBImpl::GetLiveFilesStorageInfo(
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
|
||||
+91
-1
@@ -740,7 +740,97 @@ class TestFlushListener : public EventListener {
|
||||
DBFlushTest* test_;
|
||||
};
|
||||
|
||||
// RocksDB lite does not support GetLiveFiles()
|
||||
TEST_F(
|
||||
DBFlushTest,
|
||||
FixUnrecoverableWriteDuringAtomicFlushWaitUntilFlushWouldNotStallWrites) {
|
||||
Options options = CurrentOptions();
|
||||
options.atomic_flush = true;
|
||||
|
||||
// To simulate a real-life crash where we can't flush during db's shutdown
|
||||
options.avoid_flush_during_shutdown = true;
|
||||
|
||||
// Set 3 low thresholds (while `disable_auto_compactions=false`) here so flush
|
||||
// adding one more L0 file during `GetLiveFiles()` will have to wait till such
|
||||
// flush will not stall writes
|
||||
options.level0_stop_writes_trigger = 2;
|
||||
options.level0_slowdown_writes_trigger = 2;
|
||||
// Disable level-0 compaction triggered by number of files to avoid
|
||||
// stalling check being skipped (resulting in the flush mentioned above didn't
|
||||
// wait)
|
||||
options.level0_file_num_compaction_trigger = -1;
|
||||
|
||||
CreateAndReopenWithCF({"cf1"}, options);
|
||||
|
||||
// Manually pause compaction thread to ensure enough L0 files as
|
||||
// `disable_auto_compactions=false`is needed, in order to meet the 3 low
|
||||
// thresholds above
|
||||
std::unique_ptr<test::SleepingBackgroundTask> sleeping_task_;
|
||||
sleeping_task_.reset(new test::SleepingBackgroundTask());
|
||||
env_->SetBackgroundThreads(1, Env::LOW);
|
||||
env_->Schedule(&test::SleepingBackgroundTask::DoSleepTask,
|
||||
sleeping_task_.get(), Env::Priority::LOW);
|
||||
sleeping_task_->WaitUntilSleeping();
|
||||
|
||||
// Create some initial file to help meet the 3 low thresholds above
|
||||
ASSERT_OK(Put(1, "dontcare", "dontcare"));
|
||||
ASSERT_OK(Flush(1));
|
||||
|
||||
// Insert some initial data so we have something to atomic-flush later
|
||||
// triggered by `GetLiveFiles()`
|
||||
WriteOptions write_opts;
|
||||
write_opts.disableWAL = true;
|
||||
ASSERT_OK(Put(1, "k1", "v1", write_opts));
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency({{
|
||||
"DBImpl::WaitUntilFlushWouldNotStallWrites:StallWait",
|
||||
"DBFlushTest::"
|
||||
"UnrecoverableWriteInAtomicFlushWaitUntilFlushWouldNotStallWrites::Write",
|
||||
}});
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
// Write to db when atomic flush releases the lock to wait on write stall
|
||||
// condition to be gone in `WaitUntilFlushWouldNotStallWrites()`
|
||||
port::Thread write_thread([&] {
|
||||
TEST_SYNC_POINT(
|
||||
"DBFlushTest::"
|
||||
"UnrecoverableWriteInAtomicFlushWaitUntilFlushWouldNotStallWrites::"
|
||||
"Write");
|
||||
// Before the fix, the empty default CF would've been prematurely excluded
|
||||
// from this atomic flush. The following two writes together make default CF
|
||||
// later contain data that should've been included in the atomic flush.
|
||||
ASSERT_OK(Put(0, "k2", "v2", write_opts));
|
||||
// The following write increases the max seqno of this atomic flush to be 3,
|
||||
// which is greater than the seqno of default CF's data. This then violates
|
||||
// the invariant that all entries of seqno less than the max seqno
|
||||
// of this atomic flush should've been flushed by the time of this atomic
|
||||
// flush finishes.
|
||||
ASSERT_OK(Put(1, "k3", "v3", write_opts));
|
||||
|
||||
// Resume compaction threads and reduce L0 files so `GetLiveFiles()` can
|
||||
// resume from the wait
|
||||
sleeping_task_->WakeUp();
|
||||
sleeping_task_->WaitUntilDone();
|
||||
MoveFilesToLevel(1, 1);
|
||||
});
|
||||
|
||||
// Trigger an atomic flush by `GetLiveFiles()`
|
||||
std::vector<std::string> files;
|
||||
uint64_t manifest_file_size;
|
||||
ASSERT_OK(db_->GetLiveFiles(files, &manifest_file_size, /*flush*/ true));
|
||||
|
||||
write_thread.join();
|
||||
|
||||
ReopenWithColumnFamilies({"default", "cf1"}, options);
|
||||
|
||||
ASSERT_EQ(Get(1, "k3"), "v3");
|
||||
// Prior to the fix, `Get()` will return `NotFound as "k2" entry in default CF
|
||||
// can't be recovered from a crash right after the atomic flush finishes,
|
||||
// resulting in a "recovery hole" as "k3" can be recovered. It's due to the
|
||||
// invariant violation described above.
|
||||
ASSERT_EQ(Get(0, "k2"), "v2");
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
|
||||
TEST_F(DBFlushTest, FixFlushReasonRaceFromConcurrentFlushes) {
|
||||
Options options = CurrentOptions();
|
||||
options.atomic_flush = true;
|
||||
|
||||
@@ -46,6 +46,11 @@ Status CompactedDBImpl::Get(const ReadOptions& options, ColumnFamilyHandle*,
|
||||
Status CompactedDBImpl::Get(const ReadOptions& options, ColumnFamilyHandle*,
|
||||
const Slice& key, PinnableSlice* value,
|
||||
std::string* timestamp) {
|
||||
if (options.io_activity != Env::IOActivity::kUnknown) {
|
||||
return Status::InvalidArgument(
|
||||
"Cannot call Get with `ReadOptions::io_activity` != "
|
||||
"`Env::IOActivity::kUnknown`");
|
||||
}
|
||||
assert(user_comparator_);
|
||||
if (options.timestamp) {
|
||||
const Status s = FailIfTsMismatchCf(
|
||||
|
||||
@@ -128,6 +128,13 @@ class CompactedDBImpl : public DBImpl {
|
||||
return Status::NotSupported("Not supported in compacted db mode.");
|
||||
}
|
||||
|
||||
using DB::ClipColumnFamily;
|
||||
virtual Status ClipColumnFamily(ColumnFamilyHandle* /*column_family*/,
|
||||
const Slice& /*begin*/,
|
||||
const Slice& /*end*/) override {
|
||||
return Status::NotSupported("Not supported in compacted db mode.");
|
||||
}
|
||||
|
||||
// FIXME: some missing overrides for more "write" functions
|
||||
// Share with DBImplReadOnly?
|
||||
|
||||
|
||||
+299
-48
@@ -258,6 +258,9 @@ DBImpl::DBImpl(const DBOptions& options, const std::string& dbname,
|
||||
co.capacity = table_cache_size;
|
||||
co.num_shard_bits = immutable_db_options_.table_cache_numshardbits;
|
||||
co.metadata_charge_policy = kDontChargeCacheMetadata;
|
||||
// TODO: Consider a non-fixed seed once test fallout (prefetch_test) is
|
||||
// dealt with
|
||||
co.hash_seed = 0;
|
||||
table_cache_ = NewLRUCache(co);
|
||||
SetDbSessionId();
|
||||
assert(!db_session_id_.empty());
|
||||
@@ -327,6 +330,9 @@ Status DBImpl::Resume() {
|
||||
// means a new super version wouldn't have been installed
|
||||
Status DBImpl::ResumeImpl(DBRecoverContext context) {
|
||||
mutex_.AssertHeld();
|
||||
|
||||
// TODO: plumb Env::IOActivity
|
||||
const ReadOptions read_options;
|
||||
WaitForBackgroundWork();
|
||||
|
||||
Status s;
|
||||
@@ -368,7 +374,7 @@ Status DBImpl::ResumeImpl(DBRecoverContext context) {
|
||||
assert(cfh);
|
||||
ColumnFamilyData* cfd = cfh->cfd();
|
||||
const MutableCFOptions& cf_opts = *cfd->GetLatestMutableCFOptions();
|
||||
s = versions_->LogAndApply(cfd, cf_opts, &edit, &mutex_,
|
||||
s = versions_->LogAndApply(cfd, cf_opts, read_options, &edit, &mutex_,
|
||||
directories_.GetDbDir());
|
||||
if (!s.ok()) {
|
||||
io_s = versions_->io_status();
|
||||
@@ -387,10 +393,8 @@ Status DBImpl::ResumeImpl(DBRecoverContext context) {
|
||||
// We allow flush to stall write since we are trying to resume from error.
|
||||
flush_opts.allow_write_stall = true;
|
||||
if (immutable_db_options_.atomic_flush) {
|
||||
autovector<ColumnFamilyData*> cfds;
|
||||
SelectColumnFamiliesForAtomicFlush(&cfds);
|
||||
mutex_.Unlock();
|
||||
s = AtomicFlushMemTables(cfds, flush_opts, context.flush_reason);
|
||||
s = AtomicFlushMemTables(flush_opts, context.flush_reason);
|
||||
mutex_.Lock();
|
||||
} else {
|
||||
for (auto cfd : versions_->GetRefedColumnFamilySet()) {
|
||||
@@ -507,11 +511,8 @@ void DBImpl::CancelAllBackgroundWork(bool wait) {
|
||||
has_unpersisted_data_.load(std::memory_order_relaxed) &&
|
||||
!mutable_db_options_.avoid_flush_during_shutdown) {
|
||||
if (immutable_db_options_.atomic_flush) {
|
||||
autovector<ColumnFamilyData*> cfds;
|
||||
SelectColumnFamiliesForAtomicFlush(&cfds);
|
||||
mutex_.Unlock();
|
||||
Status s =
|
||||
AtomicFlushMemTables(cfds, FlushOptions(), FlushReason::kShutDown);
|
||||
Status s = AtomicFlushMemTables(FlushOptions(), FlushReason::kShutDown);
|
||||
s.PermitUncheckedError(); //**TODO: What to do on error?
|
||||
mutex_.Lock();
|
||||
} else {
|
||||
@@ -1152,6 +1153,8 @@ FSDirectory* DBImpl::GetDataDir(ColumnFamilyData* cfd, size_t path_id) const {
|
||||
Status DBImpl::SetOptions(
|
||||
ColumnFamilyHandle* column_family,
|
||||
const std::unordered_map<std::string, std::string>& options_map) {
|
||||
// TODO: plumb Env::IOActivity
|
||||
const ReadOptions read_options;
|
||||
auto* cfd =
|
||||
static_cast_with_check<ColumnFamilyHandleImpl>(column_family)->cfd();
|
||||
if (options_map.empty()) {
|
||||
@@ -1173,8 +1176,8 @@ Status DBImpl::SetOptions(
|
||||
new_options = *cfd->GetLatestMutableCFOptions();
|
||||
// Append new version to recompute compaction score.
|
||||
VersionEdit dummy_edit;
|
||||
s = versions_->LogAndApply(cfd, new_options, &dummy_edit, &mutex_,
|
||||
directories_.GetDbDir());
|
||||
s = versions_->LogAndApply(cfd, new_options, read_options, &dummy_edit,
|
||||
&mutex_, directories_.GetDbDir());
|
||||
// Trigger possible flush/compactions. This has to be before we persist
|
||||
// options to file, otherwise there will be a deadlock with writer
|
||||
// thread.
|
||||
@@ -1512,7 +1515,9 @@ Status DBImpl::SyncWAL() {
|
||||
}
|
||||
if (status.ok() && synced_wals.IsWalAddition()) {
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
status = ApplyWALToManifest(&synced_wals);
|
||||
// TODO: plumb Env::IOActivity
|
||||
const ReadOptions read_options;
|
||||
status = ApplyWALToManifest(read_options, &synced_wals);
|
||||
}
|
||||
|
||||
TEST_SYNC_POINT("DBImpl::SyncWAL:BeforeMarkLogsSynced:2");
|
||||
@@ -1520,11 +1525,13 @@ Status DBImpl::SyncWAL() {
|
||||
return status;
|
||||
}
|
||||
|
||||
Status DBImpl::ApplyWALToManifest(VersionEdit* synced_wals) {
|
||||
Status DBImpl::ApplyWALToManifest(const ReadOptions& read_options,
|
||||
VersionEdit* synced_wals) {
|
||||
// not empty, write to MANIFEST.
|
||||
mutex_.AssertHeld();
|
||||
|
||||
Status status = versions_->LogAndApplyToDefaultColumnFamily(
|
||||
synced_wals, &mutex_, directories_.GetDbDir());
|
||||
read_options, synced_wals, &mutex_, directories_.GetDbDir());
|
||||
if (!status.ok() && versions_->io_status().IsIOError()) {
|
||||
status = error_handler_.SetBGError(versions_->io_status(),
|
||||
BackgroundErrorReason::kManifestWrite);
|
||||
@@ -1539,6 +1546,14 @@ Status DBImpl::LockWAL() {
|
||||
assert(lock_wal_write_token_);
|
||||
++lock_wal_count_;
|
||||
} else {
|
||||
// NOTE: this will "unnecessarily" wait for other non-LockWAL() write
|
||||
// stalls to clear before LockWAL returns, however fixing that would
|
||||
// not be simple because if we notice the primary queue is already
|
||||
// stalled, that stall might clear while we release DB mutex in
|
||||
// EnterUnbatched() for the nonmem queue. And if we work around that in
|
||||
// the naive way, we could deadlock by locking the two queues in different
|
||||
// orders.
|
||||
|
||||
WriteThread::Writer w;
|
||||
write_thread_.EnterUnbatched(&w, &mutex_);
|
||||
WriteThread::Writer nonmem_w;
|
||||
@@ -1571,6 +1586,8 @@ Status DBImpl::LockWAL() {
|
||||
|
||||
Status DBImpl::UnlockWAL() {
|
||||
bool signal = false;
|
||||
uint64_t maybe_stall_begun_count = 0;
|
||||
uint64_t nonmem_maybe_stall_begun_count = 0;
|
||||
{
|
||||
InstrumentedMutexLock lock(&mutex_);
|
||||
if (lock_wal_count_ == 0) {
|
||||
@@ -1580,12 +1597,28 @@ Status DBImpl::UnlockWAL() {
|
||||
if (lock_wal_count_ == 0) {
|
||||
lock_wal_write_token_.reset();
|
||||
signal = true;
|
||||
// For the last UnlockWAL, we don't want to return from UnlockWAL()
|
||||
// until the thread(s) that called BeginWriteStall() have had a chance to
|
||||
// call EndWriteStall(), so that no_slowdown writes after UnlockWAL() are
|
||||
// guaranteed to succeed if there's no other source of stall.
|
||||
maybe_stall_begun_count = write_thread_.GetBegunCountOfOutstandingStall();
|
||||
if (two_write_queues_) {
|
||||
nonmem_maybe_stall_begun_count =
|
||||
nonmem_write_thread_.GetBegunCountOfOutstandingStall();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (signal) {
|
||||
// SignalAll outside of mutex for efficiency
|
||||
bg_cv_.SignalAll();
|
||||
}
|
||||
// Ensure stalls have cleared
|
||||
if (maybe_stall_begun_count) {
|
||||
write_thread_.WaitForStallEndedCount(maybe_stall_begun_count);
|
||||
}
|
||||
if (nonmem_maybe_stall_begun_count) {
|
||||
nonmem_write_thread_.WaitForStallEndedCount(nonmem_maybe_stall_begun_count);
|
||||
}
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
@@ -1915,6 +1948,12 @@ Status DBImpl::GetEntity(const ReadOptions& read_options,
|
||||
"Cannot call GetEntity without a PinnableWideColumns object");
|
||||
}
|
||||
|
||||
if (read_options.io_activity != Env::IOActivity::kUnknown) {
|
||||
return Status::InvalidArgument(
|
||||
"Cannot call GetEntity with `ReadOptions::io_activity` != "
|
||||
"`Env::IOActivity::kUnknown`");
|
||||
}
|
||||
|
||||
columns->Reset();
|
||||
|
||||
GetImplOptions get_impl_options;
|
||||
@@ -1960,6 +1999,12 @@ Status DBImpl::GetImpl(const ReadOptions& read_options, const Slice& key,
|
||||
|
||||
assert(get_impl_options.column_family);
|
||||
|
||||
if (read_options.io_activity != Env::IOActivity::kUnknown) {
|
||||
return Status::InvalidArgument(
|
||||
"Cannot call Get with `ReadOptions::io_activity` != "
|
||||
"`Env::IOActivity::kUnknown`");
|
||||
}
|
||||
|
||||
if (read_options.timestamp) {
|
||||
const Status s = FailIfTsMismatchCf(get_impl_options.column_family,
|
||||
*(read_options.timestamp),
|
||||
@@ -2433,7 +2478,6 @@ std::vector<Status> DBImpl::MultiGet(
|
||||
|
||||
// Post processing (decrement reference counts and record statistics)
|
||||
PERF_TIMER_GUARD(get_post_process_time);
|
||||
autovector<SuperVersion*> superversions_to_delete;
|
||||
|
||||
for (auto mgd_iter : multiget_cf_data) {
|
||||
auto mgd = mgd_iter.second;
|
||||
@@ -2579,14 +2623,25 @@ void DBImpl::MultiGet(const ReadOptions& read_options, const size_t num_keys,
|
||||
ColumnFamilyHandle** column_families, const Slice* keys,
|
||||
PinnableSlice* values, Status* statuses,
|
||||
const bool sorted_input) {
|
||||
return MultiGet(read_options, num_keys, column_families, keys, values,
|
||||
/*timestamps=*/nullptr, statuses, sorted_input);
|
||||
MultiGet(read_options, num_keys, column_families, keys, values,
|
||||
/* timestamps */ nullptr, statuses, sorted_input);
|
||||
}
|
||||
|
||||
void DBImpl::MultiGet(const ReadOptions& read_options, const size_t num_keys,
|
||||
ColumnFamilyHandle** column_families, const Slice* keys,
|
||||
PinnableSlice* values, std::string* timestamps,
|
||||
Status* statuses, const bool sorted_input) {
|
||||
MultiGetCommon(read_options, num_keys, column_families, keys, values,
|
||||
/* columns */ nullptr, timestamps, statuses, sorted_input);
|
||||
}
|
||||
|
||||
void DBImpl::MultiGetCommon(const ReadOptions& read_options,
|
||||
const size_t num_keys,
|
||||
ColumnFamilyHandle** column_families,
|
||||
const Slice* keys, PinnableSlice* values,
|
||||
PinnableWideColumns* columns,
|
||||
std::string* timestamps, Status* statuses,
|
||||
const bool sorted_input) {
|
||||
if (num_keys == 0) {
|
||||
return;
|
||||
}
|
||||
@@ -2632,8 +2687,20 @@ void DBImpl::MultiGet(const ReadOptions& read_options, const size_t num_keys,
|
||||
autovector<KeyContext*, MultiGetContext::MAX_BATCH_SIZE> sorted_keys;
|
||||
sorted_keys.resize(num_keys);
|
||||
for (size_t i = 0; i < num_keys; ++i) {
|
||||
values[i].Reset();
|
||||
key_context.emplace_back(column_families[i], keys[i], &values[i],
|
||||
PinnableSlice* val = nullptr;
|
||||
PinnableWideColumns* col = nullptr;
|
||||
|
||||
if (values) {
|
||||
val = &values[i];
|
||||
val->Reset();
|
||||
} else {
|
||||
assert(columns);
|
||||
|
||||
col = &columns[i];
|
||||
col->Reset();
|
||||
}
|
||||
|
||||
key_context.emplace_back(column_families[i], keys[i], val, col,
|
||||
timestamps ? ×tamps[i] : nullptr,
|
||||
&statuses[i]);
|
||||
}
|
||||
@@ -2757,8 +2824,8 @@ void DBImpl::MultiGet(const ReadOptions& read_options,
|
||||
ColumnFamilyHandle* column_family, const size_t num_keys,
|
||||
const Slice* keys, PinnableSlice* values,
|
||||
Status* statuses, const bool sorted_input) {
|
||||
return MultiGet(read_options, column_family, num_keys, keys, values,
|
||||
/*timestamp=*/nullptr, statuses, sorted_input);
|
||||
MultiGet(read_options, column_family, num_keys, keys, values,
|
||||
/* timestamps */ nullptr, statuses, sorted_input);
|
||||
}
|
||||
|
||||
void DBImpl::MultiGet(const ReadOptions& read_options,
|
||||
@@ -2766,6 +2833,16 @@ void DBImpl::MultiGet(const ReadOptions& read_options,
|
||||
const Slice* keys, PinnableSlice* values,
|
||||
std::string* timestamps, Status* statuses,
|
||||
const bool sorted_input) {
|
||||
MultiGetCommon(read_options, column_family, num_keys, keys, values,
|
||||
/* columns */ nullptr, timestamps, statuses, sorted_input);
|
||||
}
|
||||
|
||||
void DBImpl::MultiGetCommon(const ReadOptions& read_options,
|
||||
ColumnFamilyHandle* column_family,
|
||||
const size_t num_keys, const Slice* keys,
|
||||
PinnableSlice* values, PinnableWideColumns* columns,
|
||||
std::string* timestamps, Status* statuses,
|
||||
bool sorted_input) {
|
||||
if (tracer_) {
|
||||
// TODO: This mutex should be removed later, to improve performance when
|
||||
// tracing is enabled.
|
||||
@@ -2779,8 +2856,20 @@ void DBImpl::MultiGet(const ReadOptions& read_options,
|
||||
autovector<KeyContext*, MultiGetContext::MAX_BATCH_SIZE> sorted_keys;
|
||||
sorted_keys.resize(num_keys);
|
||||
for (size_t i = 0; i < num_keys; ++i) {
|
||||
values[i].Reset();
|
||||
key_context.emplace_back(column_family, keys[i], &values[i],
|
||||
PinnableSlice* val = nullptr;
|
||||
PinnableWideColumns* col = nullptr;
|
||||
|
||||
if (values) {
|
||||
val = &values[i];
|
||||
val->Reset();
|
||||
} else {
|
||||
assert(columns);
|
||||
|
||||
col = &columns[i];
|
||||
col->Reset();
|
||||
}
|
||||
|
||||
key_context.emplace_back(column_family, keys[i], val, col,
|
||||
timestamps ? ×tamps[i] : nullptr,
|
||||
&statuses[i]);
|
||||
}
|
||||
@@ -2865,6 +2954,11 @@ Status DBImpl::MultiGetImpl(
|
||||
autovector<KeyContext*, MultiGetContext::MAX_BATCH_SIZE>* sorted_keys,
|
||||
SuperVersion* super_version, SequenceNumber snapshot,
|
||||
ReadCallback* callback) {
|
||||
if (read_options.io_activity != Env::IOActivity::kUnknown) {
|
||||
return Status::InvalidArgument(
|
||||
"Cannot call MultiGet with `ReadOptions::io_activity` != "
|
||||
"`Env::IOActivity::kUnknown`");
|
||||
}
|
||||
PERF_CPU_TIMER_GUARD(get_cpu_nanos, immutable_db_options_.clock);
|
||||
StopWatch sw(immutable_db_options_.clock, stats_, DB_MULTIGET);
|
||||
|
||||
@@ -2942,8 +3036,17 @@ Status DBImpl::MultiGetImpl(
|
||||
uint64_t bytes_read = 0;
|
||||
for (size_t i = start_key; i < start_key + num_keys - keys_left; ++i) {
|
||||
KeyContext* key = (*sorted_keys)[i];
|
||||
assert(key);
|
||||
assert(key->s);
|
||||
|
||||
if (key->s->ok()) {
|
||||
bytes_read += key->value->size();
|
||||
if (key->value) {
|
||||
bytes_read += key->value->size();
|
||||
} else {
|
||||
assert(key->columns);
|
||||
bytes_read += key->columns->serialized_size();
|
||||
}
|
||||
|
||||
num_found++;
|
||||
}
|
||||
}
|
||||
@@ -2967,6 +3070,22 @@ Status DBImpl::MultiGetImpl(
|
||||
return s;
|
||||
}
|
||||
|
||||
void DBImpl::MultiGetEntity(const ReadOptions& options, size_t num_keys,
|
||||
ColumnFamilyHandle** column_families,
|
||||
const Slice* keys, PinnableWideColumns* results,
|
||||
Status* statuses, bool sorted_input) {
|
||||
MultiGetCommon(options, num_keys, column_families, keys, /* values */ nullptr,
|
||||
results, /* timestamps */ nullptr, statuses, sorted_input);
|
||||
}
|
||||
|
||||
void DBImpl::MultiGetEntity(const ReadOptions& options,
|
||||
ColumnFamilyHandle* column_family, size_t num_keys,
|
||||
const Slice* keys, PinnableWideColumns* results,
|
||||
Status* statuses, bool sorted_input) {
|
||||
MultiGetCommon(options, column_family, num_keys, keys, /* values */ nullptr,
|
||||
results, /* timestamps */ nullptr, statuses, sorted_input);
|
||||
}
|
||||
|
||||
Status DBImpl::CreateColumnFamily(const ColumnFamilyOptions& cf_options,
|
||||
const std::string& column_family,
|
||||
ColumnFamilyHandle** handle) {
|
||||
@@ -3038,6 +3157,8 @@ Status DBImpl::CreateColumnFamilies(
|
||||
Status DBImpl::CreateColumnFamilyImpl(const ColumnFamilyOptions& cf_options,
|
||||
const std::string& column_family_name,
|
||||
ColumnFamilyHandle** handle) {
|
||||
// TODO: plumb Env::IOActivity
|
||||
const ReadOptions read_options;
|
||||
Status s;
|
||||
*handle = nullptr;
|
||||
|
||||
@@ -3078,9 +3199,9 @@ Status DBImpl::CreateColumnFamilyImpl(const ColumnFamilyOptions& cf_options,
|
||||
write_thread_.EnterUnbatched(&w, &mutex_);
|
||||
// LogAndApply will both write the creation in MANIFEST and create
|
||||
// ColumnFamilyData object
|
||||
s = versions_->LogAndApply(nullptr, MutableCFOptions(cf_options), &edit,
|
||||
&mutex_, directories_.GetDbDir(), false,
|
||||
&cf_options);
|
||||
s = versions_->LogAndApply(nullptr, MutableCFOptions(cf_options),
|
||||
read_options, &edit, &mutex_,
|
||||
directories_.GetDbDir(), false, &cf_options);
|
||||
write_thread_.ExitUnbatched(&w);
|
||||
}
|
||||
if (s.ok()) {
|
||||
@@ -3159,6 +3280,8 @@ Status DBImpl::DropColumnFamilies(
|
||||
}
|
||||
|
||||
Status DBImpl::DropColumnFamilyImpl(ColumnFamilyHandle* column_family) {
|
||||
// TODO: plumb Env::IOActivity
|
||||
const ReadOptions read_options;
|
||||
auto cfh = static_cast_with_check<ColumnFamilyHandleImpl>(column_family);
|
||||
auto cfd = cfh->cfd();
|
||||
if (cfd->GetID() == 0) {
|
||||
@@ -3181,8 +3304,9 @@ Status DBImpl::DropColumnFamilyImpl(ColumnFamilyHandle* column_family) {
|
||||
// we drop column family from a single write thread
|
||||
WriteThread::Writer w;
|
||||
write_thread_.EnterUnbatched(&w, &mutex_);
|
||||
s = versions_->LogAndApply(cfd, *cfd->GetLatestMutableCFOptions(), &edit,
|
||||
&mutex_, directories_.GetDbDir());
|
||||
s = versions_->LogAndApply(cfd, *cfd->GetLatestMutableCFOptions(),
|
||||
read_options, &edit, &mutex_,
|
||||
directories_.GetDbDir());
|
||||
write_thread_.ExitUnbatched(&w);
|
||||
}
|
||||
if (s.ok()) {
|
||||
@@ -3233,10 +3357,13 @@ bool DBImpl::KeyMayExist(const ReadOptions& read_options,
|
||||
std::string* value, std::string* timestamp,
|
||||
bool* value_found) {
|
||||
assert(value != nullptr);
|
||||
assert(read_options.io_activity == Env::IOActivity::kUnknown);
|
||||
|
||||
if (value_found != nullptr) {
|
||||
// falsify later if key-may-exist but can't fetch value
|
||||
*value_found = true;
|
||||
}
|
||||
// TODO: plumb Env::IOActivity
|
||||
ReadOptions roptions = read_options;
|
||||
roptions.read_tier = kBlockCacheTier; // read from block cache only
|
||||
PinnableSlice pinnable_val;
|
||||
@@ -3265,6 +3392,11 @@ Iterator* DBImpl::NewIterator(const ReadOptions& read_options,
|
||||
return NewErrorIterator(Status::NotSupported(
|
||||
"ReadTier::kPersistedData is not yet supported in iterators."));
|
||||
}
|
||||
if (read_options.io_activity != Env::IOActivity::kUnknown) {
|
||||
return NewErrorIterator(Status::InvalidArgument(
|
||||
"Cannot call NewIterator with `ReadOptions::io_activity` != "
|
||||
"`Env::IOActivity::kUnknown`"));
|
||||
}
|
||||
|
||||
assert(column_family);
|
||||
|
||||
@@ -3400,6 +3532,11 @@ Status DBImpl::NewIterators(
|
||||
return Status::NotSupported(
|
||||
"ReadTier::kPersistedData is not yet supported in iterators.");
|
||||
}
|
||||
if (read_options.io_activity != Env::IOActivity::kUnknown) {
|
||||
return Status::InvalidArgument(
|
||||
"Cannot call NewIterators with `ReadOptions::io_activity` != "
|
||||
"`Env::IOActivity::kUnknown`");
|
||||
}
|
||||
|
||||
if (read_options.timestamp) {
|
||||
for (auto* cf : column_families) {
|
||||
@@ -3717,7 +3854,9 @@ Status DBImpl::GetPropertiesOfAllTables(ColumnFamilyHandle* column_family,
|
||||
version->Ref();
|
||||
mutex_.Unlock();
|
||||
|
||||
auto s = version->GetPropertiesOfAllTables(props);
|
||||
// TODO: plumb Env::IOActivity
|
||||
const ReadOptions read_options;
|
||||
auto s = version->GetPropertiesOfAllTables(read_options, props);
|
||||
|
||||
// Decrement the ref count
|
||||
mutex_.Lock();
|
||||
@@ -3739,7 +3878,9 @@ Status DBImpl::GetPropertiesOfTablesInRange(ColumnFamilyHandle* column_family,
|
||||
version->Ref();
|
||||
mutex_.Unlock();
|
||||
|
||||
auto s = version->GetPropertiesOfTablesInRange(range, n, props);
|
||||
// TODO: plumb Env::IOActivity
|
||||
const ReadOptions read_options;
|
||||
auto s = version->GetPropertiesOfTablesInRange(read_options, range, n, props);
|
||||
|
||||
// Decrement the ref count
|
||||
mutex_.Lock();
|
||||
@@ -4072,6 +4213,8 @@ Status DBImpl::GetApproximateSizes(const SizeApproximationOptions& options,
|
||||
SuperVersion* sv = GetAndRefSuperVersion(cfd);
|
||||
v = sv->current;
|
||||
|
||||
// TODO: plumb Env::IOActivity
|
||||
const ReadOptions read_options;
|
||||
for (int i = 0; i < n; i++) {
|
||||
Slice start = range[i].start;
|
||||
Slice limit = range[i].limit;
|
||||
@@ -4093,7 +4236,7 @@ Status DBImpl::GetApproximateSizes(const SizeApproximationOptions& options,
|
||||
sizes[i] = 0;
|
||||
if (options.include_files) {
|
||||
sizes[i] += versions_->ApproximateSize(
|
||||
options, v, k1.Encode(), k2.Encode(), /*start_level=*/0,
|
||||
options, read_options, v, k1.Encode(), k2.Encode(), /*start_level=*/0,
|
||||
/*end_level=*/-1, TableReaderCaller::kUserApproximateSize);
|
||||
}
|
||||
if (options.include_memtables) {
|
||||
@@ -4141,6 +4284,8 @@ Status DBImpl::GetUpdatesSince(
|
||||
}
|
||||
|
||||
Status DBImpl::DeleteFile(std::string name) {
|
||||
// TODO: plumb Env::IOActivity
|
||||
const ReadOptions read_options;
|
||||
uint64_t number;
|
||||
FileType type;
|
||||
WalFileType log_type;
|
||||
@@ -4220,7 +4365,8 @@ Status DBImpl::DeleteFile(std::string name) {
|
||||
edit.SetColumnFamily(cfd->GetID());
|
||||
edit.DeleteFile(level, number);
|
||||
status = versions_->LogAndApply(cfd, *cfd->GetLatestMutableCFOptions(),
|
||||
&edit, &mutex_, directories_.GetDbDir());
|
||||
read_options, &edit, &mutex_,
|
||||
directories_.GetDbDir());
|
||||
if (status.ok()) {
|
||||
InstallSuperVersionAndScheduleWork(cfd,
|
||||
&job_context.superversion_contexts[0],
|
||||
@@ -4242,6 +4388,8 @@ Status DBImpl::DeleteFile(std::string name) {
|
||||
Status DBImpl::DeleteFilesInRanges(ColumnFamilyHandle* column_family,
|
||||
const RangePtr* ranges, size_t n,
|
||||
bool include_end) {
|
||||
// TODO: plumb Env::IOActivity
|
||||
const ReadOptions read_options;
|
||||
Status status = Status::OK();
|
||||
auto cfh = static_cast_with_check<ColumnFamilyHandleImpl>(column_family);
|
||||
ColumnFamilyData* cfd = cfh->cfd();
|
||||
@@ -4307,7 +4455,8 @@ Status DBImpl::DeleteFilesInRanges(ColumnFamilyHandle* column_family,
|
||||
}
|
||||
input_version->Ref();
|
||||
status = versions_->LogAndApply(cfd, *cfd->GetLatestMutableCFOptions(),
|
||||
&edit, &mutex_, directories_.GetDbDir());
|
||||
read_options, &edit, &mutex_,
|
||||
directories_.GetDbDir());
|
||||
if (status.ok()) {
|
||||
InstallSuperVersionAndScheduleWork(cfd,
|
||||
&job_context.superversion_contexts[0],
|
||||
@@ -4373,7 +4522,6 @@ void DBImpl::GetAllColumnFamilyMetaData(
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Status DBImpl::CheckConsistency() {
|
||||
mutex_.AssertHeld();
|
||||
std::vector<LiveFileMetaData> metadata;
|
||||
@@ -4764,6 +4912,15 @@ Status DBImpl::WriteOptionsFile(bool need_mutex_lock,
|
||||
if (s.ok()) {
|
||||
s = RenameTempFileToOptionsFile(file_name);
|
||||
}
|
||||
|
||||
if (!s.ok() && GetEnv()->FileExists(file_name).ok()) {
|
||||
if (!GetEnv()->DeleteFile(file_name).ok()) {
|
||||
ROCKS_LOG_WARN(immutable_db_options_.info_log,
|
||||
"Unable to delete temp options file %s",
|
||||
file_name.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
// restore lock
|
||||
if (!need_mutex_lock) {
|
||||
mutex_.Lock();
|
||||
@@ -4943,6 +5100,7 @@ Status DBImpl::GetLatestSequenceForKey(
|
||||
MergeContext merge_context;
|
||||
SequenceNumber max_covering_tombstone_seq = 0;
|
||||
|
||||
// TODO: plumb Env::IOActivity
|
||||
ReadOptions read_options;
|
||||
SequenceNumber current_seq = versions_->LastSequence();
|
||||
|
||||
@@ -5098,6 +5256,8 @@ Status DBImpl::IngestExternalFile(
|
||||
|
||||
Status DBImpl::IngestExternalFiles(
|
||||
const std::vector<IngestExternalFileArg>& args) {
|
||||
// TODO: plumb Env::IOActivity
|
||||
const ReadOptions read_options;
|
||||
if (args.empty()) {
|
||||
return Status::InvalidArgument("ingestion arg list is empty");
|
||||
}
|
||||
@@ -5254,12 +5414,10 @@ Status DBImpl::IngestExternalFiles(
|
||||
FlushOptions flush_opts;
|
||||
flush_opts.allow_write_stall = true;
|
||||
if (immutable_db_options_.atomic_flush) {
|
||||
autovector<ColumnFamilyData*> cfds_to_flush;
|
||||
SelectColumnFamiliesForAtomicFlush(&cfds_to_flush);
|
||||
mutex_.Unlock();
|
||||
status = AtomicFlushMemTables(cfds_to_flush, flush_opts,
|
||||
FlushReason::kExternalFileIngestion,
|
||||
true /* entered_write_thread */);
|
||||
status = AtomicFlushMemTables(
|
||||
flush_opts, FlushReason::kExternalFileIngestion,
|
||||
{} /* provided_candidate_cfds */, true /* entered_write_thread */);
|
||||
mutex_.Lock();
|
||||
} else {
|
||||
for (size_t i = 0; i != num_cfs; ++i) {
|
||||
@@ -5317,9 +5475,9 @@ Status DBImpl::IngestExternalFiles(
|
||||
}
|
||||
assert(0 == num_entries);
|
||||
}
|
||||
status =
|
||||
versions_->LogAndApply(cfds_to_commit, mutable_cf_options_list,
|
||||
edit_lists, &mutex_, directories_.GetDbDir());
|
||||
status = versions_->LogAndApply(cfds_to_commit, mutable_cf_options_list,
|
||||
read_options, edit_lists, &mutex_,
|
||||
directories_.GetDbDir());
|
||||
// It is safe to update VersionSet last seqno here after LogAndApply since
|
||||
// LogAndApply persists last sequence number from VersionEdits,
|
||||
// which are from file's largest seqno and not from VersionSet.
|
||||
@@ -5420,6 +5578,8 @@ Status DBImpl::CreateColumnFamilyWithImport(
|
||||
const ExportImportFilesMetaData& metadata, ColumnFamilyHandle** handle) {
|
||||
assert(handle != nullptr);
|
||||
assert(*handle == nullptr);
|
||||
// TODO: plumb Env::IOActivity
|
||||
const ReadOptions read_options;
|
||||
std::string cf_comparator_name = options.comparator->Name();
|
||||
if (cf_comparator_name != metadata.db_comparator_name) {
|
||||
return Status::InvalidArgument("Comparator name mismatch");
|
||||
@@ -5461,8 +5621,9 @@ Status DBImpl::CreateColumnFamilyWithImport(
|
||||
// file, we have to make sure the file number will never being reused.
|
||||
next_file_number = versions_->FetchAddFileNumber(metadata.files.size());
|
||||
auto cf_options = cfd->GetLatestMutableCFOptions();
|
||||
status = versions_->LogAndApply(cfd, *cf_options, &dummy_edit, &mutex_,
|
||||
directories_.GetDbDir());
|
||||
status =
|
||||
versions_->LogAndApply(cfd, *cf_options, read_options, &dummy_edit,
|
||||
&mutex_, directories_.GetDbDir());
|
||||
if (status.ok()) {
|
||||
InstallSuperVersionAndScheduleWork(cfd, &dummy_sv_ctx, *cf_options);
|
||||
}
|
||||
@@ -5498,8 +5659,9 @@ Status DBImpl::CreateColumnFamilyWithImport(
|
||||
// Install job edit [Mutex will be unlocked here]
|
||||
if (status.ok()) {
|
||||
auto cf_options = cfd->GetLatestMutableCFOptions();
|
||||
status = versions_->LogAndApply(cfd, *cf_options, import_job.edit(),
|
||||
&mutex_, directories_.GetDbDir());
|
||||
status = versions_->LogAndApply(cfd, *cf_options, read_options,
|
||||
import_job.edit(), &mutex_,
|
||||
directories_.GetDbDir());
|
||||
if (status.ok()) {
|
||||
InstallSuperVersionAndScheduleWork(cfd, &sv_context, *cf_options);
|
||||
}
|
||||
@@ -5542,6 +5704,81 @@ Status DBImpl::CreateColumnFamilyWithImport(
|
||||
return status;
|
||||
}
|
||||
|
||||
Status DBImpl::ClipColumnFamily(ColumnFamilyHandle* column_family,
|
||||
const Slice& begin_key, const Slice& end_key) {
|
||||
assert(column_family);
|
||||
Status status;
|
||||
// Flush memtable
|
||||
FlushOptions flush_opts;
|
||||
flush_opts.allow_write_stall = true;
|
||||
auto* cfd =
|
||||
static_cast_with_check<ColumnFamilyHandleImpl>(column_family)->cfd();
|
||||
if (immutable_db_options_.atomic_flush) {
|
||||
status = AtomicFlushMemTables(flush_opts, FlushReason::kDeleteFiles,
|
||||
{} /* provided_candidate_cfds */,
|
||||
false /* entered_write_thread */);
|
||||
} else {
|
||||
status = FlushMemTable(cfd, flush_opts, FlushReason::kDeleteFiles,
|
||||
false /* entered_write_thread */);
|
||||
}
|
||||
|
||||
if (status.ok()) {
|
||||
// DeleteFilesInRanges non-overlap files except L0
|
||||
std::vector<RangePtr> ranges;
|
||||
ranges.push_back(RangePtr(nullptr, &begin_key));
|
||||
ranges.push_back(RangePtr(&end_key, nullptr));
|
||||
status = DeleteFilesInRanges(column_family, ranges.data(), ranges.size());
|
||||
}
|
||||
|
||||
// DeleteRange the remaining overlapping keys
|
||||
bool empty_after_delete = false;
|
||||
if (status.ok()) {
|
||||
Slice smallest_user_key, largest_user_key;
|
||||
{
|
||||
// Lock db mutex
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
cfd->current()->GetSstFilesBoundaryKeys(&smallest_user_key,
|
||||
&largest_user_key);
|
||||
}
|
||||
// all the files has been deleted after DeleteFilesInRanges;
|
||||
if (smallest_user_key.empty() && largest_user_key.empty()) {
|
||||
empty_after_delete = true;
|
||||
} else {
|
||||
const Comparator* const ucmp = column_family->GetComparator();
|
||||
WriteOptions wo;
|
||||
// Delete [smallest_user_key, clip_begin_key)
|
||||
if (ucmp->Compare(smallest_user_key, begin_key) < 0) {
|
||||
status = DeleteRange(wo, column_family, smallest_user_key, begin_key);
|
||||
}
|
||||
|
||||
if (status.ok()) {
|
||||
// Delete [clip_end_key, largest_use_key]
|
||||
if (ucmp->Compare(end_key, largest_user_key) < 0) {
|
||||
status = DeleteRange(wo, column_family, end_key, largest_user_key);
|
||||
if (status.ok()) {
|
||||
status = Delete(wo, column_family, largest_user_key);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (status.ok() && !empty_after_delete) {
|
||||
// CompactRange delete all the tombstones
|
||||
CompactRangeOptions compact_options;
|
||||
compact_options.exclusive_manual_compaction = true;
|
||||
compact_options.bottommost_level_compaction =
|
||||
BottommostLevelCompaction::kForceOptimized;
|
||||
// We could just compact the ranges [null, clip_begin_key] and
|
||||
// [clip_end_key, null]. But due to how manual compaction calculates the
|
||||
// last level to compact to and that range tombstones are not dropped
|
||||
// during non-bottommost compactions, calling CompactRange() on these two
|
||||
// ranges may not clear all range tombstones.
|
||||
status = CompactRange(compact_options, nullptr, nullptr);
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
Status DBImpl::VerifyFileChecksums(const ReadOptions& read_options) {
|
||||
return VerifyChecksumInternal(read_options, /*use_file_checksum=*/true);
|
||||
}
|
||||
@@ -5559,6 +5796,12 @@ Status DBImpl::VerifyChecksumInternal(const ReadOptions& read_options,
|
||||
|
||||
Status s;
|
||||
|
||||
if (read_options.io_activity != Env::IOActivity::kUnknown) {
|
||||
s = Status::InvalidArgument(
|
||||
"Cannot verify file checksum with `ReadOptions::io_activity` != "
|
||||
"`Env::IOActivity::kUnknown`");
|
||||
return s;
|
||||
}
|
||||
if (use_file_checksum) {
|
||||
FileChecksumGenFactory* const file_checksum_gen_factory =
|
||||
immutable_db_options_.file_checksum_gen_factory.get();
|
||||
@@ -5672,6 +5915,12 @@ Status DBImpl::VerifyFullFileChecksum(const std::string& file_checksum_expected,
|
||||
const std::string& func_name_expected,
|
||||
const std::string& fname,
|
||||
const ReadOptions& read_options) {
|
||||
if (read_options.io_activity != Env::IOActivity::kUnknown) {
|
||||
return Status::InvalidArgument(
|
||||
"Cannot call VerifyChecksum with `ReadOptions::io_activity` != "
|
||||
"`Env::IOActivity::kUnknown`");
|
||||
}
|
||||
|
||||
Status s;
|
||||
if (file_checksum_expected == kUnknownFileChecksum) {
|
||||
return s;
|
||||
@@ -5804,6 +6053,8 @@ Status DBImpl::ReserveFileNumbersBeforeIngestion(
|
||||
ColumnFamilyData* cfd, uint64_t num,
|
||||
std::unique_ptr<std::list<uint64_t>::iterator>& pending_output_elem,
|
||||
uint64_t* next_file_number) {
|
||||
// TODO: plumb Env::IOActivity
|
||||
const ReadOptions read_options;
|
||||
Status s;
|
||||
SuperVersionContext dummy_sv_ctx(true /* create_superversion */);
|
||||
assert(nullptr != next_file_number);
|
||||
@@ -5821,8 +6072,8 @@ Status DBImpl::ReserveFileNumbersBeforeIngestion(
|
||||
// reuse the file number that has already assigned to the internal file,
|
||||
// and this will overwrite the external file. To protect the external
|
||||
// file, we have to make sure the file number will never being reused.
|
||||
s = versions_->LogAndApply(cfd, *cf_options, &dummy_edit, &mutex_,
|
||||
directories_.GetDbDir());
|
||||
s = versions_->LogAndApply(cfd, *cf_options, read_options, &dummy_edit,
|
||||
&mutex_, directories_.GetDbDir());
|
||||
if (s.ok()) {
|
||||
InstallSuperVersionAndScheduleWork(cfd, &dummy_sv_ctx, *cf_options);
|
||||
}
|
||||
|
||||
+81
-35
@@ -277,33 +277,40 @@ class DBImpl : public DB {
|
||||
// The values and statuses parameters are arrays with number of elements
|
||||
// equal to keys.size(). This allows the storage for those to be alloacted
|
||||
// by the caller on the stack for small batches
|
||||
virtual void MultiGet(const ReadOptions& options,
|
||||
ColumnFamilyHandle* column_family,
|
||||
const size_t num_keys, const Slice* keys,
|
||||
PinnableSlice* values, Status* statuses,
|
||||
const bool sorted_input = false) override;
|
||||
virtual void MultiGet(const ReadOptions& options,
|
||||
ColumnFamilyHandle* column_family,
|
||||
const size_t num_keys, const Slice* keys,
|
||||
PinnableSlice* values, std::string* timestamps,
|
||||
Status* statuses,
|
||||
const bool sorted_input = false) override;
|
||||
void MultiGet(const ReadOptions& options, ColumnFamilyHandle* column_family,
|
||||
const size_t num_keys, const Slice* keys, PinnableSlice* values,
|
||||
Status* statuses, const bool sorted_input = false) override;
|
||||
void MultiGet(const ReadOptions& options, ColumnFamilyHandle* column_family,
|
||||
const size_t num_keys, const Slice* keys, PinnableSlice* values,
|
||||
std::string* timestamps, Status* statuses,
|
||||
const bool sorted_input = false) override;
|
||||
|
||||
virtual void MultiGet(const ReadOptions& options, const size_t num_keys,
|
||||
ColumnFamilyHandle** column_families, const Slice* keys,
|
||||
PinnableSlice* values, Status* statuses,
|
||||
const bool sorted_input = false) override;
|
||||
virtual void MultiGet(const ReadOptions& options, const size_t num_keys,
|
||||
ColumnFamilyHandle** column_families, const Slice* keys,
|
||||
PinnableSlice* values, std::string* timestamps,
|
||||
Status* statuses,
|
||||
const bool sorted_input = false) override;
|
||||
void MultiGet(const ReadOptions& options, const size_t num_keys,
|
||||
ColumnFamilyHandle** column_families, const Slice* keys,
|
||||
PinnableSlice* values, Status* statuses,
|
||||
const bool sorted_input = false) override;
|
||||
void MultiGet(const ReadOptions& options, const size_t num_keys,
|
||||
ColumnFamilyHandle** column_families, const Slice* keys,
|
||||
PinnableSlice* values, std::string* timestamps,
|
||||
Status* statuses, const bool sorted_input = false) override;
|
||||
|
||||
virtual void MultiGetWithCallback(
|
||||
void MultiGetWithCallback(
|
||||
const ReadOptions& options, ColumnFamilyHandle* column_family,
|
||||
ReadCallback* callback,
|
||||
autovector<KeyContext*, MultiGetContext::MAX_BATCH_SIZE>* sorted_keys);
|
||||
|
||||
using DB::MultiGetEntity;
|
||||
|
||||
void MultiGetEntity(const ReadOptions& options,
|
||||
ColumnFamilyHandle* column_family, size_t num_keys,
|
||||
const Slice* keys, PinnableWideColumns* results,
|
||||
Status* statuses, bool sorted_input) override;
|
||||
|
||||
void MultiGetEntity(const ReadOptions& options, size_t num_keys,
|
||||
ColumnFamilyHandle** column_families, const Slice* keys,
|
||||
PinnableWideColumns* results, Status* statuses,
|
||||
bool sorted_input) override;
|
||||
|
||||
virtual Status CreateColumnFamily(const ColumnFamilyOptions& cf_options,
|
||||
const std::string& column_family,
|
||||
ColumnFamilyHandle** handle) override;
|
||||
@@ -527,6 +534,11 @@ class DBImpl : public DB {
|
||||
const ExportImportFilesMetaData& metadata,
|
||||
ColumnFamilyHandle** handle) override;
|
||||
|
||||
using DB::ClipColumnFamily;
|
||||
virtual Status ClipColumnFamily(ColumnFamilyHandle* column_family,
|
||||
const Slice& begin_key,
|
||||
const Slice& end_key) override;
|
||||
|
||||
using DB::VerifyFileChecksums;
|
||||
Status VerifyFileChecksums(const ReadOptions& read_options) override;
|
||||
|
||||
@@ -727,13 +739,17 @@ class DBImpl : public DB {
|
||||
// max_file_num_to_ignore allows bottom level compaction to filter out newly
|
||||
// compacted SST files. Setting max_file_num_to_ignore to kMaxUint64 will
|
||||
// disable the filtering
|
||||
// If `final_output_level` is not nullptr, it is set to manual compaction's
|
||||
// output level if returned status is OK, and it may or may not be set to
|
||||
// manual compaction's output level if returned status is not OK.
|
||||
Status RunManualCompaction(ColumnFamilyData* cfd, int input_level,
|
||||
int output_level,
|
||||
const CompactRangeOptions& compact_range_options,
|
||||
const Slice* begin, const Slice* end,
|
||||
bool exclusive, bool disallow_trivial_move,
|
||||
uint64_t max_file_num_to_ignore,
|
||||
const std::string& trim_ts);
|
||||
const std::string& trim_ts,
|
||||
int* final_output_level = nullptr);
|
||||
|
||||
// Return an internal iterator over the current state of the database.
|
||||
// The keys of this iterator are internal keys (see format.h).
|
||||
@@ -1042,10 +1058,16 @@ class DBImpl : public DB {
|
||||
|
||||
VersionSet* GetVersionSet() const { return versions_.get(); }
|
||||
|
||||
// Wait for any compaction
|
||||
// We add a bool parameter to wait for unscheduledCompactions_ == 0, but this
|
||||
// is only for the special test of CancelledCompactions
|
||||
Status WaitForCompact(bool waitUnscheduled = false);
|
||||
// Wait for all flush and compactions jobs to finish. Jobs to wait include the
|
||||
// unscheduled (queued, but not scheduled yet). If the db is shutting down,
|
||||
// Status::ShutdownInProgress will be returned. If PauseBackgroundWork() was
|
||||
// called prior to this, this may potentially wait for unscheduled jobs
|
||||
// indefinitely. abort_on_pause can be set to true to abort, and
|
||||
// Status::Aborted will be returned immediately. This may also never return if
|
||||
// there's sufficient ongoing writes that keeps flush and compaction going
|
||||
// without stopping. The user would have to cease all the writes to DB to make
|
||||
// this eventually return in a stable state.
|
||||
Status WaitForCompact(bool abort_on_pause = false);
|
||||
|
||||
#ifndef NDEBUG
|
||||
// Compact any files in the named level that overlap [*begin, *end]
|
||||
@@ -1074,8 +1096,9 @@ class DBImpl : public DB {
|
||||
// is because in certain cases, we can flush column families, wait for the
|
||||
// flush to complete, but delete the column family handle before the wait
|
||||
// finishes. For example in CompactRange.
|
||||
Status TEST_AtomicFlushMemTables(const autovector<ColumnFamilyData*>& cfds,
|
||||
const FlushOptions& flush_opts);
|
||||
Status TEST_AtomicFlushMemTables(
|
||||
const autovector<ColumnFamilyData*>& provided_candidate_cfds,
|
||||
const FlushOptions& flush_opts);
|
||||
|
||||
// Wait for background threads to complete scheduled work.
|
||||
Status TEST_WaitForBackgroundWork();
|
||||
@@ -1084,9 +1107,7 @@ class DBImpl : public DB {
|
||||
Status TEST_WaitForFlushMemTable(ColumnFamilyHandle* column_family = nullptr);
|
||||
|
||||
// Wait for any compaction
|
||||
// We add a bool parameter to wait for unscheduledCompactions_ == 0, but this
|
||||
// is only for the special test of CancelledCompactions
|
||||
Status TEST_WaitForCompact(bool waitUnscheduled = false);
|
||||
Status TEST_WaitForCompact(bool abort_on_pause = false);
|
||||
|
||||
// Wait for any background purge
|
||||
Status TEST_WaitForPurge();
|
||||
@@ -1117,6 +1138,8 @@ class DBImpl : public DB {
|
||||
|
||||
void TEST_UnlockMutex();
|
||||
|
||||
void TEST_SignalAllBgCv();
|
||||
|
||||
// REQUIRES: mutex locked
|
||||
void* TEST_BeginWrite();
|
||||
|
||||
@@ -1877,16 +1900,27 @@ class DBImpl : public DB {
|
||||
|
||||
Status SwitchMemtable(ColumnFamilyData* cfd, WriteContext* context);
|
||||
|
||||
void SelectColumnFamiliesForAtomicFlush(autovector<ColumnFamilyData*>* cfds);
|
||||
// Select and output column families qualified for atomic flush in
|
||||
// `selected_cfds`. If `provided_candidate_cfds` is non-empty, it will be used
|
||||
// as candidate CFs to select qualified ones from. Otherwise, all column
|
||||
// families are used as candidate to select from.
|
||||
//
|
||||
// REQUIRES: mutex held
|
||||
void SelectColumnFamiliesForAtomicFlush(
|
||||
autovector<ColumnFamilyData*>* selected_cfds,
|
||||
const autovector<ColumnFamilyData*>& provided_candidate_cfds = {});
|
||||
|
||||
// Force current memtable contents to be flushed.
|
||||
Status FlushMemTable(ColumnFamilyData* cfd, const FlushOptions& options,
|
||||
FlushReason flush_reason,
|
||||
bool entered_write_thread = false);
|
||||
|
||||
// Atomic-flush memtables from quanlified CFs among `provided_candidate_cfds`
|
||||
// (if non-empty) or amomg all column families and atomically record the
|
||||
// result to the MANIFEST.
|
||||
Status AtomicFlushMemTables(
|
||||
const autovector<ColumnFamilyData*>& column_family_datas,
|
||||
const FlushOptions& options, FlushReason flush_reason,
|
||||
const autovector<ColumnFamilyData*>& provided_candidate_cfds = {},
|
||||
bool entered_write_thread = false);
|
||||
|
||||
// Wait until flushing this column family won't stall writes
|
||||
@@ -2102,7 +2136,7 @@ class DBImpl : public DB {
|
||||
|
||||
// helper function to call after some of the logs_ were synced
|
||||
void MarkLogsSynced(uint64_t up_to, bool synced_dir, VersionEdit* edit);
|
||||
Status ApplyWALToManifest(VersionEdit* edit);
|
||||
Status ApplyWALToManifest(const ReadOptions& read_options, VersionEdit* edit);
|
||||
// WALs with log number up to up_to are not synced successfully.
|
||||
void MarkLogsNotSynced(uint64_t up_to);
|
||||
|
||||
@@ -2189,6 +2223,18 @@ class DBImpl : public DB {
|
||||
const size_t num_keys, bool sorted,
|
||||
autovector<KeyContext*, MultiGetContext::MAX_BATCH_SIZE>* key_ptrs);
|
||||
|
||||
void MultiGetCommon(const ReadOptions& options,
|
||||
ColumnFamilyHandle* column_family, const size_t num_keys,
|
||||
const Slice* keys, PinnableSlice* values,
|
||||
PinnableWideColumns* columns, std::string* timestamps,
|
||||
Status* statuses, bool sorted_input);
|
||||
|
||||
void MultiGetCommon(const ReadOptions& options, const size_t num_keys,
|
||||
ColumnFamilyHandle** column_families, const Slice* keys,
|
||||
PinnableSlice* values, PinnableWideColumns* columns,
|
||||
std::string* timestamps, Status* statuses,
|
||||
bool sorted_input);
|
||||
|
||||
// A structure to hold the information required to process MultiGet of keys
|
||||
// belonging to one column family. For a multi column family MultiGet, there
|
||||
// will be a container of these objects.
|
||||
@@ -2273,7 +2319,7 @@ class DBImpl : public DB {
|
||||
// logfile_number_. With two_write_queues it also protects alive_log_files_,
|
||||
// and log_empty_. Refer to the definition of each variable below for more
|
||||
// details.
|
||||
// Note: to avoid dealock, if needed to acquire both log_write_mutex_ and
|
||||
// Note: to avoid deadlock, if needed to acquire both log_write_mutex_ and
|
||||
// mutex_, the order should be first mutex_ and then log_write_mutex_.
|
||||
InstrumentedMutex log_write_mutex_;
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user