mirror of
https://github.com/facebook/rocksdb.git
synced 2026-11-07 03:22:34 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7d2d9518fb | ||
|
|
6a43615041 | ||
|
|
af72046d14 | ||
|
|
501f359dd1 | ||
|
|
fc1df4c688 | ||
|
|
3b6ac67f13 | ||
|
|
d3dbed0e96 | ||
|
|
f8741f1f3b |
No files matched your search
@@ -33,7 +33,9 @@ jobs:
|
||||
run: pip install argparse
|
||||
|
||||
- name: Download clang-format-diff.py
|
||||
run: wget https://raw.githubusercontent.com/llvm/llvm-project/release/12.x/clang/tools/clang-format/clang-format-diff.py
|
||||
uses: wei/wget@v1
|
||||
with:
|
||||
args: https://raw.githubusercontent.com/llvm/llvm-project/release/12.x/clang/tools/clang-format/clang-format-diff.py
|
||||
|
||||
- name: Check format
|
||||
run: VERBOSE_CHECK=1 make check-format
|
||||
|
||||
@@ -95,5 +95,3 @@ fuzz/crash-*
|
||||
|
||||
cmake-build-*
|
||||
third-party/folly/
|
||||
.cache
|
||||
*.sublime-*
|
||||
+53
-23
@@ -253,10 +253,33 @@ if(CMAKE_SYSTEM_PROCESSOR MATCHES "loongarch64")
|
||||
endif(HAS_LOONGARCH64)
|
||||
endif(CMAKE_SYSTEM_PROCESSOR MATCHES "loongarch64")
|
||||
|
||||
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)
|
||||
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()
|
||||
if(CMAKE_SYSTEM_PROCESSOR MATCHES "^s390x")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=z196")
|
||||
endif()
|
||||
@@ -264,21 +287,10 @@ if(PORTABLE STREQUAL 1)
|
||||
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)
|
||||
@@ -293,6 +305,25 @@ 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")
|
||||
@@ -547,7 +578,7 @@ if(HAVE_SCHED_GETCPU)
|
||||
add_definitions(-DROCKSDB_SCHED_GETCPU_PRESENT)
|
||||
endif()
|
||||
|
||||
check_cxx_symbol_exists(getauxval "sys/auxv.h" HAVE_AUXV_GETAUXVAL)
|
||||
check_cxx_symbol_exists(getauxval auvx.h HAVE_AUXV_GETAUXVAL)
|
||||
if(HAVE_AUXV_GETAUXVAL)
|
||||
add_definitions(-DROCKSDB_AUXV_GETAUXVAL_PRESENT)
|
||||
endif()
|
||||
@@ -865,8 +896,6 @@ set(SOURCES
|
||||
util/string_util.cc
|
||||
util/thread_local.cc
|
||||
util/threadpool_imp.cc
|
||||
util/udt_util.cc
|
||||
util/write_batch_util.cc
|
||||
util/xxhash.cc
|
||||
utilities/agg_merge/agg_merge.cc
|
||||
utilities/backup/backup_engine.cc
|
||||
@@ -981,6 +1010,12 @@ 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
|
||||
@@ -1292,7 +1327,6 @@ 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
|
||||
@@ -1423,7 +1457,6 @@ if(WITH_TESTS)
|
||||
util/timer_test.cc
|
||||
util/thread_list_test.cc
|
||||
util/thread_local_test.cc
|
||||
util/udt_util_test.cc
|
||||
util/work_queue_test.cc
|
||||
utilities/agg_merge/agg_merge_test.cc
|
||||
utilities/backup/backup_engine_test.cc
|
||||
@@ -1592,6 +1625,3 @@ option(WITH_BENCHMARK "build benchmark tests" OFF)
|
||||
if(WITH_BENCHMARK)
|
||||
add_subdirectory(${PROJECT_SOURCE_DIR}/microbench/)
|
||||
endif()
|
||||
|
||||
target_include_directories(${PROJECT_NAME} PUBLIC
|
||||
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>)
|
||||
+1
-156
@@ -1,162 +1,7 @@
|
||||
# Rocksdb Change Log
|
||||
> NOTE: Entries for next release do not go here. Follow instructions in `unreleased_history/README.txt`
|
||||
|
||||
## 8.6.7 (09/26/2023)
|
||||
### Bug Fixes
|
||||
* Fixed a bug where compaction read under non direct IO still falls back to RocksDB internal prefetching after file system's prefetching returns non-OK status other than `Status::NotSupported()`
|
||||
|
||||
### Behavior Changes
|
||||
* For non direct IO, eliminate the file system prefetching attempt for compaction read when `Options::compaction_readahead_size` is 0
|
||||
|
||||
## 8.6.6 (09/25/2023)
|
||||
### Bug Fixes
|
||||
* Fix a bug with atomic_flush=true that can cause DB to stuck after a flush fails (#11872).
|
||||
* Fix a bug where RocksDB (with atomic_flush=false) can delete output SST files of pending flushes when a previous concurrent flush fails (#11865). This can result in DB entering read-only state with error message like `IO error: No such file or directory: While open a file for random read: /tmp/rocksdbtest-501/db_flush_test_87732_4230653031040984171/000013.sst`.
|
||||
* When the compressed secondary cache capacity is reduced to 0, it should be completely disabled. Before this fix, inserts and lookups would still go to the backing `LRUCache` before returning, thus incurring locking overhead. With this fix, inserts and lookups are no-ops and do not add any overhead.
|
||||
|
||||
## 8.6.5 (09/15/2023)
|
||||
### Bug Fixes
|
||||
* Fixed a bug where `rocksdb.file.read.verify.file.checksums.micros` is not populated.
|
||||
|
||||
## 8.6.4 (09/13/2023)
|
||||
### Public API changes
|
||||
* Add a column family option `default_temperature` that is used for file reading accounting purpose, such as io statistics, for files that don't have an explicitly set temperature.
|
||||
|
||||
## 8.6.3 (09/12/2023)
|
||||
### Bug Fixes
|
||||
* Fix a bug where if there is an error reading from offset 0 of a file from L1+ and that the file is not the first file in the sorted run, data can be lost in compaction and read/scan can return incorrect results.
|
||||
* Fix a bug where iterator may return incorrect result for DeleteRange() users if there was an error reading from a file.
|
||||
|
||||
## 8.6.2 (09/11/2023)
|
||||
### Bug Fixes
|
||||
* Add a fix for async_io where during seek, when reading a block for seeking a target key in a file without any readahead, the iterator aligned the read on a page boundary and reading more than necessary. This increased the storage read bandwidth usage.
|
||||
|
||||
## 8.6.1 (08/30/2023)
|
||||
### Public API Changes
|
||||
* `Options::compaction_readahead_size` 's default value is changed from 0 to 2MB.
|
||||
|
||||
### Behavior Changes
|
||||
* Compaction read performance will regress when `Options::compaction_readahead_size` is explicitly set to 0
|
||||
|
||||
## 8.6.0 (08/18/2023)
|
||||
### New Features
|
||||
* Added enhanced data integrity checking on SST files with new format_version=6. Performance impact is very small or negligible. Previously if SST data was misplaced or re-arranged by the storage layer, it could pass block checksum with higher than 1 in 4 billion probability. With format_version=6, block checksums depend on what file they are in and location within the file. This way, misplaced SST data is no more likely to pass checksum verification than randomly corrupted data. Also in format_version=6, SST footers are checksum-protected.
|
||||
* Add a new feature to trim readahead_size during scans upto upper_bound when iterate_upper_bound is specified. It's enabled through ReadOptions.auto_readahead_size. Users must also specify ReadOptions.iterate_upper_bound.
|
||||
* RocksDB will compare the number of input keys to the number of keys processed after each compaction. Compaction will fail and report Corruption status if the verification fails. Option `compaction_verify_record_count` is introduced for this purpose and is enabled by default.
|
||||
* Add a CF option `bottommost_file_compaction_delay` to allow specifying the delay of bottommost level single-file compactions.
|
||||
* Add support to allow enabling / disabling user-defined timestamps feature for an existing column family in combination with the in-Memtable only feature.
|
||||
* Implement a new admission policy for the compressed secondary cache that admits blocks evicted from the primary cache with the hit bit set. This policy can be specified in TieredVolatileCacheOptions by setting the newly added adm_policy option.
|
||||
* Add a column family option `memtable_max_range_deletions` that limits the number of range deletions in a memtable. RocksDB will try to do an automatic flush after the limit is reached. (#11358)
|
||||
* Add PutEntity API in sst_file_writer
|
||||
* Add `timeout` in microsecond option to `WaitForCompactOptions` to allow timely termination of prolonged waiting in scenarios like recurring recoverable errors, such as out-of-space situations and continuous write streams that sustain ongoing flush and compactions
|
||||
* New statistics `rocksdb.file.read.{get|multiget|db.iterator|verify.checksum|verify.file.checksums}.micros` measure read time of block-based SST tables or blob files during db open, `Get()`, `MultiGet()`, using db iterator, `VerifyFileChecksums()` and `VerifyChecksum()`. They require stats level greater than `StatsLevel::kExceptDetailedTimers`.
|
||||
* Add close_db option to `WaitForCompactOptions` to call Close() after waiting is done.
|
||||
* Add a new compression option `CompressionOptions::checksum` for enabling ZSTD's checksum feature to detect corruption during decompression.
|
||||
|
||||
### Public API Changes
|
||||
* Mark `Options::access_hint_on_compaction_start` related APIs as deprecated. See #11631 for alternative behavior.
|
||||
|
||||
### Behavior Changes
|
||||
* Statistics `rocksdb.sst.read.micros` now includes time spent on multi read and async read into the file
|
||||
* For Universal Compaction users, periodic compaction (option `periodic_compaction_seconds`) will be set to 30 days by default if block based table is used.
|
||||
|
||||
### Bug Fixes
|
||||
* Fix a bug in FileTTLBooster that can cause users with a large number of levels (more than 65) to see errors like "runtime error: shift exponent .. is too large.." (#11673).
|
||||
|
||||
## 8.5.0 (07/21/2023)
|
||||
### Public API Changes
|
||||
* Removed recently added APIs `GeneralCache` and `MakeSharedGeneralCache()` as our plan changed to stop exposing a general-purpose cache interface. The old forms of these APIs, `Cache` and `NewLRUCache()`, are still available, although general-purpose caching support will be dropped eventually.
|
||||
|
||||
### Behavior Changes
|
||||
* Option `periodic_compaction_seconds` no longer supports FIFO compaction: setting it has no effect on FIFO compactions. FIFO compaction users should only set option `ttl` instead.
|
||||
* Move prefetching responsibility to page cache for compaction read for non directIO use case
|
||||
|
||||
### Performance Improvements
|
||||
* In case of direct_io, if buffer passed by callee is already aligned, RandomAccessFileRead::Read will avoid realloacting a new buffer, reducing memcpy and use already passed aligned buffer.
|
||||
* Small efficiency improvement to HyperClockCache by reducing chance of compiler-generated heap allocations
|
||||
|
||||
### Bug Fixes
|
||||
* Fix use_after_free bug in async_io MultiReads when underlying FS enabled kFSBuffer. kFSBuffer is when underlying FS pass their own buffer instead of using RocksDB scratch in FSReadRequest. Right now it's an experimental feature.
|
||||
|
||||
## 8.4.0 (06/26/2023)
|
||||
### New Features
|
||||
* Add FSReadRequest::fs_scratch which is a data buffer allocated and provided by underlying FileSystem to RocksDB during reads, when FS wants to provide its own buffer with data instead of using RocksDB provided FSReadRequest::scratch. This can help in cpu optimization by avoiding copy from file system's buffer to RocksDB buffer. More details on how to use/enable it in file_system.h. Right now its supported only for MultiReads(async + sync) with non direct io.
|
||||
* Start logging non-zero user-defined timestamp sizes in WAL to signal user key format in subsequent records and use it during recovery. This change will break recovery from WAL files written by early versions that contain user-defined timestamps. The workaround is to ensure there are no WAL files to recover (i.e. by flushing before close) before upgrade.
|
||||
* Added new property "rocksdb.obsolete-sst-files-size-property" that reports the size of SST files that have become obsolete but have not yet been deleted or scheduled for deletion
|
||||
* Start to record the value of the flag `AdvancedColumnFamilyOptions.persist_user_defined_timestamps` in the Manifest and table properties for a SST file when it is created. And use the recorded flag when creating a table reader for the SST file. This flag is only explicitly record if it's false.
|
||||
* Add a new option OptimisticTransactionDBOptions::shared_lock_buckets that enables sharing mutexes for validating transactions between DB instances, for better balancing memory efficiency and validation contention across DB instances. Different column families and DBs also now use different hash seeds in this validation, so that the same set of key names will not contend across DBs or column families.
|
||||
* Add a new ticker `rocksdb.files.marked.trash.deleted` to track the number of trash files deleted by background thread from the trash queue.
|
||||
* Add an API NewTieredVolatileCache() in include/rocksdb/cache.h to allocate an instance of a block cache with a primary block cache tier and a compressed secondary cache tier. A cache of this type distributes memory reservations against the block cache, such as WriteBufferManager, table reader memory etc., proportionally across both the primary and compressed secondary cache.
|
||||
* Add `WaitForCompact()` to wait for all flush and compactions jobs to finish. Jobs to wait include the unscheduled (queued, but not scheduled yet).
|
||||
* Add `WriteBatch::Release()` that releases the batch's serialized data to the caller.
|
||||
|
||||
### Public API Changes
|
||||
* Add C API `rocksdb_options_add_compact_on_deletion_collector_factory_del_ratio`.
|
||||
* change the FileSystem::use_async_io() API to SupportedOps API in order to extend it to various operations supported by underlying FileSystem. Right now it contains FSSupportedOps::kAsyncIO and FSSupportedOps::kFSBuffer. More details about FSSupportedOps in filesystem.h
|
||||
* Add new tickers: `rocksdb.error.handler.bg.error.count`, `rocksdb.error.handler.bg.io.error.count`, `rocksdb.error.handler.bg.retryable.io.error.count` to replace the misspelled ones: `rocksdb.error.handler.bg.errro.count`, `rocksdb.error.handler.bg.io.errro.count`, `rocksdb.error.handler.bg.retryable.io.errro.count` ('error' instead of 'errro'). Users should switch to use the new tickers before 9.0 release as the misspelled old tickers will be completely removed then.
|
||||
* Overload the API CreateColumnFamilyWithImport() to support creating ColumnFamily by importing multiple ColumnFamilies It requires that CFs should not overlap in user key range.
|
||||
|
||||
### Behavior Changes
|
||||
* Change the default value for option `level_compaction_dynamic_level_bytes` to true. This affects users who use leveled compaction and do not set this option explicitly. These users may see additional background compactions following DB open. These compactions help to shape the LSM according to `level_compaction_dynamic_level_bytes` such that the size of each level Ln is approximately size of Ln-1 * `max_bytes_for_level_multiplier`. Turning on this option has other benefits too: see more detail in wiki: https://github.com/facebook/rocksdb/wiki/Leveled-Compaction#option-level_compaction_dynamic_level_bytes-and-levels-target-size and in option comment in advanced_options.h (#11525).
|
||||
* For Leveled Compaction users, `CompactRange()` will now always try to compact to the last non-empty level. (#11468)
|
||||
For Leveled Compaction users, `CompactRange()` with `bottommost_level_compaction = BottommostLevelCompaction::kIfHaveCompactionFilter` will behave similar to `kForceOptimized` in that it will skip files created during this manual compaction when compacting files in the bottommost level. (#11468)
|
||||
* RocksDB will try to drop range tombstones during non-bottommost compaction when it is safe to do so. (#11459)
|
||||
* When a DB is openend with `allow_ingest_behind=true` (currently only Universal compaction is supported), files in the last level, i.e. the ingested files, will not be included in any compaction. (#11489)
|
||||
* Statistics `rocksdb.sst.read.micros` scope is expanded to all SST reads except for file ingestion and column family import (some compaction reads were previously excluded).
|
||||
|
||||
### 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)
|
||||
* Fix extra prefetching during seek in async_io when BlockBasedTableOptions.num_file_reads_for_auto_readahead is 1 leading to extra reads than required.
|
||||
* Fix a bug where compactions that are qualified to be run as 2 subcompactions were only run as one subcompaction.
|
||||
* Fix a use-after-move bug in block.cc.
|
||||
|
||||
## 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)).
|
||||
|
||||
## 8.1.1 (04/06/2023)
|
||||
### 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
|
||||
|
||||
+6
-9
@@ -20,15 +20,12 @@ 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 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.
|
||||
* 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`.
|
||||
|
||||
## Dependencies
|
||||
|
||||
|
||||
@@ -88,25 +88,6 @@ endif
|
||||
|
||||
$(info $$DEBUG_LEVEL is $(DEBUG_LEVEL), $$LIB_MODE is $(LIB_MODE))
|
||||
|
||||
# Detect what platform we're building on.
|
||||
# Export some common variables that might have been passed as Make variables
|
||||
# instead of environment variables.
|
||||
dummy := $(shell (export ROCKSDB_ROOT="$(CURDIR)"; \
|
||||
export CXXFLAGS="$(EXTRA_CXXFLAGS)"; \
|
||||
export LDFLAGS="$(EXTRA_LDFLAGS)"; \
|
||||
export COMPILE_WITH_ASAN="$(COMPILE_WITH_ASAN)"; \
|
||||
export COMPILE_WITH_TSAN="$(COMPILE_WITH_TSAN)"; \
|
||||
export COMPILE_WITH_UBSAN="$(COMPILE_WITH_UBSAN)"; \
|
||||
export PORTABLE="$(PORTABLE)"; \
|
||||
export ROCKSDB_NO_FBCODE="$(ROCKSDB_NO_FBCODE)"; \
|
||||
export USE_CLANG="$(USE_CLANG)"; \
|
||||
export LIB_MODE="$(LIB_MODE)"; \
|
||||
export ROCKSDB_CXX_STANDARD="$(ROCKSDB_CXX_STANDARD)"; \
|
||||
export USE_FOLLY="$(USE_FOLLY)"; \
|
||||
"$(CURDIR)/build_tools/build_detect_platform" "$(CURDIR)/make_config.mk"))
|
||||
# this file is generated by the previous line to set build flags and sources
|
||||
include make_config.mk
|
||||
|
||||
# Figure out optimize level.
|
||||
ifneq ($(DEBUG_LEVEL), 2)
|
||||
OPTIMIZE_LEVEL ?= -O2
|
||||
@@ -242,6 +223,25 @@ am__v_AR_1 =
|
||||
AM_LINK = $(AM_V_CCLD)$(CXX) -L. $(patsubst lib%.a, -l%, $(patsubst lib%.$(PLATFORM_SHARED_EXT), -l%, $^)) $(EXEC_LDFLAGS) -o $@ $(LDFLAGS) $(COVERAGEFLAGS)
|
||||
AM_SHARE = $(AM_V_CCLD) $(CXX) $(PLATFORM_SHARED_LDFLAGS)$@ -L. $(patsubst lib%.$(PLATFORM_SHARED_EXT), -l%, $^) $(EXEC_LDFLAGS) $(LDFLAGS) -o $@
|
||||
|
||||
# Detect what platform we're building on.
|
||||
# Export some common variables that might have been passed as Make variables
|
||||
# instead of environment variables.
|
||||
dummy := $(shell (export ROCKSDB_ROOT="$(CURDIR)"; \
|
||||
export CXXFLAGS="$(EXTRA_CXXFLAGS)"; \
|
||||
export LDFLAGS="$(EXTRA_LDFLAGS)"; \
|
||||
export COMPILE_WITH_ASAN="$(COMPILE_WITH_ASAN)"; \
|
||||
export COMPILE_WITH_TSAN="$(COMPILE_WITH_TSAN)"; \
|
||||
export COMPILE_WITH_UBSAN="$(COMPILE_WITH_UBSAN)"; \
|
||||
export PORTABLE="$(PORTABLE)"; \
|
||||
export ROCKSDB_NO_FBCODE="$(ROCKSDB_NO_FBCODE)"; \
|
||||
export USE_CLANG="$(USE_CLANG)"; \
|
||||
export LIB_MODE="$(LIB_MODE)"; \
|
||||
export ROCKSDB_CXX_STANDARD="$(ROCKSDB_CXX_STANDARD)"; \
|
||||
export USE_FOLLY="$(USE_FOLLY)"; \
|
||||
"$(CURDIR)/build_tools/build_detect_platform" "$(CURDIR)/make_config.mk"))
|
||||
# this file is generated by the previous line to set build flags and sources
|
||||
include make_config.mk
|
||||
|
||||
ROCKSDB_PLUGIN_MKS = $(foreach plugin, $(ROCKSDB_PLUGINS), plugin/$(plugin)/*.mk)
|
||||
include $(ROCKSDB_PLUGIN_MKS)
|
||||
ROCKSDB_PLUGIN_PROTO =ROCKSDB_NAMESPACE::ObjectLibrary\&, const std::string\&
|
||||
@@ -337,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_CCFLAGS := $(filter-out -march=native, $(PLATFORM_CCFLAGS))
|
||||
PLATFORM_CXXFLAGS := $(filter-out -march=native, $(PLATFORM_CXXFLAGS))
|
||||
PLATFORM_CFLAGS := $(filter-out -march=native -DHAVE_SSE42 -DHAVE_AVX2, $(PLATFORM_CFLAGS))
|
||||
PLATFORM_CXXFLAGS := $(filter-out -march=native -DHAVE_SSE42 -DHAVE_AVX2, $(PLATFORM_CXXFLAGS))
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
@@ -539,7 +539,7 @@ endif
|
||||
|
||||
ifdef USE_CLANG
|
||||
# Used by some teams in Facebook
|
||||
WARNING_FLAGS += -Wshift-sign-overflow -Wambiguous-reversed-operator
|
||||
WARNING_FLAGS += -Wshift-sign-overflow
|
||||
endif
|
||||
|
||||
ifeq ($(PLATFORM), OS_OPENBSD)
|
||||
@@ -1417,9 +1417,6 @@ thread_local_test: $(OBJ_DIR)/util/thread_local_test.o $(TEST_LIBRARY) $(LIBRARY
|
||||
work_queue_test: $(OBJ_DIR)/util/work_queue_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
udt_util_test: $(OBJ_DIR)/util/udt_util_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
corruption_test: $(OBJ_DIR)/db/corruption_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
@@ -1483,9 +1480,6 @@ 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)
|
||||
|
||||
@@ -2441,8 +2435,6 @@ 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 \
|
||||
@@ -2453,8 +2445,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 && \
|
||||
CXXFLAGS=" $(CXX_M_FLAGS) -DHAVE_CXX11_ATOMIC " $(PYTHON) build/fbcode_builder/getdeps.py build --no-tests
|
||||
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
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Build size testing
|
||||
|
||||
@@ -5,4 +5,3 @@ 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,6 +1,8 @@
|
||||
## 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)
|
||||
|
||||
@@ -264,8 +264,6 @@ cpp_library_wrapper(name="rocksdb_lib", srcs=[
|
||||
"util/string_util.cc",
|
||||
"util/thread_local.cc",
|
||||
"util/threadpool_imp.cc",
|
||||
"util/udt_util.cc",
|
||||
"util/write_batch_util.cc",
|
||||
"util/xxhash.cc",
|
||||
"utilities/agg_merge/agg_merge.cc",
|
||||
"utilities/backup/backup_engine.cc",
|
||||
@@ -394,7 +392,6 @@ 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",
|
||||
@@ -4752,12 +4749,6 @@ 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"],
|
||||
@@ -5510,12 +5501,6 @@ cpp_unittest_wrapper(name="ttl_test",
|
||||
extra_compiler_flags=[])
|
||||
|
||||
|
||||
cpp_unittest_wrapper(name="udt_util_test",
|
||||
srcs=["util/udt_util_test.cc"],
|
||||
deps=[":rocksdb_test_lib"],
|
||||
extra_compiler_flags=[])
|
||||
|
||||
|
||||
cpp_unittest_wrapper(name="util_merge_operators_test",
|
||||
srcs=["utilities/util_merge_operators_test.cc"],
|
||||
deps=[":rocksdb_test_lib"],
|
||||
|
||||
@@ -15,25 +15,6 @@ 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
|
||||
|
||||
@@ -48,9 +29,6 @@ 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.
|
||||
|
||||
@@ -104,18 +82,9 @@ 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.
|
||||
|
||||
|
||||
@@ -419,7 +419,7 @@ EOF
|
||||
|
||||
if ! test $ROCKSDB_DISABLE_JEMALLOC; then
|
||||
# Test whether jemalloc is available
|
||||
if echo 'int main() {}' | $CXX $PLATFORM_CXXFLAGS $LDFLAGS -x c++ - -o test.o -ljemalloc \
|
||||
if echo 'int main() {}' | $CXX $PLATFORM_CXXFLAGS -x c++ - -o test.o -ljemalloc \
|
||||
2>/dev/null; then
|
||||
# This will enable some preprocessor identifiers in the Makefile
|
||||
JEMALLOC=1
|
||||
@@ -428,19 +428,12 @@ EOF
|
||||
WITH_JEMALLOC_FLAG=1
|
||||
# check for JEMALLOC installed with HomeBrew
|
||||
if [ "$PLATFORM" == "OS_MACOSX" ]; then
|
||||
if [ "$TARGET_ARCHITECTURE" = "arm64" ]; then
|
||||
# on M1 Macs, homebrew installs here instead of /usr/local
|
||||
JEMALLOC_PREFIX="/opt/homebrew"
|
||||
else
|
||||
JEMALLOC_PREFIX="/usr/local"
|
||||
fi
|
||||
if hash brew 2>/dev/null && brew ls --versions jemalloc > /dev/null; then
|
||||
JEMALLOC_VER=$(brew ls --versions jemalloc | tail -n 1 | cut -f 2 -d ' ')
|
||||
JEMALLOC_INCLUDE="-I${JEMALLOC_PREFIX}/Cellar/jemalloc/${JEMALLOC_VER}/include"
|
||||
JEMALLOC_LIB="${JEMALLOC_PREFIX}/Cellar/jemalloc/${JEMALLOC_VER}/lib/libjemalloc_pic.a"
|
||||
PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -L${JEMALLOC_PREFIX}/lib $JEMALLOC_LIB"
|
||||
JAVA_LDFLAGS="$JAVA_LDFLAGS -L${JEMALLOC_PREFIX}/lib $JEMALLOC_LIB"
|
||||
JAVA_STATIC_LDFLAGS="$JAVA_STATIC_LDFLAGS -L${JEMALLOC_PREFIX}/lib $JEMALLOC_LIB"
|
||||
JEMALLOC_INCLUDE="-I/usr/local/Cellar/jemalloc/${JEMALLOC_VER}/include"
|
||||
JEMALLOC_LIB="/usr/local/Cellar/jemalloc/${JEMALLOC_VER}/lib/libjemalloc_pic.a"
|
||||
PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS $JEMALLOC_LIB"
|
||||
JAVA_STATIC_LDFLAGS="$JAVA_STATIC_LDFLAGS $JEMALLOC_LIB"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
@@ -628,7 +621,7 @@ EOF
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$PORTABLE" == "" ] || [ "$PORTABLE" == 0 ]; then
|
||||
if test "0$PORTABLE" -eq 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]+`
|
||||
@@ -651,36 +644,37 @@ if [ "$PORTABLE" == "" ] || [ "$PORTABLE" == 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 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}"
|
||||
# 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}"
|
||||
fi
|
||||
|
||||
if [[ "${PLATFORM}" == "OS_MACOSX" ]]; then
|
||||
# For portability compile for macOS 10.14 or newer
|
||||
COMMON_FLAGS="$COMMON_FLAGS -mmacosx-version-min=10.14"
|
||||
PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -mmacosx-version-min=10.14"
|
||||
# For portability compile for macOS 10.13 (2017) or newer
|
||||
COMMON_FLAGS="$COMMON_FLAGS -mmacosx-version-min=10.13"
|
||||
PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -mmacosx-version-min=10.13"
|
||||
# -mmacosx-version-min must come first here.
|
||||
PLATFORM_SHARED_LDFLAGS="-mmacosx-version-min=10.14 $PLATFORM_SHARED_LDFLAGS"
|
||||
PLATFORM_CMAKE_FLAGS="-DCMAKE_OSX_DEPLOYMENT_TARGET=10.14"
|
||||
JAVA_STATIC_DEPS_COMMON_FLAGS="-mmacosx-version-min=10.14"
|
||||
PLATFORM_SHARED_LDFLAGS="-mmacosx-version-min=10.13 $PLATFORM_SHARED_LDFLAGS"
|
||||
PLATFORM_CMAKE_FLAGS="-DCMAKE_OSX_DEPLOYMENT_TARGET=10.13"
|
||||
JAVA_STATIC_DEPS_COMMON_FLAGS="-mmacosx-version-min=10.13"
|
||||
JAVA_STATIC_DEPS_LDFLAGS="$JAVA_STATIC_DEPS_COMMON_FLAGS"
|
||||
JAVA_STATIC_DEPS_CCFLAGS="$JAVA_STATIC_DEPS_COMMON_FLAGS"
|
||||
JAVA_STATIC_DEPS_CXXFLAGS="$JAVA_STATIC_DEPS_COMMON_FLAGS"
|
||||
@@ -704,6 +698,101 @@ 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() {
|
||||
|
||||
@@ -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"
|
||||
CFLAGS+=" -DROCKSDB_PLATFORM_POSIX -DROCKSDB_LIB_IO_POSIX -DROCKSDB_FALLOCATE_PRESENT -DROCKSDB_MALLOC_USABLE_SIZE -DROCKSDB_RANGESYNC_PRESENT -DROCKSDB_SCHED_GETCPU_PRESENT -DHAVE_SSE42"
|
||||
CXXFLAGS+=" $CFLAGS"
|
||||
|
||||
EXEC_LDFLAGS=" $SNAPPY_LIBS $ZLIB_LIBS $BZIP_LIBS $LZ4_LIBS $ZSTD_LIBS $GFLAGS_LIBS $NUMA_LIB $TBB_LIBS"
|
||||
|
||||
@@ -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 -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 -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"
|
||||
|
||||
Vendored
+61
-157
@@ -31,7 +31,6 @@
|
||||
#include "util/hash.h"
|
||||
#include "util/mutexlock.h"
|
||||
#include "util/random.h"
|
||||
#include "util/stderr_logger.h"
|
||||
#include "util/stop_watch.h"
|
||||
#include "util/string_util.h"
|
||||
|
||||
@@ -50,11 +49,8 @@ DEFINE_double(resident_ratio, 0.25,
|
||||
"Ratio of keys fitting in cache to keyspace.");
|
||||
DEFINE_uint64(ops_per_thread, 2000000U, "Number of operations per thread.");
|
||||
DEFINE_uint32(value_bytes, 8 * KiB, "Size of each value added.");
|
||||
DEFINE_uint32(value_bytes_estimate, 0,
|
||||
"If > 0, overrides estimated_entry_charge or "
|
||||
"min_avg_entry_charge depending on cache_type.");
|
||||
|
||||
DEFINE_uint32(skew, 5, "Degree of skew in key selection. 0 = no skew");
|
||||
DEFINE_uint32(skew, 5, "Degree of skew in key selection");
|
||||
DEFINE_bool(populate_cache, true, "Populate cache before operations");
|
||||
|
||||
DEFINE_uint32(lookup_insert_percent, 87,
|
||||
@@ -75,22 +71,12 @@ DEFINE_uint32(
|
||||
|
||||
DEFINE_uint32(gather_stats_entries_per_lock, 256,
|
||||
"For Cache::ApplyToAllEntries");
|
||||
DEFINE_bool(skewed, false, "If true, skew the key access distribution");
|
||||
|
||||
DEFINE_bool(lean, false,
|
||||
"If true, no additional computation is performed besides cache "
|
||||
"operations.");
|
||||
|
||||
DEFINE_bool(early_exit, false,
|
||||
"Exit before deallocating most memory. Good for malloc stats, e.g."
|
||||
"MALLOC_CONF=\"stats_print:true\"");
|
||||
|
||||
DEFINE_bool(histograms, true,
|
||||
"Whether to track and print histogram statistics.");
|
||||
|
||||
DEFINE_bool(report_problems, true, "Whether to ReportProblems() at the end.");
|
||||
|
||||
DEFINE_uint32(seed, 0, "Hashing/random seed to use. 0 = choose at random");
|
||||
|
||||
DEFINE_string(secondary_cache_uri, "",
|
||||
"Full URI for creating a custom secondary cache object");
|
||||
static class std::shared_ptr<ROCKSDB_NAMESPACE::SecondaryCache> secondary_cache;
|
||||
@@ -159,6 +145,9 @@ class SharedState {
|
||||
public:
|
||||
explicit SharedState(CacheBench* cache_bench)
|
||||
: cv_(&mu_),
|
||||
num_initialized_(0),
|
||||
start_(false),
|
||||
num_done_(0),
|
||||
cache_bench_(cache_bench) {}
|
||||
|
||||
~SharedState() {}
|
||||
@@ -181,27 +170,15 @@ class SharedState {
|
||||
|
||||
bool Started() const { return start_; }
|
||||
|
||||
void AddLookupStats(uint64_t hits, uint64_t misses) {
|
||||
MutexLock l(&mu_);
|
||||
lookup_count_ += hits + misses;
|
||||
lookup_hits_ += hits;
|
||||
}
|
||||
|
||||
double GetLookupHitRatio() const {
|
||||
return 1.0 * lookup_hits_ / lookup_count_;
|
||||
}
|
||||
|
||||
private:
|
||||
port::Mutex mu_;
|
||||
port::CondVar cv_;
|
||||
|
||||
CacheBench* cache_bench_;
|
||||
uint64_t num_initialized_;
|
||||
bool start_;
|
||||
uint64_t num_done_;
|
||||
|
||||
uint64_t num_initialized_ = 0;
|
||||
bool start_ = false;
|
||||
uint64_t num_done_ = 0;
|
||||
uint64_t lookup_count_ = 0;
|
||||
uint64_t lookup_hits_ = 0;
|
||||
CacheBench* cache_bench_;
|
||||
};
|
||||
|
||||
// Per-thread state for concurrent executions of the same benchmark.
|
||||
@@ -213,19 +190,27 @@ struct ThreadState {
|
||||
uint64_t duration_us = 0;
|
||||
|
||||
ThreadState(uint32_t index, SharedState* _shared)
|
||||
: tid(index), rnd(FLAGS_seed + 1 + index), shared(_shared) {}
|
||||
: tid(index), rnd(1000 + index), shared(_shared) {}
|
||||
};
|
||||
|
||||
struct KeyGen {
|
||||
char key_data[27];
|
||||
|
||||
Slice GetRand(Random64& rnd, uint64_t max_key, uint32_t skew) {
|
||||
uint64_t raw = rnd.Next();
|
||||
// Skew according to setting
|
||||
for (uint32_t i = 0; i < skew; ++i) {
|
||||
raw = std::min(raw, rnd.Next());
|
||||
Slice GetRand(Random64& rnd, uint64_t max_key, int max_log) {
|
||||
uint64_t key = 0;
|
||||
if (!FLAGS_skewed) {
|
||||
uint64_t raw = rnd.Next();
|
||||
// Skew according to setting
|
||||
for (uint32_t i = 0; i < FLAGS_skew; ++i) {
|
||||
raw = std::min(raw, rnd.Next());
|
||||
}
|
||||
key = FastRange64(raw, max_key);
|
||||
} else {
|
||||
key = rnd.Skewed(max_log);
|
||||
if (key > max_key) {
|
||||
key -= max_key;
|
||||
}
|
||||
}
|
||||
uint64_t key = FastRange64(raw, max_key);
|
||||
// Variable size and alignment
|
||||
size_t off = key % 8;
|
||||
key_data[0] = char{42};
|
||||
@@ -296,38 +281,31 @@ class CacheBench {
|
||||
lookup_threshold_(insert_threshold_ +
|
||||
kHundredthUint64 * FLAGS_lookup_percent),
|
||||
erase_threshold_(lookup_threshold_ +
|
||||
kHundredthUint64 * FLAGS_erase_percent) {
|
||||
kHundredthUint64 * FLAGS_erase_percent),
|
||||
skewed_(FLAGS_skewed) {
|
||||
if (erase_threshold_ != 100U * kHundredthUint64) {
|
||||
fprintf(stderr, "Percentages must add to 100.\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
max_log_ = 0;
|
||||
if (skewed_) {
|
||||
uint64_t max_key = max_key_;
|
||||
while (max_key >>= 1) max_log_++;
|
||||
if (max_key > (static_cast<uint64_t>(1) << max_log_)) max_log_++;
|
||||
}
|
||||
|
||||
if (FLAGS_cache_type == "clock_cache") {
|
||||
fprintf(stderr, "Old clock cache implementation has been removed.\n");
|
||||
exit(1);
|
||||
} else if (EndsWith(FLAGS_cache_type, "hyper_clock_cache")) {
|
||||
HyperClockCacheOptions opts(
|
||||
FLAGS_cache_size, /*estimated_entry_charge=*/0, FLAGS_num_shard_bits);
|
||||
opts.hash_seed = BitwiseAnd(FLAGS_seed, INT32_MAX);
|
||||
if (FLAGS_cache_type == "fixed_hyper_clock_cache" ||
|
||||
FLAGS_cache_type == "hyper_clock_cache") {
|
||||
opts.estimated_entry_charge = FLAGS_value_bytes_estimate > 0
|
||||
? FLAGS_value_bytes_estimate
|
||||
: FLAGS_value_bytes;
|
||||
} else if (FLAGS_cache_type == "auto_hyper_clock_cache") {
|
||||
if (FLAGS_value_bytes_estimate > 0) {
|
||||
opts.min_avg_entry_charge = FLAGS_value_bytes_estimate;
|
||||
}
|
||||
} else {
|
||||
fprintf(stderr, "Cache type not supported.");
|
||||
exit(1);
|
||||
}
|
||||
cache_ = opts.MakeSharedCache();
|
||||
} else if (FLAGS_cache_type == "hyper_clock_cache") {
|
||||
cache_ = HyperClockCacheOptions(FLAGS_cache_size, FLAGS_value_bytes,
|
||||
FLAGS_num_shard_bits)
|
||||
.MakeSharedCache();
|
||||
} else if (FLAGS_cache_type == "lru_cache") {
|
||||
LRUCacheOptions opts(FLAGS_cache_size, FLAGS_num_shard_bits,
|
||||
false /* strict_capacity_limit */,
|
||||
0.5 /* high_pri_pool_ratio */);
|
||||
opts.hash_seed = BitwiseAnd(FLAGS_seed, INT32_MAX);
|
||||
if (!FLAGS_secondary_cache_uri.empty()) {
|
||||
Status s = SecondaryCache::CreateFromString(
|
||||
ConfigOptions(), FLAGS_secondary_cache_uri, &secondary_cache);
|
||||
@@ -351,50 +329,13 @@ class CacheBench {
|
||||
~CacheBench() {}
|
||||
|
||||
void PopulateCache() {
|
||||
Random64 rnd(FLAGS_seed);
|
||||
Random64 rnd(1);
|
||||
KeyGen keygen;
|
||||
size_t max_occ = 0;
|
||||
size_t inserts_since_max_occ_increase = 0;
|
||||
size_t keys_since_last_not_found = 0;
|
||||
|
||||
// Avoid redundant insertions by checking Lookup before Insert.
|
||||
// Loop until insertions consistently fail to increase max occupancy or
|
||||
// it becomes difficult to find keys not already inserted.
|
||||
while (inserts_since_max_occ_increase < 100 &&
|
||||
keys_since_last_not_found < 100) {
|
||||
Slice key = keygen.GetRand(rnd, max_key_, FLAGS_skew);
|
||||
|
||||
Cache::Handle* handle = cache_->Lookup(key);
|
||||
if (handle != nullptr) {
|
||||
cache_->Release(handle);
|
||||
++keys_since_last_not_found;
|
||||
continue;
|
||||
}
|
||||
keys_since_last_not_found = 0;
|
||||
|
||||
Status s =
|
||||
cache_->Insert(key, createValue(rnd), &helper1, FLAGS_value_bytes);
|
||||
for (uint64_t i = 0; i < 2 * FLAGS_cache_size; i += FLAGS_value_bytes) {
|
||||
Status s = cache_->Insert(keygen.GetRand(rnd, max_key_, max_log_),
|
||||
createValue(rnd), &helper1, FLAGS_value_bytes);
|
||||
assert(s.ok());
|
||||
|
||||
handle = cache_->Lookup(key);
|
||||
if (!handle) {
|
||||
fprintf(stderr, "Failed to lookup key just inserted.\n");
|
||||
assert(false);
|
||||
exit(42);
|
||||
} else {
|
||||
cache_->Release(handle);
|
||||
}
|
||||
|
||||
size_t occ = cache_->GetOccupancyCount();
|
||||
if (occ > max_occ) {
|
||||
max_occ = occ;
|
||||
inserts_since_max_occ_increase = 0;
|
||||
} else {
|
||||
++inserts_since_max_occ_increase;
|
||||
}
|
||||
}
|
||||
printf("Population complete (%zu entries, %g average charge)\n", max_occ,
|
||||
1.0 * FLAGS_cache_size / max_occ);
|
||||
}
|
||||
|
||||
bool Run() {
|
||||
@@ -453,33 +394,19 @@ class CacheBench {
|
||||
FLAGS_ops_per_thread / elapsed_secs);
|
||||
printf("Thread ops/sec = %u\n", ops_per_sec);
|
||||
|
||||
printf("Lookup hit ratio: %g\n", shared.GetLookupHitRatio());
|
||||
printf("\nOperation latency (ns):\n");
|
||||
HistogramImpl combined;
|
||||
for (uint32_t i = 0; i < FLAGS_threads; i++) {
|
||||
combined.Merge(threads[i]->latency_ns_hist);
|
||||
}
|
||||
printf("%s", combined.ToString().c_str());
|
||||
|
||||
size_t occ = cache_->GetOccupancyCount();
|
||||
size_t slot = cache_->GetTableAddressCount();
|
||||
printf("Final load factor: %g (%zu / %zu)\n", 1.0 * occ / slot, occ, slot);
|
||||
|
||||
if (FLAGS_histograms) {
|
||||
printf("\nOperation latency (ns):\n");
|
||||
HistogramImpl combined;
|
||||
for (uint32_t i = 0; i < FLAGS_threads; i++) {
|
||||
combined.Merge(threads[i]->latency_ns_hist);
|
||||
}
|
||||
printf("%s", combined.ToString().c_str());
|
||||
|
||||
if (FLAGS_gather_stats) {
|
||||
printf("\nGather stats latency (us):\n");
|
||||
printf("%s", stats_hist.ToString().c_str());
|
||||
}
|
||||
if (FLAGS_gather_stats) {
|
||||
printf("\nGather stats latency (us):\n");
|
||||
printf("%s", stats_hist.ToString().c_str());
|
||||
}
|
||||
|
||||
if (FLAGS_report_problems) {
|
||||
printf("\n");
|
||||
std::shared_ptr<Logger> logger =
|
||||
std::make_shared<StderrLogger>(InfoLogLevel::DEBUG_LEVEL);
|
||||
cache_->ReportProblems(logger);
|
||||
}
|
||||
printf("%s", stats_report.c_str());
|
||||
printf("\n%s", stats_report.c_str());
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -492,6 +419,8 @@ class CacheBench {
|
||||
const uint64_t insert_threshold_;
|
||||
const uint64_t lookup_threshold_;
|
||||
const uint64_t erase_threshold_;
|
||||
const bool skewed_;
|
||||
int max_log_;
|
||||
|
||||
// A benchmark version of gathering stats on an active block cache by
|
||||
// iterating over it. The primary purpose is to measure the impact of
|
||||
@@ -524,7 +453,7 @@ class CacheBench {
|
||||
for (;;) {
|
||||
if (shared->AllDone()) {
|
||||
std::ostringstream ostr;
|
||||
ostr << "\nMost recent cache entry stats:\n"
|
||||
ostr << "Most recent cache entry stats:\n"
|
||||
<< "Number of entries: " << total_entry_count << "\n"
|
||||
<< "Table occupancy: " << table_occupancy << " / "
|
||||
<< table_size << " = "
|
||||
@@ -561,17 +490,13 @@ class CacheBench {
|
||||
// Something slightly more expensive as in stats by category
|
||||
helpers.insert(helper);
|
||||
};
|
||||
if (FLAGS_histograms) {
|
||||
timer.Start();
|
||||
}
|
||||
timer.Start();
|
||||
Cache::ApplyToAllEntriesOptions opts;
|
||||
opts.average_entries_per_lock = FLAGS_gather_stats_entries_per_lock;
|
||||
shared->GetCacheBench()->cache_->ApplyToAllEntries(fn, opts);
|
||||
table_occupancy = shared->GetCacheBench()->cache_->GetOccupancyCount();
|
||||
table_size = shared->GetCacheBench()->cache_->GetTableAddressCount();
|
||||
if (FLAGS_histograms) {
|
||||
stats_hist->Add(timer.ElapsedNanos() / 1000);
|
||||
}
|
||||
stats_hist->Add(timer.ElapsedNanos() / 1000);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -602,8 +527,6 @@ class CacheBench {
|
||||
void OperateCache(ThreadState* thread) {
|
||||
// To use looked-up values
|
||||
uint64_t result = 0;
|
||||
uint64_t lookup_misses = 0;
|
||||
uint64_t lookup_hits = 0;
|
||||
// To hold handles for a non-trivial amount of time
|
||||
Cache::Handle* handle = nullptr;
|
||||
KeyGen gen;
|
||||
@@ -612,12 +535,10 @@ class CacheBench {
|
||||
StopWatchNano timer(clock);
|
||||
|
||||
for (uint64_t i = 0; i < FLAGS_ops_per_thread; i++) {
|
||||
Slice key = gen.GetRand(thread->rnd, max_key_, FLAGS_skew);
|
||||
Slice key = gen.GetRand(thread->rnd, max_key_, max_log_);
|
||||
uint64_t random_op = thread->rnd.Next();
|
||||
|
||||
if (FLAGS_histograms) {
|
||||
timer.Start();
|
||||
}
|
||||
timer.Start();
|
||||
|
||||
if (random_op < lookup_insert_threshold_) {
|
||||
if (handle) {
|
||||
@@ -628,14 +549,12 @@ class CacheBench {
|
||||
handle = cache_->Lookup(key, &helper2, /*context*/ nullptr,
|
||||
Cache::Priority::LOW);
|
||||
if (handle) {
|
||||
++lookup_hits;
|
||||
if (!FLAGS_lean) {
|
||||
// do something with the data
|
||||
result += NPHash64(static_cast<char*>(cache_->Value(handle)),
|
||||
FLAGS_value_bytes);
|
||||
}
|
||||
} else {
|
||||
++lookup_misses;
|
||||
// do insert
|
||||
Status s = cache_->Insert(key, createValue(thread->rnd), &helper2,
|
||||
FLAGS_value_bytes, &handle);
|
||||
@@ -659,14 +578,11 @@ class CacheBench {
|
||||
handle = cache_->Lookup(key, &helper2, /*context*/ nullptr,
|
||||
Cache::Priority::LOW);
|
||||
if (handle) {
|
||||
++lookup_hits;
|
||||
if (!FLAGS_lean) {
|
||||
// do something with the data
|
||||
result += NPHash64(static_cast<char*>(cache_->Value(handle)),
|
||||
FLAGS_value_bytes);
|
||||
}
|
||||
} else {
|
||||
++lookup_misses;
|
||||
}
|
||||
} else if (random_op < erase_threshold_) {
|
||||
// do erase
|
||||
@@ -675,14 +591,7 @@ class CacheBench {
|
||||
// Should be extremely unlikely (noop)
|
||||
assert(random_op >= kHundredthUint64 * 100U);
|
||||
}
|
||||
if (FLAGS_histograms) {
|
||||
thread->latency_ns_hist.Add(timer.ElapsedNanos());
|
||||
}
|
||||
thread->shared->AddLookupStats(lookup_hits, lookup_misses);
|
||||
}
|
||||
if (FLAGS_early_exit) {
|
||||
MutexLock l(thread->shared->GetMutex());
|
||||
exit(0);
|
||||
thread->latency_ns_hist.Add(timer.ElapsedNanos());
|
||||
}
|
||||
if (handle) {
|
||||
cache_->Release(handle);
|
||||
@@ -704,9 +613,7 @@ class CacheBench {
|
||||
#ifndef NDEBUG
|
||||
printf("WARNING: Assertions are enabled; benchmarks unnecessarily slow\n");
|
||||
#endif
|
||||
printf("----------------------------\n");
|
||||
printf("RocksDB version : %d.%d\n", kMajorVersion, kMinorVersion);
|
||||
printf("Cache impl name : %s\n", cache_->Name());
|
||||
printf("DMutex impl name : %s\n", DMutex::kName());
|
||||
printf("Number of threads : %u\n", FLAGS_threads);
|
||||
printf("Ops per thread : %" PRIu64 "\n", FLAGS_ops_per_thread);
|
||||
@@ -1045,14 +952,11 @@ int cache_bench_tool(int argc, char** argv) {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (FLAGS_seed == 0) {
|
||||
FLAGS_seed = static_cast<uint32_t>(port::GetProcessID());
|
||||
printf("Using seed = %" PRIu32 "\n", FLAGS_seed);
|
||||
}
|
||||
|
||||
ROCKSDB_NAMESPACE::CacheBench bench;
|
||||
if (FLAGS_populate_cache) {
|
||||
bench.PopulateCache();
|
||||
printf("Population complete\n");
|
||||
printf("----------------------------\n");
|
||||
}
|
||||
if (bench.Run()) {
|
||||
return 0;
|
||||
|
||||
Vendored
+92
-135
@@ -21,7 +21,6 @@
|
||||
#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
|
||||
@@ -70,11 +69,18 @@ const Cache::CacheItemHelper kDumbHelper{
|
||||
CacheEntryRole::kMisc,
|
||||
[](Cache::ObjectPtr /*value*/, MemoryAllocator* /*alloc*/) {}};
|
||||
|
||||
const Cache::CacheItemHelper kInvokeOnDeleteHelper{
|
||||
const Cache::CacheItemHelper kEraseOnDeleteHelper1{
|
||||
CacheEntryRole::kMisc,
|
||||
[](Cache::ObjectPtr value, MemoryAllocator* /*alloc*/) {
|
||||
auto& fn = *static_cast<std::function<void()>*>(value);
|
||||
fn();
|
||||
Cache* cache = static_cast<Cache*>(value);
|
||||
cache->Erase("foo");
|
||||
}};
|
||||
|
||||
const Cache::CacheItemHelper kEraseOnDeleteHelper2{
|
||||
CacheEntryRole::kMisc,
|
||||
[](Cache::ObjectPtr value, MemoryAllocator* /*alloc*/) {
|
||||
Cache* cache = static_cast<Cache*>(value);
|
||||
cache->Erase(EncodeKey16Bytes(1234));
|
||||
}};
|
||||
} // anonymous namespace
|
||||
|
||||
@@ -113,7 +119,8 @@ class CacheTest : public testing::Test,
|
||||
// Currently, HyperClockCache requires keys to be 16B long, whereas
|
||||
// LRUCache doesn't, so the encoding depends on the cache type.
|
||||
std::string EncodeKey(int k) {
|
||||
if (IsHyperClock()) {
|
||||
auto type = GetParam();
|
||||
if (type == kHyperClock) {
|
||||
return EncodeKey16Bytes(k);
|
||||
} else {
|
||||
return EncodeKey32Bits(k);
|
||||
@@ -121,7 +128,8 @@ class CacheTest : public testing::Test,
|
||||
}
|
||||
|
||||
int DecodeKey(const Slice& k) {
|
||||
if (IsHyperClock()) {
|
||||
auto type = GetParam();
|
||||
if (type == kHyperClock) {
|
||||
return DecodeKey16Bytes(k);
|
||||
} else {
|
||||
return DecodeKey32Bits(k);
|
||||
@@ -173,13 +181,15 @@ std::string CacheTest::type_;
|
||||
class LRUCacheTest : public CacheTest {};
|
||||
|
||||
TEST_P(CacheTest, UsageTest) {
|
||||
auto type = GetParam();
|
||||
|
||||
// cache is std::shared_ptr and will be automatically cleaned up.
|
||||
const size_t kCapacity = 100000;
|
||||
auto cache = NewCache(kCapacity, 8, false, kDontChargeCacheMetadata);
|
||||
auto precise_cache = NewCache(kCapacity, 0, false, kFullChargeCacheMetadata);
|
||||
ASSERT_EQ(0, cache->GetUsage());
|
||||
size_t baseline_meta_usage = precise_cache->GetUsage();
|
||||
if (!IsHyperClock()) {
|
||||
if (type != kHyperClock) {
|
||||
ASSERT_EQ(0, baseline_meta_usage);
|
||||
}
|
||||
|
||||
@@ -187,13 +197,18 @@ TEST_P(CacheTest, UsageTest) {
|
||||
char value[10] = "abcdef";
|
||||
// make sure everything will be cached
|
||||
for (int i = 1; i < 100; ++i) {
|
||||
std::string key = EncodeKey(i);
|
||||
std::string key;
|
||||
if (type == kLRU) {
|
||||
key = std::string(i, 'a');
|
||||
} else {
|
||||
key = EncodeKey(i);
|
||||
}
|
||||
auto kv_size = key.size() + 5;
|
||||
ASSERT_OK(cache->Insert(key, value, &kDumbHelper, kv_size));
|
||||
ASSERT_OK(precise_cache->Insert(key, value, &kDumbHelper, kv_size));
|
||||
usage += kv_size;
|
||||
ASSERT_EQ(usage, cache->GetUsage());
|
||||
if (IsHyperClock()) {
|
||||
if (type == kHyperClock) {
|
||||
ASSERT_EQ(baseline_meta_usage + usage, precise_cache->GetUsage());
|
||||
} else {
|
||||
ASSERT_LT(usage, precise_cache->GetUsage());
|
||||
@@ -207,7 +222,12 @@ TEST_P(CacheTest, UsageTest) {
|
||||
|
||||
// make sure the cache will be overloaded
|
||||
for (size_t i = 1; i < kCapacity; ++i) {
|
||||
std::string key = EncodeKey(static_cast<int>(1000 + i));
|
||||
std::string key;
|
||||
if (type == kLRU) {
|
||||
key = std::to_string(i);
|
||||
} else {
|
||||
key = EncodeKey(static_cast<int>(1000 + i));
|
||||
}
|
||||
ASSERT_OK(cache->Insert(key, value, &kDumbHelper, key.size() + 5));
|
||||
ASSERT_OK(precise_cache->Insert(key, value, &kDumbHelper, key.size() + 5));
|
||||
}
|
||||
@@ -216,7 +236,7 @@ TEST_P(CacheTest, UsageTest) {
|
||||
ASSERT_GT(kCapacity, cache->GetUsage());
|
||||
ASSERT_GT(kCapacity, precise_cache->GetUsage());
|
||||
ASSERT_LT(kCapacity * 0.95, cache->GetUsage());
|
||||
if (!IsHyperClock()) {
|
||||
if (type != kHyperClock) {
|
||||
ASSERT_LT(kCapacity * 0.95, precise_cache->GetUsage());
|
||||
} else {
|
||||
// estimated value size of 1 is weird for clock cache, because
|
||||
@@ -227,20 +247,22 @@ TEST_P(CacheTest, UsageTest) {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: This test takes longer than expected on FixedHyperClockCache.
|
||||
// This is because the values size estimate at construction is too sloppy.
|
||||
// TODO: This test takes longer than expected on ClockCache. This is
|
||||
// because the values size estimate at construction is too sloppy.
|
||||
// Fix this.
|
||||
// Why is it so slow? The cache is constructed with an estimate of 1, but
|
||||
// then the charge is claimed to be 21. This will cause the hash table
|
||||
// to be extremely sparse, which in turn means clock needs to scan too
|
||||
// many slots to find victims.
|
||||
TEST_P(CacheTest, PinnedUsageTest) {
|
||||
auto type = GetParam();
|
||||
|
||||
// cache is std::shared_ptr and will be automatically cleaned up.
|
||||
const size_t kCapacity = 200000;
|
||||
auto cache = NewCache(kCapacity, 8, false, kDontChargeCacheMetadata);
|
||||
auto precise_cache = NewCache(kCapacity, 8, false, kFullChargeCacheMetadata);
|
||||
size_t baseline_meta_usage = precise_cache->GetUsage();
|
||||
if (!IsHyperClock()) {
|
||||
if (type != kHyperClock) {
|
||||
ASSERT_EQ(0, baseline_meta_usage);
|
||||
}
|
||||
|
||||
@@ -253,7 +275,12 @@ TEST_P(CacheTest, PinnedUsageTest) {
|
||||
// Add entries. Unpin some of them after insertion. Then, pin some of them
|
||||
// again. Check GetPinnedUsage().
|
||||
for (int i = 1; i < 100; ++i) {
|
||||
std::string key = EncodeKey(i);
|
||||
std::string key;
|
||||
if (type == kLRU) {
|
||||
key = std::string(i, 'a');
|
||||
} else {
|
||||
key = EncodeKey(i);
|
||||
}
|
||||
auto kv_size = key.size() + 5;
|
||||
Cache::Handle* handle;
|
||||
Cache::Handle* handle_in_precise_cache;
|
||||
@@ -294,7 +321,12 @@ TEST_P(CacheTest, PinnedUsageTest) {
|
||||
|
||||
// check that overloading the cache does not change the pinned usage
|
||||
for (size_t i = 1; i < 2 * kCapacity; ++i) {
|
||||
std::string key = EncodeKey(static_cast<int>(1000 + i));
|
||||
std::string key;
|
||||
if (type == kLRU) {
|
||||
key = std::to_string(i);
|
||||
} else {
|
||||
key = EncodeKey(static_cast<int>(1000 + i));
|
||||
}
|
||||
ASSERT_OK(cache->Insert(key, value, &kDumbHelper, key.size() + 5));
|
||||
ASSERT_OK(precise_cache->Insert(key, value, &kDumbHelper, key.size() + 5));
|
||||
}
|
||||
@@ -335,7 +367,7 @@ TEST_P(CacheTest, HitAndMiss) {
|
||||
ASSERT_EQ(-1, Lookup(300));
|
||||
|
||||
Insert(100, 102);
|
||||
if (IsHyperClock()) {
|
||||
if (GetParam() == kHyperClock) {
|
||||
// ClockCache usually doesn't overwrite on Insert
|
||||
ASSERT_EQ(101, Lookup(100));
|
||||
} else {
|
||||
@@ -345,7 +377,7 @@ TEST_P(CacheTest, HitAndMiss) {
|
||||
ASSERT_EQ(-1, Lookup(300));
|
||||
|
||||
ASSERT_EQ(1U, deleted_values_.size());
|
||||
if (IsHyperClock()) {
|
||||
if (GetParam() == kHyperClock) {
|
||||
ASSERT_EQ(102, deleted_values_[0]);
|
||||
} else {
|
||||
ASSERT_EQ(101, deleted_values_[0]);
|
||||
@@ -353,7 +385,7 @@ TEST_P(CacheTest, HitAndMiss) {
|
||||
}
|
||||
|
||||
TEST_P(CacheTest, InsertSameKey) {
|
||||
if (IsHyperClock()) {
|
||||
if (GetParam() == kHyperClock) {
|
||||
ROCKSDB_GTEST_BYPASS(
|
||||
"ClockCache doesn't guarantee Insert overwrite same key.");
|
||||
return;
|
||||
@@ -382,7 +414,7 @@ TEST_P(CacheTest, Erase) {
|
||||
}
|
||||
|
||||
TEST_P(CacheTest, EntriesArePinned) {
|
||||
if (IsHyperClock()) {
|
||||
if (GetParam() == kHyperClock) {
|
||||
ROCKSDB_GTEST_BYPASS(
|
||||
"ClockCache doesn't guarantee Insert overwrite same key.");
|
||||
return;
|
||||
@@ -446,7 +478,7 @@ TEST_P(CacheTest, ExternalRefPinsEntries) {
|
||||
Insert(1000 + j, 2000 + j);
|
||||
}
|
||||
// Clock cache is even more stateful and needs more churn to evict
|
||||
if (IsHyperClock()) {
|
||||
if (GetParam() == kHyperClock) {
|
||||
for (int j = 0; j < kCacheSize; j++) {
|
||||
Insert(11000 + j, 11000 + j);
|
||||
}
|
||||
@@ -484,20 +516,20 @@ TEST_P(CacheTest, EvictionPolicyRef) {
|
||||
// Check whether the entries inserted in the beginning
|
||||
// are evicted. Ones without extra ref are evicted and
|
||||
// those with are not.
|
||||
EXPECT_EQ(-1, Lookup(100));
|
||||
EXPECT_EQ(-1, Lookup(101));
|
||||
EXPECT_EQ(-1, Lookup(102));
|
||||
EXPECT_EQ(-1, Lookup(103));
|
||||
ASSERT_EQ(-1, Lookup(100));
|
||||
ASSERT_EQ(-1, Lookup(101));
|
||||
ASSERT_EQ(-1, Lookup(102));
|
||||
ASSERT_EQ(-1, Lookup(103));
|
||||
|
||||
EXPECT_EQ(-1, Lookup(300));
|
||||
EXPECT_EQ(-1, Lookup(301));
|
||||
EXPECT_EQ(-1, Lookup(302));
|
||||
EXPECT_EQ(-1, Lookup(303));
|
||||
ASSERT_EQ(-1, Lookup(300));
|
||||
ASSERT_EQ(-1, Lookup(301));
|
||||
ASSERT_EQ(-1, Lookup(302));
|
||||
ASSERT_EQ(-1, Lookup(303));
|
||||
|
||||
EXPECT_EQ(101, Lookup(200));
|
||||
EXPECT_EQ(102, Lookup(201));
|
||||
EXPECT_EQ(103, Lookup(202));
|
||||
EXPECT_EQ(104, Lookup(203));
|
||||
ASSERT_EQ(101, Lookup(200));
|
||||
ASSERT_EQ(102, Lookup(201));
|
||||
ASSERT_EQ(103, Lookup(202));
|
||||
ASSERT_EQ(104, Lookup(203));
|
||||
|
||||
// Cleaning up all the handles
|
||||
cache_->Release(h201);
|
||||
@@ -507,22 +539,37 @@ TEST_P(CacheTest, EvictionPolicyRef) {
|
||||
}
|
||||
|
||||
TEST_P(CacheTest, EvictEmptyCache) {
|
||||
auto type = GetParam();
|
||||
|
||||
// Insert item large than capacity to trigger eviction on empty cache.
|
||||
auto cache = NewCache(1, 0, false);
|
||||
ASSERT_OK(cache->Insert(EncodeKey(1000), nullptr, &kDumbHelper, 10));
|
||||
if (type == kLRU) {
|
||||
ASSERT_OK(cache->Insert("foo", nullptr, &kDumbHelper, 10));
|
||||
} else {
|
||||
ASSERT_OK(cache->Insert(EncodeKey(1000), nullptr, &kDumbHelper, 10));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(CacheTest, EraseFromDeleter) {
|
||||
auto type = GetParam();
|
||||
|
||||
// Have deleter which will erase item from cache, which will re-enter
|
||||
// the cache at that point.
|
||||
std::shared_ptr<Cache> cache = NewCache(10, 0, false);
|
||||
std::string foo = EncodeKey(1234);
|
||||
std::string bar = EncodeKey(5678);
|
||||
|
||||
std::function<void()> erase_fn = [&]() { cache->Erase(foo); };
|
||||
std::string foo, bar;
|
||||
const Cache::CacheItemHelper* erase_helper;
|
||||
if (type == kLRU) {
|
||||
foo = "foo";
|
||||
bar = "bar";
|
||||
erase_helper = &kEraseOnDeleteHelper1;
|
||||
} else {
|
||||
foo = EncodeKey(1234);
|
||||
bar = EncodeKey(5678);
|
||||
erase_helper = &kEraseOnDeleteHelper2;
|
||||
}
|
||||
|
||||
ASSERT_OK(cache->Insert(foo, nullptr, &kDumbHelper, 1));
|
||||
ASSERT_OK(cache->Insert(bar, &erase_fn, &kInvokeOnDeleteHelper, 1));
|
||||
ASSERT_OK(cache->Insert(bar, cache.get(), erase_helper, 1));
|
||||
|
||||
cache->Erase(bar);
|
||||
ASSERT_EQ(nullptr, cache->Lookup(foo));
|
||||
@@ -630,10 +677,10 @@ using TypedHandle = SharedCache::TypedHandle;
|
||||
} // namespace
|
||||
|
||||
TEST_P(CacheTest, SetCapacity) {
|
||||
if (IsHyperClock()) {
|
||||
// TODO: update test & code for limited supoort
|
||||
auto type = GetParam();
|
||||
if (type == kHyperClock) {
|
||||
ROCKSDB_GTEST_BYPASS(
|
||||
"HyperClockCache doesn't support arbitrary capacity "
|
||||
"FastLRUCache and HyperClockCache don't support arbitrary capacity "
|
||||
"adjustments.");
|
||||
return;
|
||||
}
|
||||
@@ -763,7 +810,7 @@ TEST_P(CacheTest, OverCapacity) {
|
||||
cache.Release(handles[i]);
|
||||
}
|
||||
|
||||
if (IsHyperClock()) {
|
||||
if (GetParam() == kHyperClock) {
|
||||
// Make sure eviction is triggered.
|
||||
ASSERT_OK(cache.Insert(EncodeKey(-1), nullptr, 1, &handles[0]));
|
||||
|
||||
@@ -875,7 +922,8 @@ TEST_P(CacheTest, DefaultShardBits) {
|
||||
// Prevent excessive allocation (to save time & space)
|
||||
estimated_value_size_ = 100000;
|
||||
// Implementations use different minimum shard sizes
|
||||
size_t min_shard_size = (IsHyperClock() ? 32U * 1024U : 512U) * 1024U;
|
||||
size_t min_shard_size =
|
||||
(GetParam() == kHyperClock ? 32U * 1024U : 512U) * 1024U;
|
||||
|
||||
std::shared_ptr<Cache> cache = NewCache(32U * min_shard_size);
|
||||
ShardedCacheBase* sc = dynamic_cast<ShardedCacheBase*>(cache.get());
|
||||
@@ -907,97 +955,6 @@ 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,
|
||||
secondary_cache_test_util::GetTestingCacheTypes());
|
||||
INSTANTIATE_TEST_CASE_P(CacheTestInstance, LRUCacheTest,
|
||||
|
||||
Vendored
+472
-649
File diff suppressed because it is too large.
Load diff
Vendored
+153
-251
@@ -24,14 +24,12 @@
|
||||
#include "rocksdb/cache.h"
|
||||
#include "rocksdb/secondary_cache.h"
|
||||
#include "util/autovector.h"
|
||||
#include "util/math.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
namespace clock_cache {
|
||||
|
||||
// Forward declaration of friend class.
|
||||
template <class ClockCache>
|
||||
class ClockCacheTest;
|
||||
|
||||
// HyperClockCache is an alternative to LRUCache specifically tailored for
|
||||
@@ -283,6 +281,29 @@ class ClockCacheTest;
|
||||
|
||||
// ----------------------------------------------------------------------- //
|
||||
|
||||
// The load factor p is a real number in (0, 1) such that at all
|
||||
// times at most a fraction p of all slots, without counting tombstones,
|
||||
// are occupied by elements. This means that the probability that a random
|
||||
// probe hits an occupied slot is at most p, and thus at most 1/p probes
|
||||
// are required on average. For example, p = 70% implies that between 1 and 2
|
||||
// probes are needed on average (bear in mind that this reasoning doesn't
|
||||
// consider the effects of clustering over time, which should be negligible
|
||||
// with double hashing).
|
||||
// Because the size of the hash table is always rounded up to the next
|
||||
// power of 2, p is really an upper bound on the actual load factor---the
|
||||
// actual load factor is anywhere between p/2 and p. This is a bit wasteful,
|
||||
// but bear in mind that slots only hold metadata, not actual values.
|
||||
// Since space cost is dominated by the values (the LSM blocks),
|
||||
// overprovisioning the table with metadata only increases the total cache space
|
||||
// usage by a tiny fraction.
|
||||
constexpr double kLoadFactor = 0.7;
|
||||
|
||||
// The user can exceed kLoadFactor if the sizes of the inserted values don't
|
||||
// match estimated_value_size, or in some rare cases with
|
||||
// strict_capacity_limit == false. To avoid degenerate performance, we set a
|
||||
// strict upper bound on the load factor.
|
||||
constexpr double kStrictLoadFactor = 0.84;
|
||||
|
||||
struct ClockHandleBasicData {
|
||||
Cache::ObjectPtr value = nullptr;
|
||||
const Cache::CacheItemHelper* helper = nullptr;
|
||||
@@ -305,7 +326,7 @@ struct ClockHandle : public ClockHandleBasicData {
|
||||
// state of the handle. The meta word looks like this:
|
||||
// low bits high bits
|
||||
// -----------------------------------------------------------------------
|
||||
// | acquire counter | release counter | hit bit | state marker |
|
||||
// | acquire counter | release counter | state marker |
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
// For reading or updating counters in meta word.
|
||||
@@ -319,13 +340,8 @@ struct ClockHandle : public ClockHandleBasicData {
|
||||
static constexpr uint64_t kReleaseIncrement = uint64_t{1}
|
||||
<< kReleaseCounterShift;
|
||||
|
||||
// For setting the hit bit
|
||||
static constexpr uint8_t kHitBitShift = 2U * kCounterNumBits;
|
||||
static constexpr uint64_t kHitBitMask = uint64_t{1} << kHitBitShift;
|
||||
;
|
||||
|
||||
// For reading or updating the state marker in meta word
|
||||
static constexpr uint8_t kStateShift = kHitBitShift + 1;
|
||||
static constexpr uint8_t kStateShift = 2U * kCounterNumBits;
|
||||
|
||||
// Bits contribution to state marker.
|
||||
// Occupied means any state other than empty
|
||||
@@ -355,124 +371,14 @@ struct ClockHandle : public ClockHandleBasicData {
|
||||
static constexpr uint8_t kMaxCountdown = kHighCountdown;
|
||||
// TODO: make these coundown values tuning parameters for eviction?
|
||||
|
||||
// See above. Mutable for read reference counting.
|
||||
mutable std::atomic<uint64_t> meta{};
|
||||
// See above
|
||||
std::atomic<uint64_t> meta{};
|
||||
|
||||
// Anticipating use for SecondaryCache support
|
||||
void* reserved_for_future_use = nullptr;
|
||||
}; // struct ClockHandle
|
||||
|
||||
class BaseClockTable {
|
||||
public:
|
||||
BaseClockTable(CacheMetadataChargePolicy metadata_charge_policy,
|
||||
MemoryAllocator* allocator,
|
||||
const Cache::EvictionCallback* eviction_callback,
|
||||
const uint32_t* hash_seed)
|
||||
: metadata_charge_policy_(metadata_charge_policy),
|
||||
allocator_(allocator),
|
||||
eviction_callback_(*eviction_callback),
|
||||
hash_seed_(*hash_seed) {}
|
||||
|
||||
template <class Table>
|
||||
typename Table::HandleImpl* CreateStandalone(ClockHandleBasicData& proto,
|
||||
size_t capacity,
|
||||
bool strict_capacity_limit,
|
||||
bool allow_uncharged);
|
||||
|
||||
template <class Table>
|
||||
Status Insert(const ClockHandleBasicData& proto,
|
||||
typename Table::HandleImpl** handle, Cache::Priority priority,
|
||||
size_t capacity, bool strict_capacity_limit);
|
||||
|
||||
void Ref(ClockHandle& handle);
|
||||
|
||||
size_t GetOccupancy() const {
|
||||
return occupancy_.load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
size_t GetUsage() const { return 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_; }
|
||||
|
||||
struct EvictionData {
|
||||
size_t freed_charge = 0;
|
||||
size_t freed_count = 0;
|
||||
};
|
||||
|
||||
void TrackAndReleaseEvictedEntry(ClockHandle* h, EvictionData* data);
|
||||
|
||||
#ifndef NDEBUG
|
||||
// Acquire N references
|
||||
void TEST_RefN(ClockHandle& handle, size_t n);
|
||||
// Helper for TEST_ReleaseN
|
||||
void TEST_ReleaseNMinus1(ClockHandle* handle, size_t n);
|
||||
#endif
|
||||
|
||||
private: // fns
|
||||
// Creates a "standalone" handle for returning from an Insert operation that
|
||||
// cannot be completed by actually inserting into the table.
|
||||
// Updates `standalone_usage_` but not `usage_` nor `occupancy_`.
|
||||
template <class HandleImpl>
|
||||
HandleImpl* StandaloneInsert(const ClockHandleBasicData& proto);
|
||||
|
||||
// Helper for updating `usage_` for new entry with given `total_charge`
|
||||
// and evicting if needed under strict_capacity_limit=true rules. This
|
||||
// means the operation might fail with Status::MemoryLimit. If
|
||||
// `need_evict_for_occupancy`, then eviction of at least one entry is
|
||||
// required, and the operation should fail if not possible.
|
||||
// NOTE: Otherwise, occupancy_ is not managed in this function
|
||||
template <class Table>
|
||||
Status ChargeUsageMaybeEvictStrict(size_t total_charge, size_t capacity,
|
||||
bool need_evict_for_occupancy,
|
||||
typename Table::InsertState& state);
|
||||
|
||||
// Helper for updating `usage_` for new entry with given `total_charge`
|
||||
// and evicting if needed under strict_capacity_limit=false rules. This
|
||||
// means that updating `usage_` always succeeds even if forced to exceed
|
||||
// capacity. If `need_evict_for_occupancy`, then eviction of at least one
|
||||
// entry is required, and the operation should return false if such eviction
|
||||
// is not possible. `usage_` is not updated in that case. Otherwise, returns
|
||||
// true, indicating success.
|
||||
// NOTE: occupancy_ is not managed in this function
|
||||
template <class Table>
|
||||
bool ChargeUsageMaybeEvictNonStrict(size_t total_charge, size_t capacity,
|
||||
bool need_evict_for_occupancy,
|
||||
typename Table::InsertState& state);
|
||||
|
||||
protected: // data
|
||||
// We partition the following members into different cache lines
|
||||
// to avoid false sharing among Lookup, Release, Erase and Insert
|
||||
// operations in ClockCacheShard.
|
||||
|
||||
// Clock algorithm sweep pointer.
|
||||
std::atomic<uint64_t> clock_pointer_{};
|
||||
|
||||
// TODO: is this separation needed if we don't do background evictions?
|
||||
ALIGN_AS(CACHE_LINE_SIZE)
|
||||
// Number of elements in the table.
|
||||
std::atomic<size_t> occupancy_{};
|
||||
|
||||
// Memory usage by entries tracked by the cache (including standalone)
|
||||
std::atomic<size_t> usage_{};
|
||||
|
||||
// Part of usage by standalone entries (not in table)
|
||||
std::atomic<size_t> standalone_usage_{};
|
||||
|
||||
ALIGN_AS(CACHE_LINE_SIZE)
|
||||
const CacheMetadataChargePolicy metadata_charge_policy_;
|
||||
|
||||
// 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_;
|
||||
};
|
||||
|
||||
class FixedHyperClockTable : public BaseClockTable {
|
||||
class HyperClockTable {
|
||||
public:
|
||||
// Target size to be exactly a common cache line size (see static_assert in
|
||||
// clock_cache.cc)
|
||||
@@ -494,104 +400,86 @@ class FixedHyperClockTable : public BaseClockTable {
|
||||
}; // struct HandleImpl
|
||||
|
||||
struct Opts {
|
||||
explicit Opts(size_t _estimated_value_size)
|
||||
: estimated_value_size(_estimated_value_size) {}
|
||||
explicit Opts(const HyperClockCacheOptions& opts) {
|
||||
assert(opts.estimated_entry_charge > 0);
|
||||
estimated_value_size = opts.estimated_entry_charge;
|
||||
}
|
||||
size_t estimated_value_size;
|
||||
};
|
||||
|
||||
FixedHyperClockTable(size_t capacity, bool strict_capacity_limit,
|
||||
CacheMetadataChargePolicy metadata_charge_policy,
|
||||
MemoryAllocator* allocator,
|
||||
const Cache::EvictionCallback* eviction_callback,
|
||||
const uint32_t* hash_seed, const Opts& opts);
|
||||
~FixedHyperClockTable();
|
||||
HyperClockTable(size_t capacity, bool strict_capacity_limit,
|
||||
CacheMetadataChargePolicy metadata_charge_policy,
|
||||
MemoryAllocator* allocator,
|
||||
const Cache::EvictionCallback* eviction_callback,
|
||||
const Opts& opts);
|
||||
~HyperClockTable();
|
||||
|
||||
// For BaseClockTable::Insert
|
||||
struct InsertState {};
|
||||
Status Insert(const ClockHandleBasicData& proto, HandleImpl** handle,
|
||||
Cache::Priority priority, size_t capacity,
|
||||
bool strict_capacity_limit);
|
||||
|
||||
void StartInsert(InsertState& state);
|
||||
|
||||
// Returns true iff there is room for the proposed number of entries.
|
||||
bool GrowIfNeeded(size_t new_occupancy, InsertState& state);
|
||||
|
||||
HandleImpl* DoInsert(const ClockHandleBasicData& proto,
|
||||
uint64_t initial_countdown, bool take_ref,
|
||||
InsertState& state);
|
||||
|
||||
// Runs the clock eviction algorithm trying to reclaim at least
|
||||
// requested_charge. Returns how much is evicted, which could be less
|
||||
// if it appears impossible to evict the requested amount without blocking.
|
||||
void Evict(size_t requested_charge, InsertState& state, EvictionData* data);
|
||||
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);
|
||||
|
||||
void Ref(HandleImpl& handle);
|
||||
|
||||
void Erase(const UniqueId64x2& hashed_key);
|
||||
|
||||
void ConstApplyToEntriesRange(std::function<void(const HandleImpl&)> func,
|
||||
size_t index_begin, size_t index_end,
|
||||
bool apply_if_will_be_deleted) const;
|
||||
|
||||
void EraseUnRefEntries();
|
||||
|
||||
size_t GetTableSize() const { return size_t{1} << length_bits_; }
|
||||
|
||||
size_t GetOccupancyLimit() const { return occupancy_limit_; }
|
||||
int GetLengthBits() const { return length_bits_; }
|
||||
|
||||
const HandleImpl* HandlePtr(size_t idx) const { return &array_[idx]; }
|
||||
|
||||
#ifndef NDEBUG
|
||||
size_t& TEST_MutableOccupancyLimit() {
|
||||
return const_cast<size_t&>(occupancy_limit_);
|
||||
size_t GetOccupancy() const {
|
||||
return occupancy_.load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
// Release N references
|
||||
size_t GetOccupancyLimit() const { return occupancy_limit_; }
|
||||
|
||||
size_t GetUsage() const { return usage_.load(std::memory_order_relaxed); }
|
||||
|
||||
size_t GetStandaloneUsage() const {
|
||||
return standalone_usage_.load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
// Acquire/release N references
|
||||
void TEST_RefN(HandleImpl& handle, size_t n);
|
||||
void TEST_ReleaseN(HandleImpl* handle, size_t n);
|
||||
#endif
|
||||
|
||||
// The load factor p is a real number in (0, 1) such that at all
|
||||
// times at most a fraction p of all slots, without counting tombstones,
|
||||
// are occupied by elements. This means that the probability that a random
|
||||
// probe hits an occupied slot is at most p, and thus at most 1/p probes
|
||||
// are required on average. For example, p = 70% implies that between 1 and 2
|
||||
// probes are needed on average (bear in mind that this reasoning doesn't
|
||||
// consider the effects of clustering over time, which should be negligible
|
||||
// with double hashing).
|
||||
// Because the size of the hash table is always rounded up to the next
|
||||
// power of 2, p is really an upper bound on the actual load factor---the
|
||||
// actual load factor is anywhere between p/2 and p. This is a bit wasteful,
|
||||
// but bear in mind that slots only hold metadata, not actual values.
|
||||
// Since space cost is dominated by the values (the LSM blocks),
|
||||
// overprovisioning the table with metadata only increases the total cache
|
||||
// space usage by a tiny fraction.
|
||||
static constexpr double kLoadFactor = 0.7;
|
||||
|
||||
// The user can exceed kLoadFactor if the sizes of the inserted values don't
|
||||
// match estimated_value_size, or in some rare cases with
|
||||
// strict_capacity_limit == false. To avoid degenerate performance, we set a
|
||||
// strict upper bound on the load factor.
|
||||
static constexpr double kStrictLoadFactor = 0.84;
|
||||
|
||||
private: // functions
|
||||
// Returns x mod 2^{length_bits_}.
|
||||
inline size_t ModTableSize(uint64_t x) {
|
||||
return BitwiseAnd(x, length_bits_mask_);
|
||||
return static_cast<size_t>(x) & length_bits_mask_;
|
||||
}
|
||||
|
||||
// Returns the first slot in the probe sequence with a handle e such that
|
||||
// match_fn(e) is true. At every step, the function first tests whether
|
||||
// match_fn(e) holds. If this is false, it evaluates abort_fn(e) to decide
|
||||
// whether the search should be aborted, and if so, FindSlot immediately
|
||||
// returns nullptr. For every handle e that is not a match and not aborted,
|
||||
// FindSlot runs update_fn(e, is_last) where is_last is set to true iff that
|
||||
// slot will be the last probed because the next would cycle back to the first
|
||||
// slot probed. This function uses templates instead of std::function to
|
||||
// minimize the risk of heap-allocated closures being created.
|
||||
template <typename MatchFn, typename AbortFn, typename UpdateFn>
|
||||
// Runs the clock eviction algorithm trying to reclaim at least
|
||||
// requested_charge. Returns how much is evicted, which could be less
|
||||
// if it appears impossible to evict the requested amount without blocking.
|
||||
inline void Evict(size_t requested_charge, size_t* freed_charge,
|
||||
size_t* freed_count);
|
||||
|
||||
// Returns the first slot in the probe sequence, starting from the given
|
||||
// probe number, with a handle e such that match(e) is true. At every
|
||||
// step, the function first tests whether match(e) holds. If this is false,
|
||||
// it evaluates abort(e) to decide whether the search should be aborted,
|
||||
// and in the affirmative returns -1. For every handle e probed except
|
||||
// the last one, the function runs update(e).
|
||||
// The probe parameter is modified as follows. We say a probe to a handle
|
||||
// e is aborting if match(e) is false and abort(e) is true. Then the final
|
||||
// value of probe is one more than the last non-aborting probe during the
|
||||
// call. This is so that that the variable can be used to keep track of
|
||||
// progress across consecutive calls to FindSlot.
|
||||
inline HandleImpl* FindSlot(const UniqueId64x2& hashed_key,
|
||||
const MatchFn& match_fn, const AbortFn& abort_fn,
|
||||
const UpdateFn& update_fn);
|
||||
std::function<bool(HandleImpl*)> match,
|
||||
std::function<bool(HandleImpl*)> stop,
|
||||
std::function<void(HandleImpl*)> update,
|
||||
size_t& probe);
|
||||
|
||||
// Re-decrement all displacements in probe path starting from beginning
|
||||
// until (not including) the given handle
|
||||
@@ -604,6 +492,33 @@ class FixedHyperClockTable : public BaseClockTable {
|
||||
// before releasing it so that it can be provided to this function.
|
||||
inline void ReclaimEntryUsage(size_t total_charge);
|
||||
|
||||
// Helper for updating `usage_` for new entry with given `total_charge`
|
||||
// and evicting if needed under strict_capacity_limit=true rules. This
|
||||
// means the operation might fail with Status::MemoryLimit. If
|
||||
// `need_evict_for_occupancy`, then eviction of at least one entry is
|
||||
// required, and the operation should fail if not possible.
|
||||
// NOTE: Otherwise, occupancy_ is not managed in this function
|
||||
inline Status ChargeUsageMaybeEvictStrict(size_t total_charge,
|
||||
size_t capacity,
|
||||
bool need_evict_for_occupancy);
|
||||
|
||||
// Helper for updating `usage_` for new entry with given `total_charge`
|
||||
// and evicting if needed under strict_capacity_limit=false rules. This
|
||||
// means that updating `usage_` always succeeds even if forced to exceed
|
||||
// capacity. If `need_evict_for_occupancy`, then eviction of at least one
|
||||
// entry is required, and the operation should return false if such eviction
|
||||
// is not possible. `usage_` is not updated in that case. Otherwise, returns
|
||||
// true, indicating success.
|
||||
// NOTE: occupancy_ is not managed in this function
|
||||
inline bool ChargeUsageMaybeEvictNonStrict(size_t total_charge,
|
||||
size_t capacity,
|
||||
bool need_evict_for_occupancy);
|
||||
|
||||
// Creates a "standalone" handle for returning from an Insert operation that
|
||||
// cannot be completed by actually inserting into the table.
|
||||
// Updates `standalone_usage_` but not `usage_` nor `occupancy_`.
|
||||
inline HandleImpl* StandaloneInsert(const ClockHandleBasicData& proto);
|
||||
|
||||
MemoryAllocator* GetAllocator() const { return allocator_; }
|
||||
|
||||
// Returns the number of bits used to hash an element in the hash
|
||||
@@ -624,25 +539,41 @@ class FixedHyperClockTable : public BaseClockTable {
|
||||
|
||||
// Array of slots comprising the hash table.
|
||||
const std::unique_ptr<HandleImpl[]> array_;
|
||||
}; // class FixedHyperClockTable
|
||||
|
||||
// Placeholder for future automatic table variant
|
||||
// For now, just use FixedHyperClockTable.
|
||||
class AutoHyperClockTable : public FixedHyperClockTable {
|
||||
public:
|
||||
using FixedHyperClockTable::FixedHyperClockTable;
|
||||
}; // class AutoHyperClockTable
|
||||
// From Cache, for deleter
|
||||
MemoryAllocator* const allocator_;
|
||||
|
||||
// A reference to Cache::eviction_callback_
|
||||
const Cache::EvictionCallback& eviction_callback_;
|
||||
|
||||
// We partition the following members into different cache lines
|
||||
// to avoid false sharing among Lookup, Release, Erase and Insert
|
||||
// operations in ClockCacheShard.
|
||||
|
||||
ALIGN_AS(CACHE_LINE_SIZE)
|
||||
// Clock algorithm sweep pointer.
|
||||
std::atomic<uint64_t> clock_pointer_{};
|
||||
|
||||
ALIGN_AS(CACHE_LINE_SIZE)
|
||||
// Number of elements in the table.
|
||||
std::atomic<size_t> occupancy_{};
|
||||
|
||||
// Memory usage by entries tracked by the cache (including standalone)
|
||||
std::atomic<size_t> usage_{};
|
||||
|
||||
// Part of usage by standalone entries (not in table)
|
||||
std::atomic<size_t> standalone_usage_{};
|
||||
}; // class HyperClockTable
|
||||
|
||||
// A single shard of sharded cache.
|
||||
template <class TableT>
|
||||
template <class Table>
|
||||
class ALIGN_AS(CACHE_LINE_SIZE) ClockCacheShard final : public CacheShardBase {
|
||||
public:
|
||||
using Table = TableT;
|
||||
ClockCacheShard(size_t capacity, bool strict_capacity_limit,
|
||||
CacheMetadataChargePolicy metadata_charge_policy,
|
||||
MemoryAllocator* allocator,
|
||||
const Cache::EvictionCallback* eviction_callback,
|
||||
const uint32_t* hash_seed, const typename Table::Opts& opts);
|
||||
const typename Table::Opts& opts);
|
||||
|
||||
// For CacheShard concept
|
||||
using HandleImpl = typename Table::HandleImpl;
|
||||
@@ -652,23 +583,22 @@ 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, uint32_t seed) {
|
||||
static inline HashVal ComputeHash(const Slice& key) {
|
||||
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] ^ seed, &out[1], &out[0]);
|
||||
BijectiveHash2x64(in[1], in[0], &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, uint32_t seed) {
|
||||
UniqueId64x2* unhashed) {
|
||||
BijectiveUnhash2x64(hashed[1], hashed[0], &(*unhashed)[1], &(*unhashed)[0]);
|
||||
(*unhashed)[0] ^= seed;
|
||||
// NOTE: endian dependence
|
||||
return Slice(reinterpret_cast<const char*>(unhashed), kCacheKeySize);
|
||||
}
|
||||
@@ -730,17 +660,9 @@ class ALIGN_AS(CACHE_LINE_SIZE) ClockCacheShard final : public CacheShardBase {
|
||||
return Lookup(key, hashed_key);
|
||||
}
|
||||
|
||||
Table& GetTable() { return table_; }
|
||||
const Table& GetTable() const { return table_; }
|
||||
|
||||
#ifndef NDEBUG
|
||||
size_t& TEST_MutableOccupancyLimit() {
|
||||
return table_.TEST_MutableOccupancyLimit();
|
||||
}
|
||||
// Acquire/release N references
|
||||
void TEST_RefN(HandleImpl* handle, size_t n);
|
||||
void TEST_ReleaseN(HandleImpl* handle, size_t n);
|
||||
#endif
|
||||
|
||||
private: // data
|
||||
Table table_;
|
||||
@@ -752,14 +674,20 @@ class ALIGN_AS(CACHE_LINE_SIZE) ClockCacheShard final : public CacheShardBase {
|
||||
std::atomic<bool> strict_capacity_limit_;
|
||||
}; // class ClockCacheShard
|
||||
|
||||
template <class Table>
|
||||
class BaseHyperClockCache : public ShardedCache<ClockCacheShard<Table>> {
|
||||
class HyperClockCache
|
||||
#ifdef NDEBUG
|
||||
final
|
||||
#endif
|
||||
: public ShardedCache<ClockCacheShard<HyperClockTable>> {
|
||||
public:
|
||||
using Shard = ClockCacheShard<Table>;
|
||||
using Handle = Cache::Handle;
|
||||
using CacheItemHelper = Cache::CacheItemHelper;
|
||||
using Shard = ClockCacheShard<HyperClockTable>;
|
||||
|
||||
explicit BaseHyperClockCache(const HyperClockCacheOptions& opts);
|
||||
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);
|
||||
|
||||
const char* Name() const override { return "HyperClockCache"; }
|
||||
|
||||
Cache::ObjectPtr Value(Handle* handle) override;
|
||||
|
||||
@@ -769,33 +697,7 @@ class BaseHyperClockCache : public ShardedCache<ClockCacheShard<Table>> {
|
||||
|
||||
void ReportProblems(
|
||||
const std::shared_ptr<Logger>& /*info_log*/) const override;
|
||||
};
|
||||
|
||||
class FixedHyperClockCache
|
||||
#ifdef NDEBUG
|
||||
final
|
||||
#endif
|
||||
: public BaseHyperClockCache<FixedHyperClockTable> {
|
||||
public:
|
||||
using BaseHyperClockCache::BaseHyperClockCache;
|
||||
|
||||
const char* Name() const override { return "FixedHyperClockCache"; }
|
||||
|
||||
void ReportProblems(
|
||||
const std::shared_ptr<Logger>& /*info_log*/) const override;
|
||||
}; // class FixedHyperClockCache
|
||||
|
||||
// Placeholder for future automatic HCC variant
|
||||
class AutoHyperClockCache
|
||||
#ifdef NDEBUG
|
||||
final
|
||||
#endif
|
||||
: public BaseHyperClockCache<AutoHyperClockTable> {
|
||||
public:
|
||||
using BaseHyperClockCache::BaseHyperClockCache;
|
||||
|
||||
const char* Name() const override { return "AutoHyperClockCache"; }
|
||||
}; // class AutoHyperClockCache
|
||||
}; // class HyperClockCache
|
||||
|
||||
} // namespace clock_cache
|
||||
|
||||
|
||||
Vendored
+52
-43
@@ -9,7 +9,7 @@
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
|
||||
#include "memory/memory_allocator_impl.h"
|
||||
#include "memory/memory_allocator.h"
|
||||
#include "monitoring/perf_context_imp.h"
|
||||
#include "util/compression.h"
|
||||
#include "util/string_util.h"
|
||||
@@ -17,27 +17,31 @@
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
CompressedSecondaryCache::CompressedSecondaryCache(
|
||||
const CompressedSecondaryCacheOptions& opts)
|
||||
: cache_(opts.LRUCacheOptions::MakeSharedCache()),
|
||||
cache_options_(opts),
|
||||
cache_res_mgr_(std::make_shared<ConcurrentCacheReservationManager>(
|
||||
std::make_shared<CacheReservationManagerImpl<CacheEntryRole::kMisc>>(
|
||||
cache_))),
|
||||
disable_cache_(opts.capacity == 0) {}
|
||||
|
||||
CompressedSecondaryCache::~CompressedSecondaryCache() {
|
||||
assert(cache_res_mgr_->GetTotalReservedCacheSize() == 0);
|
||||
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,
|
||||
const CacheEntryRoleSet& do_not_compress_roles)
|
||||
: 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, do_not_compress_roles) {
|
||||
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);
|
||||
}
|
||||
|
||||
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& kept_in_sec_cache) {
|
||||
assert(helper);
|
||||
if (disable_cache_) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::unique_ptr<SecondaryCacheResultHandle> handle;
|
||||
kept_in_sec_cache = false;
|
||||
Cache::Handle* lru_handle = cache_->Lookup(key);
|
||||
@@ -114,27 +118,20 @@ std::unique_ptr<SecondaryCacheResultHandle> CompressedSecondaryCache::Lookup(
|
||||
|
||||
Status CompressedSecondaryCache::Insert(const Slice& key,
|
||||
Cache::ObjectPtr value,
|
||||
const Cache::CacheItemHelper* helper,
|
||||
bool force_insert) {
|
||||
const Cache::CacheItemHelper* helper) {
|
||||
if (value == nullptr) {
|
||||
return Status::InvalidArgument();
|
||||
}
|
||||
|
||||
if (disable_cache_) {
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Cache::Handle* lru_handle = cache_->Lookup(key);
|
||||
auto internal_helper = GetHelper(cache_options_.enable_custom_split_merge);
|
||||
if (!force_insert) {
|
||||
Cache::Handle* lru_handle = cache_->Lookup(key);
|
||||
if (lru_handle == nullptr) {
|
||||
PERF_COUNTER_ADD(compressed_sec_cache_insert_dummy_count, 1);
|
||||
// Insert a dummy handle if the handle is evicted for the first time.
|
||||
return cache_->Insert(key, /*obj=*/nullptr, internal_helper,
|
||||
/*charge=*/0);
|
||||
} else {
|
||||
cache_->Release(lru_handle, /*erase_if_last_ref=*/false);
|
||||
}
|
||||
if (lru_handle == nullptr) {
|
||||
PERF_COUNTER_ADD(compressed_sec_cache_insert_dummy_count, 1);
|
||||
// Insert a dummy handle if the handle is evicted for the first time.
|
||||
return cache_->Insert(key, /*obj=*/nullptr, internal_helper,
|
||||
/*charge=*/0);
|
||||
} else {
|
||||
cache_->Release(lru_handle, /*erase_if_last_ref=*/false);
|
||||
}
|
||||
|
||||
size_t size = (*helper->size_cb)(value);
|
||||
@@ -152,8 +149,7 @@ Status CompressedSecondaryCache::Insert(const Slice& key,
|
||||
!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,
|
||||
compression_opts);
|
||||
CompressionContext compression_context(cache_options_.compression_type);
|
||||
uint64_t sample_for_compression{0};
|
||||
CompressionInfo compression_info(
|
||||
compression_opts, compression_context, CompressionDict::GetEmptyDict(),
|
||||
@@ -195,7 +191,6 @@ Status CompressedSecondaryCache::SetCapacity(size_t capacity) {
|
||||
MutexLock l(&capacity_mutex_);
|
||||
cache_options_.capacity = capacity;
|
||||
cache_->SetCapacity(capacity);
|
||||
disable_cache_ = capacity == 0;
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
@@ -316,17 +311,31 @@ const Cache::CacheItemHelper* CompressedSecondaryCache::GetHelper(
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<SecondaryCache>
|
||||
CompressedSecondaryCacheOptions::MakeSharedSecondaryCache() const {
|
||||
return std::make_shared<CompressedSecondaryCache>(*this);
|
||||
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,
|
||||
const CacheEntryRoleSet& do_not_compress_roles) {
|
||||
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, do_not_compress_roles);
|
||||
}
|
||||
|
||||
Status CompressedSecondaryCache::Deflate(size_t decrease) {
|
||||
return cache_res_mgr_->UpdateCacheReservation(decrease, /*increase=*/true);
|
||||
}
|
||||
|
||||
Status CompressedSecondaryCache::Inflate(size_t increase) {
|
||||
return cache_res_mgr_->UpdateCacheReservation(increase, /*increase=*/false);
|
||||
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, opts.do_not_compress_roles);
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
Vendored
+14
-14
@@ -9,9 +9,8 @@
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
|
||||
#include "cache/cache_reservation_manager.h"
|
||||
#include "cache/lru_cache.h"
|
||||
#include "memory/memory_allocator_impl.h"
|
||||
#include "memory/memory_allocator.h"
|
||||
#include "rocksdb/secondary_cache.h"
|
||||
#include "rocksdb/slice.h"
|
||||
#include "rocksdb/status.h"
|
||||
@@ -70,15 +69,24 @@ class CompressedSecondaryCacheResultHandle : public SecondaryCacheResultHandle {
|
||||
|
||||
class CompressedSecondaryCache : public SecondaryCache {
|
||||
public:
|
||||
explicit CompressedSecondaryCache(
|
||||
const CompressedSecondaryCacheOptions& opts);
|
||||
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,
|
||||
const CacheEntryRoleSet& do_not_compress_roles = {
|
||||
CacheEntryRole::kFilterBlock});
|
||||
~CompressedSecondaryCache() override;
|
||||
|
||||
const char* Name() const override { return "CompressedSecondaryCache"; }
|
||||
|
||||
Status Insert(const Slice& key, Cache::ObjectPtr value,
|
||||
const Cache::CacheItemHelper* helper,
|
||||
bool force_insert) override;
|
||||
const Cache::CacheItemHelper* helper) override;
|
||||
|
||||
std::unique_ptr<SecondaryCacheResultHandle> Lookup(
|
||||
const Slice& key, const Cache::CacheItemHelper* helper,
|
||||
@@ -95,14 +103,8 @@ class CompressedSecondaryCache : public SecondaryCache {
|
||||
|
||||
Status GetCapacity(size_t& capacity) override;
|
||||
|
||||
Status Deflate(size_t decrease) override;
|
||||
|
||||
Status Inflate(size_t increase) override;
|
||||
|
||||
std::string GetPrintableOptions() const override;
|
||||
|
||||
size_t TEST_GetUsage() { return cache_->GetUsage(); }
|
||||
|
||||
private:
|
||||
friend class CompressedSecondaryCacheTestBase;
|
||||
static constexpr std::array<uint16_t, 8> malloc_bin_sizes_{
|
||||
@@ -135,8 +137,6 @@ class CompressedSecondaryCache : public SecondaryCache {
|
||||
std::shared_ptr<Cache> cache_;
|
||||
CompressedSecondaryCacheOptions cache_options_;
|
||||
mutable port::Mutex capacity_mutex_;
|
||||
std::shared_ptr<ConcurrentCacheReservationManager> cache_res_mgr_;
|
||||
bool disable_cache_;
|
||||
};
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
+22
-224
@@ -10,14 +10,11 @@
|
||||
#include <memory>
|
||||
#include <tuple>
|
||||
|
||||
#include "cache/secondary_cache_adapter.h"
|
||||
#include "memory/jemalloc_nodump_allocator.h"
|
||||
#include "rocksdb/cache.h"
|
||||
#include "rocksdb/convenience.h"
|
||||
#include "test_util/secondary_cache_test_util.h"
|
||||
#include "test_util/testharness.h"
|
||||
#include "test_util/testutil.h"
|
||||
#include "util/cast_util.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
@@ -52,7 +49,7 @@ class CompressedSecondaryCacheTestBase : 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(key1, &item1, GetHelper(), false));
|
||||
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);
|
||||
@@ -63,7 +60,7 @@ class CompressedSecondaryCacheTestBase : public testing::Test,
|
||||
ASSERT_EQ(handle1_1, nullptr);
|
||||
|
||||
// Insert and Lookup the item k1 for the second time and advise erasing it.
|
||||
ASSERT_OK(sec_cache->Insert(key1, &item1, GetHelper(), false));
|
||||
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 =
|
||||
@@ -95,14 +92,14 @@ class CompressedSecondaryCacheTestBase : public testing::Test,
|
||||
// Insert and Lookup the item k2.
|
||||
std::string str2(rnd.RandomString(1000));
|
||||
TestItem item2(str2.data(), str2.length());
|
||||
ASSERT_OK(sec_cache->Insert(key2, &item2, GetHelper(), false));
|
||||
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(key2, GetHelper(), this, true, /*advise_erase=*/false,
|
||||
kept_in_sec_cache);
|
||||
ASSERT_EQ(handle2_1, nullptr);
|
||||
|
||||
ASSERT_OK(sec_cache->Insert(key2, &item2, GetHelper(), false));
|
||||
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,
|
||||
@@ -184,15 +181,15 @@ class CompressedSecondaryCacheTestBase : public testing::Test,
|
||||
std::string str1(rnd.RandomString(1000));
|
||||
TestItem item1(str1.data(), str1.length());
|
||||
// Insert a dummy handle.
|
||||
ASSERT_OK(sec_cache->Insert(key1, &item1, GetHelper(), false));
|
||||
ASSERT_OK(sec_cache->Insert(key1, &item1, GetHelper()));
|
||||
// Insert k1.
|
||||
ASSERT_OK(sec_cache->Insert(key1, &item1, GetHelper(), false));
|
||||
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(key2, &item2, GetHelper(), false));
|
||||
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,
|
||||
@@ -200,7 +197,7 @@ class CompressedSecondaryCacheTestBase : public testing::Test,
|
||||
ASSERT_EQ(handle1, nullptr);
|
||||
|
||||
// Insert k2 and k1 is evicted.
|
||||
ASSERT_OK(sec_cache->Insert(key2, &item2, GetHelper(), false));
|
||||
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);
|
||||
@@ -211,7 +208,7 @@ class CompressedSecondaryCacheTestBase : 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(key1, &item1, GetHelper(), false));
|
||||
ASSERT_OK(sec_cache->Insert(key1, &item1, GetHelper()));
|
||||
|
||||
std::unique_ptr<SecondaryCacheResultHandle> handle1_1 =
|
||||
sec_cache->Lookup(key1, GetHelper(), this, true, /*advise_erase=*/false,
|
||||
@@ -229,8 +226,8 @@ class CompressedSecondaryCacheTestBase : public testing::Test,
|
||||
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(key3, &item3, GetHelperFail(), false));
|
||||
ASSERT_NOK(sec_cache->Insert(key3, &item3, GetHelperFail(), false));
|
||||
ASSERT_OK(sec_cache->Insert(key3, &item3, GetHelperFail()));
|
||||
ASSERT_NOK(sec_cache->Insert(key3, &item3, GetHelperFail()));
|
||||
|
||||
sec_cache.reset();
|
||||
}
|
||||
@@ -629,9 +626,8 @@ class CompressedSecondaryCacheTestBase : public testing::Test,
|
||||
|
||||
using CacheValueChunk = CompressedSecondaryCache::CacheValueChunk;
|
||||
std::unique_ptr<CompressedSecondaryCache> sec_cache =
|
||||
std::make_unique<CompressedSecondaryCache>(
|
||||
CompressedSecondaryCacheOptions(1000, 0, true, 0.5, 0.0,
|
||||
allocator));
|
||||
std::make_unique<CompressedSecondaryCache>(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};
|
||||
@@ -682,8 +678,7 @@ class CompressedSecondaryCacheTestBase : public testing::Test,
|
||||
std::string str = str1 + str2 + str3;
|
||||
|
||||
std::unique_ptr<CompressedSecondaryCache> sec_cache =
|
||||
std::make_unique<CompressedSecondaryCache>(
|
||||
CompressedSecondaryCacheOptions(1000, 0, true, 0.5, 0.0));
|
||||
std::make_unique<CompressedSecondaryCache>(1000, 0, true, 0.5, 0.0);
|
||||
size_t charge{0};
|
||||
CacheAllocationPtr value =
|
||||
sec_cache->MergeChunksIntoValue(chunks_head, charge);
|
||||
@@ -713,9 +708,8 @@ class CompressedSecondaryCacheTestBase : public testing::Test,
|
||||
|
||||
using CacheValueChunk = CompressedSecondaryCache::CacheValueChunk;
|
||||
std::unique_ptr<CompressedSecondaryCache> sec_cache =
|
||||
std::make_unique<CompressedSecondaryCache>(
|
||||
CompressedSecondaryCacheOptions(1000, 0, true, 0.5, 0.0,
|
||||
allocator));
|
||||
std::make_unique<CompressedSecondaryCache>(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};
|
||||
@@ -738,7 +732,7 @@ class CompressedSecondaryCacheTestBase : public testing::Test,
|
||||
class CompressedSecondaryCacheTest
|
||||
: public CompressedSecondaryCacheTestBase,
|
||||
public testing::WithParamInterface<std::string> {
|
||||
const std::string& Type() const override { return GetParam(); }
|
||||
const std::string& Type() override { return GetParam(); }
|
||||
};
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(CompressedSecondaryCacheTest,
|
||||
@@ -753,7 +747,7 @@ class CompressedSecCacheTestWithCompressAndAllocatorParam
|
||||
sec_cache_is_compressed_ = std::get<0>(GetParam());
|
||||
use_jemalloc_ = std::get<1>(GetParam());
|
||||
}
|
||||
const std::string& Type() const override { return std::get<2>(GetParam()); }
|
||||
const std::string& Type() override { return std::get<2>(GetParam()); }
|
||||
bool sec_cache_is_compressed_;
|
||||
bool use_jemalloc_;
|
||||
};
|
||||
@@ -774,7 +768,7 @@ class CompressedSecondaryCacheTestWithCompressionParam
|
||||
CompressedSecondaryCacheTestWithCompressionParam() {
|
||||
sec_cache_is_compressed_ = std::get<0>(GetParam());
|
||||
}
|
||||
const std::string& Type() const override { return std::get<1>(GetParam()); }
|
||||
const std::string& Type() override { return std::get<1>(GetParam()); }
|
||||
bool sec_cache_is_compressed_;
|
||||
};
|
||||
|
||||
@@ -905,10 +899,10 @@ TEST_P(CompressedSecondaryCacheTestWithCompressionParam, EntryRoles) {
|
||||
Slice ith_key = Slice(junk.data(), 16);
|
||||
|
||||
get_perf_context()->Reset();
|
||||
ASSERT_OK(sec_cache->Insert(ith_key, &item, GetHelper(role), false));
|
||||
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), false));
|
||||
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};
|
||||
@@ -951,7 +945,7 @@ class CompressedSecCacheTestWithCompressAndSplitParam
|
||||
sec_cache_is_compressed_ = std::get<0>(GetParam());
|
||||
enable_custom_split_merge_ = std::get<1>(GetParam());
|
||||
}
|
||||
const std::string& Type() const override { return std::get<2>(GetParam()); }
|
||||
const std::string& Type() override { return std::get<2>(GetParam()); }
|
||||
bool sec_cache_is_compressed_;
|
||||
bool enable_custom_split_merge_;
|
||||
};
|
||||
@@ -977,202 +971,6 @@ TEST_P(CompressedSecondaryCacheTest, SplictValueAndMergeChunksTest) {
|
||||
SplictValueAndMergeChunksTest();
|
||||
}
|
||||
|
||||
using secondary_cache_test_util::WithCacheType;
|
||||
|
||||
class CompressedSecCacheTestWithTiered
|
||||
: public testing::Test,
|
||||
public WithCacheType,
|
||||
public testing::WithParamInterface<
|
||||
std::tuple<PrimaryCacheType, TieredAdmissionPolicy>> {
|
||||
public:
|
||||
using secondary_cache_test_util::WithCacheType::TestItem;
|
||||
CompressedSecCacheTestWithTiered() {
|
||||
LRUCacheOptions lru_opts;
|
||||
HyperClockCacheOptions hcc_opts(
|
||||
/*_capacity=*/70 << 20,
|
||||
/*_estimated_entry_charge=*/256 << 10,
|
||||
/*_num_shard_bits=*/0);
|
||||
TieredVolatileCacheOptions opts;
|
||||
lru_opts.capacity = 70 << 20;
|
||||
lru_opts.num_shard_bits = 0;
|
||||
lru_opts.high_pri_pool_ratio = 0;
|
||||
opts.cache_type = std::get<0>(GetParam());
|
||||
if (opts.cache_type == PrimaryCacheType::kCacheTypeLRU) {
|
||||
opts.cache_opts = &lru_opts;
|
||||
} else {
|
||||
opts.cache_opts = &hcc_opts;
|
||||
}
|
||||
opts.adm_policy = std::get<1>(GetParam());
|
||||
;
|
||||
opts.comp_cache_opts.capacity = 30 << 20;
|
||||
opts.comp_cache_opts.num_shard_bits = 0;
|
||||
cache_ = NewTieredVolatileCache(opts);
|
||||
cache_res_mgr_ =
|
||||
std::make_shared<CacheReservationManagerImpl<CacheEntryRole::kMisc>>(
|
||||
cache_);
|
||||
}
|
||||
|
||||
const std::string& Type() const override {
|
||||
if (std::get<0>(GetParam()) == PrimaryCacheType::kCacheTypeLRU) {
|
||||
return lru_str;
|
||||
} else {
|
||||
return hcc_str;
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
CacheReservationManager* cache_res_mgr() { return cache_res_mgr_.get(); }
|
||||
|
||||
Cache* GetTieredCache() { return cache_.get(); }
|
||||
|
||||
Cache* GetCache() {
|
||||
return static_cast_with_check<CacheWithSecondaryAdapter, Cache>(
|
||||
cache_.get())
|
||||
->TEST_GetCache();
|
||||
}
|
||||
|
||||
SecondaryCache* GetSecondaryCache() {
|
||||
return static_cast_with_check<CacheWithSecondaryAdapter, Cache>(
|
||||
cache_.get())
|
||||
->TEST_GetSecondaryCache();
|
||||
}
|
||||
|
||||
size_t GetPercent(size_t val, unsigned int percent) {
|
||||
return static_cast<size_t>(val * percent / 100);
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr<Cache> cache_;
|
||||
std::shared_ptr<CacheReservationManager> cache_res_mgr_;
|
||||
static std::string lru_str;
|
||||
static std::string hcc_str;
|
||||
};
|
||||
|
||||
std::string CompressedSecCacheTestWithTiered::lru_str(WithCacheType::kLRU);
|
||||
std::string CompressedSecCacheTestWithTiered::hcc_str(
|
||||
WithCacheType::kFixedHyperClock);
|
||||
|
||||
bool CacheUsageWithinBounds(size_t val1, size_t val2, size_t error) {
|
||||
return ((val1 < (val2 + error)) && (val1 > (val2 - error)));
|
||||
}
|
||||
|
||||
TEST_P(CompressedSecCacheTestWithTiered, CacheReservationManager) {
|
||||
CompressedSecondaryCache* sec_cache =
|
||||
reinterpret_cast<CompressedSecondaryCache*>(GetSecondaryCache());
|
||||
|
||||
// Use EXPECT_PRED3 instead of EXPECT_NEAR to void too many size_t to
|
||||
// double explicit casts
|
||||
EXPECT_PRED3(CacheUsageWithinBounds, GetCache()->GetUsage(), (30 << 20),
|
||||
GetPercent(30 << 20, 1));
|
||||
EXPECT_EQ(sec_cache->TEST_GetUsage(), 0);
|
||||
|
||||
ASSERT_OK(cache_res_mgr()->UpdateCacheReservation(10 << 20));
|
||||
EXPECT_PRED3(CacheUsageWithinBounds, GetCache()->GetUsage(), (37 << 20),
|
||||
GetPercent(37 << 20, 1));
|
||||
EXPECT_PRED3(CacheUsageWithinBounds, sec_cache->TEST_GetUsage(), (3 << 20),
|
||||
GetPercent(3 << 20, 1));
|
||||
|
||||
ASSERT_OK(cache_res_mgr()->UpdateCacheReservation(0));
|
||||
EXPECT_PRED3(CacheUsageWithinBounds, GetCache()->GetUsage(), (30 << 20),
|
||||
GetPercent(30 << 20, 1));
|
||||
EXPECT_EQ(sec_cache->TEST_GetUsage(), 0);
|
||||
}
|
||||
|
||||
TEST_P(CompressedSecCacheTestWithTiered,
|
||||
CacheReservationManagerMultipleUpdate) {
|
||||
CompressedSecondaryCache* sec_cache =
|
||||
reinterpret_cast<CompressedSecondaryCache*>(GetSecondaryCache());
|
||||
|
||||
EXPECT_PRED3(CacheUsageWithinBounds, GetCache()->GetUsage(), (30 << 20),
|
||||
GetPercent(30 << 20, 1));
|
||||
EXPECT_EQ(sec_cache->TEST_GetUsage(), 0);
|
||||
|
||||
int i;
|
||||
for (i = 0; i < 10; ++i) {
|
||||
ASSERT_OK(cache_res_mgr()->UpdateCacheReservation((1 + i) << 20));
|
||||
}
|
||||
EXPECT_PRED3(CacheUsageWithinBounds, GetCache()->GetUsage(), (37 << 20),
|
||||
GetPercent(37 << 20, 1));
|
||||
EXPECT_PRED3(CacheUsageWithinBounds, sec_cache->TEST_GetUsage(), (3 << 20),
|
||||
GetPercent(3 << 20, 1));
|
||||
|
||||
for (i = 10; i > 0; --i) {
|
||||
ASSERT_OK(cache_res_mgr()->UpdateCacheReservation(((i - 1) << 20)));
|
||||
}
|
||||
EXPECT_PRED3(CacheUsageWithinBounds, GetCache()->GetUsage(), (30 << 20),
|
||||
GetPercent(30 << 20, 1));
|
||||
EXPECT_EQ(sec_cache->TEST_GetUsage(), 0);
|
||||
}
|
||||
|
||||
TEST_P(CompressedSecCacheTestWithTiered, AdmissionPolicy) {
|
||||
if (!LZ4_Supported()) {
|
||||
ROCKSDB_GTEST_BYPASS("This test requires LZ4 support\n");
|
||||
return;
|
||||
}
|
||||
|
||||
Cache* tiered_cache = GetTieredCache();
|
||||
Cache* cache = GetCache();
|
||||
std::vector<CacheKey> keys;
|
||||
std::vector<std::string> vals;
|
||||
// Make the item size slightly less than 10MB to ensure we can fit the
|
||||
// expected number of items in the cache
|
||||
int item_size = (10 << 20) - (1 << 18);
|
||||
int i;
|
||||
Random rnd(301);
|
||||
for (i = 0; i < 14; ++i) {
|
||||
keys.emplace_back(CacheKey::CreateUniqueForCacheLifetime(cache));
|
||||
vals.emplace_back(rnd.RandomString(item_size));
|
||||
}
|
||||
|
||||
for (i = 0; i < 7; ++i) {
|
||||
TestItem* item = new TestItem(vals[i].data(), vals[i].length());
|
||||
ASSERT_OK(tiered_cache->Insert(keys[i].AsSlice(), item, GetHelper(),
|
||||
vals[i].length()));
|
||||
}
|
||||
|
||||
Cache::Handle* handle1;
|
||||
handle1 = tiered_cache->Lookup(keys[0].AsSlice(), GetHelper(),
|
||||
/*context*/ this, Cache::Priority::LOW);
|
||||
ASSERT_NE(handle1, nullptr);
|
||||
Cache::Handle* handle2;
|
||||
handle2 = tiered_cache->Lookup(keys[1].AsSlice(), GetHelper(),
|
||||
/*context*/ this, Cache::Priority::LOW);
|
||||
ASSERT_NE(handle2, nullptr);
|
||||
tiered_cache->Release(handle1);
|
||||
tiered_cache->Release(handle2);
|
||||
|
||||
// Flush all previous entries out of the primary cache
|
||||
for (i = 7; i < 14; ++i) {
|
||||
TestItem* item = new TestItem(vals[i].data(), vals[i].length());
|
||||
ASSERT_OK(tiered_cache->Insert(keys[i].AsSlice(), item, GetHelper(),
|
||||
vals[i].length()));
|
||||
}
|
||||
// keys 0 and 1 should be found as they had the hit bit set
|
||||
handle1 = tiered_cache->Lookup(keys[0].AsSlice(), GetHelper(),
|
||||
/*context*/ this, Cache::Priority::LOW);
|
||||
ASSERT_NE(handle1, nullptr);
|
||||
handle2 = tiered_cache->Lookup(keys[1].AsSlice(), GetHelper(),
|
||||
/*context*/ this, Cache::Priority::LOW);
|
||||
ASSERT_NE(handle2, nullptr);
|
||||
tiered_cache->Release(handle1);
|
||||
tiered_cache->Release(handle2);
|
||||
|
||||
handle1 = tiered_cache->Lookup(keys[2].AsSlice(), GetHelper(),
|
||||
/*context*/ this, Cache::Priority::LOW);
|
||||
ASSERT_EQ(handle1, nullptr);
|
||||
handle1 = tiered_cache->Lookup(keys[3].AsSlice(), GetHelper(),
|
||||
/*context*/ this, Cache::Priority::LOW);
|
||||
ASSERT_EQ(handle1, nullptr);
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(
|
||||
CompressedSecCacheTests, CompressedSecCacheTestWithTiered,
|
||||
::testing::Values(
|
||||
std::make_tuple(PrimaryCacheType::kCacheTypeLRU,
|
||||
TieredAdmissionPolicy::kAdmPolicyAllowCacheHits),
|
||||
std::make_tuple(PrimaryCacheType::kCacheTypeHCC,
|
||||
TieredAdmissionPolicy::kAdmPolicyAllowCacheHits)));
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
Vendored
+49
-25
@@ -16,7 +16,7 @@
|
||||
|
||||
#include "cache/secondary_cache_adapter.h"
|
||||
#include "monitoring/perf_context_imp.h"
|
||||
#include "monitoring/statistics_impl.h"
|
||||
#include "monitoring/statistics.h"
|
||||
#include "port/lang.h"
|
||||
#include "util/distributed_mutex.h"
|
||||
|
||||
@@ -340,8 +340,7 @@ void LRUCacheShard::NotifyEvicted(
|
||||
for (LRUHandle* entry : evicted_handles) {
|
||||
if (eviction_callback_ &&
|
||||
eviction_callback_(entry->key(),
|
||||
reinterpret_cast<Cache::Handle*>(entry),
|
||||
entry->HasHit())) {
|
||||
reinterpret_cast<Cache::Handle*>(entry))) {
|
||||
// Callback took ownership of obj; just free handle
|
||||
free(entry);
|
||||
} else {
|
||||
@@ -506,8 +505,7 @@ bool LRUCacheShard::Release(LRUHandle* e, bool /*useful*/,
|
||||
// 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),
|
||||
e->HasHit())) {
|
||||
eviction_callback_(e->key(), reinterpret_cast<Cache::Handle*>(e))) {
|
||||
// Callback took ownership of obj; just free handle
|
||||
free(e);
|
||||
} else {
|
||||
@@ -648,15 +646,23 @@ void LRUCacheShard::AppendPrintableOptions(std::string& str) const {
|
||||
str.append(buffer);
|
||||
}
|
||||
|
||||
LRUCache::LRUCache(const LRUCacheOptions& opts) : ShardedCache(opts) {
|
||||
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)
|
||||
: ShardedCache(capacity, num_shard_bits, strict_capacity_limit,
|
||||
std::move(allocator)) {
|
||||
size_t per_shard = GetPerShardCapacity();
|
||||
MemoryAllocator* alloc = memory_allocator();
|
||||
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_);
|
||||
const EvictionCallback* eviction_callback = &eviction_callback_;
|
||||
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,
|
||||
eviction_callback);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -686,7 +692,13 @@ double LRUCache::GetHighPriPoolRatio() {
|
||||
|
||||
} // namespace lru_cache
|
||||
|
||||
std::shared_ptr<Cache> LRUCacheOptions::MakeSharedCache() const {
|
||||
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) {
|
||||
if (num_shard_bits >= 20) {
|
||||
return nullptr; // The cache cannot be sharded into too many fine pieces.
|
||||
}
|
||||
@@ -702,24 +714,36 @@ std::shared_ptr<Cache> LRUCacheOptions::MakeSharedCache() const {
|
||||
// Invalid high_pri_pool_ratio and low_pri_pool_ratio combination
|
||||
return nullptr;
|
||||
}
|
||||
// For sanitized options
|
||||
LRUCacheOptions opts = *this;
|
||||
if (opts.num_shard_bits < 0) {
|
||||
opts.num_shard_bits = GetDefaultCacheShardBits(capacity);
|
||||
if (num_shard_bits < 0) {
|
||||
num_shard_bits = GetDefaultCacheShardBits(capacity);
|
||||
}
|
||||
std::shared_ptr<Cache> cache = std::make_shared<LRUCache>(opts);
|
||||
std::shared_ptr<Cache> cache = 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);
|
||||
if (secondary_cache) {
|
||||
cache = std::make_shared<CacheWithSecondaryAdapter>(cache, secondary_cache);
|
||||
}
|
||||
return cache;
|
||||
}
|
||||
|
||||
std::shared_ptr<RowCache> LRUCacheOptions::MakeSharedRowCache() const {
|
||||
if (secondary_cache) {
|
||||
// Not allowed for a RowCache
|
||||
return nullptr;
|
||||
}
|
||||
// Works while RowCache is an alias for Cache
|
||||
return MakeSharedCache();
|
||||
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);
|
||||
}
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
Vendored
+8
-3
@@ -279,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, uint32_t seed) {
|
||||
return Lower32of64(GetSliceNPHash64(key, seed));
|
||||
static inline HashVal ComputeHash(const Slice& key) {
|
||||
return Lower32of64(GetSliceNPHash64(key));
|
||||
}
|
||||
|
||||
// Separate from constructor so caller can easily make an array of LRUCache
|
||||
@@ -446,7 +446,12 @@ class LRUCache
|
||||
#endif
|
||||
: public ShardedCache<LRUCacheShard> {
|
||||
public:
|
||||
explicit LRUCache(const LRUCacheOptions& opts);
|
||||
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);
|
||||
const char* Name() const override { return "LRUCache"; }
|
||||
ObjectPtr Value(Handle* handle) override;
|
||||
size_t GetCharge(Handle* handle) const override;
|
||||
|
||||
Vendored
+195
-276
@@ -371,12 +371,11 @@ TEST_F(LRUCacheTest, EntriesWithPriority) {
|
||||
|
||||
namespace clock_cache {
|
||||
|
||||
template <class ClockCache>
|
||||
class ClockCacheTest : public testing::Test {
|
||||
public:
|
||||
using Shard = typename ClockCache::Shard;
|
||||
using Table = typename Shard::Table;
|
||||
using TableOpts = typename Table::Opts;
|
||||
using Shard = HyperClockCache::Shard;
|
||||
using Table = HyperClockTable;
|
||||
using HandleImpl = Shard::HandleImpl;
|
||||
|
||||
ClockCacheTest() {}
|
||||
~ClockCacheTest() override { DeleteShard(); }
|
||||
@@ -394,10 +393,11 @@ class ClockCacheTest : public testing::Test {
|
||||
shard_ =
|
||||
reinterpret_cast<Shard*>(port::cacheline_aligned_alloc(sizeof(Shard)));
|
||||
|
||||
TableOpts opts{1 /*value_size*/};
|
||||
Table::Opts opts;
|
||||
opts.estimated_value_size = 1;
|
||||
new (shard_)
|
||||
Shard(capacity, strict_capacity_limit, kDontChargeCacheMetadata,
|
||||
/*allocator*/ nullptr, &eviction_callback_, &hash_seed_, opts);
|
||||
/*allocator*/ nullptr, &eviction_callback_, opts);
|
||||
}
|
||||
|
||||
Status Insert(const UniqueId64x2& hashed_key,
|
||||
@@ -455,56 +455,45 @@ class ClockCacheTest : public testing::Test {
|
||||
|
||||
private:
|
||||
Cache::EvictionCallback eviction_callback_;
|
||||
uint32_t hash_seed_ = 0;
|
||||
};
|
||||
|
||||
using ClockCacheTypes =
|
||||
::testing::Types<AutoHyperClockCache, FixedHyperClockCache>;
|
||||
TYPED_TEST_CASE(ClockCacheTest, ClockCacheTypes);
|
||||
|
||||
TYPED_TEST(ClockCacheTest, Misc) {
|
||||
this->NewShard(3);
|
||||
// NOTE: templated base class prevents simple naming of inherited members,
|
||||
// so lots of `this->`
|
||||
auto& shard = *this->shard_;
|
||||
TEST_F(ClockCacheTest, Misc) {
|
||||
NewShard(3);
|
||||
|
||||
// Key size stuff
|
||||
EXPECT_OK(this->InsertWithLen('a', 16));
|
||||
EXPECT_NOK(this->InsertWithLen('b', 15));
|
||||
EXPECT_OK(this->InsertWithLen('b', 16));
|
||||
EXPECT_NOK(this->InsertWithLen('c', 17));
|
||||
EXPECT_NOK(this->InsertWithLen('d', 1000));
|
||||
EXPECT_NOK(this->InsertWithLen('e', 11));
|
||||
EXPECT_NOK(this->InsertWithLen('f', 0));
|
||||
EXPECT_OK(InsertWithLen('a', 16));
|
||||
EXPECT_NOK(InsertWithLen('b', 15));
|
||||
EXPECT_OK(InsertWithLen('b', 16));
|
||||
EXPECT_NOK(InsertWithLen('c', 17));
|
||||
EXPECT_NOK(InsertWithLen('d', 1000));
|
||||
EXPECT_NOK(InsertWithLen('e', 11));
|
||||
EXPECT_NOK(InsertWithLen('f', 0));
|
||||
|
||||
// Some of this is motivated by code coverage
|
||||
std::string wrong_size_key(15, 'x');
|
||||
EXPECT_FALSE(this->Lookup(wrong_size_key, this->TestHashedKey('x')));
|
||||
EXPECT_FALSE(shard.Ref(nullptr));
|
||||
EXPECT_FALSE(shard.Release(nullptr));
|
||||
shard.Erase(wrong_size_key, this->TestHashedKey('x')); // no-op
|
||||
EXPECT_FALSE(Lookup(wrong_size_key, TestHashedKey('x')));
|
||||
EXPECT_FALSE(shard_->Ref(nullptr));
|
||||
EXPECT_FALSE(shard_->Release(nullptr));
|
||||
shard_->Erase(wrong_size_key, TestHashedKey('x')); // no-op
|
||||
}
|
||||
|
||||
TYPED_TEST(ClockCacheTest, Limits) {
|
||||
constexpr size_t kCapacity = 64;
|
||||
this->NewShard(kCapacity, false /*strict_capacity_limit*/);
|
||||
auto& shard = *this->shard_;
|
||||
using HandleImpl = typename ClockCacheTest<TypeParam>::Shard::HandleImpl;
|
||||
|
||||
TEST_F(ClockCacheTest, Limits) {
|
||||
constexpr size_t kCapacity = 3;
|
||||
NewShard(kCapacity, false /*strict_capacity_limit*/);
|
||||
for (bool strict_capacity_limit : {false, true, false}) {
|
||||
SCOPED_TRACE("strict_capacity_limit = " +
|
||||
std::to_string(strict_capacity_limit));
|
||||
|
||||
// Also tests switching between strict limit and not
|
||||
shard.SetStrictCapacityLimit(strict_capacity_limit);
|
||||
shard_->SetStrictCapacityLimit(strict_capacity_limit);
|
||||
|
||||
UniqueId64x2 hkey = this->TestHashedKey('x');
|
||||
UniqueId64x2 hkey = TestHashedKey('x');
|
||||
|
||||
// Single entry charge beyond capacity
|
||||
{
|
||||
Status s = shard.Insert(this->TestKey(hkey), hkey, nullptr /*value*/,
|
||||
&kNoopCacheItemHelper, kCapacity + 2 /*charge*/,
|
||||
nullptr /*handle*/, Cache::Priority::LOW);
|
||||
Status s = shard_->Insert(TestKey(hkey), hkey, nullptr /*value*/,
|
||||
&kNoopCacheItemHelper, 5 /*charge*/,
|
||||
nullptr /*handle*/, Cache::Priority::LOW);
|
||||
if (strict_capacity_limit) {
|
||||
EXPECT_TRUE(s.IsMemoryLimit());
|
||||
} else {
|
||||
@@ -515,11 +504,11 @@ TYPED_TEST(ClockCacheTest, Limits) {
|
||||
// Single entry fills capacity
|
||||
{
|
||||
HandleImpl* h;
|
||||
ASSERT_OK(shard.Insert(this->TestKey(hkey), hkey, nullptr /*value*/,
|
||||
&kNoopCacheItemHelper, kCapacity /*charge*/, &h,
|
||||
Cache::Priority::LOW));
|
||||
ASSERT_OK(shard_->Insert(TestKey(hkey), hkey, nullptr /*value*/,
|
||||
&kNoopCacheItemHelper, 3 /*charge*/, &h,
|
||||
Cache::Priority::LOW));
|
||||
// Try to insert more
|
||||
Status s = this->Insert('a');
|
||||
Status s = Insert('a');
|
||||
if (strict_capacity_limit) {
|
||||
EXPECT_TRUE(s.IsMemoryLimit());
|
||||
} else {
|
||||
@@ -527,22 +516,22 @@ TYPED_TEST(ClockCacheTest, Limits) {
|
||||
}
|
||||
// Release entry filling capacity.
|
||||
// Cover useful = false case.
|
||||
shard.Release(h, false /*useful*/, false /*erase_if_last_ref*/);
|
||||
shard_->Release(h, false /*useful*/, false /*erase_if_last_ref*/);
|
||||
}
|
||||
|
||||
// Insert more than table size can handle to exceed occupancy limit.
|
||||
// (Cleverly using mostly zero-charge entries, but some non-zero to
|
||||
// verify usage tracking on detached entries.)
|
||||
{
|
||||
size_t n = shard.GetTableAddressCount() + 1;
|
||||
size_t n = shard_->GetTableAddressCount() + 1;
|
||||
std::unique_ptr<HandleImpl* []> ha { new HandleImpl* [n] {} };
|
||||
Status s;
|
||||
for (size_t i = 0; i < n && s.ok(); ++i) {
|
||||
hkey[1] = i;
|
||||
s = shard.Insert(this->TestKey(hkey), hkey, nullptr /*value*/,
|
||||
&kNoopCacheItemHelper,
|
||||
(i + kCapacity < n) ? 0 : 1 /*charge*/, &ha[i],
|
||||
Cache::Priority::LOW);
|
||||
s = shard_->Insert(TestKey(hkey), hkey, nullptr /*value*/,
|
||||
&kNoopCacheItemHelper,
|
||||
(i + kCapacity < n) ? 0 : 1 /*charge*/, &ha[i],
|
||||
Cache::Priority::LOW);
|
||||
if (i == 0) {
|
||||
EXPECT_OK(s);
|
||||
}
|
||||
@@ -553,7 +542,7 @@ TYPED_TEST(ClockCacheTest, Limits) {
|
||||
EXPECT_OK(s);
|
||||
}
|
||||
// Same result if not keeping a reference
|
||||
s = this->Insert('a');
|
||||
s = Insert('a');
|
||||
if (strict_capacity_limit) {
|
||||
EXPECT_TRUE(s.IsMemoryLimit());
|
||||
} else {
|
||||
@@ -561,123 +550,122 @@ TYPED_TEST(ClockCacheTest, Limits) {
|
||||
}
|
||||
|
||||
// Regardless, we didn't allow table to actually get full
|
||||
EXPECT_LT(shard.GetOccupancyCount(), shard.GetTableAddressCount());
|
||||
EXPECT_LT(shard_->GetOccupancyCount(), shard_->GetTableAddressCount());
|
||||
|
||||
// Release handles
|
||||
for (size_t i = 0; i < n; ++i) {
|
||||
if (ha[i]) {
|
||||
shard.Release(ha[i]);
|
||||
shard_->Release(ha[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TYPED_TEST(ClockCacheTest, ClockEvictionTest) {
|
||||
TEST_F(ClockCacheTest, ClockEvictionTest) {
|
||||
for (bool strict_capacity_limit : {false, true}) {
|
||||
SCOPED_TRACE("strict_capacity_limit = " +
|
||||
std::to_string(strict_capacity_limit));
|
||||
|
||||
this->NewShard(6, strict_capacity_limit);
|
||||
auto& shard = *this->shard_;
|
||||
EXPECT_OK(this->Insert('a', Cache::Priority::BOTTOM));
|
||||
EXPECT_OK(this->Insert('b', Cache::Priority::LOW));
|
||||
EXPECT_OK(this->Insert('c', Cache::Priority::HIGH));
|
||||
EXPECT_OK(this->Insert('d', Cache::Priority::BOTTOM));
|
||||
EXPECT_OK(this->Insert('e', Cache::Priority::LOW));
|
||||
EXPECT_OK(this->Insert('f', Cache::Priority::HIGH));
|
||||
NewShard(6, strict_capacity_limit);
|
||||
EXPECT_OK(Insert('a', Cache::Priority::BOTTOM));
|
||||
EXPECT_OK(Insert('b', Cache::Priority::LOW));
|
||||
EXPECT_OK(Insert('c', Cache::Priority::HIGH));
|
||||
EXPECT_OK(Insert('d', Cache::Priority::BOTTOM));
|
||||
EXPECT_OK(Insert('e', Cache::Priority::LOW));
|
||||
EXPECT_OK(Insert('f', Cache::Priority::HIGH));
|
||||
|
||||
EXPECT_TRUE(this->Lookup('a', /*use*/ false));
|
||||
EXPECT_TRUE(this->Lookup('b', /*use*/ false));
|
||||
EXPECT_TRUE(this->Lookup('c', /*use*/ false));
|
||||
EXPECT_TRUE(this->Lookup('d', /*use*/ false));
|
||||
EXPECT_TRUE(this->Lookup('e', /*use*/ false));
|
||||
EXPECT_TRUE(this->Lookup('f', /*use*/ false));
|
||||
EXPECT_TRUE(Lookup('a', /*use*/ false));
|
||||
EXPECT_TRUE(Lookup('b', /*use*/ false));
|
||||
EXPECT_TRUE(Lookup('c', /*use*/ false));
|
||||
EXPECT_TRUE(Lookup('d', /*use*/ false));
|
||||
EXPECT_TRUE(Lookup('e', /*use*/ false));
|
||||
EXPECT_TRUE(Lookup('f', /*use*/ false));
|
||||
|
||||
// Ensure bottom are evicted first, even if new entries are low
|
||||
EXPECT_OK(this->Insert('g', Cache::Priority::LOW));
|
||||
EXPECT_OK(this->Insert('h', Cache::Priority::LOW));
|
||||
EXPECT_OK(Insert('g', Cache::Priority::LOW));
|
||||
EXPECT_OK(Insert('h', Cache::Priority::LOW));
|
||||
|
||||
EXPECT_FALSE(this->Lookup('a', /*use*/ false));
|
||||
EXPECT_TRUE(this->Lookup('b', /*use*/ false));
|
||||
EXPECT_TRUE(this->Lookup('c', /*use*/ false));
|
||||
EXPECT_FALSE(this->Lookup('d', /*use*/ false));
|
||||
EXPECT_TRUE(this->Lookup('e', /*use*/ false));
|
||||
EXPECT_TRUE(this->Lookup('f', /*use*/ false));
|
||||
EXPECT_FALSE(Lookup('a', /*use*/ false));
|
||||
EXPECT_TRUE(Lookup('b', /*use*/ false));
|
||||
EXPECT_TRUE(Lookup('c', /*use*/ false));
|
||||
EXPECT_FALSE(Lookup('d', /*use*/ false));
|
||||
EXPECT_TRUE(Lookup('e', /*use*/ false));
|
||||
EXPECT_TRUE(Lookup('f', /*use*/ false));
|
||||
// Mark g & h useful
|
||||
EXPECT_TRUE(this->Lookup('g', /*use*/ true));
|
||||
EXPECT_TRUE(this->Lookup('h', /*use*/ true));
|
||||
EXPECT_TRUE(Lookup('g', /*use*/ true));
|
||||
EXPECT_TRUE(Lookup('h', /*use*/ true));
|
||||
|
||||
// Then old LOW entries
|
||||
EXPECT_OK(this->Insert('i', Cache::Priority::LOW));
|
||||
EXPECT_OK(this->Insert('j', Cache::Priority::LOW));
|
||||
EXPECT_OK(Insert('i', Cache::Priority::LOW));
|
||||
EXPECT_OK(Insert('j', Cache::Priority::LOW));
|
||||
|
||||
EXPECT_FALSE(this->Lookup('b', /*use*/ false));
|
||||
EXPECT_TRUE(this->Lookup('c', /*use*/ false));
|
||||
EXPECT_FALSE(this->Lookup('e', /*use*/ false));
|
||||
EXPECT_TRUE(this->Lookup('f', /*use*/ false));
|
||||
EXPECT_FALSE(Lookup('b', /*use*/ false));
|
||||
EXPECT_TRUE(Lookup('c', /*use*/ false));
|
||||
EXPECT_FALSE(Lookup('e', /*use*/ false));
|
||||
EXPECT_TRUE(Lookup('f', /*use*/ false));
|
||||
// Mark g & h useful once again
|
||||
EXPECT_TRUE(this->Lookup('g', /*use*/ true));
|
||||
EXPECT_TRUE(this->Lookup('h', /*use*/ true));
|
||||
EXPECT_TRUE(this->Lookup('i', /*use*/ false));
|
||||
EXPECT_TRUE(this->Lookup('j', /*use*/ false));
|
||||
EXPECT_TRUE(Lookup('g', /*use*/ true));
|
||||
EXPECT_TRUE(Lookup('h', /*use*/ true));
|
||||
EXPECT_TRUE(Lookup('i', /*use*/ false));
|
||||
EXPECT_TRUE(Lookup('j', /*use*/ false));
|
||||
|
||||
// Then old HIGH entries
|
||||
EXPECT_OK(this->Insert('k', Cache::Priority::LOW));
|
||||
EXPECT_OK(this->Insert('l', Cache::Priority::LOW));
|
||||
EXPECT_OK(Insert('k', Cache::Priority::LOW));
|
||||
EXPECT_OK(Insert('l', Cache::Priority::LOW));
|
||||
|
||||
EXPECT_FALSE(this->Lookup('c', /*use*/ false));
|
||||
EXPECT_FALSE(this->Lookup('f', /*use*/ false));
|
||||
EXPECT_TRUE(this->Lookup('g', /*use*/ false));
|
||||
EXPECT_TRUE(this->Lookup('h', /*use*/ false));
|
||||
EXPECT_TRUE(this->Lookup('i', /*use*/ false));
|
||||
EXPECT_TRUE(this->Lookup('j', /*use*/ false));
|
||||
EXPECT_TRUE(this->Lookup('k', /*use*/ false));
|
||||
EXPECT_TRUE(this->Lookup('l', /*use*/ false));
|
||||
EXPECT_FALSE(Lookup('c', /*use*/ false));
|
||||
EXPECT_FALSE(Lookup('f', /*use*/ false));
|
||||
EXPECT_TRUE(Lookup('g', /*use*/ false));
|
||||
EXPECT_TRUE(Lookup('h', /*use*/ false));
|
||||
EXPECT_TRUE(Lookup('i', /*use*/ false));
|
||||
EXPECT_TRUE(Lookup('j', /*use*/ false));
|
||||
EXPECT_TRUE(Lookup('k', /*use*/ false));
|
||||
EXPECT_TRUE(Lookup('l', /*use*/ false));
|
||||
|
||||
// Then the (roughly) least recently useful
|
||||
EXPECT_OK(this->Insert('m', Cache::Priority::HIGH));
|
||||
EXPECT_OK(this->Insert('n', Cache::Priority::HIGH));
|
||||
EXPECT_OK(Insert('m', Cache::Priority::HIGH));
|
||||
EXPECT_OK(Insert('n', Cache::Priority::HIGH));
|
||||
|
||||
EXPECT_TRUE(this->Lookup('g', /*use*/ false));
|
||||
EXPECT_TRUE(this->Lookup('h', /*use*/ false));
|
||||
EXPECT_FALSE(this->Lookup('i', /*use*/ false));
|
||||
EXPECT_FALSE(this->Lookup('j', /*use*/ false));
|
||||
EXPECT_TRUE(this->Lookup('k', /*use*/ false));
|
||||
EXPECT_TRUE(this->Lookup('l', /*use*/ false));
|
||||
EXPECT_TRUE(Lookup('g', /*use*/ false));
|
||||
EXPECT_TRUE(Lookup('h', /*use*/ false));
|
||||
EXPECT_FALSE(Lookup('i', /*use*/ false));
|
||||
EXPECT_FALSE(Lookup('j', /*use*/ false));
|
||||
EXPECT_TRUE(Lookup('k', /*use*/ false));
|
||||
EXPECT_TRUE(Lookup('l', /*use*/ false));
|
||||
|
||||
// Now try changing capacity down
|
||||
shard.SetCapacity(4);
|
||||
shard_->SetCapacity(4);
|
||||
// Insert to ensure evictions happen
|
||||
EXPECT_OK(this->Insert('o', Cache::Priority::LOW));
|
||||
EXPECT_OK(this->Insert('p', Cache::Priority::LOW));
|
||||
EXPECT_OK(Insert('o', Cache::Priority::LOW));
|
||||
EXPECT_OK(Insert('p', Cache::Priority::LOW));
|
||||
|
||||
EXPECT_FALSE(this->Lookup('g', /*use*/ false));
|
||||
EXPECT_FALSE(this->Lookup('h', /*use*/ false));
|
||||
EXPECT_FALSE(this->Lookup('k', /*use*/ false));
|
||||
EXPECT_FALSE(this->Lookup('l', /*use*/ false));
|
||||
EXPECT_TRUE(this->Lookup('m', /*use*/ false));
|
||||
EXPECT_TRUE(this->Lookup('n', /*use*/ false));
|
||||
EXPECT_TRUE(this->Lookup('o', /*use*/ false));
|
||||
EXPECT_TRUE(this->Lookup('p', /*use*/ false));
|
||||
EXPECT_FALSE(Lookup('g', /*use*/ false));
|
||||
EXPECT_FALSE(Lookup('h', /*use*/ false));
|
||||
EXPECT_FALSE(Lookup('k', /*use*/ false));
|
||||
EXPECT_FALSE(Lookup('l', /*use*/ false));
|
||||
EXPECT_TRUE(Lookup('m', /*use*/ false));
|
||||
EXPECT_TRUE(Lookup('n', /*use*/ false));
|
||||
EXPECT_TRUE(Lookup('o', /*use*/ false));
|
||||
EXPECT_TRUE(Lookup('p', /*use*/ false));
|
||||
|
||||
// Now try changing capacity up
|
||||
EXPECT_TRUE(this->Lookup('m', /*use*/ true));
|
||||
EXPECT_TRUE(this->Lookup('n', /*use*/ true));
|
||||
shard.SetCapacity(6);
|
||||
EXPECT_OK(this->Insert('q', Cache::Priority::HIGH));
|
||||
EXPECT_OK(this->Insert('r', Cache::Priority::HIGH));
|
||||
EXPECT_OK(this->Insert('s', Cache::Priority::HIGH));
|
||||
EXPECT_OK(this->Insert('t', Cache::Priority::HIGH));
|
||||
EXPECT_TRUE(Lookup('m', /*use*/ true));
|
||||
EXPECT_TRUE(Lookup('n', /*use*/ true));
|
||||
shard_->SetCapacity(6);
|
||||
EXPECT_OK(Insert('q', Cache::Priority::HIGH));
|
||||
EXPECT_OK(Insert('r', Cache::Priority::HIGH));
|
||||
EXPECT_OK(Insert('s', Cache::Priority::HIGH));
|
||||
EXPECT_OK(Insert('t', Cache::Priority::HIGH));
|
||||
|
||||
EXPECT_FALSE(this->Lookup('o', /*use*/ false));
|
||||
EXPECT_FALSE(this->Lookup('p', /*use*/ false));
|
||||
EXPECT_TRUE(this->Lookup('m', /*use*/ false));
|
||||
EXPECT_TRUE(this->Lookup('n', /*use*/ false));
|
||||
EXPECT_TRUE(this->Lookup('q', /*use*/ false));
|
||||
EXPECT_TRUE(this->Lookup('r', /*use*/ false));
|
||||
EXPECT_TRUE(this->Lookup('s', /*use*/ false));
|
||||
EXPECT_TRUE(this->Lookup('t', /*use*/ false));
|
||||
EXPECT_FALSE(Lookup('o', /*use*/ false));
|
||||
EXPECT_FALSE(Lookup('p', /*use*/ false));
|
||||
EXPECT_TRUE(Lookup('m', /*use*/ false));
|
||||
EXPECT_TRUE(Lookup('n', /*use*/ false));
|
||||
EXPECT_TRUE(Lookup('q', /*use*/ false));
|
||||
EXPECT_TRUE(Lookup('r', /*use*/ false));
|
||||
EXPECT_TRUE(Lookup('s', /*use*/ false));
|
||||
EXPECT_TRUE(Lookup('t', /*use*/ false));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -693,178 +681,113 @@ const Cache::CacheItemHelper kDeleteCounterHelper{
|
||||
} // namespace
|
||||
|
||||
// Testing calls to CorrectNearOverflow in Release
|
||||
TYPED_TEST(ClockCacheTest, ClockCounterOverflowTest) {
|
||||
this->NewShard(6, /*strict_capacity_limit*/ false);
|
||||
auto& shard = *this->shard_;
|
||||
using HandleImpl = typename ClockCacheTest<TypeParam>::Shard::HandleImpl;
|
||||
|
||||
TEST_F(ClockCacheTest, ClockCounterOverflowTest) {
|
||||
NewShard(6, /*strict_capacity_limit*/ false);
|
||||
HandleImpl* h;
|
||||
DeleteCounter val;
|
||||
UniqueId64x2 hkey = this->TestHashedKey('x');
|
||||
ASSERT_OK(shard.Insert(this->TestKey(hkey), hkey, &val, &kDeleteCounterHelper,
|
||||
1, &h, Cache::Priority::HIGH));
|
||||
UniqueId64x2 hkey = TestHashedKey('x');
|
||||
ASSERT_OK(shard_->Insert(TestKey(hkey), hkey, &val, &kDeleteCounterHelper, 1,
|
||||
&h, Cache::Priority::HIGH));
|
||||
|
||||
// Some large number outstanding
|
||||
shard.TEST_RefN(h, 123456789);
|
||||
shard_->TEST_RefN(h, 123456789);
|
||||
// Simulate many lookup/ref + release, plenty to overflow counters
|
||||
for (int i = 0; i < 10000; ++i) {
|
||||
shard.TEST_RefN(h, 1234567);
|
||||
shard.TEST_ReleaseN(h, 1234567);
|
||||
shard_->TEST_RefN(h, 1234567);
|
||||
shard_->TEST_ReleaseN(h, 1234567);
|
||||
}
|
||||
// Mark it invisible (to reach a different CorrectNearOverflow() in Release)
|
||||
shard.Erase(this->TestKey(hkey), hkey);
|
||||
shard_->Erase(TestKey(hkey), hkey);
|
||||
// Simulate many more lookup/ref + release (one-by-one would be too
|
||||
// expensive for unit test)
|
||||
for (int i = 0; i < 10000; ++i) {
|
||||
shard.TEST_RefN(h, 1234567);
|
||||
shard.TEST_ReleaseN(h, 1234567);
|
||||
shard_->TEST_RefN(h, 1234567);
|
||||
shard_->TEST_ReleaseN(h, 1234567);
|
||||
}
|
||||
// Free all but last 1
|
||||
shard.TEST_ReleaseN(h, 123456789);
|
||||
shard_->TEST_ReleaseN(h, 123456789);
|
||||
// Still alive
|
||||
ASSERT_EQ(val.deleted, 0);
|
||||
// Free last ref, which will finalize erasure
|
||||
shard.Release(h);
|
||||
shard_->Release(h);
|
||||
// Deleted
|
||||
ASSERT_EQ(val.deleted, 1);
|
||||
}
|
||||
|
||||
TYPED_TEST(ClockCacheTest, ClockTableFull) {
|
||||
// Force clock cache table to fill up (not usually allowed) in order
|
||||
// to test full probe sequence that is theoretically possible due to
|
||||
// parallel operations
|
||||
this->NewShard(6, /*strict_capacity_limit*/ false);
|
||||
auto& shard = *this->shard_;
|
||||
using HandleImpl = typename ClockCacheTest<TypeParam>::Shard::HandleImpl;
|
||||
|
||||
size_t size = shard.GetTableAddressCount();
|
||||
ASSERT_LE(size + 3, 256); // for using char keys
|
||||
// Modify occupancy and capacity limits to attempt insert on full
|
||||
shard.TEST_MutableOccupancyLimit() = size + 100;
|
||||
shard.SetCapacity(size + 100);
|
||||
|
||||
DeleteCounter val;
|
||||
std::vector<HandleImpl*> handles;
|
||||
// NOTE: the three extra insertions should create standalone entries
|
||||
for (size_t i = 0; i < size + 3; ++i) {
|
||||
UniqueId64x2 hkey = this->TestHashedKey(static_cast<char>(i));
|
||||
ASSERT_OK(shard.Insert(this->TestKey(hkey), hkey, &val,
|
||||
&kDeleteCounterHelper, 1, &handles.emplace_back(),
|
||||
Cache::Priority::HIGH));
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < size + 3; ++i) {
|
||||
UniqueId64x2 hkey = this->TestHashedKey(static_cast<char>(i));
|
||||
HandleImpl* h = shard.Lookup(this->TestKey(hkey), hkey);
|
||||
if (i < size) {
|
||||
ASSERT_NE(h, nullptr);
|
||||
shard.Release(h);
|
||||
} else {
|
||||
// Standalone entries not visible by lookup
|
||||
ASSERT_EQ(h, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < size + 3; ++i) {
|
||||
ASSERT_NE(handles[i], nullptr);
|
||||
shard.Release(handles[i]);
|
||||
if (i < size) {
|
||||
// Everything still in cache
|
||||
ASSERT_EQ(val.deleted, 0);
|
||||
} else {
|
||||
// Standalone entries freed on release
|
||||
ASSERT_EQ(val.deleted, i + 1 - size);
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t i = size + 3; i > 0; --i) {
|
||||
UniqueId64x2 hkey = this->TestHashedKey(static_cast<char>(i - 1));
|
||||
shard.Erase(this->TestKey(hkey), hkey);
|
||||
if (i - 1 > size) {
|
||||
ASSERT_EQ(val.deleted, 3);
|
||||
} else {
|
||||
ASSERT_EQ(val.deleted, 3 + size - (i - 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This test is mostly to exercise some corner case logic, by forcing two
|
||||
// keys to have the same hash, and more
|
||||
TYPED_TEST(ClockCacheTest, CollidingInsertEraseTest) {
|
||||
this->NewShard(6, /*strict_capacity_limit*/ false);
|
||||
auto& shard = *this->shard_;
|
||||
using HandleImpl = typename ClockCacheTest<TypeParam>::Shard::HandleImpl;
|
||||
|
||||
TEST_F(ClockCacheTest, CollidingInsertEraseTest) {
|
||||
NewShard(6, /*strict_capacity_limit*/ false);
|
||||
DeleteCounter val;
|
||||
UniqueId64x2 hkey1 = this->TestHashedKey('x');
|
||||
Slice key1 = this->TestKey(hkey1);
|
||||
UniqueId64x2 hkey2 = this->TestHashedKey('y');
|
||||
Slice key2 = this->TestKey(hkey2);
|
||||
UniqueId64x2 hkey3 = this->TestHashedKey('z');
|
||||
Slice key3 = this->TestKey(hkey3);
|
||||
UniqueId64x2 hkey1 = TestHashedKey('x');
|
||||
Slice key1 = TestKey(hkey1);
|
||||
UniqueId64x2 hkey2 = TestHashedKey('y');
|
||||
Slice key2 = TestKey(hkey2);
|
||||
UniqueId64x2 hkey3 = TestHashedKey('z');
|
||||
Slice key3 = TestKey(hkey3);
|
||||
HandleImpl* h1;
|
||||
ASSERT_OK(shard.Insert(key1, hkey1, &val, &kDeleteCounterHelper, 1, &h1,
|
||||
Cache::Priority::HIGH));
|
||||
ASSERT_OK(shard_->Insert(key1, hkey1, &val, &kDeleteCounterHelper, 1, &h1,
|
||||
Cache::Priority::HIGH));
|
||||
HandleImpl* h2;
|
||||
ASSERT_OK(shard.Insert(key2, hkey2, &val, &kDeleteCounterHelper, 1, &h2,
|
||||
Cache::Priority::HIGH));
|
||||
ASSERT_OK(shard_->Insert(key2, hkey2, &val, &kDeleteCounterHelper, 1, &h2,
|
||||
Cache::Priority::HIGH));
|
||||
HandleImpl* h3;
|
||||
ASSERT_OK(shard.Insert(key3, hkey3, &val, &kDeleteCounterHelper, 1, &h3,
|
||||
Cache::Priority::HIGH));
|
||||
ASSERT_OK(shard_->Insert(key3, hkey3, &val, &kDeleteCounterHelper, 1, &h3,
|
||||
Cache::Priority::HIGH));
|
||||
|
||||
// Can repeatedly lookup+release despite the hash collision
|
||||
HandleImpl* tmp_h;
|
||||
for (bool erase_if_last_ref : {true, false}) { // but not last ref
|
||||
tmp_h = shard.Lookup(key1, hkey1);
|
||||
tmp_h = shard_->Lookup(key1, hkey1);
|
||||
ASSERT_EQ(h1, tmp_h);
|
||||
ASSERT_FALSE(shard.Release(tmp_h, erase_if_last_ref));
|
||||
ASSERT_FALSE(shard_->Release(tmp_h, erase_if_last_ref));
|
||||
|
||||
tmp_h = shard.Lookup(key2, hkey2);
|
||||
tmp_h = shard_->Lookup(key2, hkey2);
|
||||
ASSERT_EQ(h2, tmp_h);
|
||||
ASSERT_FALSE(shard.Release(tmp_h, erase_if_last_ref));
|
||||
ASSERT_FALSE(shard_->Release(tmp_h, erase_if_last_ref));
|
||||
|
||||
tmp_h = shard.Lookup(key3, hkey3);
|
||||
tmp_h = shard_->Lookup(key3, hkey3);
|
||||
ASSERT_EQ(h3, tmp_h);
|
||||
ASSERT_FALSE(shard.Release(tmp_h, erase_if_last_ref));
|
||||
ASSERT_FALSE(shard_->Release(tmp_h, erase_if_last_ref));
|
||||
}
|
||||
|
||||
// Make h1 invisible
|
||||
shard.Erase(key1, hkey1);
|
||||
shard_->Erase(key1, hkey1);
|
||||
// Redundant erase
|
||||
shard.Erase(key1, hkey1);
|
||||
shard_->Erase(key1, hkey1);
|
||||
|
||||
// All still alive
|
||||
ASSERT_EQ(val.deleted, 0);
|
||||
|
||||
// Invisible to Lookup
|
||||
tmp_h = shard.Lookup(key1, hkey1);
|
||||
tmp_h = shard_->Lookup(key1, hkey1);
|
||||
ASSERT_EQ(nullptr, tmp_h);
|
||||
|
||||
// Can still find h2, h3
|
||||
for (bool erase_if_last_ref : {true, false}) { // but not last ref
|
||||
tmp_h = shard.Lookup(key2, hkey2);
|
||||
tmp_h = shard_->Lookup(key2, hkey2);
|
||||
ASSERT_EQ(h2, tmp_h);
|
||||
ASSERT_FALSE(shard.Release(tmp_h, erase_if_last_ref));
|
||||
ASSERT_FALSE(shard_->Release(tmp_h, erase_if_last_ref));
|
||||
|
||||
tmp_h = shard.Lookup(key3, hkey3);
|
||||
tmp_h = shard_->Lookup(key3, hkey3);
|
||||
ASSERT_EQ(h3, tmp_h);
|
||||
ASSERT_FALSE(shard.Release(tmp_h, erase_if_last_ref));
|
||||
ASSERT_FALSE(shard_->Release(tmp_h, erase_if_last_ref));
|
||||
}
|
||||
|
||||
// Also Insert with invisible entry there
|
||||
ASSERT_OK(shard.Insert(key1, hkey1, &val, &kDeleteCounterHelper, 1, nullptr,
|
||||
Cache::Priority::HIGH));
|
||||
tmp_h = shard.Lookup(key1, hkey1);
|
||||
ASSERT_OK(shard_->Insert(key1, hkey1, &val, &kDeleteCounterHelper, 1, nullptr,
|
||||
Cache::Priority::HIGH));
|
||||
tmp_h = shard_->Lookup(key1, hkey1);
|
||||
// Found but distinct handle
|
||||
ASSERT_NE(nullptr, tmp_h);
|
||||
ASSERT_NE(h1, tmp_h);
|
||||
ASSERT_TRUE(shard.Release(tmp_h, /*erase_if_last_ref*/ true));
|
||||
ASSERT_TRUE(shard_->Release(tmp_h, /*erase_if_last_ref*/ true));
|
||||
|
||||
// tmp_h deleted
|
||||
ASSERT_EQ(val.deleted--, 1);
|
||||
|
||||
// Release last ref on h1 (already invisible)
|
||||
ASSERT_TRUE(shard.Release(h1, /*erase_if_last_ref*/ false));
|
||||
ASSERT_TRUE(shard_->Release(h1, /*erase_if_last_ref*/ false));
|
||||
|
||||
// h1 deleted
|
||||
ASSERT_EQ(val.deleted--, 1);
|
||||
@@ -872,57 +795,57 @@ TYPED_TEST(ClockCacheTest, CollidingInsertEraseTest) {
|
||||
|
||||
// Can still find h2, h3
|
||||
for (bool erase_if_last_ref : {true, false}) { // but not last ref
|
||||
tmp_h = shard.Lookup(key2, hkey2);
|
||||
tmp_h = shard_->Lookup(key2, hkey2);
|
||||
ASSERT_EQ(h2, tmp_h);
|
||||
ASSERT_FALSE(shard.Release(tmp_h, erase_if_last_ref));
|
||||
ASSERT_FALSE(shard_->Release(tmp_h, erase_if_last_ref));
|
||||
|
||||
tmp_h = shard.Lookup(key3, hkey3);
|
||||
tmp_h = shard_->Lookup(key3, hkey3);
|
||||
ASSERT_EQ(h3, tmp_h);
|
||||
ASSERT_FALSE(shard.Release(tmp_h, erase_if_last_ref));
|
||||
ASSERT_FALSE(shard_->Release(tmp_h, erase_if_last_ref));
|
||||
}
|
||||
|
||||
// Release last ref on h2
|
||||
ASSERT_FALSE(shard.Release(h2, /*erase_if_last_ref*/ false));
|
||||
ASSERT_FALSE(shard_->Release(h2, /*erase_if_last_ref*/ false));
|
||||
|
||||
// h2 still not deleted (unreferenced in cache)
|
||||
ASSERT_EQ(val.deleted, 0);
|
||||
|
||||
// Can still find it
|
||||
tmp_h = shard.Lookup(key2, hkey2);
|
||||
tmp_h = shard_->Lookup(key2, hkey2);
|
||||
ASSERT_EQ(h2, tmp_h);
|
||||
|
||||
// Release last ref on h2, with erase
|
||||
ASSERT_TRUE(shard.Release(h2, /*erase_if_last_ref*/ true));
|
||||
ASSERT_TRUE(shard_->Release(h2, /*erase_if_last_ref*/ true));
|
||||
|
||||
// h2 deleted
|
||||
ASSERT_EQ(val.deleted--, 1);
|
||||
tmp_h = shard.Lookup(key2, hkey2);
|
||||
tmp_h = shard_->Lookup(key2, hkey2);
|
||||
ASSERT_EQ(nullptr, tmp_h);
|
||||
|
||||
// Can still find h3
|
||||
for (bool erase_if_last_ref : {true, false}) { // but not last ref
|
||||
tmp_h = shard.Lookup(key3, hkey3);
|
||||
tmp_h = shard_->Lookup(key3, hkey3);
|
||||
ASSERT_EQ(h3, tmp_h);
|
||||
ASSERT_FALSE(shard.Release(tmp_h, erase_if_last_ref));
|
||||
ASSERT_FALSE(shard_->Release(tmp_h, erase_if_last_ref));
|
||||
}
|
||||
|
||||
// Release last ref on h3, without erase
|
||||
ASSERT_FALSE(shard.Release(h3, /*erase_if_last_ref*/ false));
|
||||
ASSERT_FALSE(shard_->Release(h3, /*erase_if_last_ref*/ false));
|
||||
|
||||
// h3 still not deleted (unreferenced in cache)
|
||||
ASSERT_EQ(val.deleted, 0);
|
||||
|
||||
// Explicit erase
|
||||
shard.Erase(key3, hkey3);
|
||||
shard_->Erase(key3, hkey3);
|
||||
|
||||
// h3 deleted
|
||||
ASSERT_EQ(val.deleted--, 1);
|
||||
tmp_h = shard.Lookup(key3, hkey3);
|
||||
tmp_h = shard_->Lookup(key3, hkey3);
|
||||
ASSERT_EQ(nullptr, tmp_h);
|
||||
}
|
||||
|
||||
// This uses the public API to effectively test CalcHashBits etc.
|
||||
TYPED_TEST(ClockCacheTest, TableSizesTest) {
|
||||
TEST_F(ClockCacheTest, TableSizesTest) {
|
||||
for (size_t est_val_size : {1U, 5U, 123U, 2345U, 345678U}) {
|
||||
SCOPED_TRACE("est_val_size = " + std::to_string(est_val_size));
|
||||
for (double est_count : {1.1, 2.2, 511.9, 512.1, 2345.0}) {
|
||||
@@ -935,10 +858,8 @@ TYPED_TEST(ClockCacheTest, TableSizesTest) {
|
||||
/*memory_allocator*/ nullptr, kDontChargeCacheMetadata)
|
||||
.MakeSharedCache();
|
||||
// Table sizes are currently only powers of two
|
||||
EXPECT_GE(cache->GetTableAddressCount(),
|
||||
est_count / FixedHyperClockTable::kLoadFactor);
|
||||
EXPECT_LE(cache->GetTableAddressCount(),
|
||||
est_count / FixedHyperClockTable::kLoadFactor * 2.0);
|
||||
EXPECT_GE(cache->GetTableAddressCount(), est_count / kLoadFactor);
|
||||
EXPECT_LE(cache->GetTableAddressCount(), est_count / kLoadFactor * 2.0);
|
||||
EXPECT_EQ(cache->GetUsage(), 0);
|
||||
|
||||
// kFullChargeMetaData
|
||||
@@ -955,10 +876,9 @@ TYPED_TEST(ClockCacheTest, TableSizesTest) {
|
||||
double est_count_after_meta =
|
||||
(capacity - cache->GetUsage()) * 1.0 / est_val_size;
|
||||
EXPECT_GE(cache->GetTableAddressCount(),
|
||||
est_count_after_meta / FixedHyperClockTable::kLoadFactor);
|
||||
EXPECT_LE(
|
||||
cache->GetTableAddressCount(),
|
||||
est_count_after_meta / FixedHyperClockTable::kLoadFactor * 2.0);
|
||||
est_count_after_meta / kLoadFactor);
|
||||
EXPECT_LE(cache->GetTableAddressCount(),
|
||||
est_count_after_meta / kLoadFactor * 2.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -996,8 +916,7 @@ class TestSecondaryCache : public SecondaryCache {
|
||||
void ResetInjectFailure() { inject_failure_ = false; }
|
||||
|
||||
Status Insert(const Slice& key, Cache::ObjectPtr value,
|
||||
const Cache::CacheItemHelper* helper,
|
||||
bool /*force_insert*/) override {
|
||||
const Cache::CacheItemHelper* helper) override {
|
||||
if (inject_failure_) {
|
||||
return Status::Corruption("Insertion Data Corrupted");
|
||||
}
|
||||
@@ -1447,7 +1366,7 @@ TEST_P(BasicSecondaryCacheTest, FullCapacityTest) {
|
||||
k2.AsSlice(),
|
||||
GetHelper(CacheEntryRole::kDataBlock, /*secondary_compatible=*/false),
|
||||
/*context*/ this, Cache::Priority::LOW);
|
||||
if (strict_capacity_limit || IsHyperClock()) {
|
||||
if (strict_capacity_limit || GetParam() == kHyperClock) {
|
||||
ASSERT_NE(handle2, nullptr);
|
||||
cache->Release(handle2);
|
||||
ASSERT_EQ(secondary_cache->num_inserts(), 1u);
|
||||
@@ -1472,12 +1391,12 @@ TEST_P(BasicSecondaryCacheTest, FullCapacityTest) {
|
||||
// CORRECTION: this is not quite right. block_1 can be inserted into the block
|
||||
// cache because strict_capacity_limit=false, but it is removed from the cache
|
||||
// in Release() because of being over-capacity, without demoting to secondary
|
||||
// cache. FixedHyperClockCache doesn't check capacity on release (for
|
||||
// efficiency) so can demote the over-capacity item to secondary cache. Also, we
|
||||
// intend to add support for demotion in Release, but that currently causes too
|
||||
// much unit test churn.
|
||||
// cache. HyperClockCache doesn't check capacity on release (for efficiency)
|
||||
// so can demote the over-capacity item to secondary cache. Also, we intend to
|
||||
// add support for demotion in Release, but that currently causes too much
|
||||
// unit test churn.
|
||||
TEST_P(DBSecondaryCacheTest, TestSecondaryCacheCorrectness1) {
|
||||
if (IsHyperClock()) {
|
||||
if (GetParam() == kHyperClock) {
|
||||
// See CORRECTION above
|
||||
ROCKSDB_GTEST_BYPASS("Test depends on LRUCache-specific behaviors");
|
||||
return;
|
||||
@@ -1575,7 +1494,7 @@ TEST_P(DBSecondaryCacheTest, TestSecondaryCacheCorrectness1) {
|
||||
// insert and cache block_1 in the block cache (this is the different place
|
||||
// from TestSecondaryCacheCorrectness1)
|
||||
TEST_P(DBSecondaryCacheTest, TestSecondaryCacheCorrectness2) {
|
||||
if (IsHyperClock()) {
|
||||
if (GetParam() == kHyperClock) {
|
||||
ROCKSDB_GTEST_BYPASS("Test depends on LRUCache-specific behaviors");
|
||||
return;
|
||||
}
|
||||
@@ -1763,7 +1682,7 @@ TEST_P(DBSecondaryCacheTest, SecondaryCacheIntensiveTesting) {
|
||||
// if we try to insert block_1 to the block cache, it will always fails. Only
|
||||
// block_2 will be successfully inserted into the block cache.
|
||||
TEST_P(DBSecondaryCacheTest, SecondaryCacheFailureTest) {
|
||||
if (IsHyperClock()) {
|
||||
if (GetParam() == kHyperClock) {
|
||||
ROCKSDB_GTEST_BYPASS("Test depends on LRUCache-specific behaviors");
|
||||
return;
|
||||
}
|
||||
@@ -1873,7 +1792,7 @@ TEST_P(BasicSecondaryCacheTest, BasicWaitAllTest) {
|
||||
str.length()));
|
||||
}
|
||||
// Force all entries to be evicted to the secondary cache
|
||||
if (IsHyperClock()) {
|
||||
if (GetParam() == kHyperClock) {
|
||||
// HCC doesn't respond immediately to SetCapacity
|
||||
for (int i = 9000; i < 9030; ++i) {
|
||||
ASSERT_OK(cache->Insert(ock.WithOffset(i).AsSlice(), nullptr,
|
||||
@@ -1928,7 +1847,7 @@ TEST_P(BasicSecondaryCacheTest, BasicWaitAllTest) {
|
||||
// a sync point callback in TestSecondaryCache::Lookup. We then control the
|
||||
// lookup result by setting the ResultMap.
|
||||
TEST_P(DBSecondaryCacheTest, TestSecondaryCacheMultiGet) {
|
||||
if (IsHyperClock()) {
|
||||
if (GetParam() == kHyperClock) {
|
||||
ROCKSDB_GTEST_BYPASS("Test depends on LRUCache-specific behaviors");
|
||||
return;
|
||||
}
|
||||
@@ -2429,7 +2348,7 @@ TEST_P(DBSecondaryCacheTest, TestSecondaryCacheOptionBasic) {
|
||||
// with new options, which set the lowest_used_cache_tier to
|
||||
// kNonVolatileBlockTier. So secondary cache will be used.
|
||||
TEST_P(DBSecondaryCacheTest, TestSecondaryCacheOptionChange) {
|
||||
if (IsHyperClock()) {
|
||||
if (GetParam() == kHyperClock) {
|
||||
ROCKSDB_GTEST_BYPASS("Test depends on LRUCache-specific behaviors");
|
||||
return;
|
||||
}
|
||||
@@ -2524,7 +2443,7 @@ TEST_P(DBSecondaryCacheTest, TestSecondaryCacheOptionChange) {
|
||||
// Two DB test. We create 2 DBs sharing the same block cache and secondary
|
||||
// cache. We diable the secondary cache option for DB2.
|
||||
TEST_P(DBSecondaryCacheTest, TestSecondaryCacheOptionTwoDB) {
|
||||
if (IsHyperClock()) {
|
||||
if (GetParam() == kHyperClock) {
|
||||
ROCKSDB_GTEST_BYPASS("Test depends on LRUCache-specific behaviors");
|
||||
return;
|
||||
}
|
||||
|
||||
Vendored
+1
-2
@@ -38,8 +38,7 @@ Status SecondaryCache::InsertSaved(const Slice& key, const Slice& saved) {
|
||||
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,
|
||||
/*force_insert=*/true);
|
||||
return Insert(key, const_cast<Slice*>(&saved), &helper);
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
Vendored
+10
-160
@@ -6,7 +6,6 @@
|
||||
#include "cache/secondary_cache_adapter.h"
|
||||
|
||||
#include "monitoring/perf_context_imp.h"
|
||||
#include "util/cast_util.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
@@ -19,116 +18,31 @@ const Dummy kDummy{};
|
||||
Cache::ObjectPtr const kDummyObj = const_cast<Dummy*>(&kDummy);
|
||||
} // namespace
|
||||
|
||||
// When CacheWithSecondaryAdapter is constructed with the distribute_cache_res
|
||||
// parameter set to true, it manages the entire memory budget across the
|
||||
// primary and secondary cache. The secondary cache is assumed to be in
|
||||
// memory, such as the CompressedSecondaryCache. When a placeholder entry
|
||||
// is inserted by a CacheReservationManager instance to reserve memory,
|
||||
// the CacheWithSecondaryAdapter ensures that the reservation is distributed
|
||||
// proportionally across the primary/secondary caches.
|
||||
//
|
||||
// The primary block cache is initially sized to the sum of the primary cache
|
||||
// budget + teh secondary cache budget, as follows -
|
||||
// |--------- Primary Cache Configured Capacity -----------|
|
||||
// |---Secondary Cache Budget----|----Primary Cache Budget-----|
|
||||
//
|
||||
// A ConcurrentCacheReservationManager member in the CacheWithSecondaryAdapter,
|
||||
// pri_cache_res_,
|
||||
// is used to help with tracking the distribution of memory reservations.
|
||||
// Initially, it accounts for the entire secondary cache budget as a
|
||||
// reservation against the primary cache. This shrinks the usable capacity of
|
||||
// the primary cache to the budget that the user originally desired.
|
||||
//
|
||||
// |--Reservation for Sec Cache--|-Pri Cache Usable Capacity---|
|
||||
//
|
||||
// When a reservation placeholder is inserted into the adapter, it is inserted
|
||||
// directly into the primary cache. This means the entire charge of the
|
||||
// placeholder is counted against the primary cache. To compensate and count
|
||||
// a portion of it against the secondary cache, the secondary cache Deflate()
|
||||
// method is called to shrink it. Since the Deflate() causes the secondary
|
||||
// actual usage to shrink, it is refelcted here by releasing an equal amount
|
||||
// from the pri_cache_res_ reservation. The Deflate() in the secondary cache
|
||||
// can be, but is not required to be, implemented using its own cache
|
||||
// reservation manager.
|
||||
//
|
||||
// For example, if the pri/sec ratio is 70/30, and the combined capacity is
|
||||
// 100MB, the intermediate and final state after inserting a reservation
|
||||
// placeholder for 10MB would be as follows -
|
||||
//
|
||||
// |-Reservation for Sec Cache-|-Pri Cache Usable Capacity-|---R---|
|
||||
// 1. After inserting the placeholder in primary
|
||||
// |------- 30MB -------------|------- 60MB -------------|-10MB--|
|
||||
// 2. After deflating the secondary and adjusting the reservation for
|
||||
// secondary against the primary
|
||||
// |------- 27MB -------------|------- 63MB -------------|-10MB--|
|
||||
//
|
||||
// Likewise, when the user inserted placeholder is released, the secondary
|
||||
// cache Inflate() method is called to grow it, and the pri_cache_res_
|
||||
// reservation is increased by an equal amount.
|
||||
//
|
||||
// Another way of implementing this would have been to simply split the user
|
||||
// reservation into primary and seconary components. However, this would
|
||||
// require allocating a structure to track the associated secondary cache
|
||||
// reservation, which adds some complexity and overhead.
|
||||
//
|
||||
CacheWithSecondaryAdapter::CacheWithSecondaryAdapter(
|
||||
std::shared_ptr<Cache> target,
|
||||
std::shared_ptr<SecondaryCache> secondary_cache,
|
||||
TieredAdmissionPolicy adm_policy, bool distribute_cache_res)
|
||||
std::shared_ptr<SecondaryCache> secondary_cache)
|
||||
: CacheWrapper(std::move(target)),
|
||||
secondary_cache_(std::move(secondary_cache)),
|
||||
adm_policy_(adm_policy),
|
||||
distribute_cache_res_(distribute_cache_res) {
|
||||
target_->SetEvictionCallback(
|
||||
[this](const Slice& key, Handle* handle, bool was_hit) {
|
||||
return EvictionHandler(key, handle, was_hit);
|
||||
});
|
||||
if (distribute_cache_res_) {
|
||||
size_t sec_capacity = 0;
|
||||
pri_cache_res_ = std::make_shared<ConcurrentCacheReservationManager>(
|
||||
std::make_shared<CacheReservationManagerImpl<CacheEntryRole::kMisc>>(
|
||||
target_));
|
||||
Status s = secondary_cache_->GetCapacity(sec_capacity);
|
||||
assert(s.ok());
|
||||
// Initially, the primary cache is sized to uncompressed cache budget plsu
|
||||
// compressed secondary cache budget. The secondary cache budget is then
|
||||
// taken away from the primary cache through cache reservations. Later,
|
||||
// when a placeholder entry is inserted by the caller, its inserted
|
||||
// into the primary cache and the portion that should be assigned to the
|
||||
// secondary cache is freed from the reservation.
|
||||
s = pri_cache_res_->UpdateCacheReservation(sec_capacity);
|
||||
assert(s.ok());
|
||||
sec_cache_res_ratio_ = (double)sec_capacity / target_->GetCapacity();
|
||||
}
|
||||
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({});
|
||||
#ifndef NDEBUG
|
||||
if (distribute_cache_res_) {
|
||||
size_t sec_capacity = 0;
|
||||
Status s = secondary_cache_->GetCapacity(sec_capacity);
|
||||
assert(s.ok());
|
||||
assert(pri_cache_res_->GetTotalReservedCacheSize() == sec_capacity);
|
||||
}
|
||||
#endif // NDEBUG
|
||||
}
|
||||
|
||||
bool CacheWithSecondaryAdapter::EvictionHandler(const Slice& key,
|
||||
Handle* handle, bool was_hit) {
|
||||
Handle* handle) {
|
||||
auto helper = GetCacheItemHelper(handle);
|
||||
if (helper->IsSecondaryCacheCompatible()) {
|
||||
auto obj = target_->Value(handle);
|
||||
// Ignore dummy entry
|
||||
if (obj != kDummyObj) {
|
||||
bool hit = false;
|
||||
if (adm_policy_ == TieredAdmissionPolicy::kAdmPolicyAllowCacheHits) {
|
||||
hit = was_hit;
|
||||
}
|
||||
// Spill into secondary cache.
|
||||
secondary_cache_->Insert(key, obj, helper, hit).PermitUncheckedError();
|
||||
secondary_cache_->Insert(key, obj, helper).PermitUncheckedError();
|
||||
}
|
||||
}
|
||||
// Never takes ownership of obj
|
||||
@@ -181,7 +95,8 @@ Cache::Handle* CacheWithSecondaryAdapter::Promote(
|
||||
PERF_COUNTER_ADD(secondary_cache_hit_count, 1);
|
||||
RecordTick(stats, SECONDARY_CACHE_HITS);
|
||||
|
||||
// Note: SecondaryCache::Size() is really charge (from the CreateCallback)
|
||||
// FIXME? using charge=Size() is suspicious but inherited from older
|
||||
// implementation
|
||||
size_t charge = secondary_handle->Size();
|
||||
Handle* result = nullptr;
|
||||
// Insert into primary cache, possibly as a standalone+dummy entries.
|
||||
@@ -222,22 +137,6 @@ Cache::Handle* CacheWithSecondaryAdapter::Promote(
|
||||
return result;
|
||||
}
|
||||
|
||||
Status CacheWithSecondaryAdapter::Insert(const Slice& key, ObjectPtr value,
|
||||
const CacheItemHelper* helper,
|
||||
size_t charge, Handle** handle,
|
||||
Priority priority) {
|
||||
Status s = target_->Insert(key, value, helper, charge, handle, priority);
|
||||
if (s.ok() && value == nullptr && distribute_cache_res_) {
|
||||
size_t sec_charge = static_cast<size_t>(charge * (sec_cache_res_ratio_));
|
||||
s = secondary_cache_->Deflate(sec_charge);
|
||||
assert(s.ok());
|
||||
s = pri_cache_res_->UpdateCacheReservation(sec_charge, /*increase=*/false);
|
||||
assert(s.ok());
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
Cache::Handle* CacheWithSecondaryAdapter::Lookup(const Slice& key,
|
||||
const CacheItemHelper* helper,
|
||||
CreateContext* create_context,
|
||||
@@ -264,22 +163,6 @@ Cache::Handle* CacheWithSecondaryAdapter::Lookup(const Slice& key,
|
||||
return result;
|
||||
}
|
||||
|
||||
bool CacheWithSecondaryAdapter::Release(Handle* handle,
|
||||
bool erase_if_last_ref) {
|
||||
if (erase_if_last_ref) {
|
||||
ObjectPtr v = target_->Value(handle);
|
||||
if (v == nullptr && distribute_cache_res_) {
|
||||
size_t charge = target_->GetCharge(handle);
|
||||
size_t sec_charge = static_cast<size_t>(charge * (sec_cache_res_ratio_));
|
||||
Status s = secondary_cache_->Inflate(sec_charge);
|
||||
assert(s.ok());
|
||||
s = pri_cache_res_->UpdateCacheReservation(sec_charge, /*increase=*/true);
|
||||
assert(s.ok());
|
||||
}
|
||||
}
|
||||
return target_->Release(handle, erase_if_last_ref);
|
||||
}
|
||||
|
||||
Cache::ObjectPtr CacheWithSecondaryAdapter::Value(Handle* handle) {
|
||||
ObjectPtr v = target_->Value(handle);
|
||||
// TODO with stacked secondaries: might fail in EvictionHandler
|
||||
@@ -383,7 +266,7 @@ void CacheWithSecondaryAdapter::WaitAll(AsyncLookupHandle* async_handles,
|
||||
for (AsyncLookupHandle* cur : my_pending) {
|
||||
my_secondary_handles.push_back(cur->pending_handle);
|
||||
}
|
||||
secondary_cache_->WaitAll(std::move(my_secondary_handles));
|
||||
secondary_cache_->WaitAll(my_secondary_handles);
|
||||
}
|
||||
|
||||
// Process results
|
||||
@@ -410,37 +293,4 @@ const char* CacheWithSecondaryAdapter::Name() const {
|
||||
// a secondary cache. So we pretend to be that cache
|
||||
return target_->Name();
|
||||
}
|
||||
|
||||
std::shared_ptr<Cache> NewTieredVolatileCache(
|
||||
TieredVolatileCacheOptions& opts) {
|
||||
if (!opts.cache_opts) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (opts.adm_policy >= TieredAdmissionPolicy::kAdmPolicyMax) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::shared_ptr<Cache> cache;
|
||||
if (opts.cache_type == PrimaryCacheType::kCacheTypeLRU) {
|
||||
LRUCacheOptions cache_opts =
|
||||
*(static_cast_with_check<LRUCacheOptions, ShardedCacheOptions>(
|
||||
opts.cache_opts));
|
||||
cache_opts.capacity += opts.comp_cache_opts.capacity;
|
||||
cache = cache_opts.MakeSharedCache();
|
||||
} else if (opts.cache_type == PrimaryCacheType::kCacheTypeHCC) {
|
||||
HyperClockCacheOptions cache_opts =
|
||||
*(static_cast_with_check<HyperClockCacheOptions, ShardedCacheOptions>(
|
||||
opts.cache_opts));
|
||||
cache_opts.capacity += opts.comp_cache_opts.capacity;
|
||||
cache = cache_opts.MakeSharedCache();
|
||||
} else {
|
||||
return nullptr;
|
||||
}
|
||||
std::shared_ptr<SecondaryCache> sec_cache;
|
||||
sec_cache = NewCompressedSecondaryCache(opts.comp_cache_opts);
|
||||
|
||||
return std::make_shared<CacheWithSecondaryAdapter>(
|
||||
cache, sec_cache, opts.adm_policy, /*distribute_cache_res=*/true);
|
||||
}
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
Vendored
+2
-28
@@ -5,7 +5,6 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "cache/cache_reservation_manager.h"
|
||||
#include "rocksdb/secondary_cache.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
@@ -14,25 +13,15 @@ class CacheWithSecondaryAdapter : public CacheWrapper {
|
||||
public:
|
||||
explicit CacheWithSecondaryAdapter(
|
||||
std::shared_ptr<Cache> target,
|
||||
std::shared_ptr<SecondaryCache> secondary_cache,
|
||||
TieredAdmissionPolicy adm_policy = TieredAdmissionPolicy::kAdmPolicyAuto,
|
||||
bool distribute_cache_res = false);
|
||||
std::shared_ptr<SecondaryCache> secondary_cache);
|
||||
|
||||
~CacheWithSecondaryAdapter() override;
|
||||
|
||||
Status Insert(const Slice& key, ObjectPtr value,
|
||||
const CacheItemHelper* helper, size_t charge,
|
||||
Handle** handle = nullptr,
|
||||
Priority priority = Priority::LOW) override;
|
||||
|
||||
Handle* Lookup(const Slice& key, const CacheItemHelper* helper,
|
||||
CreateContext* create_context,
|
||||
Priority priority = Priority::LOW,
|
||||
Statistics* stats = nullptr) override;
|
||||
|
||||
using Cache::Release;
|
||||
bool Release(Handle* handle, bool erase_if_last_ref = false) override;
|
||||
|
||||
ObjectPtr Value(Handle* handle) override;
|
||||
|
||||
void StartAsyncLookup(AsyncLookupHandle& async_handle) override;
|
||||
@@ -43,12 +32,8 @@ class CacheWithSecondaryAdapter : public CacheWrapper {
|
||||
|
||||
const char* Name() const override;
|
||||
|
||||
Cache* TEST_GetCache() { return target_.get(); }
|
||||
|
||||
SecondaryCache* TEST_GetSecondaryCache() { return secondary_cache_.get(); }
|
||||
|
||||
private:
|
||||
bool EvictionHandler(const Slice& key, Handle* handle, bool was_hit);
|
||||
bool EvictionHandler(const Slice& key, Handle* handle);
|
||||
|
||||
void StartAsyncLookupOnMySecondary(AsyncLookupHandle& async_handle);
|
||||
|
||||
@@ -62,17 +47,6 @@ class CacheWithSecondaryAdapter : public CacheWrapper {
|
||||
void CleanupCacheObject(ObjectPtr obj, const CacheItemHelper* helper);
|
||||
|
||||
std::shared_ptr<SecondaryCache> secondary_cache_;
|
||||
TieredAdmissionPolicy adm_policy_;
|
||||
// Whether to proportionally distribute cache memory reservations, i.e
|
||||
// placeholder entries with null value and a non-zero charge, across
|
||||
// the primary and secondary caches.
|
||||
bool distribute_cache_res_;
|
||||
// A cache reservation manager to keep track of secondary cache memory
|
||||
// usage by reserving equivalent capacity against the primary cache
|
||||
std::shared_ptr<ConcurrentCacheReservationManager> pri_cache_res_;
|
||||
// Fraction of a cache memory reservation to be assigned to the secondary
|
||||
// cache
|
||||
double sec_cache_res_ratio_;
|
||||
};
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
Vendored
+7
-44
@@ -13,57 +13,20 @@
|
||||
#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 BitwiseAnd(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(const ShardedCacheOptions& opts)
|
||||
: Cache(opts.memory_allocator),
|
||||
ShardedCacheBase::ShardedCacheBase(size_t capacity, int num_shard_bits,
|
||||
bool strict_capacity_limit,
|
||||
std::shared_ptr<MemoryAllocator> allocator)
|
||||
: Cache(std::move(allocator)),
|
||||
last_id_(1),
|
||||
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) {}
|
||||
shard_mask_((uint32_t{1} << num_shard_bits) - 1),
|
||||
strict_capacity_limit_(strict_capacity_limit),
|
||||
capacity_(capacity) {}
|
||||
|
||||
size_t ShardedCacheBase::ComputePerShardCapacity(size_t capacity) const {
|
||||
uint32_t num_shards = GetNumShards();
|
||||
|
||||
Vendored
+14
-21
@@ -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, uint32_t seed) {
|
||||
return GetSliceNPHash64(key, seed);
|
||||
static inline HashVal ComputeHash(const Slice& key) {
|
||||
return GetSliceNPHash64(key);
|
||||
}
|
||||
static inline uint32_t HashPieceForSharding(HashCref hash) {
|
||||
return Lower32of64(hash);
|
||||
@@ -89,7 +89,9 @@ class CacheShardBase {
|
||||
// Portions of ShardedCache that do not depend on the template parameter
|
||||
class ShardedCacheBase : public Cache {
|
||||
public:
|
||||
explicit ShardedCacheBase(const ShardedCacheOptions& opts);
|
||||
ShardedCacheBase(size_t capacity, int num_shard_bits,
|
||||
bool strict_capacity_limit,
|
||||
std::shared_ptr<MemoryAllocator> memory_allocator);
|
||||
virtual ~ShardedCacheBase() = default;
|
||||
|
||||
int GetNumShardBits() const;
|
||||
@@ -104,8 +106,6 @@ 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;
|
||||
@@ -114,7 +114,6 @@ 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_;
|
||||
@@ -135,8 +134,10 @@ class ShardedCache : public ShardedCacheBase {
|
||||
using HashCref = typename CacheShard::HashCref;
|
||||
using HandleImpl = typename CacheShard::HandleImpl;
|
||||
|
||||
explicit ShardedCache(const ShardedCacheOptions& opts)
|
||||
: ShardedCacheBase(opts),
|
||||
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),
|
||||
shards_(reinterpret_cast<CacheShard*>(port::cacheline_aligned_alloc(
|
||||
sizeof(CacheShard) * GetNumShards()))),
|
||||
destroy_shards_in_dtor_(false) {}
|
||||
@@ -174,7 +175,7 @@ class ShardedCache : public ShardedCacheBase {
|
||||
size_t charge, Handle** handle = nullptr,
|
||||
Priority priority = Priority::LOW) override {
|
||||
assert(helper);
|
||||
HashVal hash = CacheShard::ComputeHash(key, hash_seed_);
|
||||
HashVal hash = CacheShard::ComputeHash(key);
|
||||
auto h_out = reinterpret_cast<HandleImpl**>(handle);
|
||||
return GetShard(hash).Insert(key, hash, obj, helper, charge, h_out,
|
||||
priority);
|
||||
@@ -184,7 +185,7 @@ class ShardedCache : public ShardedCacheBase {
|
||||
const CacheItemHelper* helper, size_t charge,
|
||||
bool allow_uncharged) override {
|
||||
assert(helper);
|
||||
HashVal hash = CacheShard::ComputeHash(key, hash_seed_);
|
||||
HashVal hash = CacheShard::ComputeHash(key);
|
||||
HandleImpl* result = GetShard(hash).CreateStandalone(
|
||||
key, hash, obj, helper, charge, allow_uncharged);
|
||||
return reinterpret_cast<Handle*>(result);
|
||||
@@ -194,14 +195,14 @@ class ShardedCache : public ShardedCacheBase {
|
||||
CreateContext* create_context = nullptr,
|
||||
Priority priority = Priority::LOW,
|
||||
Statistics* stats = nullptr) override {
|
||||
HashVal hash = CacheShard::ComputeHash(key, hash_seed_);
|
||||
HashVal hash = CacheShard::ComputeHash(key);
|
||||
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, hash_seed_);
|
||||
HashVal hash = CacheShard::ComputeHash(key);
|
||||
GetShard(hash).Erase(key, hash);
|
||||
}
|
||||
|
||||
@@ -225,7 +226,7 @@ class ShardedCache : public ShardedCacheBase {
|
||||
return SumOverShards2(&CacheShard::GetPinnedUsage);
|
||||
}
|
||||
size_t GetOccupancyCount() const override {
|
||||
return SumOverShards2(&CacheShard::GetOccupancyCount);
|
||||
return SumOverShards2(&CacheShard::GetPinnedUsage);
|
||||
}
|
||||
size_t GetTableAddressCount() const override {
|
||||
return SumOverShards2(&CacheShard::GetTableAddressCount);
|
||||
@@ -273,14 +274,6 @@ class ShardedCache : public ShardedCacheBase {
|
||||
}
|
||||
}
|
||||
|
||||
inline void ForEachShard(
|
||||
const std::function<void(const CacheShard*)>& fn) const {
|
||||
uint32_t num_shards = GetNumShards();
|
||||
for (uint32_t i = 0; i < num_shards; i++) {
|
||||
fn(shards_ + i);
|
||||
}
|
||||
}
|
||||
|
||||
inline size_t SumOverShards(
|
||||
const std::function<size_t(CacheShard&)>& fn) const {
|
||||
uint32_t num_shards = GetNumShards();
|
||||
|
||||
@@ -21,8 +21,6 @@ CRASHTEST_PY=$(PYTHON) -u tools/db_crashtest.py --stress_cmd=$(DB_STRESS_CMD) --
|
||||
blackbox_crash_test_with_multiops_wp_txn \
|
||||
crash_test_with_tiered_storage blackbox_crash_test_with_tiered_storage \
|
||||
whitebox_crash_test_with_tiered_storage \
|
||||
whitebox_crash_test_with_optimistic_txn \
|
||||
blackbox_crash_test_with_optimistic_txn \
|
||||
|
||||
crash_test: $(DB_STRESS_CMD)
|
||||
# Do not parallelize
|
||||
@@ -39,11 +37,6 @@ crash_test_with_txn: $(DB_STRESS_CMD)
|
||||
$(CRASHTEST_MAKE) whitebox_crash_test_with_txn
|
||||
$(CRASHTEST_MAKE) blackbox_crash_test_with_txn
|
||||
|
||||
crash_test_with_optimistic_txn: $(DB_STRESS_CMD)
|
||||
# Do not parallelize
|
||||
$(CRASHTEST_MAKE) whitebox_crash_test_with_optimistic_txn
|
||||
$(CRASHTEST_MAKE) blackbox_crash_test_with_optimistic_txn
|
||||
|
||||
crash_test_with_best_efforts_recovery: blackbox_crash_test_with_best_efforts_recovery
|
||||
|
||||
crash_test_with_ts: $(DB_STRESS_CMD)
|
||||
@@ -87,9 +80,6 @@ blackbox_crash_test_with_multiops_wp_txn: $(DB_STRESS_CMD)
|
||||
blackbox_crash_test_with_tiered_storage: $(DB_STRESS_CMD)
|
||||
$(CRASHTEST_PY) --test_tiered_storage blackbox $(CRASH_TEST_EXT_ARGS)
|
||||
|
||||
blackbox_crash_test_with_optimistic_txn: $(DB_STRESS_CMD)
|
||||
$(CRASHTEST_PY) --optimistic_txn blackbox $(CRASH_TEST_EXT_ARGS)
|
||||
|
||||
ifeq ($(CRASH_TEST_KILL_ODD),)
|
||||
CRASH_TEST_KILL_ODD=888887
|
||||
endif
|
||||
@@ -115,7 +105,3 @@ whitebox_crash_test_with_ts: $(DB_STRESS_CMD)
|
||||
whitebox_crash_test_with_tiered_storage: $(DB_STRESS_CMD)
|
||||
$(CRASHTEST_PY) --test_tiered_storage whitebox --random_kill_odd \
|
||||
$(CRASH_TEST_KILL_ODD) $(CRASH_TEST_EXT_ARGS)
|
||||
|
||||
whitebox_crash_test_with_optimistic_txn: $(DB_STRESS_CMD)
|
||||
$(CRASHTEST_PY) --optimistic_txn whitebox --random_kill_odd \
|
||||
$(CRASH_TEST_KILL_ODD) $(CRASH_TEST_EXT_ARGS)
|
||||
@@ -47,9 +47,7 @@ void ArenaWrappedDBIter::Init(
|
||||
read_options_ = read_options;
|
||||
allow_refresh_ = allow_refresh;
|
||||
memtable_range_tombstone_iter_ = nullptr;
|
||||
|
||||
if (!CheckFSFeatureSupport(env->GetFileSystem().get(),
|
||||
FSSupportedOps::kAsyncIO)) {
|
||||
if (!env->GetFileSystem()->use_async_io()) {
|
||||
read_options_.async_io = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "memory/memory_allocator_impl.h"
|
||||
#include "memory/memory_allocator.h"
|
||||
#include "rocksdb/advanced_cache.h"
|
||||
#include "rocksdb/rocksdb_namespace.h"
|
||||
#include "rocksdb/slice.h"
|
||||
|
||||
@@ -259,9 +259,8 @@ Status BlobFileBuilder::CompressBlobIfNeeded(
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
// TODO: allow user CompressionOptions, including max_compressed_bytes_per_kb
|
||||
CompressionOptions opts;
|
||||
CompressionContext context(blob_compression_type_, opts);
|
||||
CompressionContext context(blob_compression_type_);
|
||||
constexpr uint64_t sample_for_compression = 0;
|
||||
|
||||
CompressionInfo info(opts, context, CompressionDict::GetEmptyDict(),
|
||||
|
||||
@@ -406,7 +406,7 @@ TEST_F(BlobFileBuilderTest, Compression) {
|
||||
ASSERT_EQ(blob_file_addition.GetTotalBlobCount(), 1);
|
||||
|
||||
CompressionOptions opts;
|
||||
CompressionContext context(kSnappyCompression, opts);
|
||||
CompressionContext context(kSnappyCompression);
|
||||
constexpr uint64_t sample_for_compression = 0;
|
||||
|
||||
CompressionInfo info(opts, context, CompressionDict::GetEmptyDict(),
|
||||
|
||||
@@ -25,7 +25,7 @@ BlobFileCache::BlobFileCache(Cache* cache,
|
||||
HistogramImpl* blob_file_read_hist,
|
||||
const std::shared_ptr<IOTracer>& io_tracer)
|
||||
: cache_(cache),
|
||||
mutex_(kNumberOfMutexStripes),
|
||||
mutex_(kNumberOfMutexStripes, kGetSliceNPHash64UnseededFnPtr),
|
||||
immutable_options_(immutable_options),
|
||||
file_options_(file_options),
|
||||
column_family_id_(column_family_id),
|
||||
@@ -37,7 +37,7 @@ BlobFileCache::BlobFileCache(Cache* cache,
|
||||
}
|
||||
|
||||
Status BlobFileCache::GetBlobFileReader(
|
||||
const ReadOptions& read_options, uint64_t blob_file_number,
|
||||
uint64_t blob_file_number,
|
||||
CacheHandleGuard<BlobFileReader>* blob_file_reader) {
|
||||
assert(blob_file_reader);
|
||||
assert(blob_file_reader->IsEmpty());
|
||||
@@ -55,7 +55,7 @@ Status BlobFileCache::GetBlobFileReader(
|
||||
TEST_SYNC_POINT("BlobFileCache::GetBlobFileReader:DoubleCheck");
|
||||
|
||||
// Check again while holding mutex
|
||||
MutexLock lock(&mutex_.Get(key));
|
||||
MutexLock lock(mutex_.get(key));
|
||||
|
||||
handle = cache_.Lookup(key);
|
||||
if (handle) {
|
||||
@@ -73,7 +73,7 @@ Status BlobFileCache::GetBlobFileReader(
|
||||
{
|
||||
assert(file_options_);
|
||||
const Status s = BlobFileReader::Create(
|
||||
*immutable_options_, read_options, *file_options_, column_family_id_,
|
||||
*immutable_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,8 +32,7 @@ class BlobFileCache {
|
||||
BlobFileCache(const BlobFileCache&) = delete;
|
||||
BlobFileCache& operator=(const BlobFileCache&) = delete;
|
||||
|
||||
Status GetBlobFileReader(const ReadOptions& read_options,
|
||||
uint64_t blob_file_number,
|
||||
Status GetBlobFileReader(uint64_t blob_file_number,
|
||||
CacheHandleGuard<BlobFileReader>* blob_file_reader);
|
||||
|
||||
private:
|
||||
@@ -43,7 +42,7 @@ class BlobFileCache {
|
||||
CacheInterface cache_;
|
||||
// Note: mutex_ below is used to guard against multiple threads racing to open
|
||||
// the same file.
|
||||
Striped<CacheAlignedWrapper<port::Mutex>> mutex_;
|
||||
Striped<port::Mutex, Slice> mutex_;
|
||||
const ImmutableOptions* immutable_options_;
|
||||
const FileOptions* file_options_;
|
||||
uint32_t column_family_id_;
|
||||
|
||||
@@ -118,9 +118,7 @@ TEST_F(BlobFileCacheTest, GetBlobFileReader) {
|
||||
// First try: reader should be opened and put in cache
|
||||
CacheHandleGuard<BlobFileReader> first;
|
||||
|
||||
const ReadOptions read_options;
|
||||
ASSERT_OK(blob_file_cache.GetBlobFileReader(read_options, blob_file_number,
|
||||
&first));
|
||||
ASSERT_OK(blob_file_cache.GetBlobFileReader(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);
|
||||
@@ -128,8 +126,7 @@ TEST_F(BlobFileCacheTest, GetBlobFileReader) {
|
||||
// Second try: reader should be served from cache
|
||||
CacheHandleGuard<BlobFileReader> second;
|
||||
|
||||
ASSERT_OK(blob_file_cache.GetBlobFileReader(read_options, blob_file_number,
|
||||
&second));
|
||||
ASSERT_OK(blob_file_cache.GetBlobFileReader(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);
|
||||
@@ -166,21 +163,19 @@ 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(read_options,
|
||||
blob_file_number, &second));
|
||||
|
||||
ASSERT_OK(blob_file_cache.GetBlobFileReader(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(read_options, blob_file_number,
|
||||
&first));
|
||||
ASSERT_OK(blob_file_cache.GetBlobFileReader(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);
|
||||
@@ -218,10 +213,8 @@ TEST_F(BlobFileCacheTest, GetBlobFileReader_IOError) {
|
||||
|
||||
CacheHandleGuard<BlobFileReader> reader;
|
||||
|
||||
const ReadOptions read_options;
|
||||
ASSERT_TRUE(
|
||||
blob_file_cache.GetBlobFileReader(read_options, blob_file_number, &reader)
|
||||
.IsIOError());
|
||||
blob_file_cache.GetBlobFileReader(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);
|
||||
@@ -260,10 +253,8 @@ TEST_F(BlobFileCacheTest, GetBlobFileReader_CacheFull) {
|
||||
// strict_capacity_limit is set
|
||||
CacheHandleGuard<BlobFileReader> reader;
|
||||
|
||||
const ReadOptions read_options;
|
||||
ASSERT_TRUE(
|
||||
blob_file_cache.GetBlobFileReader(read_options, blob_file_number, &reader)
|
||||
.IsMemoryLimit());
|
||||
ASSERT_TRUE(blob_file_cache.GetBlobFileReader(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);
|
||||
|
||||
+33
-49
@@ -12,7 +12,7 @@
|
||||
#include "db/blob/blob_log_format.h"
|
||||
#include "file/file_prefetch_buffer.h"
|
||||
#include "file/filename.h"
|
||||
#include "monitoring/statistics_impl.h"
|
||||
#include "monitoring/statistics.h"
|
||||
#include "options/cf_options.h"
|
||||
#include "rocksdb/file_system.h"
|
||||
#include "rocksdb/slice.h"
|
||||
@@ -26,10 +26,9 @@
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
Status BlobFileReader::Create(
|
||||
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,
|
||||
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,
|
||||
std::unique_ptr<BlobFileReader>* blob_file_reader) {
|
||||
assert(blob_file_reader);
|
||||
assert(!*blob_file_reader);
|
||||
@@ -53,17 +52,15 @@ Status BlobFileReader::Create(
|
||||
CompressionType compression_type = kNoCompression;
|
||||
|
||||
{
|
||||
const Status s =
|
||||
ReadHeader(file_reader.get(), read_options, column_family_id,
|
||||
statistics, &compression_type);
|
||||
const Status s = ReadHeader(file_reader.get(), column_family_id, statistics,
|
||||
&compression_type);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
const Status s =
|
||||
ReadFooter(file_reader.get(), read_options, file_size, statistics);
|
||||
const Status s = ReadFooter(file_reader.get(), file_size, statistics);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
@@ -137,7 +134,6 @@ Status BlobFileReader::OpenFile(
|
||||
}
|
||||
|
||||
Status BlobFileReader::ReadHeader(const RandomAccessFileReader* file_reader,
|
||||
const ReadOptions& read_options,
|
||||
uint32_t column_family_id,
|
||||
Statistics* statistics,
|
||||
CompressionType* compression_type) {
|
||||
@@ -154,9 +150,10 @@ Status BlobFileReader::ReadHeader(const RandomAccessFileReader* file_reader,
|
||||
constexpr uint64_t read_offset = 0;
|
||||
constexpr size_t read_size = BlobLogHeader::kSize;
|
||||
|
||||
const Status s =
|
||||
ReadFromFile(file_reader, read_options, read_offset, read_size,
|
||||
statistics, &header_slice, &buf, &aligned_buf);
|
||||
// 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 */);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
@@ -190,7 +187,6 @@ 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);
|
||||
@@ -205,9 +201,10 @@ Status BlobFileReader::ReadFooter(const RandomAccessFileReader* file_reader,
|
||||
const uint64_t read_offset = file_size - BlobLogFooter::kSize;
|
||||
constexpr size_t read_size = BlobLogFooter::kSize;
|
||||
|
||||
const Status s =
|
||||
ReadFromFile(file_reader, read_options, read_offset, read_size,
|
||||
statistics, &footer_slice, &buf, &aligned_buf);
|
||||
// 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 */);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
@@ -235,10 +232,10 @@ 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) {
|
||||
Buffer* buf, AlignedBuf* aligned_buf,
|
||||
Env::IOPriority rate_limiter_priority) {
|
||||
assert(slice);
|
||||
assert(buf);
|
||||
assert(aligned_buf);
|
||||
@@ -249,23 +246,17 @@ 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(io_options, read_offset, read_size, slice, scratch,
|
||||
aligned_buf);
|
||||
s = file_reader->Read(IOOptions(), 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(io_options, read_offset, read_size, slice, buf->get(),
|
||||
aligned_scratch);
|
||||
s = file_reader->Read(IOOptions(), read_offset, read_size, slice,
|
||||
buf->get(), aligned_scratch, rate_limiter_priority);
|
||||
}
|
||||
|
||||
if (!s.ok()) {
|
||||
@@ -333,14 +324,10 @@ 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(
|
||||
io_options, file_reader_.get(), record_offset,
|
||||
static_cast<size_t>(record_size), &record_slice, &s, for_compaction);
|
||||
IOOptions(), file_reader_.get(), record_offset,
|
||||
static_cast<size_t>(record_size), &record_slice, &s,
|
||||
read_options.rate_limiter_priority, for_compaction);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
@@ -351,10 +338,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(), read_options, record_offset,
|
||||
static_cast<size_t>(record_size), statistics_,
|
||||
&record_slice, &buf, &aligned_buf);
|
||||
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);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
@@ -433,11 +420,11 @@ void BlobFileReader::MultiGetBlob(
|
||||
assert(req->offset >= adjustment);
|
||||
adjustments.push_back(adjustment);
|
||||
|
||||
FSReadRequest read_req;
|
||||
FSReadRequest read_req = {};
|
||||
read_req.offset = req->offset - adjustment;
|
||||
read_req.len = req->len + adjustment;
|
||||
read_reqs.emplace_back(read_req);
|
||||
total_len += read_req.len;
|
||||
read_reqs.emplace_back(std::move(read_req));
|
||||
}
|
||||
|
||||
RecordTick(statistics_, BLOB_DB_BLOB_FILE_BYTES_READ, total_len);
|
||||
@@ -462,12 +449,9 @@ void BlobFileReader::MultiGetBlob(
|
||||
TEST_SYNC_POINT("BlobFileReader::MultiGetBlob:ReadFromFile");
|
||||
PERF_COUNTER_ADD(blob_read_count, num_blobs);
|
||||
PERF_COUNTER_ADD(blob_read_byte, total_len);
|
||||
IOOptions opts;
|
||||
s = file_reader_->PrepareIOOptions(read_options, opts);
|
||||
if (s.ok()) {
|
||||
s = file_reader_->MultiRead(opts, read_reqs.data(), read_reqs.size(),
|
||||
direct_io ? &aligned_buf : nullptr);
|
||||
}
|
||||
s = file_reader_->MultiRead(IOOptions(), read_reqs.data(), read_reqs.size(),
|
||||
direct_io ? &aligned_buf : nullptr,
|
||||
read_options.rate_limiter_priority);
|
||||
if (!s.ok()) {
|
||||
for (auto& req : read_reqs) {
|
||||
req.status.PermitUncheckedError();
|
||||
|
||||
@@ -29,7 +29,6 @@ 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,
|
||||
@@ -75,21 +74,19 @@ 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,
|
||||
const ReadOptions& read_options, uint64_t file_size,
|
||||
Statistics* statistics);
|
||||
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);
|
||||
AlignedBuf* aligned_buf,
|
||||
Env::IOPriority rate_limiter_priority);
|
||||
|
||||
static Status VerifyBlob(const Slice& record_slice, const Slice& user_key,
|
||||
uint64_t value_size);
|
||||
|
||||
@@ -74,7 +74,7 @@ void WriteBlobFile(const ImmutableOptions& immutable_options,
|
||||
}
|
||||
} else {
|
||||
CompressionOptions opts;
|
||||
CompressionContext context(compression, opts);
|
||||
CompressionContext context(compression);
|
||||
constexpr uint64_t sample_for_compression = 0;
|
||||
CompressionInfo info(opts, context, CompressionDict::GetEmptyDict(),
|
||||
compression, sample_for_compression);
|
||||
@@ -172,12 +172,12 @@ TEST_F(BlobFileReaderTest, CreateReaderAndGetBlob) {
|
||||
|
||||
std::unique_ptr<BlobFileReader> reader;
|
||||
|
||||
ReadOptions read_options;
|
||||
ASSERT_OK(BlobFileReader::Create(
|
||||
immutable_options, read_options, FileOptions(), column_family_id,
|
||||
blob_file_read_hist, blob_file_number, nullptr /*IOTracer*/, &reader));
|
||||
immutable_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;
|
||||
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)
|
||||
|
||||
ASSERT_TRUE(BlobFileReader::Create(immutable_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;
|
||||
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)
|
||||
|
||||
ASSERT_TRUE(BlobFileReader::Create(immutable_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;
|
||||
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)
|
||||
|
||||
ASSERT_TRUE(BlobFileReader::Create(immutable_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;
|
||||
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)
|
||||
|
||||
ASSERT_TRUE(BlobFileReader::Create(immutable_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;
|
||||
const ReadOptions read_options;
|
||||
ASSERT_TRUE(BlobFileReader::Create(immutable_options, read_options,
|
||||
FileOptions(), incorrect_column_family_id,
|
||||
|
||||
ASSERT_TRUE(BlobFileReader::Create(immutable_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, read_options, FileOptions(), column_family_id,
|
||||
blob_file_read_hist, blob_file_number, nullptr /*IOTracer*/, &reader));
|
||||
immutable_options, FileOptions(), column_family_id, blob_file_read_hist,
|
||||
blob_file_number, nullptr /*IOTracer*/, &reader));
|
||||
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"BlobFileReader::VerifyBlob:CheckBlobCRC", [](void* arg) {
|
||||
@@ -728,12 +728,13 @@ 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, read_options, FileOptions(), column_family_id,
|
||||
blob_file_read_hist, blob_file_number, nullptr /*IOTracer*/, &reader));
|
||||
immutable_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;
|
||||
@@ -802,10 +803,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, read_options, FileOptions(), column_family_id,
|
||||
blob_file_read_hist, blob_file_number, nullptr /*IOTracer*/, &reader));
|
||||
immutable_options, FileOptions(), column_family_id, blob_file_read_hist,
|
||||
blob_file_number, nullptr /*IOTracer*/, &reader));
|
||||
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"BlobFileReader::UncompressBlobIfNeeded:TamperWithResult", [](void* arg) {
|
||||
@@ -894,10 +895,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, read_options, FileOptions(), column_family_id,
|
||||
blob_file_read_hist, blob_file_number, nullptr /*IOTracer*/, &reader);
|
||||
immutable_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");
|
||||
@@ -982,10 +983,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, read_options, FileOptions(), column_family_id,
|
||||
blob_file_read_hist, blob_file_number, nullptr /*IOTracer*/, &reader);
|
||||
immutable_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_impl.h"
|
||||
#include "monitoring/statistics.h"
|
||||
#include "util/stop_watch.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
@@ -29,8 +29,9 @@ Status BlobLogSequentialReader::ReadSlice(uint64_t size, Slice* slice,
|
||||
|
||||
StopWatch read_sw(clock_, statistics_, BLOB_DB_BLOB_FILE_READ_MICROS);
|
||||
// TODO: rate limit `BlobLogSequentialReader` reads (it appears unused?)
|
||||
Status s = file_->Read(IOOptions(), next_byte_, static_cast<size_t>(size),
|
||||
slice, buf, nullptr);
|
||||
Status s =
|
||||
file_->Read(IOOptions(), next_byte_, static_cast<size_t>(size), slice,
|
||||
buf, nullptr, Env::IO_TOTAL /* rate_limiter_priority */);
|
||||
next_byte_ += size;
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
#include "db/blob/blob_log_format.h"
|
||||
#include "file/writable_file_writer.h"
|
||||
#include "monitoring/statistics_impl.h"
|
||||
#include "monitoring/statistics.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_impl.h"
|
||||
#include "monitoring/statistics.h"
|
||||
#include "options/cf_options.h"
|
||||
#include "table/get_context.h"
|
||||
#include "table/multiget_context.h"
|
||||
@@ -209,8 +209,7 @@ Status BlobSource::GetBlob(const ReadOptions& read_options,
|
||||
|
||||
{
|
||||
CacheHandleGuard<BlobFileReader> blob_file_reader;
|
||||
s = blob_file_cache_->GetBlobFileReader(read_options, file_number,
|
||||
&blob_file_reader);
|
||||
s = blob_file_cache_->GetBlobFileReader(file_number, &blob_file_reader);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
@@ -373,8 +372,8 @@ void BlobSource::MultiGetBlobFromOneFile(const ReadOptions& read_options,
|
||||
}
|
||||
|
||||
CacheHandleGuard<BlobFileReader> blob_file_reader;
|
||||
Status s = blob_file_cache_->GetBlobFileReader(read_options, file_number,
|
||||
&blob_file_reader);
|
||||
Status s =
|
||||
blob_file_cache_->GetBlobFileReader(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(
|
||||
const ReadOptions& read_options, uint64_t blob_file_number,
|
||||
uint64_t blob_file_number,
|
||||
CacheHandleGuard<BlobFileReader>* blob_file_reader) {
|
||||
return blob_file_cache_->GetBlobFileReader(read_options, blob_file_number,
|
||||
return blob_file_cache_->GetBlobFileReader(blob_file_number,
|
||||
blob_file_reader);
|
||||
}
|
||||
|
||||
|
||||
@@ -76,7 +76,7 @@ void WriteBlobFile(const ImmutableOptions& immutable_options,
|
||||
}
|
||||
} else {
|
||||
CompressionOptions opts;
|
||||
CompressionContext context(compression, opts);
|
||||
CompressionContext context(compression);
|
||||
constexpr uint64_t sample_for_compression = 0;
|
||||
CompressionInfo info(opts, context, CompressionDict::GetEmptyDict(),
|
||||
compression, sample_for_compression);
|
||||
@@ -517,8 +517,7 @@ TEST_F(BlobSourceTest, GetCompressedBlobs) {
|
||||
compression, blob_offsets, blob_sizes);
|
||||
|
||||
CacheHandleGuard<BlobFileReader> blob_file_reader;
|
||||
ASSERT_OK(blob_source.GetBlobFileReader(read_options, file_number,
|
||||
&blob_file_reader));
|
||||
ASSERT_OK(blob_source.GetBlobFileReader(file_number, &blob_file_reader));
|
||||
ASSERT_NE(blob_file_reader.GetValue(), nullptr);
|
||||
|
||||
const uint64_t file_size = blob_file_reader.GetValue()->GetFileSize();
|
||||
@@ -1140,13 +1139,12 @@ TEST_F(BlobSecondaryCacheTest, GetBlobsFromSecondaryCache) {
|
||||
blob_file_cache.get());
|
||||
|
||||
CacheHandleGuard<BlobFileReader> file_reader;
|
||||
ReadOptions read_options;
|
||||
ASSERT_OK(
|
||||
blob_source.GetBlobFileReader(read_options, file_number, &file_reader));
|
||||
ASSERT_OK(blob_source.GetBlobFileReader(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;
|
||||
|
||||
+13
-39
@@ -15,7 +15,6 @@
|
||||
|
||||
#include "db/blob/blob_file_builder.h"
|
||||
#include "db/compaction/compaction_iterator.h"
|
||||
#include "db/dbformat.h"
|
||||
#include "db/event_helpers.h"
|
||||
#include "db/internal_stats.h"
|
||||
#include "db/merge_helper.h"
|
||||
@@ -57,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, const ReadOptions& read_options,
|
||||
TableCache* table_cache, InternalIterator* iter,
|
||||
const FileOptions& file_options, TableCache* table_cache,
|
||||
InternalIterator* iter,
|
||||
std::vector<std::unique_ptr<FragmentedRangeTombstoneIterator>>
|
||||
range_del_iters,
|
||||
FileMetaData* meta, std::vector<BlobFileAddition>* blob_file_additions,
|
||||
@@ -203,42 +202,24 @@ Status BuildTable(
|
||||
blob_file_builder.get(), ioptions.allow_data_in_errors,
|
||||
ioptions.enforce_single_del_contracts,
|
||||
/*manual_compaction_canceled=*/kManualCompactionCanceledFalse,
|
||||
true /* must_count_input_entries */,
|
||||
/*compaction=*/nullptr, compaction_filter.get(),
|
||||
/*shutting_down=*/nullptr, db_options.info_log, full_history_ts_low);
|
||||
|
||||
const size_t ts_sz = ucmp->timestamp_size();
|
||||
const bool strip_timestamp =
|
||||
ts_sz > 0 && !ioptions.persist_user_defined_timestamps;
|
||||
|
||||
std::string key_after_flush_buf;
|
||||
c_iter.SeekToFirst();
|
||||
for (; c_iter.Valid(); c_iter.Next()) {
|
||||
const Slice& key = c_iter.key();
|
||||
const Slice& value = c_iter.value();
|
||||
const ParsedInternalKey& ikey = c_iter.ikey();
|
||||
Slice key_after_flush = key;
|
||||
// If user defined timestamps will be stripped from user key after flush,
|
||||
// the in memory version of the key act logically the same as one with a
|
||||
// minimum timestamp. We update the timestamp here so file boundary and
|
||||
// output validator, block builder all see the effect of the stripping.
|
||||
if (strip_timestamp) {
|
||||
key_after_flush_buf.clear();
|
||||
ReplaceInternalKeyWithMinTimestamp(&key_after_flush_buf, key, ts_sz);
|
||||
key_after_flush = key_after_flush_buf;
|
||||
}
|
||||
|
||||
// Generate a rolling 64-bit hash of the key and values
|
||||
// Note :
|
||||
// Here "key" integrates 'sequence_number'+'kType'+'user key'.
|
||||
s = output_validator.Add(key_after_flush, value);
|
||||
// Generate a rolling 64-bit hash of the key and values
|
||||
// Note :
|
||||
// Here "key" integrates 'sequence_number'+'kType'+'user key'.
|
||||
s = output_validator.Add(key, value);
|
||||
if (!s.ok()) {
|
||||
break;
|
||||
}
|
||||
builder->Add(key_after_flush, value);
|
||||
builder->Add(key, value);
|
||||
|
||||
s = meta->UpdateBoundaries(key_after_flush, value, ikey.sequence,
|
||||
ikey.type);
|
||||
s = meta->UpdateBoundaries(key, value, ikey.sequence, ikey.type);
|
||||
if (!s.ok()) {
|
||||
break;
|
||||
}
|
||||
@@ -263,7 +244,6 @@ Status BuildTable(
|
||||
range_del_it->Next()) {
|
||||
auto tombstone = range_del_it->Tombstone();
|
||||
auto kv = tombstone.Serialize();
|
||||
// TODO(yuzhangyu): handle range deletion for UDT in memtables only.
|
||||
builder->Add(kv.first.Encode(), kv.second);
|
||||
InternalKey tombstone_end = tombstone.SerializeEndKey();
|
||||
meta->UpdateBoundariesForRange(kv.first, tombstone_end, tombstone.seq_,
|
||||
@@ -275,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, read_options, version, kv.first.Encode(),
|
||||
tombstone_end.Encode(), 0 /* start_level */, -1 /* end_level */,
|
||||
approx_opts, 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();
|
||||
@@ -287,9 +267,8 @@ Status BuildTable(
|
||||
TEST_SYNC_POINT("BuildTable:BeforeFinishBuildTable");
|
||||
const bool empty = builder->IsEmpty();
|
||||
if (num_input_entries != nullptr) {
|
||||
assert(c_iter.HasNumInputEntryScanned());
|
||||
*num_input_entries =
|
||||
c_iter.NumInputEntryScanned() + num_unfragmented_tombstones;
|
||||
c_iter.num_input_entry_scanned() + num_unfragmented_tombstones;
|
||||
}
|
||||
if (!s.ok() || empty) {
|
||||
builder->Abandon();
|
||||
@@ -312,10 +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();
|
||||
meta->user_defined_timestamps_persisted =
|
||||
ioptions.persist_user_defined_timestamps;
|
||||
assert(meta->fd.GetFileSize() > 0);
|
||||
tp = builder
|
||||
->GetTableProperties(); // refresh now that builder is finished
|
||||
@@ -375,8 +351,6 @@ Status BuildTable(
|
||||
s = *io_status;
|
||||
}
|
||||
|
||||
// TODO(yuzhangyu): handle the key copy in the blob when ts should be
|
||||
// stripped.
|
||||
if (blob_file_builder) {
|
||||
if (s.ok()) {
|
||||
s = blob_file_builder->Finish();
|
||||
@@ -395,6 +369,7 @@ 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,
|
||||
@@ -406,8 +381,7 @@ Status BuildTable(
|
||||
MaxFileSizeForL0MetaPin(mutable_cf_options),
|
||||
/*smallest_compaction_key=*/nullptr,
|
||||
/*largest_compaction_key*/ nullptr,
|
||||
/*allow_unprepared_value*/ false,
|
||||
mutable_cf_options.block_protection_bytes_per_key));
|
||||
/*allow_unprepared_value*/ false));
|
||||
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, const ReadOptions& read_options,
|
||||
TableCache* table_cache, InternalIterator* iter,
|
||||
const FileOptions& file_options, TableCache* table_cache,
|
||||
InternalIterator* iter,
|
||||
std::vector<std::unique_ptr<FragmentedRangeTombstoneIterator>>
|
||||
range_del_iters,
|
||||
FileMetaData* meta, std::vector<BlobFileAddition>* blob_file_additions,
|
||||
|
||||
@@ -77,7 +77,6 @@ using ROCKSDB_NAMESPACE::EnvOptions;
|
||||
using ROCKSDB_NAMESPACE::FileLock;
|
||||
using ROCKSDB_NAMESPACE::FilterPolicy;
|
||||
using ROCKSDB_NAMESPACE::FlushOptions;
|
||||
using ROCKSDB_NAMESPACE::HistogramData;
|
||||
using ROCKSDB_NAMESPACE::HyperClockCacheOptions;
|
||||
using ROCKSDB_NAMESPACE::InfoLogLevel;
|
||||
using ROCKSDB_NAMESPACE::IngestExternalFileOptions;
|
||||
@@ -280,11 +279,6 @@ struct rocksdb_compactionfiltercontext_t {
|
||||
CompactionFilter::Context rep;
|
||||
};
|
||||
|
||||
struct rocksdb_statistics_histogram_data_t {
|
||||
rocksdb_statistics_histogram_data_t() : rep() {}
|
||||
HistogramData rep;
|
||||
};
|
||||
|
||||
struct rocksdb_compactionfilter_t : public CompactionFilter {
|
||||
void* state_;
|
||||
void (*destructor_)(void*);
|
||||
@@ -1063,36 +1057,6 @@ rocksdb_column_family_handle_t* rocksdb_create_column_family(
|
||||
return handle;
|
||||
}
|
||||
|
||||
rocksdb_column_family_handle_t** rocksdb_create_column_families(
|
||||
rocksdb_t* db, const rocksdb_options_t* column_family_options,
|
||||
int num_column_families, const char* const* column_family_names,
|
||||
size_t* lencfs, char** errptr) {
|
||||
std::vector<ColumnFamilyHandle*> handles;
|
||||
std::vector<std::string> names;
|
||||
for (int i = 0; i != num_column_families; ++i) {
|
||||
names.push_back(std::string(column_family_names[i]));
|
||||
}
|
||||
SaveError(errptr, db->rep->CreateColumnFamilies(
|
||||
ColumnFamilyOptions(column_family_options->rep), names,
|
||||
&handles));
|
||||
|
||||
*lencfs = handles.size();
|
||||
rocksdb_column_family_handle_t** c_handles =
|
||||
static_cast<rocksdb_column_family_handle_t**>(
|
||||
malloc(sizeof(rocksdb_column_family_handle_t*) * handles.size()));
|
||||
for (size_t i = 0; i != handles.size(); ++i) {
|
||||
c_handles[i] = new rocksdb_column_family_handle_t;
|
||||
c_handles[i]->rep = handles[i];
|
||||
}
|
||||
|
||||
return c_handles;
|
||||
}
|
||||
|
||||
void rocksdb_create_column_families_destroy(
|
||||
rocksdb_column_family_handle_t** list) {
|
||||
free(list);
|
||||
}
|
||||
|
||||
rocksdb_column_family_handle_t* rocksdb_create_column_family_with_ttl(
|
||||
rocksdb_t* db, const rocksdb_options_t* column_family_options,
|
||||
const char* column_family_name, int ttl, char** errptr) {
|
||||
@@ -3029,29 +2993,6 @@ void rocksdb_options_enable_statistics(rocksdb_options_t* opt) {
|
||||
opt->rep.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics();
|
||||
}
|
||||
|
||||
void rocksdb_options_set_statistics_level(rocksdb_options_t* opt, int level) {
|
||||
if (!opt->rep.statistics) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (level < rocksdb_statistics_level_disable_all) {
|
||||
level = rocksdb_statistics_level_disable_all;
|
||||
}
|
||||
if (level > rocksdb_statistics_level_all) {
|
||||
level = rocksdb_statistics_level_all;
|
||||
}
|
||||
opt->rep.statistics->set_stats_level(
|
||||
static_cast<ROCKSDB_NAMESPACE::StatsLevel>(level));
|
||||
}
|
||||
|
||||
int rocksdb_options_get_statistics_level(rocksdb_options_t* opt) {
|
||||
if (!opt->rep.statistics) {
|
||||
return ROCKSDB_NAMESPACE::StatsLevel::kDisableAll;
|
||||
}
|
||||
|
||||
return static_cast<int>(opt->rep.statistics->get_stats_level());
|
||||
}
|
||||
|
||||
void rocksdb_options_set_skip_stats_update_on_db_open(rocksdb_options_t* opt,
|
||||
unsigned char val) {
|
||||
opt->rep.skip_stats_update_on_db_open = val;
|
||||
@@ -3799,21 +3740,16 @@ void rocksdb_options_set_hash_link_list_rep(rocksdb_options_t* opt,
|
||||
ROCKSDB_NAMESPACE::NewHashLinkListRepFactory(bucket_count));
|
||||
}
|
||||
|
||||
void rocksdb_options_set_plain_table_factory(
|
||||
rocksdb_options_t* opt, uint32_t user_key_len, int bloom_bits_per_key,
|
||||
double hash_table_ratio, size_t index_sparseness, size_t huge_page_tlb_size,
|
||||
char encoding_type, unsigned char full_scan_mode,
|
||||
unsigned char store_index_in_file) {
|
||||
void rocksdb_options_set_plain_table_factory(rocksdb_options_t* opt,
|
||||
uint32_t user_key_len,
|
||||
int bloom_bits_per_key,
|
||||
double hash_table_ratio,
|
||||
size_t index_sparseness) {
|
||||
ROCKSDB_NAMESPACE::PlainTableOptions options;
|
||||
options.user_key_len = user_key_len;
|
||||
options.bloom_bits_per_key = bloom_bits_per_key;
|
||||
options.hash_table_ratio = hash_table_ratio;
|
||||
options.index_sparseness = index_sparseness;
|
||||
options.huge_page_tlb_size = huge_page_tlb_size;
|
||||
options.encoding_type =
|
||||
static_cast<ROCKSDB_NAMESPACE::EncodingType>(encoding_type);
|
||||
options.full_scan_mode = full_scan_mode;
|
||||
options.store_index_in_file = store_index_in_file;
|
||||
|
||||
ROCKSDB_NAMESPACE::TableFactory* factory =
|
||||
ROCKSDB_NAMESPACE::NewPlainTableFactory(options);
|
||||
@@ -3891,26 +3827,6 @@ char* rocksdb_options_statistics_get_string(rocksdb_options_t* opt) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
uint64_t rocksdb_options_statistics_get_ticker_count(rocksdb_options_t* opt,
|
||||
uint32_t ticker_type) {
|
||||
ROCKSDB_NAMESPACE::Statistics* statistics = opt->rep.statistics.get();
|
||||
if (statistics) {
|
||||
return statistics->getTickerCount(ticker_type);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void rocksdb_options_statistics_get_histogram_data(
|
||||
rocksdb_options_t* opt, uint32_t type,
|
||||
rocksdb_statistics_histogram_data_t* const data) {
|
||||
ROCKSDB_NAMESPACE::Statistics* statistics = opt->rep.statistics.get();
|
||||
if (statistics) {
|
||||
statistics->histogramData(type, &data->rep);
|
||||
} else {
|
||||
*data = rocksdb_statistics_histogram_data_t{};
|
||||
}
|
||||
}
|
||||
|
||||
void rocksdb_options_set_ratelimiter(rocksdb_options_t* opt,
|
||||
rocksdb_ratelimiter_t* limiter) {
|
||||
if (limiter) {
|
||||
@@ -3972,15 +3888,6 @@ void rocksdb_options_add_compact_on_deletion_collector_factory(
|
||||
opt->rep.table_properties_collector_factories.emplace_back(compact_on_del);
|
||||
}
|
||||
|
||||
void rocksdb_options_add_compact_on_deletion_collector_factory_del_ratio(
|
||||
rocksdb_options_t* opt, size_t window_size, size_t num_dels_trigger,
|
||||
double deletion_ratio) {
|
||||
std::shared_ptr<ROCKSDB_NAMESPACE::TablePropertiesCollectorFactory>
|
||||
compact_on_del = NewCompactOnDeletionCollectorFactory(
|
||||
window_size, num_dels_trigger, deletion_ratio);
|
||||
opt->rep.table_properties_collector_factories.emplace_back(compact_on_del);
|
||||
}
|
||||
|
||||
void rocksdb_set_perf_level(int v) {
|
||||
PerfLevel level = static_cast<PerfLevel>(v);
|
||||
SetPerfLevel(level);
|
||||
@@ -4157,8 +4064,6 @@ 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;
|
||||
}
|
||||
@@ -4774,7 +4679,7 @@ rocksdb_cache_t* rocksdb_cache_create_lru_with_strict_capacity_limit(
|
||||
}
|
||||
|
||||
rocksdb_cache_t* rocksdb_cache_create_lru_opts(
|
||||
const rocksdb_lru_cache_options_t* opt) {
|
||||
rocksdb_lru_cache_options_t* opt) {
|
||||
rocksdb_cache_t* c = new rocksdb_cache_t;
|
||||
c->rep = NewLRUCache(opt->rep);
|
||||
return c;
|
||||
@@ -4821,7 +4726,7 @@ rocksdb_cache_t* rocksdb_cache_create_hyper_clock(
|
||||
}
|
||||
|
||||
rocksdb_cache_t* rocksdb_cache_create_hyper_clock_opts(
|
||||
const rocksdb_hyper_clock_cache_options_t* opts) {
|
||||
rocksdb_hyper_clock_cache_options_t* opts) {
|
||||
rocksdb_cache_t* c = new rocksdb_cache_t;
|
||||
c->rep = opts->rep.MakeSharedCache();
|
||||
return c;
|
||||
@@ -4837,26 +4742,18 @@ void rocksdb_cache_set_capacity(rocksdb_cache_t* cache, size_t capacity) {
|
||||
cache->rep->SetCapacity(capacity);
|
||||
}
|
||||
|
||||
size_t rocksdb_cache_get_capacity(const rocksdb_cache_t* cache) {
|
||||
size_t rocksdb_cache_get_capacity(rocksdb_cache_t* cache) {
|
||||
return cache->rep->GetCapacity();
|
||||
}
|
||||
|
||||
size_t rocksdb_cache_get_usage(const rocksdb_cache_t* cache) {
|
||||
size_t rocksdb_cache_get_usage(rocksdb_cache_t* cache) {
|
||||
return cache->rep->GetUsage();
|
||||
}
|
||||
|
||||
size_t rocksdb_cache_get_pinned_usage(const rocksdb_cache_t* cache) {
|
||||
size_t rocksdb_cache_get_pinned_usage(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;
|
||||
@@ -5243,8 +5140,7 @@ rocksdb_fifo_compaction_options_t* rocksdb_fifo_compaction_options_create() {
|
||||
}
|
||||
|
||||
void rocksdb_fifo_compaction_options_set_allow_compaction(
|
||||
rocksdb_fifo_compaction_options_t* fifo_opts,
|
||||
unsigned char allow_compaction) {
|
||||
rocksdb_fifo_compaction_options_t* fifo_opts, unsigned char allow_compaction) {
|
||||
fifo_opts->rep.allow_compaction = allow_compaction;
|
||||
}
|
||||
|
||||
@@ -5673,20 +5569,6 @@ int rocksdb_transactiondb_property_int(rocksdb_transactiondb_t* db,
|
||||
}
|
||||
}
|
||||
|
||||
rocksdb_t* rocksdb_transactiondb_get_base_db(rocksdb_transactiondb_t* txn_db) {
|
||||
DB* base_db = txn_db->rep->GetBaseDB();
|
||||
|
||||
if (base_db != nullptr) {
|
||||
rocksdb_t* result = new rocksdb_t;
|
||||
result->rep = base_db;
|
||||
return result;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void rocksdb_transactiondb_close_base_db(rocksdb_t* base_db) { delete base_db; }
|
||||
|
||||
rocksdb_transaction_t* rocksdb_transaction_begin(
|
||||
rocksdb_transactiondb_t* txn_db,
|
||||
const rocksdb_writeoptions_t* write_options,
|
||||
@@ -5971,35 +5853,6 @@ 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,
|
||||
@@ -6032,38 +5885,6 @@ 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,
|
||||
@@ -6664,59 +6485,4 @@ void rocksdb_enable_manual_compaction(rocksdb_t* db) {
|
||||
db->rep->EnableManualCompaction();
|
||||
}
|
||||
|
||||
rocksdb_statistics_histogram_data_t*
|
||||
rocksdb_statistics_histogram_data_create() {
|
||||
return new rocksdb_statistics_histogram_data_t{};
|
||||
}
|
||||
|
||||
void rocksdb_statistics_histogram_data_destroy(
|
||||
rocksdb_statistics_histogram_data_t* data) {
|
||||
delete data;
|
||||
}
|
||||
|
||||
double rocksdb_statistics_histogram_data_get_median(
|
||||
rocksdb_statistics_histogram_data_t* data) {
|
||||
return data->rep.median;
|
||||
}
|
||||
|
||||
double rocksdb_statistics_histogram_data_get_p95(
|
||||
rocksdb_statistics_histogram_data_t* data) {
|
||||
return data->rep.percentile95;
|
||||
}
|
||||
|
||||
double rocksdb_statistics_histogram_data_get_p99(
|
||||
rocksdb_statistics_histogram_data_t* data) {
|
||||
return data->rep.percentile99;
|
||||
}
|
||||
|
||||
double rocksdb_statistics_histogram_data_get_average(
|
||||
rocksdb_statistics_histogram_data_t* data) {
|
||||
return data->rep.average;
|
||||
}
|
||||
|
||||
double rocksdb_statistics_histogram_data_get_std_dev(
|
||||
rocksdb_statistics_histogram_data_t* data) {
|
||||
return data->rep.standard_deviation;
|
||||
}
|
||||
|
||||
double rocksdb_statistics_histogram_data_get_max(
|
||||
rocksdb_statistics_histogram_data_t* data) {
|
||||
return data->rep.max;
|
||||
}
|
||||
|
||||
uint64_t rocksdb_statistics_histogram_data_get_count(
|
||||
rocksdb_statistics_histogram_data_t* data) {
|
||||
return data->rep.count;
|
||||
}
|
||||
|
||||
uint64_t rocksdb_statistics_histogram_data_get_sum(
|
||||
rocksdb_statistics_histogram_data_t* data) {
|
||||
return data->rep.sum;
|
||||
}
|
||||
|
||||
double rocksdb_statistics_histogram_data_get_min(
|
||||
rocksdb_statistics_histogram_data_t* data) {
|
||||
return data->rep.min;
|
||||
}
|
||||
|
||||
} // end extern "C"
|
||||
+6
-245
@@ -3,14 +3,15 @@
|
||||
found in the LICENSE file. See the AUTHORS file for names of contributors. */
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
|
||||
|
||||
#include "rocksdb/c.h"
|
||||
#include <stdio.h>
|
||||
|
||||
#include <assert.h>
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include "rocksdb/c.h"
|
||||
#ifndef OS_WIN
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
@@ -487,19 +488,6 @@ 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) {
|
||||
@@ -527,20 +515,6 @@ 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) {
|
||||
@@ -720,8 +694,6 @@ int main(int argc, char** argv) {
|
||||
|
||||
rocksdb_options_add_compact_on_deletion_collector_factory(options, 10000,
|
||||
10001);
|
||||
rocksdb_options_add_compact_on_deletion_collector_factory_del_ratio(
|
||||
options, 10000, 10001, 0.0);
|
||||
|
||||
StartPhase("destroy");
|
||||
rocksdb_destroy_db(options, dbname, &err);
|
||||
@@ -1673,8 +1645,7 @@ int main(int argc, char** argv) {
|
||||
rocksdb_options_set_prefix_extractor(
|
||||
options, rocksdb_slicetransform_create_fixed_prefix(3));
|
||||
rocksdb_options_set_hash_skip_list_rep(options, 5000, 4, 4);
|
||||
rocksdb_options_set_plain_table_factory(options, 4, 10, 0.75, 16, 0, 0, 0,
|
||||
0);
|
||||
rocksdb_options_set_plain_table_factory(options, 4, 10, 0.75, 16);
|
||||
rocksdb_options_set_allow_concurrent_memtable_write(options, 0);
|
||||
|
||||
db = rocksdb_open(options, dbname, &err);
|
||||
@@ -2060,15 +2031,6 @@ int main(int argc, char** argv) {
|
||||
CheckCondition(29.0 ==
|
||||
rocksdb_options_get_experimental_mempurge_threshold(o));
|
||||
|
||||
CheckCondition(rocksdb_statistics_level_disable_all ==
|
||||
rocksdb_options_get_statistics_level(o));
|
||||
rocksdb_options_enable_statistics(o);
|
||||
CheckCondition(rocksdb_statistics_level_disable_all !=
|
||||
rocksdb_options_get_statistics_level(o));
|
||||
rocksdb_options_set_statistics_level(o, rocksdb_statistics_level_all);
|
||||
CheckCondition(rocksdb_statistics_level_all ==
|
||||
rocksdb_options_get_statistics_level(o));
|
||||
|
||||
/* Blob Options */
|
||||
rocksdb_options_set_enable_blob_files(o, 1);
|
||||
CheckCondition(1 == rocksdb_options_get_enable_blob_files(o));
|
||||
@@ -3130,17 +3092,6 @@ int main(int argc, char** argv) {
|
||||
CheckTxnDBGetCF(txn_db, roptions, cfh, "cf_foo", NULL);
|
||||
CheckTxnDBPinGetCF(txn_db, roptions, cfh, "cf_foo", NULL);
|
||||
|
||||
// memory usage
|
||||
rocksdb_t* base_db = rocksdb_transactiondb_get_base_db(txn_db);
|
||||
rocksdb_memory_consumers_t* consumers = rocksdb_memory_consumers_create();
|
||||
rocksdb_memory_consumers_add_db(consumers, base_db);
|
||||
rocksdb_memory_usage_t* usage =
|
||||
rocksdb_approximate_memory_usage_create(consumers, &err);
|
||||
CheckNoError(err);
|
||||
rocksdb_approximate_memory_usage_destroy(usage);
|
||||
rocksdb_memory_consumers_destroy(consumers);
|
||||
rocksdb_transactiondb_close_base_db(base_db);
|
||||
|
||||
// flush
|
||||
rocksdb_flushoptions_t* flush_options = rocksdb_flushoptions_create();
|
||||
rocksdb_flushoptions_set_wait(flush_options, 1);
|
||||
@@ -3253,120 +3204,6 @@ 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();
|
||||
@@ -3396,19 +3233,8 @@ int main(int argc, char** argv) {
|
||||
rocksdb_put(db, woptions, "key", 3, "value", 5, &err);
|
||||
CheckNoError(err);
|
||||
rocksdb_column_family_handle_t *cfh1, *cfh2;
|
||||
char** list_const_cf_names = (char**)malloc(2 * sizeof(char*));
|
||||
list_const_cf_names[0] = "txn_db_cf1";
|
||||
list_const_cf_names[1] = "txn_db_cf2";
|
||||
size_t cflen;
|
||||
rocksdb_column_family_handle_t** list_cfh = rocksdb_create_column_families(
|
||||
db, db_options, 2, (const char* const*)list_const_cf_names, &cflen,
|
||||
&err);
|
||||
free(list_const_cf_names);
|
||||
CheckNoError(err);
|
||||
assert(cflen == 2);
|
||||
cfh1 = list_cfh[0];
|
||||
cfh2 = list_cfh[1];
|
||||
rocksdb_create_column_families_destroy(list_cfh);
|
||||
cfh1 = rocksdb_create_column_family(db, db_options, "txn_db_cf1", &err);
|
||||
cfh2 = rocksdb_create_column_family(db, db_options, "txn_db_cf2", &err);
|
||||
txn = rocksdb_optimistictransaction_begin(otxn_db, woptions, otxn_options,
|
||||
NULL);
|
||||
rocksdb_transaction_put_cf(txn, cfh1, "key_cf1", 7, "val_cf1", 7, &err);
|
||||
@@ -3622,71 +3448,6 @@ int main(int argc, char** argv) {
|
||||
rocksdb_readoptions_destroy(ropts);
|
||||
}
|
||||
|
||||
StartPhase("statistics");
|
||||
{
|
||||
const uint32_t BYTES_WRITTEN_TICKER = 40;
|
||||
const uint32_t DB_WRITE_HIST = 1;
|
||||
|
||||
rocksdb_statistics_histogram_data_t* hist =
|
||||
rocksdb_statistics_histogram_data_create();
|
||||
{
|
||||
// zero by default
|
||||
CheckCondition(0.0 == rocksdb_statistics_histogram_data_get_median(hist));
|
||||
CheckCondition(0.0 == rocksdb_statistics_histogram_data_get_p95(hist));
|
||||
CheckCondition(0.0 == rocksdb_statistics_histogram_data_get_p99(hist));
|
||||
CheckCondition(0.0 ==
|
||||
rocksdb_statistics_histogram_data_get_average(hist));
|
||||
CheckCondition(0.0 ==
|
||||
rocksdb_statistics_histogram_data_get_std_dev(hist));
|
||||
CheckCondition(0.0 == rocksdb_statistics_histogram_data_get_max(hist));
|
||||
CheckCondition(0 == rocksdb_statistics_histogram_data_get_count(hist));
|
||||
CheckCondition(0 == rocksdb_statistics_histogram_data_get_sum(hist));
|
||||
CheckCondition(0.0 == rocksdb_statistics_histogram_data_get_min(hist));
|
||||
}
|
||||
|
||||
rocksdb_close(db);
|
||||
rocksdb_destroy_db(options, dbname, &err);
|
||||
CheckNoError(err);
|
||||
|
||||
rocksdb_options_enable_statistics(options);
|
||||
rocksdb_options_set_statistics_level(options, rocksdb_statistics_level_all);
|
||||
|
||||
db = rocksdb_open(options, dbname, &err);
|
||||
CheckNoError(err);
|
||||
|
||||
CheckCondition(0 == rocksdb_options_statistics_get_ticker_count(
|
||||
options, BYTES_WRITTEN_TICKER));
|
||||
rocksdb_options_statistics_get_histogram_data(options, DB_WRITE_HIST, hist);
|
||||
CheckCondition(0.0 == rocksdb_statistics_histogram_data_get_median(hist));
|
||||
CheckCondition(0.0 == rocksdb_statistics_histogram_data_get_p95(hist));
|
||||
CheckCondition(0.0 == rocksdb_statistics_histogram_data_get_p99(hist));
|
||||
CheckCondition(0.0 == rocksdb_statistics_histogram_data_get_average(hist));
|
||||
CheckCondition(0.0 == rocksdb_statistics_histogram_data_get_std_dev(hist));
|
||||
CheckCondition(0.0 == rocksdb_statistics_histogram_data_get_max(hist));
|
||||
CheckCondition(0 == rocksdb_statistics_histogram_data_get_count(hist));
|
||||
CheckCondition(0 == rocksdb_statistics_histogram_data_get_sum(hist));
|
||||
|
||||
int i;
|
||||
for (i = 0; i < 10; ++i) {
|
||||
char key = '0' + (char)i;
|
||||
rocksdb_put(db, woptions, &key, 1, "", 1, &err);
|
||||
CheckNoError(err);
|
||||
}
|
||||
CheckCondition(0 != rocksdb_options_statistics_get_ticker_count(
|
||||
options, BYTES_WRITTEN_TICKER));
|
||||
rocksdb_options_statistics_get_histogram_data(options, DB_WRITE_HIST, hist);
|
||||
CheckCondition(0.0 != rocksdb_statistics_histogram_data_get_median(hist));
|
||||
CheckCondition(0.0 != rocksdb_statistics_histogram_data_get_p95(hist));
|
||||
CheckCondition(0.0 != rocksdb_statistics_histogram_data_get_p99(hist));
|
||||
CheckCondition(0.0 != rocksdb_statistics_histogram_data_get_average(hist));
|
||||
CheckCondition(0.0 != rocksdb_statistics_histogram_data_get_std_dev(hist));
|
||||
CheckCondition(0.0 != rocksdb_statistics_histogram_data_get_max(hist));
|
||||
CheckCondition(0 != rocksdb_statistics_histogram_data_get_count(hist));
|
||||
CheckCondition(0 != rocksdb_statistics_histogram_data_get_sum(hist));
|
||||
|
||||
rocksdb_statistics_histogram_data_destroy(hist);
|
||||
}
|
||||
|
||||
StartPhase("cancel_all_background_work");
|
||||
rocksdb_cancel_all_background_work(db, 1);
|
||||
|
||||
|
||||
+47
-130
@@ -382,9 +382,8 @@ ColumnFamilyOptions SanitizeOptions(const ImmutableDBOptions& db_options,
|
||||
|
||||
const uint64_t kAdjustedTtl = 30 * 24 * 60 * 60;
|
||||
if (result.ttl == kDefaultTtl) {
|
||||
if (is_block_based_table) {
|
||||
// FIFO also requires max_open_files=-1, which is checked in
|
||||
// ValidateOptions().
|
||||
if (is_block_based_table &&
|
||||
result.compaction_style != kCompactionStyleFIFO) {
|
||||
result.ttl = kAdjustedTtl;
|
||||
} else {
|
||||
result.ttl = 0;
|
||||
@@ -392,35 +391,40 @@ ColumnFamilyOptions SanitizeOptions(const ImmutableDBOptions& db_options,
|
||||
}
|
||||
|
||||
const uint64_t kAdjustedPeriodicCompSecs = 30 * 24 * 60 * 60;
|
||||
if (result.compaction_style == kCompactionStyleLevel) {
|
||||
|
||||
// Turn on periodic compactions and set them to occur once every 30 days if
|
||||
// compaction filters are used and periodic_compaction_seconds is set to the
|
||||
// default value.
|
||||
if (result.compaction_style != kCompactionStyleFIFO) {
|
||||
if ((result.compaction_filter != nullptr ||
|
||||
result.compaction_filter_factory != nullptr) &&
|
||||
result.periodic_compaction_seconds == kDefaultPeriodicCompSecs &&
|
||||
is_block_based_table) {
|
||||
result.periodic_compaction_seconds = kAdjustedPeriodicCompSecs;
|
||||
}
|
||||
} else if (result.compaction_style == kCompactionStyleUniversal) {
|
||||
if (result.periodic_compaction_seconds == kDefaultPeriodicCompSecs &&
|
||||
is_block_based_table) {
|
||||
result.periodic_compaction_seconds = kAdjustedPeriodicCompSecs;
|
||||
}
|
||||
} else if (result.compaction_style == kCompactionStyleFIFO) {
|
||||
if (result.periodic_compaction_seconds != kDefaultPeriodicCompSecs) {
|
||||
ROCKS_LOG_WARN(
|
||||
db_options.info_log.get(),
|
||||
"periodic_compaction_seconds does not support FIFO compaction. You"
|
||||
"may want to set option TTL instead.");
|
||||
} else {
|
||||
// result.compaction_style == kCompactionStyleFIFO
|
||||
if (result.ttl == 0) {
|
||||
if (is_block_based_table) {
|
||||
if (result.periodic_compaction_seconds == kDefaultPeriodicCompSecs) {
|
||||
result.periodic_compaction_seconds = kAdjustedPeriodicCompSecs;
|
||||
}
|
||||
result.ttl = result.periodic_compaction_seconds;
|
||||
}
|
||||
} else if (result.periodic_compaction_seconds != 0) {
|
||||
result.ttl = std::min(result.ttl, result.periodic_compaction_seconds);
|
||||
}
|
||||
}
|
||||
|
||||
// For universal compaction, `ttl` and `periodic_compaction_seconds` mean the
|
||||
// same thing, take the stricter value.
|
||||
if (result.compaction_style == kCompactionStyleUniversal) {
|
||||
if (result.periodic_compaction_seconds == 0) {
|
||||
result.periodic_compaction_seconds = result.ttl;
|
||||
} else if (result.ttl != 0) {
|
||||
// TTL compactions would work similar to Periodic Compactions in Universal in
|
||||
// most of the cases. So, if ttl is set, execute the periodic compaction
|
||||
// codepath.
|
||||
if (result.compaction_style == kCompactionStyleUniversal && result.ttl != 0) {
|
||||
if (result.periodic_compaction_seconds != 0) {
|
||||
result.periodic_compaction_seconds =
|
||||
std::min(result.ttl, result.periodic_compaction_seconds);
|
||||
} else {
|
||||
result.periodic_compaction_seconds = result.ttl;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1137,7 +1141,6 @@ 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);
|
||||
@@ -1241,11 +1244,30 @@ SuperVersion* ColumnFamilyData::GetThreadLocalSuperVersion(DBImpl* db) {
|
||||
// (if no Scrape happens).
|
||||
assert(ptr != SuperVersion::kSVInUse);
|
||||
SuperVersion* sv = static_cast<SuperVersion*>(ptr);
|
||||
if (sv == SuperVersion::kSVObsolete) {
|
||||
if (sv == SuperVersion::kSVObsolete ||
|
||||
sv->version_number != super_version_number_.load()) {
|
||||
RecordTick(ioptions_.stats, NUMBER_SUPERVERSION_ACQUIRES);
|
||||
db->mutex()->Lock();
|
||||
SuperVersion* sv_to_delete = nullptr;
|
||||
|
||||
if (sv && sv->Unref()) {
|
||||
RecordTick(ioptions_.stats, NUMBER_SUPERVERSION_CLEANUPS);
|
||||
db->mutex()->Lock();
|
||||
// NOTE: underlying resources held by superversion (sst files) might
|
||||
// not be released until the next background job.
|
||||
sv->Cleanup();
|
||||
if (db->immutable_db_options().avoid_unnecessary_blocking_io) {
|
||||
db->AddSuperVersionsToFreeQueue(sv);
|
||||
db->SchedulePurge();
|
||||
} else {
|
||||
sv_to_delete = sv;
|
||||
}
|
||||
} else {
|
||||
db->mutex()->Lock();
|
||||
}
|
||||
sv = super_version_->Ref();
|
||||
db->mutex()->Unlock();
|
||||
|
||||
delete sv_to_delete;
|
||||
}
|
||||
assert(sv != nullptr);
|
||||
return sv;
|
||||
@@ -1376,33 +1398,6 @@ Status ColumnFamilyData::ValidateOptions(
|
||||
}
|
||||
}
|
||||
|
||||
const auto* ucmp = cf_options.comparator;
|
||||
assert(ucmp);
|
||||
if (ucmp->timestamp_size() > 0 &&
|
||||
!cf_options.persist_user_defined_timestamps) {
|
||||
if (db_options.atomic_flush) {
|
||||
return Status::NotSupported(
|
||||
"Not persisting user-defined timestamps feature is not supported"
|
||||
"in combination with atomic flush.");
|
||||
}
|
||||
if (db_options.allow_concurrent_memtable_write) {
|
||||
return Status::NotSupported(
|
||||
"Not persisting user-defined timestamps feature is not supported"
|
||||
" in combination with concurrent memtable write.");
|
||||
}
|
||||
const char* comparator_name = cf_options.comparator->Name();
|
||||
size_t name_size = strlen(comparator_name);
|
||||
const char* suffix = ".u64ts";
|
||||
size_t suffix_size = strlen(suffix);
|
||||
if (name_size <= suffix_size ||
|
||||
strcmp(comparator_name + name_size - suffix_size, suffix) != 0) {
|
||||
return Status::NotSupported(
|
||||
"Not persisting user-defined timestamps"
|
||||
"feature only support user-defined timestamps formatted as "
|
||||
"uint64_t.");
|
||||
}
|
||||
}
|
||||
|
||||
if (cf_options.enable_blob_garbage_collection) {
|
||||
if (cf_options.blob_garbage_collection_age_cutoff < 0.0 ||
|
||||
cf_options.blob_garbage_collection_age_cutoff > 1.0) {
|
||||
@@ -1432,37 +1427,6 @@ 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;
|
||||
}
|
||||
|
||||
@@ -1542,43 +1506,6 @@ FSDirectory* ColumnFamilyData::GetDataDir(size_t path_id) const {
|
||||
return data_dirs_[path_id].get();
|
||||
}
|
||||
|
||||
bool ColumnFamilyData::ShouldPostponeFlushToRetainUDT(
|
||||
uint64_t max_memtable_id) {
|
||||
const Comparator* ucmp = user_comparator();
|
||||
const size_t ts_sz = ucmp->timestamp_size();
|
||||
if (ts_sz == 0 || ioptions_.persist_user_defined_timestamps) {
|
||||
return false;
|
||||
}
|
||||
// If users set the `persist_user_defined_timestamps` flag to false, they
|
||||
// should also set the `full_history_ts_low` flag to indicate the range of
|
||||
// user-defined timestamps to retain in memory. Otherwise, we do not
|
||||
// explicitly postpone flush to retain UDTs.
|
||||
const std::string& full_history_ts_low = GetFullHistoryTsLow();
|
||||
if (full_history_ts_low.empty()) {
|
||||
return false;
|
||||
}
|
||||
#ifndef NDEBUG
|
||||
Slice last_table_newest_udt;
|
||||
#endif /* !NDEBUG */
|
||||
for (const Slice& table_newest_udt :
|
||||
imm()->GetTablesNewestUDT(max_memtable_id)) {
|
||||
assert(table_newest_udt.size() == full_history_ts_low.size());
|
||||
assert(last_table_newest_udt.empty() ||
|
||||
ucmp->CompareTimestamp(table_newest_udt, last_table_newest_udt) >=
|
||||
0);
|
||||
// Checking the newest UDT contained in MemTable with ascending ID up to
|
||||
// `max_memtable_id`. MemTable with bigger ID will have newer UDT, return
|
||||
// immediately on finding the first MemTable that needs postponing.
|
||||
if (ucmp->CompareTimestamp(table_newest_udt, full_history_ts_low) >= 0) {
|
||||
return true;
|
||||
}
|
||||
#ifndef NDEBUG
|
||||
last_table_newest_udt = table_newest_udt;
|
||||
#endif /* !NDEBUG */
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void ColumnFamilyData::RecoverEpochNumbers() {
|
||||
assert(current_);
|
||||
auto* vstorage = current_->storage_info();
|
||||
@@ -1681,13 +1608,6 @@ ColumnFamilyData* ColumnFamilySet::CreateColumnFamily(
|
||||
db_id_, db_session_id_);
|
||||
column_families_.insert({name, id});
|
||||
column_family_data_.insert({id, new_cfd});
|
||||
auto ucmp = new_cfd->user_comparator();
|
||||
assert(ucmp);
|
||||
size_t ts_sz = ucmp->timestamp_size();
|
||||
running_ts_sz_.insert({id, ts_sz});
|
||||
if (ts_sz > 0) {
|
||||
ts_sz_for_record_.insert({id, ts_sz});
|
||||
}
|
||||
max_column_family_ = std::max(max_column_family_, id);
|
||||
// add to linked list
|
||||
new_cfd->next_ = dummy_cfd_;
|
||||
@@ -1703,13 +1623,10 @@ ColumnFamilyData* ColumnFamilySet::CreateColumnFamily(
|
||||
|
||||
// under a DB mutex AND from a write thread
|
||||
void ColumnFamilySet::RemoveColumnFamily(ColumnFamilyData* cfd) {
|
||||
uint32_t cf_id = cfd->GetID();
|
||||
auto cfd_iter = column_family_data_.find(cf_id);
|
||||
auto cfd_iter = column_family_data_.find(cfd->GetID());
|
||||
assert(cfd_iter != column_family_data_.end());
|
||||
column_family_data_.erase(cfd_iter);
|
||||
column_families_.erase(cfd->GetName());
|
||||
running_ts_sz_.erase(cf_id);
|
||||
ts_sz_for_record_.erase(cf_id);
|
||||
}
|
||||
|
||||
// under a DB mutex OR from a write thread
|
||||
|
||||
+2
-25
@@ -506,12 +506,6 @@ class ColumnFamilyData {
|
||||
return full_history_ts_low_;
|
||||
}
|
||||
|
||||
// REQUIRES: DB mutex held.
|
||||
// Return true if flushing up to MemTables with ID `max_memtable_id`
|
||||
// should be postponed to retain user-defined timestamps according to the
|
||||
// user's setting. Called by background flush job.
|
||||
bool ShouldPostponeFlushToRetainUDT(uint64_t max_memtable_id);
|
||||
|
||||
ThreadLocalPtr* TEST_GetLocalSV() { return local_sv_.get(); }
|
||||
WriteBufferManager* write_buffer_mgr() { return write_buffer_manager_; }
|
||||
std::shared_ptr<CacheReservationManager>
|
||||
@@ -519,6 +513,8 @@ class ColumnFamilyData {
|
||||
return file_metadata_cache_res_mgr_;
|
||||
}
|
||||
|
||||
SequenceNumber GetFirstMemtableSequenceNumber() const;
|
||||
|
||||
static const uint32_t kDummyColumnFamilyDataId;
|
||||
|
||||
// Keep track of whether the mempurge feature was ever used.
|
||||
@@ -709,16 +705,6 @@ class ColumnFamilySet {
|
||||
Version* dummy_version,
|
||||
const ColumnFamilyOptions& options);
|
||||
|
||||
const UnorderedMap<uint32_t, size_t>& GetRunningColumnFamiliesTimestampSize()
|
||||
const {
|
||||
return running_ts_sz_;
|
||||
}
|
||||
|
||||
const UnorderedMap<uint32_t, size_t>&
|
||||
GetColumnFamiliesTimestampSizeForRecord() const {
|
||||
return ts_sz_for_record_;
|
||||
}
|
||||
|
||||
iterator begin() { return iterator(dummy_cfd_->next_); }
|
||||
iterator end() { return iterator(dummy_cfd_); }
|
||||
|
||||
@@ -744,15 +730,6 @@ class ColumnFamilySet {
|
||||
UnorderedMap<std::string, uint32_t> column_families_;
|
||||
UnorderedMap<uint32_t, ColumnFamilyData*> column_family_data_;
|
||||
|
||||
// Mutating / reading `running_ts_sz_` and `ts_sz_for_record_` follow
|
||||
// the same requirements as `column_families_` and `column_family_data_`.
|
||||
// Mapping from column family id to user-defined timestamp size for all
|
||||
// running column families.
|
||||
UnorderedMap<uint32_t, size_t> running_ts_sz_;
|
||||
// Mapping from column family id to user-defined timestamp size for
|
||||
// column families with non-zero user-defined timestamp size.
|
||||
UnorderedMap<uint32_t, size_t> ts_sz_for_record_;
|
||||
|
||||
uint32_t max_column_family_;
|
||||
const FileOptions file_options_;
|
||||
|
||||
|
||||
+2
-213
@@ -17,7 +17,6 @@
|
||||
#include "options/options_parser.h"
|
||||
#include "port/port.h"
|
||||
#include "port/stack_trace.h"
|
||||
#include "rocksdb/comparator.h"
|
||||
#include "rocksdb/convenience.h"
|
||||
#include "rocksdb/db.h"
|
||||
#include "rocksdb/env.h"
|
||||
@@ -64,9 +63,6 @@ class ColumnFamilyTestBase : public testing::Test {
|
||||
db_options_.create_if_missing = true;
|
||||
db_options_.fail_if_options_file_error = true;
|
||||
db_options_.env = env_;
|
||||
}
|
||||
|
||||
void SetUp() override {
|
||||
EXPECT_OK(DestroyDB(dbname_, Options(db_options_, column_family_options_)));
|
||||
}
|
||||
|
||||
@@ -1259,7 +1255,6 @@ TEST_P(ColumnFamilyTest, DifferentCompactionStyles) {
|
||||
ColumnFamilyOptions default_cf, one, two;
|
||||
db_options_.max_open_files = 20; // only 10 files in file cache
|
||||
|
||||
default_cf.level_compaction_dynamic_level_bytes = false;
|
||||
default_cf.compaction_style = kCompactionStyleLevel;
|
||||
default_cf.num_levels = 3;
|
||||
default_cf.write_buffer_size = 64 << 10; // 64KB
|
||||
@@ -1277,7 +1272,6 @@ TEST_P(ColumnFamilyTest, DifferentCompactionStyles) {
|
||||
one.level0_file_num_compaction_trigger = 4;
|
||||
one.write_buffer_size = 120000;
|
||||
|
||||
two.level_compaction_dynamic_level_bytes = false;
|
||||
two.compaction_style = kCompactionStyleLevel;
|
||||
two.num_levels = 4;
|
||||
two.level0_file_num_compaction_trigger = 3;
|
||||
@@ -1332,7 +1326,6 @@ TEST_P(ColumnFamilyTest, MultipleManualCompactions) {
|
||||
db_options_.max_open_files = 20; // only 10 files in file cache
|
||||
db_options_.max_background_compactions = 3;
|
||||
|
||||
default_cf.level_compaction_dynamic_level_bytes = false;
|
||||
default_cf.compaction_style = kCompactionStyleLevel;
|
||||
default_cf.num_levels = 3;
|
||||
default_cf.write_buffer_size = 64 << 10; // 64KB
|
||||
@@ -1349,7 +1342,6 @@ TEST_P(ColumnFamilyTest, MultipleManualCompactions) {
|
||||
one.level0_file_num_compaction_trigger = 4;
|
||||
one.write_buffer_size = 120000;
|
||||
|
||||
two.level_compaction_dynamic_level_bytes = false;
|
||||
two.compaction_style = kCompactionStyleLevel;
|
||||
two.num_levels = 4;
|
||||
two.level0_file_num_compaction_trigger = 3;
|
||||
@@ -1432,14 +1424,13 @@ TEST_P(ColumnFamilyTest, AutomaticAndManualCompactions) {
|
||||
db_options_.max_open_files = 20; // only 10 files in file cache
|
||||
db_options_.max_background_compactions = 3;
|
||||
|
||||
default_cf.level_compaction_dynamic_level_bytes = false;
|
||||
default_cf.compaction_style = kCompactionStyleLevel;
|
||||
default_cf.num_levels = 3;
|
||||
default_cf.write_buffer_size = 64 << 10; // 64KB
|
||||
default_cf.target_file_size_base = 30 << 10;
|
||||
default_cf.max_compaction_bytes = default_cf.target_file_size_base * 1100;
|
||||
BlockBasedTableOptions table_options = GetBlockBasedTableOptions();
|
||||
|
||||
;
|
||||
table_options.no_block_cache = true;
|
||||
default_cf.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
|
||||
@@ -1450,7 +1441,6 @@ TEST_P(ColumnFamilyTest, AutomaticAndManualCompactions) {
|
||||
one.level0_file_num_compaction_trigger = 4;
|
||||
one.write_buffer_size = 120000;
|
||||
|
||||
two.level_compaction_dynamic_level_bytes = false;
|
||||
two.compaction_style = kCompactionStyleLevel;
|
||||
two.num_levels = 4;
|
||||
two.level0_file_num_compaction_trigger = 3;
|
||||
@@ -1529,14 +1519,13 @@ TEST_P(ColumnFamilyTest, ManualAndAutomaticCompactions) {
|
||||
db_options_.max_open_files = 20; // only 10 files in file cache
|
||||
db_options_.max_background_compactions = 3;
|
||||
|
||||
default_cf.level_compaction_dynamic_level_bytes = false;
|
||||
default_cf.compaction_style = kCompactionStyleLevel;
|
||||
default_cf.num_levels = 3;
|
||||
default_cf.write_buffer_size = 64 << 10; // 64KB
|
||||
default_cf.target_file_size_base = 30 << 10;
|
||||
default_cf.max_compaction_bytes = default_cf.target_file_size_base * 1100;
|
||||
BlockBasedTableOptions table_options = GetBlockBasedTableOptions();
|
||||
|
||||
;
|
||||
table_options.no_block_cache = true;
|
||||
default_cf.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
|
||||
@@ -1547,7 +1536,6 @@ TEST_P(ColumnFamilyTest, ManualAndAutomaticCompactions) {
|
||||
one.level0_file_num_compaction_trigger = 4;
|
||||
one.write_buffer_size = 120000;
|
||||
|
||||
two.level_compaction_dynamic_level_bytes = false;
|
||||
two.compaction_style = kCompactionStyleLevel;
|
||||
two.num_levels = 4;
|
||||
two.level0_file_num_compaction_trigger = 3;
|
||||
@@ -3384,205 +3372,6 @@ TEST(ColumnFamilyTest, ValidateMemtableKVChecksumOption) {
|
||||
ASSERT_OK(ColumnFamilyData::ValidateOptions(db_options, cf_options));
|
||||
}
|
||||
|
||||
// Tests the flushing behavior of a column family to retain user-defined
|
||||
// timestamp when `persist_user_defined_timestamp` is false.
|
||||
class ColumnFamilyRetainUDTTest : public ColumnFamilyTestBase {
|
||||
public:
|
||||
ColumnFamilyRetainUDTTest() : ColumnFamilyTestBase(kLatestFormatVersion) {}
|
||||
|
||||
void SetUp() override {
|
||||
db_options_.allow_concurrent_memtable_write = false;
|
||||
column_family_options_.comparator =
|
||||
test::BytewiseComparatorWithU64TsWrapper();
|
||||
column_family_options_.persist_user_defined_timestamps = false;
|
||||
ColumnFamilyTestBase::SetUp();
|
||||
}
|
||||
|
||||
Status Put(int cf, const std::string& key, const std::string& ts,
|
||||
const std::string& value) {
|
||||
return db_->Put(WriteOptions(), handles_[cf], Slice(key), Slice(ts),
|
||||
Slice(value));
|
||||
}
|
||||
};
|
||||
|
||||
class TestTsComparator : public Comparator {
|
||||
public:
|
||||
TestTsComparator() : Comparator(8 /*ts_sz*/) {}
|
||||
|
||||
int Compare(const ROCKSDB_NAMESPACE::Slice& /*a*/,
|
||||
const ROCKSDB_NAMESPACE::Slice& /*b*/) const override {
|
||||
return 0;
|
||||
}
|
||||
const char* Name() const override { return "TestTs"; }
|
||||
void FindShortestSeparator(
|
||||
std::string* /*start*/,
|
||||
const ROCKSDB_NAMESPACE::Slice& /*limit*/) const override {}
|
||||
void FindShortSuccessor(std::string* /*key*/) const override {}
|
||||
};
|
||||
|
||||
TEST_F(ColumnFamilyRetainUDTTest, SanityCheck) {
|
||||
Open();
|
||||
ColumnFamilyOptions cf_options;
|
||||
cf_options.persist_user_defined_timestamps = false;
|
||||
TestTsComparator test_comparator;
|
||||
cf_options.comparator = &test_comparator;
|
||||
ColumnFamilyHandle* handle;
|
||||
// Not persisting user-defined timestamps feature only supports user-defined
|
||||
// timestamps formatted as uint64_t.
|
||||
ASSERT_TRUE(
|
||||
db_->CreateColumnFamily(cf_options, "pikachu", &handle).IsNotSupported());
|
||||
|
||||
Destroy();
|
||||
// Not persisting user-defined timestamps feature doesn't work in combination
|
||||
// with atomic flush.
|
||||
db_options_.atomic_flush = true;
|
||||
ASSERT_TRUE(TryOpen({"default"}).IsNotSupported());
|
||||
|
||||
// Not persisting user-defined timestamps feature doesn't work in combination
|
||||
// with concurrent memtable write.
|
||||
db_options_.atomic_flush = false;
|
||||
db_options_.allow_concurrent_memtable_write = true;
|
||||
ASSERT_TRUE(TryOpen({"default"}).IsNotSupported());
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(ColumnFamilyRetainUDTTest, FullHistoryTsLowNotSet) {
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::BackgroundFlush:CheckFlushRequest:cb", [&](void* arg) {
|
||||
ASSERT_NE(nullptr, arg);
|
||||
auto reschedule_count = *static_cast<int*>(arg);
|
||||
ASSERT_EQ(1, reschedule_count);
|
||||
});
|
||||
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
Open();
|
||||
std::string write_ts;
|
||||
PutFixed64(&write_ts, 1);
|
||||
ASSERT_OK(Put(0, "foo", write_ts, "v1"));
|
||||
// No `full_history_ts_low` explicitly set by user, flush is continued
|
||||
// without checking if its UDTs expired.
|
||||
ASSERT_OK(Flush(0));
|
||||
|
||||
// After flush, `full_history_ts_low` should be automatically advanced to
|
||||
// the effective cutoff timestamp: write_ts + 1
|
||||
std::string cutoff_ts;
|
||||
PutFixed64(&cutoff_ts, 2);
|
||||
std::string effective_full_history_ts_low;
|
||||
ASSERT_OK(
|
||||
db_->GetFullHistoryTsLow(handles_[0], &effective_full_history_ts_low));
|
||||
ASSERT_EQ(cutoff_ts, effective_full_history_ts_low);
|
||||
Close();
|
||||
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
}
|
||||
|
||||
TEST_F(ColumnFamilyRetainUDTTest, AllKeysExpired) {
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::BackgroundFlush:CheckFlushRequest:cb", [&](void* arg) {
|
||||
ASSERT_NE(nullptr, arg);
|
||||
auto reschedule_count = *static_cast<int*>(arg);
|
||||
ASSERT_EQ(1, reschedule_count);
|
||||
});
|
||||
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
Open();
|
||||
std::string write_ts;
|
||||
PutFixed64(&write_ts, 1);
|
||||
ASSERT_OK(Put(0, "foo", write_ts, "v1"));
|
||||
std::string cutoff_ts;
|
||||
PutFixed64(&cutoff_ts, 3);
|
||||
ASSERT_OK(db_->IncreaseFullHistoryTsLow(handles_[0], cutoff_ts));
|
||||
// All keys expired w.r.t the configured `full_history_ts_low`, flush continue
|
||||
// without the need for a re-schedule.
|
||||
ASSERT_OK(Flush(0));
|
||||
|
||||
// `full_history_ts_low` stays unchanged after flush.
|
||||
std::string effective_full_history_ts_low;
|
||||
ASSERT_OK(
|
||||
db_->GetFullHistoryTsLow(handles_[0], &effective_full_history_ts_low));
|
||||
ASSERT_EQ(cutoff_ts, effective_full_history_ts_low);
|
||||
Close();
|
||||
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
}
|
||||
TEST_F(ColumnFamilyRetainUDTTest, NotAllKeysExpiredFlushToAvoidWriteStall) {
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::BackgroundFlush:CheckFlushRequest:cb", [&](void* arg) {
|
||||
ASSERT_NE(nullptr, arg);
|
||||
auto reschedule_count = *static_cast<int*>(arg);
|
||||
ASSERT_EQ(1, reschedule_count);
|
||||
});
|
||||
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
Open();
|
||||
std::string cutoff_ts;
|
||||
std::string write_ts;
|
||||
PutFixed64(&write_ts, 1);
|
||||
ASSERT_OK(Put(0, "foo", write_ts, "v1"));
|
||||
PutFixed64(&cutoff_ts, 1);
|
||||
ASSERT_OK(db_->IncreaseFullHistoryTsLow(handles_[0], cutoff_ts));
|
||||
ASSERT_OK(db_->SetOptions(handles_[0], {{"max_write_buffer_number", "1"}}));
|
||||
// Not all keys expired, but flush is continued without a re-schedule because
|
||||
// of risk of write stall.
|
||||
ASSERT_OK(Flush(0));
|
||||
|
||||
// After flush, `full_history_ts_low` should be automatically advanced to
|
||||
// the effective cutoff timestamp: write_ts + 1
|
||||
std::string effective_full_history_ts_low;
|
||||
ASSERT_OK(
|
||||
db_->GetFullHistoryTsLow(handles_[0], &effective_full_history_ts_low));
|
||||
|
||||
cutoff_ts.clear();
|
||||
PutFixed64(&cutoff_ts, 2);
|
||||
ASSERT_EQ(cutoff_ts, effective_full_history_ts_low);
|
||||
Close();
|
||||
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
}
|
||||
|
||||
TEST_F(ColumnFamilyRetainUDTTest, NotAllKeysExpiredFlushRescheduled) {
|
||||
std::string cutoff_ts;
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::AfterRetainUDTReschedule:cb", [&](void* /*arg*/) {
|
||||
// Increasing full_history_ts_low so all keys expired after the initial
|
||||
// FlushRequest is rescheduled
|
||||
cutoff_ts.clear();
|
||||
PutFixed64(&cutoff_ts, 3);
|
||||
ASSERT_OK(db_->IncreaseFullHistoryTsLow(handles_[0], cutoff_ts));
|
||||
});
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::BackgroundFlush:CheckFlushRequest:cb", [&](void* arg) {
|
||||
ASSERT_NE(nullptr, arg);
|
||||
auto reschedule_count = *static_cast<int*>(arg);
|
||||
ASSERT_EQ(2, reschedule_count);
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
Open();
|
||||
std::string write_ts;
|
||||
PutFixed64(&write_ts, 1);
|
||||
ASSERT_OK(Put(0, "foo", write_ts, "v1"));
|
||||
PutFixed64(&cutoff_ts, 1);
|
||||
ASSERT_OK(db_->IncreaseFullHistoryTsLow(handles_[0], cutoff_ts));
|
||||
// Not all keys expired, and there is no risk of write stall. Flush is
|
||||
// rescheduled. The actual flush happens after `full_history_ts_low` is
|
||||
// increased to mark all keys expired.
|
||||
ASSERT_OK(Flush(0));
|
||||
|
||||
std::string effective_full_history_ts_low;
|
||||
ASSERT_OK(
|
||||
db_->GetFullHistoryTsLow(handles_[0], &effective_full_history_ts_low));
|
||||
// `full_history_ts_low` stays unchanged.
|
||||
ASSERT_EQ(cutoff_ts, effective_full_history_ts_low);
|
||||
Close();
|
||||
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
@@ -66,7 +66,6 @@ TEST_F(CompactFilesTest, L0ConflictsFiles) {
|
||||
const int kWriteBufferSize = 10000;
|
||||
const int kLevel0Trigger = 2;
|
||||
options.create_if_missing = true;
|
||||
options.level_compaction_dynamic_level_bytes = false;
|
||||
options.compaction_style = kCompactionStyleLevel;
|
||||
// Small slowdown and stop trigger for experimental purpose.
|
||||
options.level0_slowdown_writes_trigger = 20;
|
||||
@@ -121,9 +120,7 @@ TEST_F(CompactFilesTest, L0ConflictsFiles) {
|
||||
TEST_F(CompactFilesTest, MultipleLevel) {
|
||||
Options options;
|
||||
options.create_if_missing = true;
|
||||
// Otherwise background compaction can happen to
|
||||
// drain unnecessary level
|
||||
options.level_compaction_dynamic_level_bytes = false;
|
||||
options.level_compaction_dynamic_level_bytes = true;
|
||||
options.num_levels = 6;
|
||||
// Add listener
|
||||
FlushedFileCollector* collector = new FlushedFileCollector();
|
||||
@@ -184,6 +181,7 @@ 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());
|
||||
}
|
||||
|
||||
@@ -345,7 +343,7 @@ TEST_F(CompactFilesTest, CompactionFilterWithGetSv) {
|
||||
return true;
|
||||
}
|
||||
std::string res;
|
||||
EXPECT_TRUE(db_->Get(ReadOptions(), "", &res).IsNotFound());
|
||||
db_->Get(ReadOptions(), "", &res);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -360,7 +358,6 @@ TEST_F(CompactFilesTest, CompactionFilterWithGetSv) {
|
||||
std::shared_ptr<FilterWithGet> cf(new FilterWithGet());
|
||||
|
||||
Options options;
|
||||
options.level_compaction_dynamic_level_bytes = false;
|
||||
options.create_if_missing = true;
|
||||
options.compaction_filter = cf.get();
|
||||
|
||||
@@ -403,7 +400,6 @@ TEST_F(CompactFilesTest, SentinelCompressionType) {
|
||||
CompactionStyle::kCompactionStyleNone}) {
|
||||
ASSERT_OK(DestroyDB(db_name_, Options()));
|
||||
Options options;
|
||||
options.level_compaction_dynamic_level_bytes = false;
|
||||
options.compaction_style = compaction_style;
|
||||
// L0: Snappy, L1: ZSTD, L2: Snappy
|
||||
options.compression_per_level = {CompressionType::kSnappyCompression,
|
||||
|
||||
+23
-98
@@ -13,7 +13,6 @@
|
||||
#include <vector>
|
||||
|
||||
#include "db/column_family.h"
|
||||
#include "logging/logging.h"
|
||||
#include "rocksdb/compaction_filter.h"
|
||||
#include "rocksdb/sst_partitioner.h"
|
||||
#include "test_util/sync_point.h"
|
||||
@@ -24,13 +23,14 @@ namespace ROCKSDB_NAMESPACE {
|
||||
const uint64_t kRangeTombstoneSentinel =
|
||||
PackSequenceAndType(kMaxSequenceNumber, kTypeRangeDeletion);
|
||||
|
||||
int sstableKeyCompare(const Comparator* uc, const Slice& a, const Slice& b) {
|
||||
auto c = uc->CompareWithoutTimestamp(ExtractUserKey(a), ExtractUserKey(b));
|
||||
int sstableKeyCompare(const Comparator* user_cmp, const InternalKey& a,
|
||||
const InternalKey& b) {
|
||||
auto c = user_cmp->CompareWithoutTimestamp(a.user_key(), b.user_key());
|
||||
if (c != 0) {
|
||||
return c;
|
||||
}
|
||||
auto a_footer = ExtractInternalKeyFooter(a);
|
||||
auto b_footer = ExtractInternalKeyFooter(b);
|
||||
auto a_footer = ExtractInternalKeyFooter(a.Encode());
|
||||
auto b_footer = ExtractInternalKeyFooter(b.Encode());
|
||||
if (a_footer == kRangeTombstoneSentinel) {
|
||||
if (b_footer != kRangeTombstoneSentinel) {
|
||||
return -1;
|
||||
@@ -204,34 +204,6 @@ bool Compaction::IsFullCompaction(
|
||||
return num_files_in_compaction == total_num_files;
|
||||
}
|
||||
|
||||
const TablePropertiesCollection& Compaction::GetTableProperties() {
|
||||
if (!input_table_properties_initialized_) {
|
||||
const ReadOptions read_options(Env::IOActivity::kCompaction);
|
||||
for (size_t i = 0; i < num_input_levels(); ++i) {
|
||||
for (const FileMetaData* fmd : *(this->inputs(i))) {
|
||||
std::shared_ptr<const TableProperties> tp;
|
||||
std::string file_name =
|
||||
TableFileName(immutable_options_.cf_paths, fmd->fd.GetNumber(),
|
||||
fmd->fd.GetPathId());
|
||||
Status s = input_version_->GetTableProperties(read_options, &tp, fmd,
|
||||
&file_name);
|
||||
if (s.ok()) {
|
||||
table_properties_[file_name] = tp;
|
||||
} else {
|
||||
ROCKS_LOG_ERROR(immutable_options_.info_log,
|
||||
"Unable to load table properties for file %" PRIu64
|
||||
" --- %s\n",
|
||||
fmd->fd.GetNumber(), s.ToString().c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
input_table_properties_initialized_ = true;
|
||||
};
|
||||
|
||||
return table_properties_;
|
||||
}
|
||||
|
||||
Compaction::Compaction(
|
||||
VersionStorageInfo* vstorage, const ImmutableOptions& _immutable_options,
|
||||
const MutableCFOptions& _mutable_cf_options,
|
||||
@@ -493,11 +465,6 @@ 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) &&
|
||||
@@ -514,24 +481,25 @@ bool Compaction::IsTrivialMove() const {
|
||||
|
||||
// assert inputs_.size() == 1
|
||||
|
||||
if (output_level_ + 1 < number_levels_) {
|
||||
std::unique_ptr<SstPartitioner> partitioner = CreateSstPartitioner();
|
||||
for (const auto& file : inputs_.front().files) {
|
||||
std::vector<FileMetaData*> file_grand_parents;
|
||||
input_vstorage_->GetOverlappingInputs(output_level_ + 1, &file->smallest,
|
||||
&file->largest,
|
||||
&file_grand_parents);
|
||||
const auto compaction_size =
|
||||
file->fd.GetFileSize() + TotalFileSize(file_grand_parents);
|
||||
if (compaction_size > max_compaction_bytes_) {
|
||||
return false;
|
||||
}
|
||||
std::unique_ptr<SstPartitioner> partitioner = CreateSstPartitioner();
|
||||
|
||||
if (partitioner.get() != nullptr) {
|
||||
if (!partitioner->CanDoTrivialMove(file->smallest.user_key(),
|
||||
file->largest.user_key())) {
|
||||
return false;
|
||||
}
|
||||
for (const auto& file : inputs_.front().files) {
|
||||
std::vector<FileMetaData*> file_grand_parents;
|
||||
if (output_level_ + 1 >= number_levels_) {
|
||||
continue;
|
||||
}
|
||||
input_vstorage_->GetOverlappingInputs(output_level_ + 1, &file->smallest,
|
||||
&file->largest, &file_grand_parents);
|
||||
const auto compaction_size =
|
||||
file->fd.GetFileSize() + TotalFileSize(file_grand_parents);
|
||||
if (compaction_size > max_compaction_bytes_) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (partitioner.get() != nullptr) {
|
||||
if (!partitioner->CanDoTrivialMove(file->smallest.user_key(),
|
||||
file->largest.user_key())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -590,49 +558,6 @@ bool Compaction::KeyNotExistsBeyondOutputLevel(
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Compaction::KeyRangeNotExistsBeyondOutputLevel(
|
||||
const Slice& begin_key, const Slice& end_key,
|
||||
std::vector<size_t>* level_ptrs) const {
|
||||
assert(input_version_ != nullptr);
|
||||
assert(level_ptrs != nullptr);
|
||||
assert(level_ptrs->size() == static_cast<size_t>(number_levels_));
|
||||
assert(cfd_->user_comparator()->CompareWithoutTimestamp(begin_key, end_key) <
|
||||
0);
|
||||
if (bottommost_level_) {
|
||||
return true /* does not overlap */;
|
||||
} else if (output_level_ != 0 &&
|
||||
cfd_->ioptions()->compaction_style == kCompactionStyleLevel) {
|
||||
const Comparator* user_cmp = cfd_->user_comparator();
|
||||
for (int lvl = output_level_ + 1; lvl < number_levels_; lvl++) {
|
||||
const std::vector<FileMetaData*>& files =
|
||||
input_vstorage_->LevelFiles(lvl);
|
||||
for (; level_ptrs->at(lvl) < files.size(); level_ptrs->at(lvl)++) {
|
||||
auto* f = files[level_ptrs->at(lvl)];
|
||||
// Advance until the first file with begin_key <= f->largest.user_key()
|
||||
if (user_cmp->CompareWithoutTimestamp(begin_key,
|
||||
f->largest.user_key()) > 0) {
|
||||
continue;
|
||||
}
|
||||
// We know that the previous file prev_f, if exists, has
|
||||
// prev_f->largest.user_key() < begin_key.
|
||||
if (user_cmp->CompareWithoutTimestamp(end_key,
|
||||
f->smallest.user_key()) <= 0) {
|
||||
// not overlapping with this level
|
||||
break;
|
||||
} else {
|
||||
// We have:
|
||||
// - begin_key < end_key,
|
||||
// - begin_key <= f->largest.user_key(), and
|
||||
// - end_key > f->smallest.user_key()
|
||||
return false /* overlap */;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true /* does not overlap */;
|
||||
}
|
||||
return false /* overlaps */;
|
||||
};
|
||||
|
||||
// Mark (or clear) each file that is being compacted
|
||||
void Compaction::MarkFilesBeingCompacted(bool mark_as_compacted) {
|
||||
for (size_t i = 0; i < num_input_levels(); i++) {
|
||||
|
||||
+14
-41
@@ -31,19 +31,8 @@ namespace ROCKSDB_NAMESPACE {
|
||||
// that key never appears in the database. We don't want adjacent sstables to
|
||||
// be considered overlapping if they are separated by the range tombstone
|
||||
// sentinel.
|
||||
int sstableKeyCompare(const Comparator* user_cmp, const Slice&, const Slice&);
|
||||
inline int sstableKeyCompare(const Comparator* user_cmp, const Slice& a,
|
||||
const InternalKey& b) {
|
||||
return sstableKeyCompare(user_cmp, a, b.Encode());
|
||||
}
|
||||
inline int sstableKeyCompare(const Comparator* user_cmp, const InternalKey& a,
|
||||
const Slice& b) {
|
||||
return sstableKeyCompare(user_cmp, a.Encode(), b);
|
||||
}
|
||||
inline int sstableKeyCompare(const Comparator* user_cmp, const InternalKey& a,
|
||||
const InternalKey& b) {
|
||||
return sstableKeyCompare(user_cmp, a.Encode(), b.Encode());
|
||||
}
|
||||
int sstableKeyCompare(const Comparator* user_cmp, const InternalKey& a,
|
||||
const InternalKey& b);
|
||||
int sstableKeyCompare(const Comparator* user_cmp, const InternalKey* a,
|
||||
const InternalKey& b);
|
||||
int sstableKeyCompare(const Comparator* user_cmp, const InternalKey& a,
|
||||
@@ -214,18 +203,10 @@ class Compaction {
|
||||
void AddInputDeletions(VersionEdit* edit);
|
||||
|
||||
// Returns true if the available information we have guarantees that
|
||||
// the input "user_key" does not exist in any level beyond `output_level()`.
|
||||
// the input "user_key" does not exist in any level beyond "output_level()".
|
||||
bool KeyNotExistsBeyondOutputLevel(const Slice& user_key,
|
||||
std::vector<size_t>* level_ptrs) const;
|
||||
|
||||
// Returns true if the user key range [begin_key, end_key) does not exist
|
||||
// in any level beyond `output_level()`.
|
||||
// Used for checking range tombstones, so we assume begin_key < end_key.
|
||||
// begin_key and end_key should include timestamp if enabled.
|
||||
bool KeyRangeNotExistsBeyondOutputLevel(
|
||||
const Slice& begin_key, const Slice& end_key,
|
||||
std::vector<size_t>* level_ptrs) const;
|
||||
|
||||
// Clear all files to indicate that they are not being compacted
|
||||
// Delete this compaction from the list of running compactions.
|
||||
//
|
||||
@@ -326,16 +307,12 @@ class Compaction {
|
||||
int output_level, VersionStorageInfo* vstorage,
|
||||
const std::vector<CompactionInputFiles>& inputs);
|
||||
|
||||
// If called before a compaction finishes, will return
|
||||
// table properties of all compaction input files.
|
||||
// If called after a compaction finished, will return
|
||||
// table properties of all compaction input and output files.
|
||||
const TablePropertiesCollection& GetTableProperties();
|
||||
TablePropertiesCollection GetOutputTableProperties() const {
|
||||
return output_table_properties_;
|
||||
}
|
||||
|
||||
void SetOutputTableProperties(
|
||||
const std::string& file_name,
|
||||
const std::shared_ptr<const TableProperties>& tp) {
|
||||
table_properties_[file_name] = tp;
|
||||
void SetOutputTableProperties(TablePropertiesCollection tp) {
|
||||
output_table_properties_ = std::move(tp);
|
||||
}
|
||||
|
||||
Slice GetSmallestUserKey() const { return smallest_user_key_; }
|
||||
@@ -522,9 +499,8 @@ class Compaction {
|
||||
// Does input compression match the output compression?
|
||||
bool InputCompressionMatchesOutput() const;
|
||||
|
||||
bool input_table_properties_initialized_ = false;
|
||||
// table properties of output files
|
||||
TablePropertiesCollection table_properties_;
|
||||
TablePropertiesCollection output_table_properties_;
|
||||
|
||||
// smallest user keys in compaction
|
||||
// includes timestamp if user-defined timestamp is enabled.
|
||||
@@ -570,16 +546,13 @@ 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,
|
||||
bool& _output_to_penultimate_level)
|
||||
: level(_level),
|
||||
key(_key),
|
||||
value(_value),
|
||||
seq_num(_seq_num),
|
||||
output_to_penultimate_level(_output_to_penultimate_level) {}
|
||||
SequenceNumber _seq_num)
|
||||
: level(_level), key(_key), value(_value), seq_num(_seq_num) {
|
||||
output_to_penultimate_level = false;
|
||||
}
|
||||
};
|
||||
#endif /* !NDEBUG */
|
||||
|
||||
|
||||
@@ -31,8 +31,7 @@ CompactionIterator::CompactionIterator(
|
||||
BlobFileBuilder* blob_file_builder, bool allow_data_in_errors,
|
||||
bool enforce_single_del_contracts,
|
||||
const std::atomic<bool>& manual_compaction_canceled,
|
||||
bool must_count_input_entries, const Compaction* compaction,
|
||||
const CompactionFilter* compaction_filter,
|
||||
const Compaction* compaction, const CompactionFilter* compaction_filter,
|
||||
const std::atomic<bool>* shutting_down,
|
||||
const std::shared_ptr<Logger> info_log,
|
||||
const std::string* full_history_ts_low,
|
||||
@@ -46,9 +45,8 @@ CompactionIterator::CompactionIterator(
|
||||
manual_compaction_canceled,
|
||||
std::unique_ptr<CompactionProxy>(
|
||||
compaction ? new RealCompaction(compaction) : nullptr),
|
||||
must_count_input_entries, compaction_filter, shutting_down, info_log,
|
||||
full_history_ts_low, preserve_time_min_seqno,
|
||||
preclude_last_level_min_seqno) {}
|
||||
compaction_filter, shutting_down, info_log, full_history_ts_low,
|
||||
preserve_time_min_seqno, preclude_last_level_min_seqno) {}
|
||||
|
||||
CompactionIterator::CompactionIterator(
|
||||
InternalIterator* input, const Comparator* cmp, MergeHelper* merge_helper,
|
||||
@@ -60,14 +58,15 @@ CompactionIterator::CompactionIterator(
|
||||
BlobFileBuilder* blob_file_builder, bool allow_data_in_errors,
|
||||
bool enforce_single_del_contracts,
|
||||
const std::atomic<bool>& manual_compaction_canceled,
|
||||
std::unique_ptr<CompactionProxy> compaction, bool must_count_input_entries,
|
||||
std::unique_ptr<CompactionProxy> compaction,
|
||||
const CompactionFilter* compaction_filter,
|
||||
const std::atomic<bool>* shutting_down,
|
||||
const std::shared_ptr<Logger> info_log,
|
||||
const std::string* full_history_ts_low,
|
||||
const SequenceNumber preserve_time_min_seqno,
|
||||
const SequenceNumber preclude_last_level_min_seqno)
|
||||
: input_(input, cmp, must_count_input_entries),
|
||||
: input_(input, cmp,
|
||||
!compaction || compaction->DoesInputReferenceBlobFiles()),
|
||||
cmp_(cmp),
|
||||
merge_helper_(merge_helper),
|
||||
snapshots_(snapshots),
|
||||
@@ -1202,7 +1201,17 @@ 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_ ||
|
||||
@@ -1210,17 +1219,6 @@ 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
|
||||
@@ -1413,7 +1411,6 @@ 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));
|
||||
|
||||
@@ -38,18 +38,15 @@ class SequenceIterWrapper : public InternalIterator {
|
||||
bool Valid() const override { return inner_iter_->Valid(); }
|
||||
Status status() const override { return inner_iter_->status(); }
|
||||
void Next() override {
|
||||
if (!inner_iter_->IsDeleteRangeSentinelKey()) {
|
||||
num_itered_++;
|
||||
}
|
||||
num_itered_++;
|
||||
inner_iter_->Next();
|
||||
}
|
||||
void Seek(const Slice& target) override {
|
||||
if (!need_count_entries_) {
|
||||
has_num_itered_ = false;
|
||||
inner_iter_->Seek(target);
|
||||
} else {
|
||||
// Need to count total number of entries,
|
||||
// so we do Next() rather than Seek().
|
||||
// For flush cases, we need to count total number of entries, so we
|
||||
// do Next() rather than Seek().
|
||||
while (inner_iter_->Valid() &&
|
||||
icmp_.Compare(inner_iter_->key(), target) < 0) {
|
||||
Next();
|
||||
@@ -65,8 +62,7 @@ class SequenceIterWrapper : public InternalIterator {
|
||||
void SeekForPrev(const Slice& /* target */) override { assert(false); }
|
||||
void SeekToLast() override { assert(false); }
|
||||
|
||||
uint64_t NumItered() const { return num_itered_; }
|
||||
bool HasNumItered() const { return has_num_itered_; }
|
||||
uint64_t num_itered() const { return num_itered_; }
|
||||
bool IsDeleteRangeSentinelKey() const override {
|
||||
assert(Valid());
|
||||
return inner_iter_->IsDeleteRangeSentinelKey();
|
||||
@@ -77,7 +73,6 @@ class SequenceIterWrapper : public InternalIterator {
|
||||
InternalIterator* inner_iter_; // not owned
|
||||
uint64_t num_itered_ = 0;
|
||||
bool need_count_entries_;
|
||||
bool has_num_itered_ = true;
|
||||
};
|
||||
|
||||
class CompactionIterator {
|
||||
@@ -194,10 +189,6 @@ class CompactionIterator {
|
||||
const Compaction* compaction_;
|
||||
};
|
||||
|
||||
// @param must_count_input_entries if true, `NumInputEntryScanned()` will
|
||||
// return the number of input keys scanned. If false, `NumInputEntryScanned()`
|
||||
// will return this number if no Seek was called on `input`. User should call
|
||||
// `HasNumInputEntryScanned()` first in this case.
|
||||
CompactionIterator(
|
||||
InternalIterator* input, const Comparator* cmp, MergeHelper* merge_helper,
|
||||
SequenceNumber last_sequence, std::vector<SequenceNumber>* snapshots,
|
||||
@@ -208,7 +199,7 @@ class CompactionIterator {
|
||||
BlobFileBuilder* blob_file_builder, bool allow_data_in_errors,
|
||||
bool enforce_single_del_contracts,
|
||||
const std::atomic<bool>& manual_compaction_canceled,
|
||||
bool must_count_input_entries, const Compaction* compaction = nullptr,
|
||||
const Compaction* compaction = nullptr,
|
||||
const CompactionFilter* compaction_filter = nullptr,
|
||||
const std::atomic<bool>* shutting_down = nullptr,
|
||||
const std::shared_ptr<Logger> info_log = nullptr,
|
||||
@@ -228,7 +219,6 @@ class CompactionIterator {
|
||||
bool enforce_single_del_contracts,
|
||||
const std::atomic<bool>& manual_compaction_canceled,
|
||||
std::unique_ptr<CompactionProxy> compaction,
|
||||
bool must_count_input_entries,
|
||||
const CompactionFilter* compaction_filter = nullptr,
|
||||
const std::atomic<bool>* shutting_down = nullptr,
|
||||
const std::shared_ptr<Logger> info_log = nullptr,
|
||||
@@ -263,8 +253,7 @@ class CompactionIterator {
|
||||
return current_user_key_;
|
||||
}
|
||||
const CompactionIterationStats& iter_stats() const { return iter_stats_; }
|
||||
bool HasNumInputEntryScanned() const { return input_.HasNumItered(); }
|
||||
uint64_t NumInputEntryScanned() const { return input_.NumItered(); }
|
||||
uint64_t num_input_entry_scanned() const { return input_.num_itered(); }
|
||||
// If the current key should be placed on penultimate level, only valid if
|
||||
// per_key_placement is supported
|
||||
bool output_to_penultimate_level() const {
|
||||
|
||||
@@ -293,8 +293,8 @@ class CompactionIteratorTest : public testing::TestWithParam<bool> {
|
||||
nullptr /* blob_file_builder */, true /*allow_data_in_errors*/,
|
||||
true /*enforce_single_del_contracts*/,
|
||||
/*manual_compaction_canceled=*/kManualCompactionCanceledFalse_,
|
||||
std::move(compaction), /*must_count_input_entries=*/false, filter,
|
||||
&shutting_down_, /*info_log=*/nullptr, full_history_ts_low));
|
||||
std::move(compaction), filter, &shutting_down_, /*info_log=*/nullptr,
|
||||
full_history_ts_low));
|
||||
}
|
||||
|
||||
void AddSnapshot(SequenceNumber snapshot,
|
||||
|
||||
+60
-128
@@ -192,8 +192,8 @@ CompactionJob::CompactionJob(
|
||||
assert(log_buffer_ != nullptr);
|
||||
|
||||
const auto* cfd = compact_->compaction->column_family_data();
|
||||
ThreadStatusUtil::SetEnableTracking(db_options_.enable_thread_tracking);
|
||||
ThreadStatusUtil::SetColumnFamily(cfd);
|
||||
ThreadStatusUtil::SetColumnFamily(cfd, cfd->ioptions()->env,
|
||||
db_options_.enable_thread_tracking);
|
||||
ThreadStatusUtil::SetThreadOperation(ThreadStatus::OP_COMPACTION);
|
||||
ReportStartedCompaction(compaction);
|
||||
}
|
||||
@@ -204,6 +204,10 @@ 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_);
|
||||
|
||||
@@ -260,7 +264,7 @@ void CompactionJob::Prepare() {
|
||||
StopWatch sw(db_options_.clock, stats_, SUBCOMPACTION_SETUP_TIME);
|
||||
GenSubcompactionBoundaries();
|
||||
}
|
||||
if (boundaries_.size() >= 1) {
|
||||
if (boundaries_.size() > 1) {
|
||||
for (size_t i = 0; i <= boundaries_.size(); i++) {
|
||||
compact_->sub_compact_states.emplace_back(
|
||||
c, (i != 0) ? std::optional<Slice>(boundaries_[i - 1]) : std::nullopt,
|
||||
@@ -287,14 +291,12 @@ 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(read_options, &tp, fmd, nullptr);
|
||||
Status s = cfd->current()->GetTableProperties(&tp, fmd, nullptr);
|
||||
if (s.ok()) {
|
||||
seqno_time_mapping_.Add(tp->seqno_to_time_mapping)
|
||||
.PermitUncheckedError();
|
||||
@@ -470,7 +472,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 &&
|
||||
@@ -504,9 +506,7 @@ void CompactionJob::GenSubcompactionBoundaries() {
|
||||
FileMetaData* f = flevel->files[i].file_metadata;
|
||||
std::vector<TableReader::Anchor> my_anchors;
|
||||
Status s = cfd->table_cache()->ApproximateKeyAnchors(
|
||||
read_options, icomp, *f,
|
||||
c->mutable_cf_options()->block_protection_bytes_per_key,
|
||||
my_anchors);
|
||||
ReadOptions(), icomp, *f, my_anchors);
|
||||
if (!s.ok() || my_anchors.empty()) {
|
||||
my_anchors.emplace_back(f->largest.user_key(), f->fd.GetFileSize());
|
||||
}
|
||||
@@ -722,12 +722,11 @@ 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
|
||||
const ReadOptions verify_table_read_options(
|
||||
Env::IOActivity::kCompaction);
|
||||
ReadOptions read_options;
|
||||
InternalIterator* iter = cfd->table_cache()->NewIterator(
|
||||
verify_table_read_options, file_options_,
|
||||
cfd->internal_comparator(), files_output[file_idx]->meta,
|
||||
/*range_del_agg=*/nullptr, prefix_extractor,
|
||||
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()),
|
||||
@@ -737,9 +736,7 @@ Status CompactionJob::Run() {
|
||||
*compact_->compaction->mutable_cf_options()),
|
||||
/*smallest_compaction_key=*/nullptr,
|
||||
/*largest_compaction_key=*/nullptr,
|
||||
/*allow_unprepared_value=*/false,
|
||||
compact_->compaction->mutable_cf_options()
|
||||
->block_protection_bytes_per_key);
|
||||
/*allow_unprepared_value=*/false);
|
||||
auto s = iter->status();
|
||||
|
||||
if (s.ok() && paranoid_file_checks_) {
|
||||
@@ -796,51 +793,20 @@ Status CompactionJob::Run() {
|
||||
auto fn =
|
||||
TableFileName(state.compaction->immutable_options()->cf_paths,
|
||||
output.meta.fd.GetNumber(), output.meta.fd.GetPathId());
|
||||
compact_->compaction->SetOutputTableProperties(fn,
|
||||
output.table_properties);
|
||||
tp[fn] = output.table_properties;
|
||||
}
|
||||
}
|
||||
compact_->compaction->SetOutputTableProperties(std::move(tp));
|
||||
|
||||
// Finish up all bookkeeping to unify the subcompaction results.
|
||||
// Finish up all book-keeping to unify the subcompaction results
|
||||
compact_->AggregateCompactionStats(compaction_stats_, *compaction_job_stats_);
|
||||
uint64_t num_input_range_del = 0;
|
||||
bool ok = UpdateCompactionStats(&num_input_range_del);
|
||||
// (Sub)compactions returned ok, do sanity check on the number of input keys.
|
||||
if (status.ok() && ok && compaction_job_stats_->has_num_input_records) {
|
||||
size_t ts_sz = compact_->compaction->column_family_data()
|
||||
->user_comparator()
|
||||
->timestamp_size();
|
||||
// When trim_ts_ is non-empty, CompactionIterator takes
|
||||
// HistoryTrimmingIterator as input iterator and sees a trimmed view of
|
||||
// input keys. So the number of keys it processed is not suitable for
|
||||
// verification here.
|
||||
// TODO: support verification when trim_ts_ is non-empty.
|
||||
if (!(ts_sz > 0 && !trim_ts_.empty()) &&
|
||||
db_options_.compaction_verify_record_count) {
|
||||
assert(compaction_stats_.stats.num_input_records > 0);
|
||||
// TODO: verify the number of range deletion entries.
|
||||
uint64_t expected =
|
||||
compaction_stats_.stats.num_input_records - num_input_range_del;
|
||||
uint64_t actual = compaction_job_stats_->num_input_records;
|
||||
if (expected != actual) {
|
||||
std::string msg =
|
||||
"Total number of input records: " + std::to_string(expected) +
|
||||
", but processed " + std::to_string(actual) + " records.";
|
||||
ROCKS_LOG_WARN(
|
||||
db_options_.info_log, "[%s] [JOB %d] Compaction %s",
|
||||
compact_->compaction->column_family_data()->GetName().c_str(),
|
||||
job_context_->job_id, msg.c_str());
|
||||
status = Status::Corruption(
|
||||
"Compaction number of input keys does not match number of keys "
|
||||
"processed.");
|
||||
}
|
||||
}
|
||||
}
|
||||
UpdateCompactionStats();
|
||||
|
||||
RecordCompactionIOStats();
|
||||
LogFlush(db_options_.info_log);
|
||||
TEST_SYNC_POINT("CompactionJob::Run():End");
|
||||
|
||||
compact_->status = status;
|
||||
TEST_SYNC_POINT_CALLBACK("CompactionJob::Run():EndStatusSet", &status);
|
||||
return status;
|
||||
}
|
||||
|
||||
@@ -1066,6 +1032,7 @@ 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);
|
||||
@@ -1116,7 +1083,6 @@ 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,
|
||||
@@ -1127,17 +1093,17 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
|
||||
// GenSubcompactionBoundaries doesn't strip away the timestamp.
|
||||
size_t ts_sz = cfd->user_comparator()->timestamp_size();
|
||||
if (start.has_value()) {
|
||||
read_options.iterate_lower_bound = &(*start);
|
||||
read_options.iterate_lower_bound = &start.value();
|
||||
if (ts_sz > 0) {
|
||||
start_without_ts = StripTimestampFromUserKey(*start, ts_sz);
|
||||
read_options.iterate_lower_bound = &(*start_without_ts);
|
||||
start_without_ts = StripTimestampFromUserKey(start.value(), ts_sz);
|
||||
read_options.iterate_lower_bound = &start_without_ts.value();
|
||||
}
|
||||
}
|
||||
if (end.has_value()) {
|
||||
read_options.iterate_upper_bound = &(*end);
|
||||
read_options.iterate_upper_bound = &end.value();
|
||||
if (ts_sz > 0) {
|
||||
end_without_ts = StripTimestampFromUserKey(*end, ts_sz);
|
||||
read_options.iterate_upper_bound = &(*end_without_ts);
|
||||
end_without_ts = StripTimestampFromUserKey(end.value(), ts_sz);
|
||||
read_options.iterate_upper_bound = &end_without_ts.value();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1169,7 +1135,8 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
|
||||
}
|
||||
|
||||
if (start.has_value()) {
|
||||
start_ikey.SetInternalKey(*start, kMaxSequenceNumber, kValueTypeForSeek);
|
||||
start_ikey.SetInternalKey(start.value(), kMaxSequenceNumber,
|
||||
kValueTypeForSeek);
|
||||
if (ts_sz > 0) {
|
||||
start_ikey.UpdateInternalKey(kMaxSequenceNumber, kValueTypeForSeek,
|
||||
&ts_slice);
|
||||
@@ -1178,7 +1145,7 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
|
||||
start_user_key = start_ikey.GetUserKey();
|
||||
}
|
||||
if (end.has_value()) {
|
||||
end_ikey.SetInternalKey(*end, kMaxSequenceNumber, kValueTypeForSeek);
|
||||
end_ikey.SetInternalKey(end.value(), kMaxSequenceNumber, kValueTypeForSeek);
|
||||
if (ts_sz > 0) {
|
||||
end_ikey.UpdateInternalKey(kMaxSequenceNumber, kValueTypeForSeek,
|
||||
&ts_slice);
|
||||
@@ -1283,8 +1250,6 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
|
||||
/*expect_valid_internal_key=*/true, range_del_agg.get(),
|
||||
blob_file_builder.get(), db_options_.allow_data_in_errors,
|
||||
db_options_.enforce_single_del_contracts, manual_compaction_canceled_,
|
||||
sub_compact->compaction
|
||||
->DoesInputReferenceBlobFiles() /* must_count_input_entries */,
|
||||
sub_compact->compaction, compaction_filter, shutting_down_,
|
||||
db_options_.info_log, full_history_ts_low, preserve_time_min_seqno_,
|
||||
preclude_last_level_min_seqno_);
|
||||
@@ -1321,8 +1286,8 @@ 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) < 0);
|
||||
assert(!end.has_value() || cfd->user_comparator()->Compare(
|
||||
c_iter->user_key(), end.value()) < 0);
|
||||
|
||||
if (c_iter_stats.num_input_records % kRecordStatsEvery ==
|
||||
kRecordStatsEvery - 1) {
|
||||
@@ -1349,25 +1314,8 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
|
||||
if (c_iter->status().IsManualCompactionPaused()) {
|
||||
break;
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
bool stop = false;
|
||||
TEST_SYNC_POINT_CALLBACK("CompactionJob::ProcessKeyValueCompaction()::stop",
|
||||
static_cast<void*>(&stop));
|
||||
if (stop) {
|
||||
break;
|
||||
}
|
||||
#endif // NDEBUG
|
||||
}
|
||||
|
||||
// This number may not be accurate when CompactionIterator was created
|
||||
// with `must_count_input_entries=false`.
|
||||
assert(!sub_compact->compaction->DoesInputReferenceBlobFiles() ||
|
||||
c_iter->HasNumInputEntryScanned());
|
||||
sub_compact->compaction_job_stats.has_num_input_records =
|
||||
c_iter->HasNumInputEntryScanned();
|
||||
sub_compact->compaction_job_stats.num_input_records =
|
||||
c_iter->NumInputEntryScanned();
|
||||
sub_compact->compaction_job_stats.num_blobs_read =
|
||||
c_iter_stats.num_blobs_read;
|
||||
sub_compact->compaction_job_stats.total_blob_bytes_read =
|
||||
@@ -1692,7 +1640,6 @@ Status CompactionJob::InstallCompactionResults(
|
||||
|
||||
db_mutex_->AssertHeld();
|
||||
|
||||
const ReadOptions read_options(Env::IOActivity::kCompaction);
|
||||
auto* compaction = compact_->compaction;
|
||||
assert(compaction);
|
||||
|
||||
@@ -1770,8 +1717,8 @@ Status CompactionJob::InstallCompactionResults(
|
||||
}
|
||||
|
||||
return versions_->LogAndApply(compaction->column_family_data(),
|
||||
mutable_cf_options, read_options, edit,
|
||||
db_mutex_, db_directory_);
|
||||
mutable_cf_options, edit, db_mutex_,
|
||||
db_directory_);
|
||||
}
|
||||
|
||||
void CompactionJob::RecordCompactionIOStats() {
|
||||
@@ -1867,10 +1814,10 @@ Status CompactionJob::OpenCompactionOutputFile(SubcompactionState* sub_compact,
|
||||
uint64_t current_time = static_cast<uint64_t>(temp_current_time);
|
||||
InternalKey tmp_start, tmp_end;
|
||||
if (sub_compact->start.has_value()) {
|
||||
tmp_start.SetMinPossibleForUserKey(*(sub_compact->start));
|
||||
tmp_start.SetMinPossibleForUserKey(sub_compact->start.value());
|
||||
}
|
||||
if (sub_compact->end.has_value()) {
|
||||
tmp_end.SetMinPossibleForUserKey(*(sub_compact->end));
|
||||
tmp_end.SetMinPossibleForUserKey(sub_compact->end.value());
|
||||
}
|
||||
uint64_t oldest_ancester_time =
|
||||
sub_compact->compaction->MinInputFileOldestAncesterTime(
|
||||
@@ -1953,53 +1900,24 @@ void CopyPrefix(const Slice& src, size_t prefix_length, std::string* dst) {
|
||||
}
|
||||
} // namespace
|
||||
|
||||
bool CompactionJob::UpdateCompactionStats(uint64_t* num_input_range_del) {
|
||||
|
||||
void CompactionJob::UpdateCompactionStats() {
|
||||
assert(compact_);
|
||||
|
||||
Compaction* compaction = compact_->compaction;
|
||||
compaction_stats_.stats.num_input_files_in_non_output_levels = 0;
|
||||
compaction_stats_.stats.num_input_files_in_output_level = 0;
|
||||
|
||||
bool has_error = false;
|
||||
const ReadOptions read_options(Env::IOActivity::kCompaction);
|
||||
const auto& input_table_properties = compaction->GetTableProperties();
|
||||
for (int input_level = 0;
|
||||
input_level < static_cast<int>(compaction->num_input_levels());
|
||||
++input_level) {
|
||||
size_t num_input_files = compaction->num_input_files(input_level);
|
||||
uint64_t* bytes_read;
|
||||
if (compaction->level(input_level) != compaction->output_level()) {
|
||||
compaction_stats_.stats.num_input_files_in_non_output_levels +=
|
||||
static_cast<int>(num_input_files);
|
||||
bytes_read = &compaction_stats_.stats.bytes_read_non_output_levels;
|
||||
UpdateCompactionInputStatsHelper(
|
||||
&compaction_stats_.stats.num_input_files_in_non_output_levels,
|
||||
&compaction_stats_.stats.bytes_read_non_output_levels, input_level);
|
||||
} else {
|
||||
compaction_stats_.stats.num_input_files_in_output_level +=
|
||||
static_cast<int>(num_input_files);
|
||||
bytes_read = &compaction_stats_.stats.bytes_read_output_level;
|
||||
}
|
||||
for (size_t i = 0; i < num_input_files; ++i) {
|
||||
const FileMetaData* file_meta = compaction->input(input_level, i);
|
||||
*bytes_read += file_meta->fd.GetFileSize();
|
||||
uint64_t file_input_entries = file_meta->num_entries;
|
||||
uint64_t file_num_range_del = file_meta->num_range_deletions;
|
||||
if (file_input_entries == 0) {
|
||||
uint64_t file_number = file_meta->fd.GetNumber();
|
||||
// Try getting info from table property
|
||||
std::string fn =
|
||||
TableFileName(compaction->immutable_options()->cf_paths,
|
||||
file_number, file_meta->fd.GetPathId());
|
||||
const auto& tp = input_table_properties.find(fn);
|
||||
if (tp != input_table_properties.end()) {
|
||||
file_input_entries = tp->second->num_entries;
|
||||
file_num_range_del = tp->second->num_range_deletions;
|
||||
} else {
|
||||
has_error = true;
|
||||
}
|
||||
}
|
||||
compaction_stats_.stats.num_input_records += file_input_entries;
|
||||
if (num_input_range_del) {
|
||||
*num_input_range_del += file_num_range_del;
|
||||
}
|
||||
UpdateCompactionInputStatsHelper(
|
||||
&compaction_stats_.stats.num_input_files_in_output_level,
|
||||
&compaction_stats_.stats.bytes_read_output_level, input_level);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2009,7 +1927,21 @@ bool CompactionJob::UpdateCompactionStats(uint64_t* num_input_range_del) {
|
||||
|
||||
compaction_stats_.stats.num_dropped_records =
|
||||
compaction_stats_.DroppedRecords();
|
||||
return !has_error;
|
||||
}
|
||||
|
||||
void CompactionJob::UpdateCompactionInputStatsHelper(int* num_files,
|
||||
uint64_t* bytes_read,
|
||||
int input_level) {
|
||||
const Compaction* compaction = compact_->compaction;
|
||||
auto num_input_files = compaction->num_input_files(input_level);
|
||||
*num_files += static_cast<int>(num_input_files);
|
||||
|
||||
for (size_t i = 0; i < num_input_files; ++i) {
|
||||
const auto* file_meta = compaction->input(input_level, i);
|
||||
*bytes_read += file_meta->fd.GetFileSize();
|
||||
compaction_stats_.stats.num_input_records +=
|
||||
static_cast<uint64_t>(file_meta->num_entries);
|
||||
}
|
||||
}
|
||||
|
||||
void CompactionJob::UpdateCompactionJobStats(
|
||||
|
||||
@@ -192,21 +192,7 @@ class CompactionJob {
|
||||
IOStatus io_status() const { return io_status_; }
|
||||
|
||||
protected:
|
||||
// Update the following stats in compaction_stats_.stats
|
||||
// - num_input_files_in_non_output_levels
|
||||
// - num_input_files_in_output_level
|
||||
// - bytes_read_non_output_levels
|
||||
// - bytes_read_output_level
|
||||
// - num_input_records
|
||||
// - bytes_read_blob
|
||||
// - num_dropped_records
|
||||
//
|
||||
// @param num_input_range_del if non-null, will be set to the number of range
|
||||
// deletion entries in this compaction input.
|
||||
//
|
||||
// Returns true iff compaction_stats_.stats.num_input_records and
|
||||
// num_input_range_del are calculated successfully.
|
||||
bool UpdateCompactionStats(uint64_t* num_input_range_del = nullptr);
|
||||
void UpdateCompactionStats();
|
||||
void LogCompaction();
|
||||
virtual void RecordCompactionIOStats();
|
||||
void CleanupCompaction();
|
||||
@@ -281,6 +267,9 @@ class CompactionJob {
|
||||
void RecordDroppedKeys(const CompactionIterationStats& c_iter_stats,
|
||||
CompactionJobStats* compaction_job_stats = nullptr);
|
||||
|
||||
void UpdateCompactionInputStatsHelper(int* num_files, uint64_t* bytes_read,
|
||||
int input_level);
|
||||
|
||||
void NotifyOnSubcompactionBegin(SubcompactionState* sub_compact);
|
||||
|
||||
void NotifyOnSubcompactionCompleted(SubcompactionState* sub_compact);
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
#include "db/write_batch_internal.h"
|
||||
#include "env/mock_env.h"
|
||||
#include "file/filename.h"
|
||||
#include "monitoring/statistics_impl.h"
|
||||
#include "monitoring/statistics.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_impl.h"
|
||||
#include "util/rate_limiter.h"
|
||||
#include "util/string_util.h"
|
||||
#include "utilities/merge_operators.h"
|
||||
|
||||
@@ -616,7 +616,6 @@ TEST_P(CompactionJobStatsTest, CompactionJobStatsTest) {
|
||||
// via AddExpectedStats().
|
||||
auto* stats_checker = new CompactionJobStatsChecker();
|
||||
Options options;
|
||||
options.level_compaction_dynamic_level_bytes = false;
|
||||
options.listeners.emplace_back(stats_checker);
|
||||
options.create_if_missing = true;
|
||||
// just enough setting to hold off auto-compaction.
|
||||
@@ -816,7 +815,6 @@ TEST_P(CompactionJobStatsTest, DeletionStatsTest) {
|
||||
// what we expect.
|
||||
auto* stats_checker = new CompactionJobDeletionStatsChecker();
|
||||
Options options;
|
||||
options.level_compaction_dynamic_level_bytes = false;
|
||||
options.listeners.emplace_back(stats_checker);
|
||||
options.create_if_missing = true;
|
||||
options.level0_file_num_compaction_trigger = kTestScale + 1;
|
||||
|
||||
@@ -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), contents));
|
||||
env_, GenerateFileName(file_number), std::move(contents)));
|
||||
} else {
|
||||
assert(false);
|
||||
}
|
||||
@@ -386,13 +386,12 @@ class CompactionJobTestBase : public testing::Test {
|
||||
kUnknownFileCreationTime,
|
||||
versions_->GetColumnFamilySet()->GetDefault()->NewEpochNumber(),
|
||||
kUnknownFileChecksum, kUnknownFileChecksumFuncName, kNullUniqueId64x2,
|
||||
/*compensated_range_deletion_size=*/0, /*tail_size=*/0,
|
||||
/*user_defined_timestamps_persisted=*/true);
|
||||
0);
|
||||
|
||||
mutex_.Lock();
|
||||
EXPECT_OK(versions_->LogAndApply(
|
||||
versions_->GetColumnFamilySet()->GetDefault(), mutable_cf_options_,
|
||||
read_options_, &edit, &mutex_, nullptr));
|
||||
EXPECT_OK(
|
||||
versions_->LogAndApply(versions_->GetColumnFamilySet()->GetDefault(),
|
||||
mutable_cf_options_, &edit, &mutex_, nullptr));
|
||||
mutex_.Unlock();
|
||||
}
|
||||
|
||||
@@ -455,8 +454,7 @@ class CompactionJobTestBase : public testing::Test {
|
||||
Status s = cf_options_.table_factory->NewTableReader(
|
||||
read_opts,
|
||||
TableReaderOptions(*cfd->ioptions(), nullptr, FileOptions(),
|
||||
cfd_->internal_comparator(),
|
||||
0 /* block_protection_bytes_per_key */),
|
||||
cfd_->internal_comparator()),
|
||||
std::move(freader), file_size, &table_reader, false);
|
||||
ASSERT_OK(s);
|
||||
assert(table_reader);
|
||||
@@ -655,12 +653,11 @@ class CompactionJobTestBase : public testing::Test {
|
||||
ASSERT_TRUE(full_history_ts_low_.empty() ||
|
||||
ucmp_->timestamp_size() == full_history_ts_low_.size());
|
||||
const std::atomic<bool> kManualCompactionCanceledFalse{false};
|
||||
JobContext job_context(1, false /* create_superversion */);
|
||||
CompactionJob compaction_job(
|
||||
0, &compaction, db_options_, mutable_db_options_, env_options_,
|
||||
versions_.get(), &shutting_down_, &log_buffer, nullptr, nullptr,
|
||||
nullptr, nullptr, &mutex_, &error_handler_, snapshots,
|
||||
earliest_write_conflict_snapshot, snapshot_checker, &job_context,
|
||||
earliest_write_conflict_snapshot, snapshot_checker, nullptr,
|
||||
table_cache_, &event_logger, false, false, dbname_,
|
||||
&compaction_job_stats_, Env::Priority::USER, nullptr /* IOTracer */,
|
||||
/*manual_compaction_canceled=*/kManualCompactionCanceledFalse,
|
||||
@@ -730,7 +727,6 @@ 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_;
|
||||
@@ -2444,3 +2440,4 @@ int main(int argc, char** argv) {
|
||||
RegisterCustomObjects(argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
|
||||
@@ -43,10 +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();
|
||||
meta->user_defined_timestamps_persisted = static_cast<bool>(
|
||||
builder_->GetTableProperties().user_defined_timestamps_persisted);
|
||||
}
|
||||
current_output().finished = true;
|
||||
stats_.bytes_written += current_bytes;
|
||||
@@ -127,6 +124,11 @@ size_t CompactionOutputs::UpdateGrandparentBoundaryInfo(
|
||||
if (grandparents.empty()) {
|
||||
return curr_key_boundary_switched_num;
|
||||
}
|
||||
assert(!internal_key.empty());
|
||||
InternalKey ikey;
|
||||
ikey.DecodeFrom(internal_key);
|
||||
assert(ikey.Valid());
|
||||
|
||||
const Comparator* ucmp = compaction_->column_family_data()->user_comparator();
|
||||
|
||||
// Move the grandparent_index_ to the file containing the current user_key.
|
||||
@@ -134,7 +136,7 @@ size_t CompactionOutputs::UpdateGrandparentBoundaryInfo(
|
||||
// index points to the last file containing the key.
|
||||
while (grandparent_index_ < grandparents.size()) {
|
||||
if (being_grandparent_gap_) {
|
||||
if (sstableKeyCompare(ucmp, internal_key,
|
||||
if (sstableKeyCompare(ucmp, ikey,
|
||||
grandparents[grandparent_index_]->smallest) < 0) {
|
||||
break;
|
||||
}
|
||||
@@ -147,13 +149,13 @@ size_t CompactionOutputs::UpdateGrandparentBoundaryInfo(
|
||||
being_grandparent_gap_ = false;
|
||||
} else {
|
||||
int cmp_result = sstableKeyCompare(
|
||||
ucmp, internal_key, grandparents[grandparent_index_]->largest);
|
||||
ucmp, ikey, grandparents[grandparent_index_]->largest);
|
||||
// If it's same key, make sure grandparent_index_ is pointing to the last
|
||||
// one.
|
||||
if (cmp_result < 0 ||
|
||||
(cmp_result == 0 &&
|
||||
(grandparent_index_ == grandparents.size() - 1 ||
|
||||
sstableKeyCompare(ucmp, internal_key,
|
||||
sstableKeyCompare(ucmp, ikey,
|
||||
grandparents[grandparent_index_ + 1]->smallest) <
|
||||
0))) {
|
||||
break;
|
||||
@@ -572,7 +574,6 @@ Status CompactionOutputs::AddRangeDels(
|
||||
auto it = range_del_agg_->NewIterator(lower_bound, upper_bound);
|
||||
Slice last_tombstone_start_user_key{};
|
||||
bool reached_lower_bound = false;
|
||||
const ReadOptions read_options(Env::IOActivity::kCompaction);
|
||||
for (it->SeekToFirst(); it->Valid(); it->Next()) {
|
||||
auto tombstone = it->Tombstone();
|
||||
auto kv = tombstone.Serialize();
|
||||
@@ -595,12 +596,10 @@ Status CompactionOutputs::AddRangeDels(
|
||||
// in any snapshot. trim_ts_ is passed to the constructor for
|
||||
// range_del_agg_, and range_del_agg_ internally drops tombstones above
|
||||
// trim_ts_.
|
||||
bool consider_drop =
|
||||
tombstone.seq_ <= earliest_snapshot &&
|
||||
if (bottommost_level && tombstone.seq_ <= earliest_snapshot &&
|
||||
(ts_sz == 0 ||
|
||||
(!full_history_ts_low.empty() &&
|
||||
ucmp->CompareTimestamp(tombstone.ts_, full_history_ts_low) < 0));
|
||||
if (consider_drop && bottommost_level) {
|
||||
ucmp->CompareTimestamp(tombstone.ts_, full_history_ts_low) < 0))) {
|
||||
// TODO(andrewkr): tombstones that span multiple output files are
|
||||
// counted for each compaction output file, so lots of double
|
||||
// counting.
|
||||
@@ -634,20 +633,6 @@ Status CompactionOutputs::AddRangeDels(
|
||||
icmp.Compare(*upper_bound, tombstone_start.Encode()) < 0) {
|
||||
break;
|
||||
}
|
||||
if (lower_bound &&
|
||||
icmp.Compare(tombstone_start.Encode(), *lower_bound) < 0) {
|
||||
tombstone_start.DecodeFrom(*lower_bound);
|
||||
}
|
||||
if (upper_bound && icmp.Compare(*upper_bound, tombstone_end.Encode()) < 0) {
|
||||
tombstone_end.DecodeFrom(*upper_bound);
|
||||
}
|
||||
if (consider_drop && compaction_->KeyRangeNotExistsBeyondOutputLevel(
|
||||
tombstone_start.user_key(),
|
||||
tombstone_end.user_key(), &level_ptrs_)) {
|
||||
range_del_out_stats.num_range_del_drop_obsolete++;
|
||||
range_del_out_stats.num_record_drop_obsolete++;
|
||||
continue;
|
||||
}
|
||||
// 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:
|
||||
@@ -701,6 +686,13 @@ Status CompactionOutputs::AddRangeDels(
|
||||
|
||||
// Range tombstone is not supported by output validator yet.
|
||||
builder_->Add(kv.first.Encode(), kv.second);
|
||||
if (lower_bound &&
|
||||
icmp.Compare(tombstone_start.Encode(), *lower_bound) < 0) {
|
||||
tombstone_start.DecodeFrom(*lower_bound);
|
||||
}
|
||||
if (upper_bound && icmp.Compare(*upper_bound, tombstone_end.Encode()) < 0) {
|
||||
tombstone_end.DecodeFrom(*upper_bound);
|
||||
}
|
||||
assert(icmp.Compare(tombstone_start, tombstone_end) <= 0);
|
||||
meta.UpdateBoundariesForRange(tombstone_start, tombstone_end,
|
||||
tombstone.seq_, icmp);
|
||||
@@ -721,7 +713,7 @@ Status CompactionOutputs::AddRangeDels(
|
||||
approx_opts.files_size_error_margin = 0.1;
|
||||
auto approximate_covered_size =
|
||||
compaction_->input_version()->version_set()->ApproximateSize(
|
||||
approx_opts, read_options, compaction_->input_version(),
|
||||
approx_opts, compaction_->input_version(),
|
||||
tombstone_start.Encode(), tombstone_end.Encode(),
|
||||
compaction_->output_level() + 1 /* start_level */,
|
||||
-1 /* end_level */, kCompaction);
|
||||
@@ -785,8 +777,6 @@ CompactionOutputs::CompactionOutputs(const Compaction* compaction,
|
||||
if (compaction->output_level() != 0) {
|
||||
FillFilesToCutForTtl();
|
||||
}
|
||||
|
||||
level_ptrs_ = std::vector<size_t>(compaction_->number_levels(), 0);
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
@@ -206,10 +206,10 @@ class CompactionOutputs {
|
||||
// We may only split the output when the cursor is in the range. Split
|
||||
if ((!end.has_value() ||
|
||||
icmp->user_comparator()->Compare(
|
||||
ExtractUserKey(output_split_key->Encode()), *end) < 0) &&
|
||||
(!start.has_value() ||
|
||||
icmp->user_comparator()->Compare(
|
||||
ExtractUserKey(output_split_key->Encode()), *start) > 0)) {
|
||||
ExtractUserKey(output_split_key->Encode()), end.value()) < 0) &&
|
||||
(!start.has_value() || icmp->user_comparator()->Compare(
|
||||
ExtractUserKey(output_split_key->Encode()),
|
||||
start.value()) > 0)) {
|
||||
local_output_split_key_ = output_split_key;
|
||||
}
|
||||
}
|
||||
@@ -356,15 +356,6 @@ class CompactionOutputs {
|
||||
// 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_;
|
||||
|
||||
// Used for calls to compaction->KeyRangeNotExistsBeyondOutputLevel() in
|
||||
// CompactionOutputs::AddRangeDels().
|
||||
// level_ptrs_[i] holds index of the file that was checked during the last
|
||||
// call to compaction->KeyRangeNotExistsBeyondOutputLevel(). This allows
|
||||
// future calls to the function to pick up where it left off, since each
|
||||
// range tombstone added to output file within each subcompaction is in
|
||||
// increasing key range.
|
||||
std::vector<size_t> level_ptrs_;
|
||||
};
|
||||
|
||||
// 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_impl.h"
|
||||
#include "monitoring/statistics.h"
|
||||
#include "test_util/sync_point.h"
|
||||
#include "util/random.h"
|
||||
#include "util/string_util.h"
|
||||
@@ -611,21 +611,23 @@ Compaction* CompactionPicker::CompactRange(
|
||||
// Universal compaction with more than one level always compacts all the
|
||||
// files together to the last level.
|
||||
assert(vstorage->num_levels() > 1);
|
||||
int max_output_level =
|
||||
vstorage->MaxOutputLevel(ioptions_.allow_ingest_behind);
|
||||
// DBImpl::CompactRange() set output level to be the last level
|
||||
assert(output_level == max_output_level);
|
||||
if (ioptions_.allow_ingest_behind) {
|
||||
assert(output_level == vstorage->num_levels() - 2);
|
||||
} else {
|
||||
assert(output_level == vstorage->num_levels() - 1);
|
||||
}
|
||||
// DBImpl::RunManualCompaction will make full range for universal compaction
|
||||
assert(begin == nullptr);
|
||||
assert(end == nullptr);
|
||||
*compaction_end = nullptr;
|
||||
|
||||
int start_level = 0;
|
||||
for (; start_level <= max_output_level &&
|
||||
for (; start_level < vstorage->num_levels() &&
|
||||
vstorage->NumLevelFiles(start_level) == 0;
|
||||
start_level++) {
|
||||
}
|
||||
if (start_level > max_output_level) {
|
||||
if (start_level == vstorage->num_levels()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@@ -635,9 +637,9 @@ Compaction* CompactionPicker::CompactRange(
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::vector<CompactionInputFiles> inputs(max_output_level + 1 -
|
||||
std::vector<CompactionInputFiles> inputs(vstorage->num_levels() -
|
||||
start_level);
|
||||
for (int level = start_level; level <= max_output_level; level++) {
|
||||
for (int level = start_level; level < vstorage->num_levels(); level++) {
|
||||
inputs[level - start_level].level = level;
|
||||
auto& files = inputs[level - start_level].files;
|
||||
for (FileMetaData* f : vstorage->LevelFiles(level)) {
|
||||
@@ -751,10 +753,8 @@ Compaction* CompactionPicker::CompactRange(
|
||||
|
||||
// for BOTTOM LEVEL compaction only, use max_file_num_to_ignore to filter out
|
||||
// files that are created during the current compaction.
|
||||
if ((compact_range_options.bottommost_level_compaction ==
|
||||
BottommostLevelCompaction::kForceOptimized ||
|
||||
compact_range_options.bottommost_level_compaction ==
|
||||
BottommostLevelCompaction::kIfHaveCompactionFilter) &&
|
||||
if (compact_range_options.bottommost_level_compaction ==
|
||||
BottommostLevelCompaction::kForceOptimized &&
|
||||
max_file_num_to_ignore != std::numeric_limits<uint64_t>::max()) {
|
||||
assert(input_level == output_level);
|
||||
// inputs_shrunk holds a continuous subset of input files which were all
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
#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 {
|
||||
@@ -285,36 +284,31 @@ Compaction* FIFOCompactionPicker::PickSizeCompaction(
|
||||
return c;
|
||||
}
|
||||
|
||||
Compaction* FIFOCompactionPicker::PickTemperatureChangeCompaction(
|
||||
Compaction* FIFOCompactionPicker::PickCompactionToWarm(
|
||||
const std::string& cf_name, const MutableCFOptions& mutable_cf_options,
|
||||
const MutableDBOptions& mutable_db_options, VersionStorageInfo* vstorage,
|
||||
LogBuffer* log_buffer) {
|
||||
const std::vector<FileTemperatureAge>& ages =
|
||||
mutable_cf_options.compaction_options_fifo
|
||||
.file_temperature_age_thresholds;
|
||||
if (ages.empty()) {
|
||||
if (mutable_cf_options.compaction_options_fifo.age_for_warm == 0) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Does not apply to multi-level FIFO.
|
||||
if (vstorage->num_levels() > 1) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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 file temperature-age 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 warm threshold. ",
|
||||
cf_name.c_str(), status.ToString().c_str());
|
||||
return nullptr;
|
||||
}
|
||||
const uint64_t current_time = static_cast<uint64_t>(_current_time);
|
||||
@@ -333,77 +327,56 @@ Compaction* FIFOCompactionPicker::PickTemperatureChangeCompaction(
|
||||
inputs[0].level = 0;
|
||||
|
||||
// avoid underflow
|
||||
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;
|
||||
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 compaction_size = 0;
|
||||
// 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.
|
||||
// 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.
|
||||
return nullptr;
|
||||
}
|
||||
uint64_t oldest_ancestor_time = prev_file->TryGetOldestAncesterTime();
|
||||
if (oldest_ancestor_time == kUnknownOldestAncesterTime) {
|
||||
uint64_t oldest_ancester_time = f->TryGetOldestAncesterTime();
|
||||
if (oldest_ancester_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_ancestor_time > create_time_threshold) {
|
||||
// cur_file is too fresh
|
||||
if (oldest_ancester_time > create_time_threshold) {
|
||||
// The previous file (which has slightly older data) doesn't qualify
|
||||
// for warm tier.
|
||||
break;
|
||||
}
|
||||
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 {
|
||||
if (prev_file != nullptr) {
|
||||
compaction_size += prev_file->fd.GetFileSize();
|
||||
if (compaction_size > mutable_cf_options.max_compaction_bytes) {
|
||||
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);
|
||||
}
|
||||
|
||||
// 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) {
|
||||
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.
|
||||
break;
|
||||
} else {
|
||||
assert(prev_file == nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -417,7 +390,7 @@ Compaction* FIFOCompactionPicker::PickTemperatureChangeCompaction(
|
||||
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,
|
||||
compaction_target_temp,
|
||||
Temperature::kWarm,
|
||||
/* max_subcompactions */ 0, {}, /* is manual */ false, /* trim_ts */ "",
|
||||
vstorage->CompactionScore(0),
|
||||
/* is deletion compaction */ false, /* l0_files_might_overlap */ true,
|
||||
@@ -439,8 +412,8 @@ Compaction* FIFOCompactionPicker::PickCompaction(
|
||||
vstorage, log_buffer);
|
||||
}
|
||||
if (c == nullptr) {
|
||||
c = PickTemperatureChangeCompaction(
|
||||
cf_name, mutable_cf_options, mutable_db_options, vstorage, log_buffer);
|
||||
c = PickCompactionToWarm(cf_name, mutable_cf_options, mutable_db_options,
|
||||
vstorage, log_buffer);
|
||||
}
|
||||
RegisterCompaction(c);
|
||||
return c;
|
||||
|
||||
@@ -52,9 +52,10 @@ class FIFOCompactionPicker : public CompactionPicker {
|
||||
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);
|
||||
Compaction* PickCompactionToWarm(const std::string& cf_name,
|
||||
const MutableCFOptions& mutable_cf_options,
|
||||
const MutableDBOptions& mutable_db_options,
|
||||
VersionStorageInfo* version,
|
||||
LogBuffer* log_buffer);
|
||||
};
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
@@ -83,7 +83,7 @@ class LevelCompactionBuilder {
|
||||
|
||||
Compaction* GetCompaction();
|
||||
|
||||
// From `start_level_`, pick files to compact to `output_level_`.
|
||||
// For the specfied level, pick a file that we want to compact.
|
||||
// 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,9 +107,8 @@ class LevelCompactionBuilder {
|
||||
bool PickIntraL0Compaction();
|
||||
|
||||
// Return true if TrivialMove is extended. `start_index` is the index of
|
||||
// the initial file picked, which should already be in `start_level_inputs_`.
|
||||
bool TryExtendNonL0TrivialMove(int start_index,
|
||||
bool only_expand_right = false);
|
||||
// the intiial file picked, which should already be in `start_level_inputs_`.
|
||||
bool TryExtendNonL0TrivialMove(int start_index);
|
||||
|
||||
// Picks a file from level_files to compact.
|
||||
// level_files is a vector of (level, file metadata) in ascending order of
|
||||
@@ -356,8 +355,7 @@ void LevelCompactionBuilder::SetupOtherFilesWithRoundRobinExpansion() {
|
||||
vstorage_->GetOverlappingInputs(output_level_, &smallest, &largest,
|
||||
&output_level_inputs.files);
|
||||
if (output_level_inputs.empty()) {
|
||||
if (TryExtendNonL0TrivialMove((int)start_index,
|
||||
true /* only_expand_right */)) {
|
||||
if (TryExtendNonL0TrivialMove((int)start_index)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -503,16 +501,6 @@ 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_,
|
||||
@@ -527,7 +515,8 @@ 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, compaction_reason_);
|
||||
/* l0_files_might_overlap */ start_level_ == 0 && !is_l0_trivial_move_,
|
||||
compaction_reason_);
|
||||
|
||||
// If it's level 0 compaction, make sure we don't execute any other level 0
|
||||
// compactions in parallel
|
||||
@@ -664,8 +653,7 @@ bool LevelCompactionBuilder::TryPickL0TrivialMove() {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool LevelCompactionBuilder::TryExtendNonL0TrivialMove(int start_index,
|
||||
bool only_expand_right) {
|
||||
bool LevelCompactionBuilder::TryExtendNonL0TrivialMove(int start_index) {
|
||||
if (start_level_inputs_.size() == 1 &&
|
||||
(ioptions_.db_paths.empty() || ioptions_.db_paths.size() == 1) &&
|
||||
(mutable_cf_options_.compression_per_level.empty())) {
|
||||
@@ -682,7 +670,6 @@ 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;
|
||||
@@ -715,37 +702,6 @@ 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;
|
||||
@@ -829,10 +785,7 @@ bool LevelCompactionBuilder::PickFileToCompact() {
|
||||
vstorage_->GetOverlappingInputs(output_level_, &smallest, &largest,
|
||||
&output_level_inputs.files);
|
||||
if (output_level_inputs.empty()) {
|
||||
if (start_level_ > 0 &&
|
||||
TryExtendNonL0TrivialMove(index,
|
||||
ioptions_.compaction_pri ==
|
||||
kRoundRobin /* only_expand_right */)) {
|
||||
if (TryExtendNonL0TrivialMove(index)) {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -70,11 +70,6 @@ class CompactionPickerTestBase : public testing::Test {
|
||||
mutable_cf_options_.RefreshDerivedOptions(ioptions_);
|
||||
ioptions_.cf_paths.emplace_back("dummy",
|
||||
std::numeric_limits<uint64_t>::max());
|
||||
// When the default value of this option is true, universal compaction
|
||||
// tests can encounter assertion failure since SanitizeOption() is
|
||||
// not run to set this option to false. So we do the sanitization
|
||||
// here. Tests that test this option set this option to true explicitly.
|
||||
ioptions_.level_compaction_dynamic_level_bytes = false;
|
||||
}
|
||||
|
||||
~CompactionPickerTestBase() override {}
|
||||
@@ -84,8 +79,7 @@ class CompactionPickerTestBase : public testing::Test {
|
||||
options_.num_levels = num_levels;
|
||||
vstorage_.reset(new VersionStorageInfo(
|
||||
&icmp_, ucmp_, options_.num_levels, style, nullptr, false,
|
||||
EpochNumberRequirement::kMustPresent, ioptions_.clock,
|
||||
options_.bottommost_file_compaction_delay));
|
||||
EpochNumberRequirement::kMustPresent));
|
||||
vstorage_->PrepareForVersionAppend(ioptions_, mutable_cf_options_);
|
||||
}
|
||||
|
||||
@@ -94,8 +88,7 @@ class CompactionPickerTestBase : public testing::Test {
|
||||
void AddVersionStorage() {
|
||||
temp_vstorage_.reset(new VersionStorageInfo(
|
||||
&icmp_, ucmp_, options_.num_levels, ioptions_.compaction_style,
|
||||
vstorage_.get(), false, EpochNumberRequirement::kMustPresent,
|
||||
ioptions_.clock, options_.bottommost_file_compaction_delay));
|
||||
vstorage_.get(), false, EpochNumberRequirement::kMustPresent));
|
||||
}
|
||||
|
||||
void DeleteVersionStorage() {
|
||||
@@ -155,8 +148,7 @@ class CompactionPickerTestBase : public testing::Test {
|
||||
smallest_seq, largest_seq, marked_for_compact, temperature,
|
||||
kInvalidBlobFileNumber, kUnknownOldestAncesterTime,
|
||||
kUnknownFileCreationTime, epoch_number, kUnknownFileChecksum,
|
||||
kUnknownFileChecksumFuncName, kNullUniqueId64x2, 0, 0,
|
||||
true /* user_defined_timestamps_persisted */);
|
||||
kUnknownFileChecksumFuncName, kNullUniqueId64x2, 0);
|
||||
f->compensated_file_size =
|
||||
(compensated_file_size != 0) ? compensated_file_size : file_size;
|
||||
f->oldest_ancester_time = oldest_ancestor_time;
|
||||
@@ -513,7 +505,7 @@ TEST_F(CompactionPickerTest, NeedsCompactionUniversal) {
|
||||
|
||||
TEST_F(CompactionPickerTest, CompactionUniversalIngestBehindReservedLevel) {
|
||||
const uint64_t kFileSize = 100000;
|
||||
NewVersionStorage(3 /* num_levels */, kCompactionStyleUniversal);
|
||||
NewVersionStorage(1, kCompactionStyleUniversal);
|
||||
ioptions_.allow_ingest_behind = true;
|
||||
ioptions_.num_levels = 3;
|
||||
UniversalCompactionPicker universal_compaction_picker(ioptions_, &icmp_);
|
||||
@@ -540,14 +532,6 @@ TEST_F(CompactionPickerTest, CompactionUniversalIngestBehindReservedLevel) {
|
||||
|
||||
// output level should be the one above the bottom-most
|
||||
ASSERT_EQ(1, compaction->output_level());
|
||||
|
||||
// input should not include the reserved level
|
||||
const std::vector<CompactionInputFiles>* inputs = compaction->inputs();
|
||||
for (const auto& compaction_input : *inputs) {
|
||||
if (!compaction_input.empty()) {
|
||||
ASSERT_LT(compaction_input.level, 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Tests if the files can be trivially moved in multi level
|
||||
// universal compaction when allow_trivial_move option is set
|
||||
@@ -1021,28 +1005,29 @@ TEST_F(CompactionPickerTest, NeedsCompactionFIFO) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(CompactionPickerTest, FIFOToCold1) {
|
||||
TEST_F(CompactionPickerTest, FIFOToWarm1) {
|
||||
NewVersionStorage(1, kCompactionStyleFIFO);
|
||||
const uint64_t kFileSize = 100000;
|
||||
const uint64_t kMaxSize = kFileSize * 100000;
|
||||
uint64_t kColdThreshold = 2000;
|
||||
uint64_t kWarmThreshold = 2000;
|
||||
|
||||
fifo_options_.max_table_files_size = kMaxSize;
|
||||
fifo_options_.file_temperature_age_thresholds = {
|
||||
{Temperature::kCold, kColdThreshold}};
|
||||
fifo_options_.age_for_warm = kWarmThreshold;
|
||||
mutable_cf_options_.compaction_options_fifo = fifo_options_;
|
||||
mutable_cf_options_.level0_file_num_compaction_trigger = 100;
|
||||
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) - 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.
|
||||
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::kUnknown, threshold_time - 3000);
|
||||
UpdateVersionStorageInfo();
|
||||
@@ -1052,36 +1037,33 @@ TEST_F(CompactionPickerTest, FIFOToCold1) {
|
||||
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, FIFOToCold2) {
|
||||
TEST_F(CompactionPickerTest, FIFOToWarm2) {
|
||||
NewVersionStorage(1, kCompactionStyleFIFO);
|
||||
const uint64_t kFileSize = 100000;
|
||||
const uint64_t kMaxSize = kFileSize * 100000;
|
||||
uint64_t kColdThreshold = 2000;
|
||||
uint64_t kWarmThreshold = 2000;
|
||||
|
||||
fifo_options_.max_table_files_size = kMaxSize;
|
||||
fifo_options_.file_temperature_age_thresholds = {
|
||||
{Temperature::kCold, kColdThreshold}};
|
||||
fifo_options_.age_for_warm = kWarmThreshold;
|
||||
mutable_cf_options_.compaction_options_fifo = fifo_options_;
|
||||
mutable_cf_options_.level0_file_num_compaction_trigger = 100;
|
||||
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) - kColdThreshold;
|
||||
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);
|
||||
// The following two files qualify for compaction to kCold.
|
||||
Temperature::kUnknown, threshold_time - 2000);
|
||||
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,
|
||||
@@ -1093,40 +1075,34 @@ TEST_F(CompactionPickerTest, FIFOToCold2) {
|
||||
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, FIFOToColdMaxCompactionSize) {
|
||||
TEST_F(CompactionPickerTest, FIFOToWarmMaxSize) {
|
||||
NewVersionStorage(1, kCompactionStyleFIFO);
|
||||
const uint64_t kFileSize = 100000;
|
||||
const uint64_t kMaxSize = kFileSize * 100000;
|
||||
uint64_t kColdThreshold = 2000;
|
||||
uint64_t kWarmThreshold = 2000;
|
||||
|
||||
fifo_options_.max_table_files_size = kMaxSize;
|
||||
fifo_options_.file_temperature_age_thresholds = {
|
||||
{Temperature::kCold, kColdThreshold}};
|
||||
fifo_options_.age_for_warm = kWarmThreshold;
|
||||
mutable_cf_options_.compaction_options_fifo = fifo_options_;
|
||||
mutable_cf_options_.level0_file_num_compaction_trigger = 100;
|
||||
mutable_cf_options_.level0_file_num_compaction_trigger = 2;
|
||||
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) - kColdThreshold;
|
||||
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);
|
||||
// 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,
|
||||
@@ -1140,45 +1116,40 @@ TEST_F(CompactionPickerTest, FIFOToColdMaxCompactionSize) {
|
||||
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, FIFOToColdWithExistingCold) {
|
||||
TEST_F(CompactionPickerTest, FIFOToWarmWithExistingWarm) {
|
||||
NewVersionStorage(1, kCompactionStyleFIFO);
|
||||
const uint64_t kFileSize = 100000;
|
||||
const uint64_t kMaxSize = kFileSize * 100000;
|
||||
uint64_t kColdThreshold = 2000;
|
||||
uint64_t kWarmThreshold = 2000;
|
||||
|
||||
fifo_options_.max_table_files_size = kMaxSize;
|
||||
fifo_options_.file_temperature_age_thresholds = {
|
||||
{Temperature::kCold, kColdThreshold}};
|
||||
fifo_options_.age_for_warm = kWarmThreshold;
|
||||
mutable_cf_options_.compaction_options_fifo = fifo_options_;
|
||||
mutable_cf_options_.level0_file_num_compaction_trigger = 100;
|
||||
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) - kColdThreshold;
|
||||
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);
|
||||
// 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::kCold, threshold_time - 5000);
|
||||
Temperature::kWarm, threshold_time - 5000);
|
||||
UpdateVersionStorageInfo();
|
||||
|
||||
ASSERT_EQ(fifo_compaction_picker.NeedsCompaction(vstorage_.get()), true);
|
||||
@@ -1186,32 +1157,28 @@ TEST_F(CompactionPickerTest, FIFOToColdWithExistingCold) {
|
||||
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->input(0, 0)->fd.GetNumber());
|
||||
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, FIFOToColdWithHotBetweenCold) {
|
||||
TEST_F(CompactionPickerTest, FIFOToWarmWithOngoing) {
|
||||
NewVersionStorage(1, kCompactionStyleFIFO);
|
||||
const uint64_t kFileSize = 100000;
|
||||
const uint64_t kMaxSize = kFileSize * 100000;
|
||||
uint64_t kColdThreshold = 2000;
|
||||
uint64_t kWarmThreshold = 2000;
|
||||
|
||||
fifo_options_.max_table_files_size = kMaxSize;
|
||||
fifo_options_.file_temperature_age_thresholds = {
|
||||
{Temperature::kCold, kColdThreshold}};
|
||||
fifo_options_.age_for_warm = kWarmThreshold;
|
||||
mutable_cf_options_.compaction_options_fifo = fifo_options_;
|
||||
mutable_cf_options_.level0_file_num_compaction_trigger = 100;
|
||||
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) - kColdThreshold;
|
||||
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,
|
||||
@@ -1219,78 +1186,64 @@ TEST_F(CompactionPickerTest, FIFOToColdWithHotBetweenCold) {
|
||||
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::kCold, threshold_time - 3000);
|
||||
// Qualifies for compaction to kCold.
|
||||
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::kCold, threshold_time - 5000);
|
||||
Temperature::kWarm, threshold_time - 5000);
|
||||
file_map_[2].first->being_compacted = true;
|
||||
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);
|
||||
ASSERT_EQ(compaction->output_temperature(), Temperature::kCold);
|
||||
ASSERT_EQ(1U, compaction->num_input_files(0));
|
||||
ASSERT_EQ(2U, compaction->input(0, 0)->fd.GetNumber());
|
||||
// Stop if a file is being compacted
|
||||
ASSERT_TRUE(compaction.get() == nullptr);
|
||||
}
|
||||
|
||||
TEST_F(CompactionPickerTest, FIFOToColdAndWarm) {
|
||||
TEST_F(CompactionPickerTest, FIFOToWarmWithHotBetweenWarms) {
|
||||
NewVersionStorage(1, kCompactionStyleFIFO);
|
||||
const uint64_t kFileSize = 100000;
|
||||
const uint64_t kMaxSize = kFileSize * 100000;
|
||||
uint64_t kWarmThreshold = 10000;
|
||||
uint64_t kHotThreshold = 2000;
|
||||
uint64_t kWarmThreshold = 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}};
|
||||
fifo_options_.age_for_warm = kWarmThreshold;
|
||||
mutable_cf_options_.compaction_options_fifo = fifo_options_;
|
||||
mutable_cf_options_.level0_file_num_compaction_trigger = 100;
|
||||
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 hot_threshold_time =
|
||||
static_cast<uint64_t>(current_time) - kHotThreshold;
|
||||
uint64_t warm_threshold_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, hot_threshold_time + 100);
|
||||
Temperature::kUnknown, threshold_time + 100);
|
||||
Add(0, 4U, "260", "300", 1 * kFileSize, 0, 2500, 2600, 0, true,
|
||||
Temperature::kUnknown, hot_threshold_time - 200);
|
||||
// Qualifies for Hot
|
||||
Temperature::kUnknown, threshold_time - 2000);
|
||||
Add(0, 3U, "200", "300", 4 * kFileSize, 0, 2300, 2400, 0, true,
|
||||
Temperature::kUnknown, warm_threshold_time - 100);
|
||||
// Qualifies for Warm
|
||||
Temperature::kWarm, threshold_time - 3000);
|
||||
Add(0, 2U, "200", "300", 4 * kFileSize, 0, 2100, 2200, 0, true,
|
||||
Temperature::kUnknown, warm_threshold_time - 4000);
|
||||
Temperature::kUnknown, threshold_time - 4000);
|
||||
Add(0, 1U, "200", "300", 4 * kFileSize, 0, 2000, 2100, 0, true,
|
||||
Temperature::kUnknown, warm_threshold_time - 5000);
|
||||
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);
|
||||
// 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());
|
||||
ASSERT_EQ(1U, compaction->num_input_files(0));
|
||||
ASSERT_EQ(2U, compaction->input(0, 0)->fd.GetNumber());
|
||||
}
|
||||
|
||||
|
||||
TEST_F(CompactionPickerTest, CompactionPriMinOverlapping1) {
|
||||
NewVersionStorage(6, kCompactionStyleLevel);
|
||||
ioptions_.compaction_pri = kMinOverlappingRatio;
|
||||
@@ -1970,15 +1923,6 @@ TEST_F(CompactionPickerTest, OverlappingUserKeys11) {
|
||||
ASSERT_EQ(7U, compaction->input(1, 0)->fd.GetNumber());
|
||||
}
|
||||
|
||||
TEST_F(CompactionPickerTest, FileTtlBoosterLargeNumLevels) {
|
||||
const uint64_t kCurrentTime = 1000000;
|
||||
FileTtlBooster booster(kCurrentTime, /*ttl=*/2048,
|
||||
/*num_non_empty_levels=*/100, /*level=*/1);
|
||||
FileMetaData meta;
|
||||
meta.oldest_ancester_time = kCurrentTime - 1023;
|
||||
ASSERT_EQ(1, booster.GetBoostScore(&meta));
|
||||
}
|
||||
|
||||
TEST_F(CompactionPickerTest, FileTtlBooster) {
|
||||
// Set TTL to 2048
|
||||
// TTL boosting for all levels starts at 1024,
|
||||
@@ -2576,61 +2520,6 @@ 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_impl.h"
|
||||
#include "monitoring/statistics.h"
|
||||
#include "test_util/sync_point.h"
|
||||
#include "util/random.h"
|
||||
#include "util/string_util.h"
|
||||
@@ -133,8 +133,8 @@ class UniversalCompactionBuilder {
|
||||
UniversalCompactionPicker* picker_;
|
||||
LogBuffer* log_buffer_;
|
||||
|
||||
static std::vector<UniversalCompactionBuilder::SortedRun> CalculateSortedRuns(
|
||||
const VersionStorageInfo& vstorage, int last_level);
|
||||
static std::vector<SortedRun> CalculateSortedRuns(
|
||||
const VersionStorageInfo& vstorage);
|
||||
|
||||
// Pick a path ID to place a newly generated file, with its estimated file
|
||||
// size.
|
||||
@@ -339,13 +339,13 @@ void UniversalCompactionBuilder::SortedRun::DumpSizeInfo(
|
||||
|
||||
std::vector<UniversalCompactionBuilder::SortedRun>
|
||||
UniversalCompactionBuilder::CalculateSortedRuns(
|
||||
const VersionStorageInfo& vstorage, int last_level) {
|
||||
const VersionStorageInfo& vstorage) {
|
||||
std::vector<UniversalCompactionBuilder::SortedRun> ret;
|
||||
for (FileMetaData* f : vstorage.LevelFiles(0)) {
|
||||
ret.emplace_back(0, f, f->fd.GetFileSize(), f->compensated_file_size,
|
||||
f->being_compacted);
|
||||
}
|
||||
for (int level = 1; level <= last_level; level++) {
|
||||
for (int level = 1; level < vstorage.num_levels(); level++) {
|
||||
uint64_t total_compensated_size = 0U;
|
||||
uint64_t total_size = 0U;
|
||||
bool being_compacted = false;
|
||||
@@ -374,9 +374,7 @@ UniversalCompactionBuilder::CalculateSortedRuns(
|
||||
Compaction* UniversalCompactionBuilder::PickCompaction() {
|
||||
const int kLevel0 = 0;
|
||||
score_ = vstorage_->CompactionScore(kLevel0);
|
||||
int max_output_level =
|
||||
vstorage_->MaxOutputLevel(ioptions_.allow_ingest_behind);
|
||||
sorted_runs_ = CalculateSortedRuns(*vstorage_, max_output_level);
|
||||
sorted_runs_ = CalculateSortedRuns(*vstorage_);
|
||||
|
||||
if (sorted_runs_.size() == 0 ||
|
||||
(vstorage_->FilesMarkedForPeriodicCompaction().empty() &&
|
||||
@@ -473,8 +471,6 @@ Compaction* UniversalCompactionBuilder::PickCompaction() {
|
||||
"UniversalCompactionBuilder::PickCompaction:Return", nullptr);
|
||||
return nullptr;
|
||||
}
|
||||
assert(c->output_level() <=
|
||||
vstorage_->MaxOutputLevel(ioptions_.allow_ingest_behind));
|
||||
|
||||
if (mutable_cf_options_.compaction_options_universal.allow_trivial_move ==
|
||||
true &&
|
||||
@@ -702,18 +698,22 @@ Compaction* UniversalCompactionBuilder::PickCompactionToReduceSortedRuns(
|
||||
GetPathId(ioptions_, mutable_cf_options_, estimated_total_size);
|
||||
int start_level = sorted_runs_[start_index].level;
|
||||
int output_level;
|
||||
// last level is reserved for the files ingested behind
|
||||
int max_output_level =
|
||||
vstorage_->MaxOutputLevel(ioptions_.allow_ingest_behind);
|
||||
if (first_index_after == sorted_runs_.size()) {
|
||||
output_level = max_output_level;
|
||||
output_level = vstorage_->num_levels() - 1;
|
||||
} else if (sorted_runs_[first_index_after].level == 0) {
|
||||
output_level = 0;
|
||||
} else {
|
||||
output_level = sorted_runs_[first_index_after].level - 1;
|
||||
}
|
||||
|
||||
std::vector<CompactionInputFiles> inputs(max_output_level + 1);
|
||||
// last level is reserved for the files ingested behind
|
||||
if (ioptions_.allow_ingest_behind &&
|
||||
(output_level == vstorage_->num_levels() - 1)) {
|
||||
assert(output_level > 1);
|
||||
output_level--;
|
||||
}
|
||||
|
||||
std::vector<CompactionInputFiles> inputs(vstorage_->num_levels());
|
||||
for (size_t i = 0; i < inputs.size(); ++i) {
|
||||
inputs[i].level = start_level + static_cast<int>(i);
|
||||
}
|
||||
@@ -1192,10 +1192,8 @@ Compaction* UniversalCompactionBuilder::PickDeleteTriggeredCompaction() {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int max_output_level =
|
||||
vstorage_->MaxOutputLevel(ioptions_.allow_ingest_behind);
|
||||
// Pick the first non-empty level after the start_level
|
||||
for (output_level = start_level + 1; output_level <= max_output_level;
|
||||
for (output_level = start_level + 1; output_level < vstorage_->num_levels();
|
||||
output_level++) {
|
||||
if (vstorage_->NumLevelFiles(output_level) != 0) {
|
||||
break;
|
||||
@@ -1203,9 +1201,9 @@ Compaction* UniversalCompactionBuilder::PickDeleteTriggeredCompaction() {
|
||||
}
|
||||
|
||||
// If all higher levels are empty, pick the highest level as output level
|
||||
if (output_level > max_output_level) {
|
||||
if (output_level == vstorage_->num_levels()) {
|
||||
if (start_level == 0) {
|
||||
output_level = max_output_level;
|
||||
output_level = vstorage_->num_levels() - 1;
|
||||
} else {
|
||||
// If start level is non-zero and all higher levels are empty, this
|
||||
// compaction will translate into a trivial move. Since the idea is
|
||||
@@ -1214,7 +1212,11 @@ Compaction* UniversalCompactionBuilder::PickDeleteTriggeredCompaction() {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
assert(output_level <= max_output_level);
|
||||
if (ioptions_.allow_ingest_behind &&
|
||||
output_level == vstorage_->num_levels() - 1) {
|
||||
assert(output_level > 1);
|
||||
output_level--;
|
||||
}
|
||||
|
||||
if (output_level != 0) {
|
||||
if (start_level == 0) {
|
||||
@@ -1291,9 +1293,8 @@ Compaction* UniversalCompactionBuilder::PickCompactionWithSortedRunRange(
|
||||
uint32_t path_id =
|
||||
GetPathId(ioptions_, mutable_cf_options_, estimated_total_size);
|
||||
int start_level = sorted_runs_[start_index].level;
|
||||
int max_output_level =
|
||||
vstorage_->MaxOutputLevel(ioptions_.allow_ingest_behind);
|
||||
std::vector<CompactionInputFiles> inputs(max_output_level + 1);
|
||||
|
||||
std::vector<CompactionInputFiles> inputs(vstorage_->num_levels());
|
||||
for (size_t i = 0; i < inputs.size(); ++i) {
|
||||
inputs[i].level = start_level + static_cast<int>(i);
|
||||
}
|
||||
@@ -1330,7 +1331,13 @@ Compaction* UniversalCompactionBuilder::PickCompactionWithSortedRunRange(
|
||||
|
||||
int output_level;
|
||||
if (end_index == sorted_runs_.size() - 1) {
|
||||
output_level = max_output_level;
|
||||
// output files at the last level, unless it's reserved
|
||||
output_level = vstorage_->num_levels() - 1;
|
||||
// last level is reserved for the files ingested behind
|
||||
if (ioptions_.allow_ingest_behind) {
|
||||
assert(output_level > 1);
|
||||
output_level--;
|
||||
}
|
||||
} else {
|
||||
// if it's not including all sorted_runs, it can only output to the level
|
||||
// above the `end_index + 1` sorted_run.
|
||||
|
||||
@@ -928,7 +928,7 @@ TEST_F(CompactionServiceTest, TablePropertiesCollector) {
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact(true));
|
||||
|
||||
ASSERT_OK(db_->GetPropertiesOfAllTables(&fname_to_props));
|
||||
|
||||
@@ -952,3 +952,4 @@ int main(int argc, char** argv) {
|
||||
RegisterCustomObjects(argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
|
||||
@@ -53,10 +53,8 @@ class FileTtlBooster {
|
||||
enabled_ = true;
|
||||
uint64_t all_boost_start_age = ttl / 2;
|
||||
uint64_t all_boost_age_range = (ttl / 32) * 31 - all_boost_start_age;
|
||||
// TODO(cbi): more reasonable algorithm that gives different values
|
||||
// when num_non_empty_levels - level - 1 > 63.
|
||||
uint64_t boost_age_range =
|
||||
all_boost_age_range >> std::min(63, num_non_empty_levels - level - 1);
|
||||
all_boost_age_range >> (num_non_empty_levels - level - 1);
|
||||
boost_age_start_ = all_boost_start_age + boost_age_range;
|
||||
const uint64_t kBoostRatio = 16;
|
||||
// prevent 0 value to avoid divide 0 error.
|
||||
|
||||
@@ -209,7 +209,7 @@ TEST_P(TieredCompactionTest, SequenceBasedTieredStorageUniversal) {
|
||||
seq_history.emplace_back(dbfull()->GetLatestSequenceNumber());
|
||||
expect_stats[0].Add(kBasicFlushStats);
|
||||
}
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
ASSERT_OK(dbfull()->WaitForCompact(true));
|
||||
|
||||
// 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()->TEST_WaitForCompact());
|
||||
ASSERT_OK(dbfull()->WaitForCompact(true));
|
||||
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());
|
||||
}
|
||||
// make sure the compaction is able to finish
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
ASSERT_OK(dbfull()->WaitForCompact(
|
||||
true)); // make sure the compaction is able to finish
|
||||
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()->TEST_WaitForCompact());
|
||||
ASSERT_OK(dbfull()->WaitForCompact(true));
|
||||
|
||||
// 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()->TEST_WaitForCompact());
|
||||
ASSERT_OK(dbfull()->WaitForCompact(true));
|
||||
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()->TEST_WaitForCompact());
|
||||
ASSERT_OK(dbfull()->WaitForCompact(true));
|
||||
|
||||
latest_cold_seq = seq_history[0];
|
||||
ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr));
|
||||
@@ -1111,7 +1111,6 @@ 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;
|
||||
|
||||
@@ -1135,7 +1134,7 @@ TEST_P(TieredCompactionTest, RangeBasedTieredStorageLevel) {
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
ASSERT_OK(dbfull()->WaitForCompact(true));
|
||||
ASSERT_EQ("0,0,0,0,0,1,1", FilesPerLevel());
|
||||
ASSERT_GT(GetSstSizeHelper(Temperature::kUnknown), 0);
|
||||
ASSERT_GT(GetSstSizeHelper(Temperature::kCold), 0);
|
||||
@@ -1265,7 +1264,7 @@ TEST_F(PrecludeLastLevelTest, MigrationFromPreserveTimeManualCompaction) {
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
ASSERT_OK(dbfull()->WaitForCompact(true));
|
||||
|
||||
// all data is pushed to the last level
|
||||
ASSERT_EQ("0,0,0,0,0,0,1", FilesPerLevel());
|
||||
@@ -1327,7 +1326,7 @@ TEST_F(PrecludeLastLevelTest, MigrationFromPreserveTimeAutoCompaction) {
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
ASSERT_OK(dbfull()->WaitForCompact(true));
|
||||
|
||||
// all data is pushed to the last level
|
||||
ASSERT_EQ("0,0,0,0,0,0,1", FilesPerLevel());
|
||||
@@ -1360,7 +1359,7 @@ TEST_F(PrecludeLastLevelTest, MigrationFromPreserveTimeAutoCompaction) {
|
||||
});
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
ASSERT_OK(dbfull()->WaitForCompact(true));
|
||||
}
|
||||
|
||||
// all data is moved up to the penultimate level
|
||||
@@ -1403,7 +1402,7 @@ TEST_F(PrecludeLastLevelTest, MigrationFromPreserveTimePartial) {
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
ASSERT_OK(dbfull()->WaitForCompact(true));
|
||||
|
||||
// all data is pushed to the last level
|
||||
ASSERT_EQ("0,0,0,0,0,0,1", FilesPerLevel());
|
||||
@@ -1530,7 +1529,7 @@ TEST_F(PrecludeLastLevelTest, LastLevelOnlyCompactionPartial) {
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
ASSERT_OK(dbfull()->WaitForCompact(true));
|
||||
|
||||
// all data is pushed to the last level
|
||||
ASSERT_EQ("0,0,0,0,0,0,1", FilesPerLevel());
|
||||
@@ -1609,7 +1608,7 @@ TEST_P(PrecludeLastLevelTestWithParms, LastLevelOnlyCompactionNoPreclude) {
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
ASSERT_OK(dbfull()->WaitForCompact(true));
|
||||
|
||||
// all data is pushed to the last level
|
||||
ASSERT_EQ("0,0,0,0,0,0,1", FilesPerLevel());
|
||||
@@ -1705,7 +1704,7 @@ TEST_P(PrecludeLastLevelTestWithParms, LastLevelOnlyCompactionNoPreclude) {
|
||||
|
||||
manual_compaction_thread.join();
|
||||
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
ASSERT_OK(dbfull()->WaitForCompact(true));
|
||||
|
||||
if (enable_preclude_last_level) {
|
||||
ASSERT_NE("0,0,0,0,0,1,1", FilesPerLevel());
|
||||
@@ -1841,7 +1840,7 @@ TEST_P(PrecludeLastLevelTestWithParms, PeriodicCompactionToPenultimateLevel) {
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
ASSERT_OK(dbfull()->WaitForCompact(true));
|
||||
|
||||
stop_token.reset();
|
||||
|
||||
@@ -1940,7 +1939,7 @@ TEST_F(PrecludeLastLevelTest, PartialPenultimateLevelCompaction) {
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
ASSERT_OK(dbfull()->WaitForCompact(true));
|
||||
|
||||
// L5: [0,19] [20,39] [40,299]
|
||||
// L6: [0, 299]
|
||||
@@ -2106,7 +2105,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()->TEST_WaitForCompact());
|
||||
ASSERT_OK(dbfull()->WaitForCompact(true));
|
||||
ASSERT_EQ("0,0,0,0,0,3,3", FilesPerLevel());
|
||||
ASSERT_EQ(1, per_key_comp_num);
|
||||
verify_db();
|
||||
@@ -2116,7 +2115,7 @@ TEST_F(PrecludeLastLevelTest, RangeDelsCauseFileEndpointsToOverlap) {
|
||||
db_->ReleaseSnapshot(snap2);
|
||||
ASSERT_OK(db_->SuggestCompactRange(db_->DefaultColumnFamily(), &begin_key,
|
||||
&end_key));
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
ASSERT_OK(dbfull()->WaitForCompact(true));
|
||||
ASSERT_EQ("0,0,0,0,0,3,3", FilesPerLevel());
|
||||
ASSERT_EQ(2, per_key_comp_num);
|
||||
verify_db();
|
||||
@@ -2126,7 +2125,7 @@ TEST_F(PrecludeLastLevelTest, RangeDelsCauseFileEndpointsToOverlap) {
|
||||
db_->ReleaseSnapshot(snap1);
|
||||
ASSERT_OK(db_->SuggestCompactRange(db_->DefaultColumnFamily(), &begin_key,
|
||||
&end_key));
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
ASSERT_OK(dbfull()->WaitForCompact(true));
|
||||
ASSERT_EQ("0,0,0,0,0,2,3", FilesPerLevel());
|
||||
ASSERT_EQ(3, per_key_comp_num);
|
||||
verify_db();
|
||||
|
||||
@@ -77,7 +77,7 @@ void DoRandomIteraratorTest(DB* db, std::vector<std::string> source_strings,
|
||||
|
||||
for (int i = 0; i < num_writes; i++) {
|
||||
if (num_trigger_flush > 0 && i != 0 && i % num_trigger_flush == 0) {
|
||||
ASSERT_OK(db->Flush(FlushOptions()));
|
||||
db->Flush(FlushOptions());
|
||||
}
|
||||
|
||||
int type = rnd->Uniform(2);
|
||||
@@ -156,7 +156,6 @@ void DoRandomIteraratorTest(DB* db, std::vector<std::string> source_strings,
|
||||
if (map.find(key) == map.end()) {
|
||||
ASSERT_TRUE(status.IsNotFound());
|
||||
} else {
|
||||
ASSERT_OK(status);
|
||||
ASSERT_EQ(map[key], result);
|
||||
}
|
||||
break;
|
||||
|
||||
+9
-28
@@ -7,7 +7,6 @@
|
||||
|
||||
#include "rocksdb/convenience.h"
|
||||
|
||||
#include "db/convenience_impl.h"
|
||||
#include "db/db_impl/db_impl.h"
|
||||
#include "util/cast_util.h"
|
||||
|
||||
@@ -34,31 +33,13 @@ Status DeleteFilesInRanges(DB* db, ColumnFamilyHandle* column_family,
|
||||
Status VerifySstFileChecksum(const Options& options,
|
||||
const EnvOptions& env_options,
|
||||
const std::string& file_path) {
|
||||
// TODO: plumb Env::IOActivity
|
||||
const ReadOptions read_options;
|
||||
return VerifySstFileChecksum(options, env_options, read_options, file_path);
|
||||
return VerifySstFileChecksum(options, env_options, ReadOptions(), file_path);
|
||||
}
|
||||
Status VerifySstFileChecksum(const Options& options,
|
||||
const EnvOptions& env_options,
|
||||
const ReadOptions& _read_options,
|
||||
const ReadOptions& read_options,
|
||||
const std::string& file_path,
|
||||
const SequenceNumber& largest_seqno) {
|
||||
if (_read_options.io_activity != Env::IOActivity::kUnknown) {
|
||||
return Status::InvalidArgument(
|
||||
"Can only call VerifySstFileChecksum with `ReadOptions::io_activity` "
|
||||
"is "
|
||||
"`Env::IOActivity::kUnknown`");
|
||||
}
|
||||
ReadOptions read_options(_read_options);
|
||||
return VerifySstFileChecksumInternal(options, env_options, read_options,
|
||||
file_path, largest_seqno);
|
||||
}
|
||||
|
||||
Status VerifySstFileChecksumInternal(const Options& options,
|
||||
const EnvOptions& env_options,
|
||||
const ReadOptions& read_options,
|
||||
const std::string& file_path,
|
||||
const SequenceNumber& largest_seqno) {
|
||||
std::unique_ptr<FSRandomAccessFile> file;
|
||||
uint64_t file_size;
|
||||
InternalKeyComparator internal_comparator(options.comparator);
|
||||
@@ -75,18 +56,17 @@ Status VerifySstFileChecksumInternal(const Options& options,
|
||||
std::unique_ptr<RandomAccessFileReader> file_reader(
|
||||
new RandomAccessFileReader(
|
||||
std::move(file), file_path, ioptions.clock, nullptr /* io_tracer */,
|
||||
ioptions.stats /* stats */,
|
||||
Histograms::SST_READ_MICROS /* hist_type */,
|
||||
nullptr /* file_read_hist */, ioptions.rate_limiter.get()));
|
||||
nullptr /* stats */, 0 /* 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,
|
||||
options.block_protection_bytes_per_key, false /* skip_filters */,
|
||||
!kImmortal, false /* force_direct_prefetch */, -1 /* level */);
|
||||
false /* skip_filters */, !kImmortal, false /* force_direct_prefetch */,
|
||||
-1 /* level */);
|
||||
reader_options.largest_seqno = largest_seqno;
|
||||
s = ioptions.table_factory->NewTableReader(
|
||||
read_options, reader_options, std::move(file_reader), file_size,
|
||||
&table_reader, false /* prefetch_index_and_filter_in_cache */);
|
||||
reader_options, std::move(file_reader), file_size, &table_reader,
|
||||
false /* prefetch_index_and_filter_in_cache */);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
@@ -96,3 +76,4 @@ Status VerifySstFileChecksumInternal(const Options& options,
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under both the GPLv2 (found in the
|
||||
// COPYING file in the root directory) and Apache 2.0 License
|
||||
// (found in the LICENSE.Apache file in the root directory).
|
||||
|
||||
#pragma once
|
||||
#include "rocksdb/db.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
Status VerifySstFileChecksumInternal(const Options& options,
|
||||
const EnvOptions& env_options,
|
||||
const ReadOptions& read_options,
|
||||
const std::string& file_path,
|
||||
const SequenceNumber& largest_seqno = 0);
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
+4
-22
@@ -450,7 +450,6 @@ TEST_F(CorruptionTest, TableFile) {
|
||||
|
||||
TEST_F(CorruptionTest, VerifyChecksumReadahead) {
|
||||
Options options;
|
||||
options.level_compaction_dynamic_level_bytes = false;
|
||||
SpecialEnv senv(base_env_);
|
||||
options.env = &senv;
|
||||
// Disable block cache as we are going to check checksum for
|
||||
@@ -504,7 +503,6 @@ TEST_F(CorruptionTest, VerifyChecksumReadahead) {
|
||||
|
||||
TEST_F(CorruptionTest, TableFileIndexData) {
|
||||
Options options;
|
||||
options.level_compaction_dynamic_level_bytes = false;
|
||||
// very big, we'll trigger flushes manually
|
||||
options.write_buffer_size = 100 * 1024 * 1024;
|
||||
Reopen(&options);
|
||||
@@ -661,7 +659,6 @@ TEST_F(CorruptionTest, CorruptedDescriptor) {
|
||||
|
||||
TEST_F(CorruptionTest, CompactionInputError) {
|
||||
Options options;
|
||||
options.level_compaction_dynamic_level_bytes = false;
|
||||
options.env = env_.get();
|
||||
Reopen(&options);
|
||||
Build(10);
|
||||
@@ -683,7 +680,6 @@ TEST_F(CorruptionTest, CompactionInputError) {
|
||||
|
||||
TEST_F(CorruptionTest, CompactionInputErrorParanoid) {
|
||||
Options options;
|
||||
options.level_compaction_dynamic_level_bytes = false;
|
||||
options.env = env_.get();
|
||||
options.paranoid_checks = true;
|
||||
options.write_buffer_size = 131072;
|
||||
@@ -766,11 +762,9 @@ TEST_F(CorruptionTest, RangeDeletionCorrupted) {
|
||||
fs->GetFileSize(filename, file_opts.io_options, &file_size, nullptr));
|
||||
|
||||
BlockHandle 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(FindMetaBlockInFile(
|
||||
file_reader.get(), file_size, kBlockBasedTableMagicNumber,
|
||||
ImmutableOptions(options_), kRangeDelBlockName, &range_del_handle));
|
||||
|
||||
ASSERT_OK(TryReopen());
|
||||
ASSERT_OK(test::CorruptFile(env_.get(), filename,
|
||||
@@ -781,7 +775,6 @@ TEST_F(CorruptionTest, RangeDeletionCorrupted) {
|
||||
TEST_F(CorruptionTest, FileSystemStateCorrupted) {
|
||||
for (int iter = 0; iter < 2; ++iter) {
|
||||
Options options;
|
||||
options.level_compaction_dynamic_level_bytes = false;
|
||||
options.env = env_.get();
|
||||
options.paranoid_checks = true;
|
||||
options.create_if_missing = true;
|
||||
@@ -821,7 +814,6 @@ static const auto& corruption_modes = {
|
||||
|
||||
TEST_F(CorruptionTest, ParanoidFileChecksOnFlush) {
|
||||
Options options;
|
||||
options.level_compaction_dynamic_level_bytes = false;
|
||||
options.env = env_.get();
|
||||
options.check_flush_compaction_key_order = false;
|
||||
options.paranoid_file_checks = true;
|
||||
@@ -850,7 +842,6 @@ TEST_F(CorruptionTest, ParanoidFileChecksOnFlush) {
|
||||
|
||||
TEST_F(CorruptionTest, ParanoidFileChecksOnCompact) {
|
||||
Options options;
|
||||
options.level_compaction_dynamic_level_bytes = false;
|
||||
options.env = env_.get();
|
||||
options.paranoid_file_checks = true;
|
||||
options.create_if_missing = true;
|
||||
@@ -884,7 +875,6 @@ TEST_F(CorruptionTest, ParanoidFileChecksOnCompact) {
|
||||
|
||||
TEST_F(CorruptionTest, ParanoidFileChecksWithDeleteRangeFirst) {
|
||||
Options options;
|
||||
options.level_compaction_dynamic_level_bytes = false;
|
||||
options.env = env_.get();
|
||||
options.check_flush_compaction_key_order = false;
|
||||
options.paranoid_file_checks = true;
|
||||
@@ -921,7 +911,6 @@ TEST_F(CorruptionTest, ParanoidFileChecksWithDeleteRangeFirst) {
|
||||
|
||||
TEST_F(CorruptionTest, ParanoidFileChecksWithDeleteRange) {
|
||||
Options options;
|
||||
options.level_compaction_dynamic_level_bytes = false;
|
||||
options.env = env_.get();
|
||||
options.check_flush_compaction_key_order = false;
|
||||
options.paranoid_file_checks = true;
|
||||
@@ -961,7 +950,6 @@ TEST_F(CorruptionTest, ParanoidFileChecksWithDeleteRange) {
|
||||
|
||||
TEST_F(CorruptionTest, ParanoidFileChecksWithDeleteRangeLast) {
|
||||
Options options;
|
||||
options.level_compaction_dynamic_level_bytes = false;
|
||||
options.env = env_.get();
|
||||
options.check_flush_compaction_key_order = false;
|
||||
options.paranoid_file_checks = true;
|
||||
@@ -998,7 +986,6 @@ TEST_F(CorruptionTest, ParanoidFileChecksWithDeleteRangeLast) {
|
||||
|
||||
TEST_F(CorruptionTest, LogCorruptionErrorsInCompactionIterator) {
|
||||
Options options;
|
||||
options.level_compaction_dynamic_level_bytes = false;
|
||||
options.env = env_.get();
|
||||
options.create_if_missing = true;
|
||||
options.allow_data_in_errors = true;
|
||||
@@ -1028,7 +1015,6 @@ TEST_F(CorruptionTest, LogCorruptionErrorsInCompactionIterator) {
|
||||
|
||||
TEST_F(CorruptionTest, CompactionKeyOrderCheck) {
|
||||
Options options;
|
||||
options.level_compaction_dynamic_level_bytes = false;
|
||||
options.env = env_.get();
|
||||
options.paranoid_file_checks = false;
|
||||
options.create_if_missing = true;
|
||||
@@ -1056,7 +1042,6 @@ TEST_F(CorruptionTest, CompactionKeyOrderCheck) {
|
||||
|
||||
TEST_F(CorruptionTest, FlushKeyOrderCheck) {
|
||||
Options options;
|
||||
options.level_compaction_dynamic_level_bytes = false;
|
||||
options.env = env_.get();
|
||||
options.paranoid_file_checks = false;
|
||||
options.create_if_missing = true;
|
||||
@@ -1110,7 +1095,6 @@ TEST_F(CorruptionTest, DisableKeyOrderCheck) {
|
||||
TEST_F(CorruptionTest, VerifyWholeTableChecksum) {
|
||||
CloseDb();
|
||||
Options options;
|
||||
options.level_compaction_dynamic_level_bytes = false;
|
||||
options.env = env_.get();
|
||||
ASSERT_OK(DestroyDB(dbname_, options));
|
||||
options.create_if_missing = true;
|
||||
@@ -1196,7 +1180,6 @@ INSTANTIATE_TEST_CASE_P(CorruptionTest, CrashDuringRecoveryWithCorruptionTest,
|
||||
TEST_P(CrashDuringRecoveryWithCorruptionTest, CrashDuringRecovery) {
|
||||
CloseDb();
|
||||
Options options;
|
||||
options.level_compaction_dynamic_level_bytes = false;
|
||||
options.track_and_verify_wals_in_manifest =
|
||||
track_and_verify_wals_in_manifest_;
|
||||
options.wal_recovery_mode = WALRecoveryMode::kPointInTimeRecovery;
|
||||
@@ -1369,7 +1352,6 @@ TEST_P(CrashDuringRecoveryWithCorruptionTest, CrashDuringRecovery) {
|
||||
TEST_P(CrashDuringRecoveryWithCorruptionTest, TxnDbCrashDuringRecovery) {
|
||||
CloseDb();
|
||||
Options options;
|
||||
options.level_compaction_dynamic_level_bytes = false;
|
||||
options.wal_recovery_mode = WALRecoveryMode::kPointInTimeRecovery;
|
||||
options.track_and_verify_wals_in_manifest =
|
||||
track_and_verify_wals_in_manifest_;
|
||||
@@ -1567,7 +1549,6 @@ TEST_P(CrashDuringRecoveryWithCorruptionTest, TxnDbCrashDuringRecovery) {
|
||||
TEST_P(CrashDuringRecoveryWithCorruptionTest, CrashDuringRecoveryWithFlush) {
|
||||
CloseDb();
|
||||
Options options;
|
||||
options.level_compaction_dynamic_level_bytes = false;
|
||||
options.wal_recovery_mode = WALRecoveryMode::kPointInTimeRecovery;
|
||||
options.avoid_flush_during_recovery = false;
|
||||
options.env = env_.get();
|
||||
@@ -1685,3 +1666,4 @@ int main(int argc, char** argv) {
|
||||
RegisterCustomObjects(argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
|
||||
@@ -39,7 +39,6 @@ class CuckooTableDBTest : public testing::Test {
|
||||
|
||||
Options CurrentOptions() {
|
||||
Options options;
|
||||
options.level_compaction_dynamic_level_bytes = false;
|
||||
options.table_factory.reset(NewCuckooTableFactory());
|
||||
options.memtable_factory.reset(NewHashLinkListRepFactory(4, 0, 3, true));
|
||||
options.allow_mmap_reads = true;
|
||||
|
||||
+3
-17
@@ -1204,23 +1204,9 @@ TEST_F(DBBasicTest, DBClose) {
|
||||
delete db;
|
||||
ASSERT_EQ(env->GetCloseCount(), 2);
|
||||
|
||||
// close by WaitForCompact() with close_db option
|
||||
options.create_if_missing = false;
|
||||
s = DB::Open(options, dbname, &db);
|
||||
ASSERT_OK(s);
|
||||
ASSERT_TRUE(db != nullptr);
|
||||
WaitForCompactOptions wait_for_compact_options = WaitForCompactOptions();
|
||||
wait_for_compact_options.close_db = true;
|
||||
s = db->WaitForCompact(wait_for_compact_options);
|
||||
ASSERT_EQ(env->GetCloseCount(), 3);
|
||||
// see TestLogger::CloseHelper()
|
||||
ASSERT_EQ(s, Status::IOError());
|
||||
|
||||
delete db;
|
||||
ASSERT_EQ(env->GetCloseCount(), 3);
|
||||
|
||||
// Provide our own logger and ensure DB::Close() does not close it
|
||||
options.info_log.reset(new TestEnv::TestLogger(env));
|
||||
options.create_if_missing = false;
|
||||
s = DB::Open(options, dbname, &db);
|
||||
ASSERT_OK(s);
|
||||
ASSERT_TRUE(db != nullptr);
|
||||
@@ -1228,9 +1214,9 @@ TEST_F(DBBasicTest, DBClose) {
|
||||
s = db->Close();
|
||||
ASSERT_EQ(s, Status::OK());
|
||||
delete db;
|
||||
ASSERT_EQ(env->GetCloseCount(), 3);
|
||||
ASSERT_EQ(env->GetCloseCount(), 2);
|
||||
options.info_log.reset();
|
||||
ASSERT_EQ(env->GetCloseCount(), 4);
|
||||
ASSERT_EQ(env->GetCloseCount(), 3);
|
||||
}
|
||||
|
||||
TEST_F(DBBasicTest, DBCloseAllDirectoryFDs) {
|
||||
|
||||
@@ -620,9 +620,9 @@ class MockCache : public LRUCache {
|
||||
static uint32_t low_pri_insert_count;
|
||||
|
||||
MockCache()
|
||||
: LRUCache(LRUCacheOptions(
|
||||
size_t{1} << 25 /*capacity*/, 0 /*num_shard_bits*/,
|
||||
false /*strict_capacity_limit*/, 0.0 /*high_pri_pool_ratio*/)) {}
|
||||
: 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*/) {}
|
||||
|
||||
using ShardedCache::Insert;
|
||||
|
||||
@@ -741,15 +741,10 @@ TEST_F(DBBlockCacheTest, AddRedundantStats) {
|
||||
int iterations_tested = 0;
|
||||
for (std::shared_ptr<Cache> base_cache :
|
||||
{NewLRUCache(capacity, num_shard_bits),
|
||||
// FixedHyperClockCache
|
||||
HyperClockCacheOptions(
|
||||
capacity,
|
||||
BlockBasedTableOptions().block_size /*estimated_value_size*/,
|
||||
num_shard_bits)
|
||||
.MakeSharedCache(),
|
||||
// AutoHyperClockCache
|
||||
HyperClockCacheOptions(capacity, 0 /*estimated_value_size*/,
|
||||
num_shard_bits)
|
||||
.MakeSharedCache()}) {
|
||||
if (!base_cache) {
|
||||
// Skip clock cache when not supported
|
||||
@@ -984,14 +979,12 @@ 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();
|
||||
@@ -1285,7 +1278,6 @@ 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>();
|
||||
|
||||
|
||||
+235
-275
File diff suppressed because it is too large.
Load diff
@@ -1,142 +0,0 @@
|
||||
// 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 applies to all records, regardless snapshots.
|
||||
// Compaction filters aplies to all records, regardless snapshots.
|
||||
TEST_F(DBTestCompactionFilter, CompactionFilterIgnoreSnapshot) {
|
||||
std::string five = std::to_string(5);
|
||||
Options options = CurrentOptions();
|
||||
|
||||
+71
-999
File diff suppressed because it is too large.
Load diff
+29
-2
@@ -29,8 +29,35 @@
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
Status DBImpl::FlushForGetLiveFiles() {
|
||||
return DBImpl::FlushAllColumnFamilies(FlushOptions(),
|
||||
FlushReason::kGetLiveFiles);
|
||||
mutex_.AssertHeld();
|
||||
|
||||
// flush all dirty data to disk.
|
||||
Status status;
|
||||
if (immutable_db_options_.atomic_flush) {
|
||||
mutex_.Unlock();
|
||||
status = AtomicFlushMemTables(FlushOptions(), FlushReason::kGetLiveFiles);
|
||||
if (status.IsColumnFamilyDropped()) {
|
||||
status = Status::OK();
|
||||
}
|
||||
mutex_.Lock();
|
||||
} else {
|
||||
for (auto cfd : versions_->GetRefedColumnFamilySet()) {
|
||||
if (cfd->IsDropped()) {
|
||||
continue;
|
||||
}
|
||||
mutex_.Unlock();
|
||||
status = FlushMemTable(cfd, FlushOptions(), FlushReason::kGetLiveFiles);
|
||||
TEST_SYNC_POINT("DBImpl::GetLiveFiles:1");
|
||||
TEST_SYNC_POINT("DBImpl::GetLiveFiles:2");
|
||||
mutex_.Lock();
|
||||
if (!status.ok() && !status.IsColumnFamilyDropped()) {
|
||||
break;
|
||||
} else if (status.IsColumnFamilyDropped()) {
|
||||
status = Status::OK();
|
||||
}
|
||||
}
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
Status DBImpl::GetLiveFiles(std::vector<std::string>& ret,
|
||||
|
||||
+1
-274
@@ -222,7 +222,7 @@ TEST_F(DBFlushTest, CloseDBWhenFlushInLowPri) {
|
||||
sleeping_task_low.WaitUntilDone();
|
||||
ASSERT_EQ(0, num_flushes);
|
||||
|
||||
ASSERT_OK(TryReopenWithColumnFamilies({"default", "cf1", "cf2"}, options));
|
||||
TryReopenWithColumnFamilies({"default", "cf1", "cf2"}, options);
|
||||
ASSERT_OK(Put(0, "key3", DummyString(8192)));
|
||||
ASSERT_OK(Flush(0));
|
||||
ASSERT_EQ(1, num_flushes);
|
||||
@@ -3193,279 +3193,6 @@ INSTANTIATE_TEST_CASE_P(DBFlushDirectIOTest, DBFlushDirectIOTest,
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(DBAtomicFlushTest, DBAtomicFlushTest, testing::Bool());
|
||||
|
||||
TEST_F(DBFlushTest, NonAtomicFlushRollbackPendingFlushes) {
|
||||
// Fix a bug in when atomic_flush=false.
|
||||
// The bug can happen as follows:
|
||||
// Start Flush0 for memtable M0 to SST0
|
||||
// Start Flush1 for memtable M1 to SST1
|
||||
// Flush1 returns OK, but don't install to MANIFEST and let whoever flushes
|
||||
// M0 to take care of it
|
||||
// Flush0 finishes with a retryable IOError
|
||||
// - It rollbacks M0, (incorrectly) not M1
|
||||
// - Deletes SST1 and SST2
|
||||
//
|
||||
// Auto-recovery will start Flush2 for M0, it does not pick up M1 since it
|
||||
// thinks that M1 is flushed
|
||||
// Flush2 writes SST3 and finishes OK, tries to install SST3 and SST2
|
||||
// Error opening SST2 since it's already deleted
|
||||
//
|
||||
// The fix is to let Flush0 also rollback M1.
|
||||
Options opts = CurrentOptions();
|
||||
opts.atomic_flush = false;
|
||||
opts.memtable_factory.reset(test::NewSpecialSkipListFactory(1));
|
||||
opts.max_write_buffer_number = 64;
|
||||
opts.max_background_flushes = 4;
|
||||
env_->SetBackgroundThreads(4, Env::HIGH);
|
||||
DestroyAndReopen(opts);
|
||||
std::atomic_int flush_count = 0;
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"FlushJob::WriteLevel0Table:s", [&](void* s_ptr) {
|
||||
int c = flush_count.fetch_add(1);
|
||||
if (c == 0) {
|
||||
Status* s = (Status*)(s_ptr);
|
||||
IOStatus io_error = IOStatus::IOError("injected foobar");
|
||||
io_error.SetRetryable(true);
|
||||
*s = io_error;
|
||||
TEST_SYNC_POINT("Let mem1 flush start");
|
||||
TEST_SYNC_POINT("Wait for mem1 flush to finish");
|
||||
}
|
||||
});
|
||||
SyncPoint::GetInstance()->LoadDependency(
|
||||
{{"Let mem1 flush start", "Mem1 flush starts"},
|
||||
{"DBImpl::BGWorkFlush:done", "Wait for mem1 flush to finish"},
|
||||
{"RecoverFromRetryableBGIOError:RecoverSuccess",
|
||||
"Wait for error recover"}});
|
||||
// Need first flush to wait for the second flush to finish
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
ASSERT_OK(Put(Key(1), "val1"));
|
||||
// trigger bg flush mem0
|
||||
ASSERT_OK(Put(Key(2), "val2"));
|
||||
TEST_SYNC_POINT("Mem1 flush starts");
|
||||
// trigger bg flush mem1
|
||||
ASSERT_OK(Put(Key(3), "val3"));
|
||||
|
||||
TEST_SYNC_POINT("Wait for error recover");
|
||||
ASSERT_EQ(1, NumTableFilesAtLevel(0));
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
|
||||
TEST_F(DBFlushTest, AbortNonAtomicFlushWhenBGError) {
|
||||
// Fix a bug in when atomic_flush=false.
|
||||
// The bug can happen as follows:
|
||||
// Start Flush0 for memtable M0 to SST0
|
||||
// Start Flush1 for memtable M1 to SST1
|
||||
// Flush1 returns OK, but doesn't install output MANIFEST and let whoever
|
||||
// flushes M0 to take care of it
|
||||
// Start Flush2 for memtable M2 to SST2
|
||||
// Flush0 finishes with a retryable IOError
|
||||
// - It rollbacks M0 AND M1
|
||||
// - Deletes SST1 and SST2
|
||||
// Flush2 finishes, does not rollback M2,
|
||||
// - releases the pending file number that keeps SST2 alive
|
||||
// - deletes SST2
|
||||
//
|
||||
// Then auto-recovery starts, error opening SST2 when try to install
|
||||
// flush result
|
||||
//
|
||||
// The fix is to let Flush2 rollback M2 if it finds that
|
||||
// there is a background error.
|
||||
Options opts = CurrentOptions();
|
||||
opts.atomic_flush = false;
|
||||
opts.memtable_factory.reset(test::NewSpecialSkipListFactory(1));
|
||||
opts.max_write_buffer_number = 64;
|
||||
opts.max_background_flushes = 4;
|
||||
env_->SetBackgroundThreads(4, Env::HIGH);
|
||||
DestroyAndReopen(opts);
|
||||
std::atomic_int flush_count = 0;
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"FlushJob::WriteLevel0Table:s", [&](void* s_ptr) {
|
||||
int c = flush_count.fetch_add(1);
|
||||
if (c == 0) {
|
||||
Status* s = (Status*)(s_ptr);
|
||||
IOStatus io_error = IOStatus::IOError("injected foobar");
|
||||
io_error.SetRetryable(true);
|
||||
*s = io_error;
|
||||
TEST_SYNC_POINT("Let mem1 flush start");
|
||||
TEST_SYNC_POINT("Wait for mem1 flush to finish");
|
||||
|
||||
TEST_SYNC_POINT("Let mem2 flush start");
|
||||
TEST_SYNC_POINT("Wait for mem2 to start writing table");
|
||||
}
|
||||
});
|
||||
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"FlushJob::WriteLevel0Table", [&](void* mems) {
|
||||
autovector<MemTable*>* mems_ptr = (autovector<MemTable*>*)mems;
|
||||
if ((*mems_ptr)[0]->GetID() == 3) {
|
||||
TEST_SYNC_POINT("Mem2 flush starts writing table");
|
||||
TEST_SYNC_POINT("Mem2 flush waits until rollback");
|
||||
}
|
||||
});
|
||||
SyncPoint::GetInstance()->LoadDependency(
|
||||
{{"Let mem1 flush start", "Mem1 flush starts"},
|
||||
{"DBImpl::BGWorkFlush:done", "Wait for mem1 flush to finish"},
|
||||
{"Let mem2 flush start", "Mem2 flush starts"},
|
||||
{"Mem2 flush starts writing table",
|
||||
"Wait for mem2 to start writing table"},
|
||||
{"RollbackMemtableFlush", "Mem2 flush waits until rollback"},
|
||||
{"RecoverFromRetryableBGIOError:RecoverSuccess",
|
||||
"Wait for error recover"}});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
ASSERT_OK(Put(Key(1), "val1"));
|
||||
// trigger bg flush mem0
|
||||
ASSERT_OK(Put(Key(2), "val2"));
|
||||
TEST_SYNC_POINT("Mem1 flush starts");
|
||||
// trigger bg flush mem1
|
||||
ASSERT_OK(Put(Key(3), "val3"));
|
||||
|
||||
TEST_SYNC_POINT("Mem2 flush starts");
|
||||
ASSERT_OK(Put(Key(4), "val4"));
|
||||
|
||||
TEST_SYNC_POINT("Wait for error recover");
|
||||
// Recovery flush writes 3 memtables together into 1 file.
|
||||
ASSERT_EQ(1, NumTableFilesAtLevel(0));
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
|
||||
TEST_F(DBFlushTest, NonAtomicNormalFlushAbortWhenBGError) {
|
||||
Options opts = CurrentOptions();
|
||||
opts.atomic_flush = false;
|
||||
opts.memtable_factory.reset(test::NewSpecialSkipListFactory(1));
|
||||
opts.max_write_buffer_number = 64;
|
||||
opts.max_background_flushes = 1;
|
||||
env_->SetBackgroundThreads(2, Env::HIGH);
|
||||
DestroyAndReopen(opts);
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
std::atomic_int flush_write_table_count = 0;
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"FlushJob::WriteLevel0Table:s", [&](void* s_ptr) {
|
||||
int c = flush_write_table_count.fetch_add(1);
|
||||
if (c == 0) {
|
||||
Status* s = (Status*)(s_ptr);
|
||||
IOStatus io_error = IOStatus::IOError("injected foobar");
|
||||
io_error.SetRetryable(true);
|
||||
*s = io_error;
|
||||
}
|
||||
});
|
||||
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
SyncPoint::GetInstance()->LoadDependency(
|
||||
{{"Let error recovery start",
|
||||
"RecoverFromRetryableBGIOError:BeforeStart"},
|
||||
{"RecoverFromRetryableBGIOError:RecoverSuccess",
|
||||
"Wait for error recover"}});
|
||||
|
||||
ASSERT_OK(Put(Key(1), "val1"));
|
||||
// trigger bg flush0 for mem0
|
||||
ASSERT_OK(Put(Key(2), "val2"));
|
||||
// Not checking status since this wait can finish before flush starts.
|
||||
dbfull()->TEST_WaitForFlushMemTable().PermitUncheckedError();
|
||||
|
||||
// trigger bg flush1 for mem1, should see bg error and abort
|
||||
// before picking a memtable to flush
|
||||
ASSERT_OK(Put(Key(3), "val3"));
|
||||
ASSERT_NOK(dbfull()->TEST_WaitForFlushMemTable());
|
||||
ASSERT_EQ(0, NumTableFilesAtLevel(0));
|
||||
|
||||
TEST_SYNC_POINT("Let error recovery start");
|
||||
TEST_SYNC_POINT("Wait for error recover");
|
||||
// Recovery flush writes 2 memtables together into 1 file.
|
||||
ASSERT_EQ(1, NumTableFilesAtLevel(0));
|
||||
// 1 for flush 0 and 1 for recovery flush
|
||||
ASSERT_EQ(2, flush_write_table_count);
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
|
||||
TEST_F(DBFlushTest, DBStuckAfterAtomicFlushError) {
|
||||
// Test for a bug with atomic flush where DB can become stuck
|
||||
// after a flush error. A repro timeline:
|
||||
//
|
||||
// Start Flush0 for mem0
|
||||
// Start Flush1 for mem1
|
||||
// Now Flush1 will wait for Flush0 to install mem0
|
||||
// Flush0 finishes with retryable IOError, rollbacks mem0
|
||||
// Resume starts and waits for background job to finish, i.e., Flush1
|
||||
// Fill memtable again, trigger Flush2 for mem0
|
||||
// Flush2 will get error status, and not rollback mem0, see code in
|
||||
// https://github.com/facebook/rocksdb/blob/b927ba5936216861c2c35ab68f50ba4a78e65747/db/db_impl/db_impl_compaction_flush.cc#L725
|
||||
//
|
||||
// DB is stuck since mem0 can never be picked now
|
||||
//
|
||||
// The fix is to rollback mem0 in Flush2, and let Flush1 also abort upon
|
||||
// background error besides waiting for older memtables to be installed.
|
||||
// The recovery flush in this case should pick up all memtables
|
||||
// and write them to a single L0 file.
|
||||
Options opts = CurrentOptions();
|
||||
opts.atomic_flush = true;
|
||||
opts.memtable_factory.reset(test::NewSpecialSkipListFactory(1));
|
||||
opts.max_write_buffer_number = 64;
|
||||
opts.max_background_flushes = 4;
|
||||
env_->SetBackgroundThreads(4, Env::HIGH);
|
||||
DestroyAndReopen(opts);
|
||||
|
||||
std::atomic_int flush_count = 0;
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"FlushJob::WriteLevel0Table:s", [&](void* s_ptr) {
|
||||
int c = flush_count.fetch_add(1);
|
||||
if (c == 0) {
|
||||
Status* s = (Status*)(s_ptr);
|
||||
IOStatus io_error = IOStatus::IOError("injected foobar");
|
||||
io_error.SetRetryable(true);
|
||||
*s = io_error;
|
||||
TEST_SYNC_POINT("Let flush for mem1 start");
|
||||
// Wait for Flush1 to start waiting to install flush result
|
||||
TEST_SYNC_POINT("Wait for flush for mem1");
|
||||
}
|
||||
});
|
||||
SyncPoint::GetInstance()->LoadDependency(
|
||||
{{"Let flush for mem1 start", "Flush for mem1"},
|
||||
{"DBImpl::AtomicFlushMemTablesToOutputFiles:WaitCV",
|
||||
"Wait for flush for mem1"},
|
||||
{"RecoverFromRetryableBGIOError:BeforeStart",
|
||||
"Wait for resume to start"},
|
||||
{"Recovery should continue here",
|
||||
"RecoverFromRetryableBGIOError:BeforeStart2"},
|
||||
{"RecoverFromRetryableBGIOError:RecoverSuccess",
|
||||
"Wait for error recover"}});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
ASSERT_OK(Put(Key(1), "val1"));
|
||||
// trigger Flush0 for mem0
|
||||
ASSERT_OK(Put(Key(2), "val2"));
|
||||
|
||||
// trigger Flush1 for mem1
|
||||
TEST_SYNC_POINT("Flush for mem1");
|
||||
ASSERT_OK(Put(Key(3), "val3"));
|
||||
|
||||
// Wait until resume started to schedule another flush
|
||||
TEST_SYNC_POINT("Wait for resume to start");
|
||||
// This flush should not be scheduled due to bg error
|
||||
ASSERT_OK(Put(Key(4), "val4"));
|
||||
|
||||
// TEST_WaitForBackgroundWork() returns background error
|
||||
// after all background work is done.
|
||||
ASSERT_NOK(dbfull()->TEST_WaitForBackgroundWork());
|
||||
// Flush should abort and not writing any table
|
||||
ASSERT_EQ(0, NumTableFilesAtLevel(0));
|
||||
|
||||
// Wait until this flush is done.
|
||||
TEST_SYNC_POINT("Recovery should continue here");
|
||||
TEST_SYNC_POINT("Wait for error recover");
|
||||
// error recovery can schedule new flushes, but should not
|
||||
// encounter error
|
||||
ASSERT_OK(dbfull()->TEST_WaitForBackgroundWork());
|
||||
ASSERT_EQ(1, NumTableFilesAtLevel(0));
|
||||
}
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
@@ -43,25 +43,13 @@ Status CompactedDBImpl::Get(const ReadOptions& options, ColumnFamilyHandle*,
|
||||
/*timestamp*/ nullptr);
|
||||
}
|
||||
|
||||
Status CompactedDBImpl::Get(const ReadOptions& _read_options,
|
||||
ColumnFamilyHandle*, const Slice& key,
|
||||
PinnableSlice* value, std::string* timestamp) {
|
||||
if (_read_options.io_activity != Env::IOActivity::kUnknown &&
|
||||
_read_options.io_activity != Env::IOActivity::kGet) {
|
||||
return Status::InvalidArgument(
|
||||
"Can only call Get with `ReadOptions::io_activity` is "
|
||||
"`Env::IOActivity::kUnknown` or `Env::IOActivity::kGet`");
|
||||
}
|
||||
ReadOptions read_options(_read_options);
|
||||
if (read_options.io_activity == Env::IOActivity::kUnknown) {
|
||||
read_options.io_activity = Env::IOActivity::kGet;
|
||||
}
|
||||
|
||||
Status CompactedDBImpl::Get(const ReadOptions& options, ColumnFamilyHandle*,
|
||||
const Slice& key, PinnableSlice* value,
|
||||
std::string* timestamp) {
|
||||
assert(user_comparator_);
|
||||
if (read_options.timestamp) {
|
||||
const Status s =
|
||||
FailIfTsMismatchCf(DefaultColumnFamily(), *(read_options.timestamp),
|
||||
/*ts_for_read=*/true);
|
||||
if (options.timestamp) {
|
||||
const Status s = FailIfTsMismatchCf(
|
||||
DefaultColumnFamily(), *(options.timestamp), /*ts_for_read=*/true);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
@@ -81,7 +69,7 @@ Status CompactedDBImpl::Get(const ReadOptions& _read_options,
|
||||
GetWithTimestampReadCallback read_cb(kMaxSequenceNumber);
|
||||
std::string* ts =
|
||||
user_comparator_->timestamp_size() > 0 ? timestamp : nullptr;
|
||||
LookupKey lkey(key, kMaxSequenceNumber, read_options.timestamp);
|
||||
LookupKey lkey(key, kMaxSequenceNumber, options.timestamp);
|
||||
GetContext get_context(user_comparator_, nullptr, nullptr, nullptr,
|
||||
GetContext::kNotFound, lkey.user_key(), value,
|
||||
/*columns=*/nullptr, ts, nullptr, nullptr, true,
|
||||
@@ -95,8 +83,8 @@ Status CompactedDBImpl::Get(const ReadOptions& _read_options,
|
||||
/*b_has_ts=*/false) < 0) {
|
||||
return Status::NotFound();
|
||||
}
|
||||
Status s = f.fd.table_reader->Get(read_options, lkey.internal_key(),
|
||||
&get_context, nullptr);
|
||||
Status s = f.fd.table_reader->Get(options, lkey.internal_key(), &get_context,
|
||||
nullptr);
|
||||
if (!s.ok() && !s.IsNotFound()) {
|
||||
return s;
|
||||
}
|
||||
@@ -113,28 +101,15 @@ std::vector<Status> CompactedDBImpl::MultiGet(
|
||||
}
|
||||
|
||||
std::vector<Status> CompactedDBImpl::MultiGet(
|
||||
const ReadOptions& _read_options, const std::vector<ColumnFamilyHandle*>&,
|
||||
const ReadOptions& options, const std::vector<ColumnFamilyHandle*>&,
|
||||
const std::vector<Slice>& keys, std::vector<std::string>* values,
|
||||
std::vector<std::string>* timestamps) {
|
||||
assert(user_comparator_);
|
||||
size_t num_keys = keys.size();
|
||||
if (_read_options.io_activity != Env::IOActivity::kUnknown &&
|
||||
_read_options.io_activity != Env::IOActivity::kMultiGet) {
|
||||
Status s = Status::InvalidArgument(
|
||||
"Can only call MultiGet with `ReadOptions::io_activity` is "
|
||||
"`Env::IOActivity::kUnknown` or `Env::IOActivity::kMultiGet`");
|
||||
return std::vector<Status>(num_keys, s);
|
||||
}
|
||||
|
||||
ReadOptions read_options(_read_options);
|
||||
if (read_options.io_activity == Env::IOActivity::kUnknown) {
|
||||
read_options.io_activity = Env::IOActivity::kMultiGet;
|
||||
}
|
||||
|
||||
if (read_options.timestamp) {
|
||||
Status s =
|
||||
FailIfTsMismatchCf(DefaultColumnFamily(), *(read_options.timestamp),
|
||||
/*ts_for_read=*/true);
|
||||
if (options.timestamp) {
|
||||
Status s = FailIfTsMismatchCf(DefaultColumnFamily(), *(options.timestamp),
|
||||
/*ts_for_read=*/true);
|
||||
if (!s.ok()) {
|
||||
return std::vector<Status>(num_keys, s);
|
||||
}
|
||||
@@ -156,7 +131,7 @@ std::vector<Status> CompactedDBImpl::MultiGet(
|
||||
GetWithTimestampReadCallback read_cb(kMaxSequenceNumber);
|
||||
autovector<TableReader*, 16> reader_list;
|
||||
for (const auto& key : keys) {
|
||||
LookupKey lkey(key, kMaxSequenceNumber, read_options.timestamp);
|
||||
LookupKey lkey(key, kMaxSequenceNumber, options.timestamp);
|
||||
const FdWithKeyRange& f = files_.files[FindFile(lkey.user_key())];
|
||||
if (user_comparator_->CompareWithoutTimestamp(
|
||||
key, /*a_has_ts=*/false,
|
||||
@@ -179,15 +154,14 @@ std::vector<Status> CompactedDBImpl::MultiGet(
|
||||
if (r != nullptr) {
|
||||
PinnableSlice pinnable_val;
|
||||
std::string& value = (*values)[idx];
|
||||
LookupKey lkey(keys[idx], kMaxSequenceNumber, read_options.timestamp);
|
||||
LookupKey lkey(keys[idx], kMaxSequenceNumber, options.timestamp);
|
||||
std::string* timestamp = timestamps ? &(*timestamps)[idx] : nullptr;
|
||||
GetContext get_context(
|
||||
user_comparator_, nullptr, nullptr, nullptr, GetContext::kNotFound,
|
||||
lkey.user_key(), &pinnable_val, /*columns=*/nullptr,
|
||||
user_comparator_->timestamp_size() > 0 ? timestamp : nullptr, nullptr,
|
||||
nullptr, true, nullptr, nullptr, nullptr, nullptr, &read_cb);
|
||||
Status s =
|
||||
r->Get(read_options, lkey.internal_key(), &get_context, nullptr);
|
||||
Status s = r->Get(options, lkey.internal_key(), &get_context, nullptr);
|
||||
assert(static_cast<size_t>(idx) < statuses.size());
|
||||
if (!s.ok() && !s.IsNotFound()) {
|
||||
statuses[idx] = s;
|
||||
|
||||
@@ -30,9 +30,9 @@ class CompactedDBImpl : public DBImpl {
|
||||
ColumnFamilyHandle* column_family, const Slice& key,
|
||||
PinnableSlice* value) override;
|
||||
|
||||
Status Get(const ReadOptions& _read_options,
|
||||
ColumnFamilyHandle* column_family, const Slice& key,
|
||||
PinnableSlice* value, std::string* timestamp) override;
|
||||
Status Get(const ReadOptions& options, ColumnFamilyHandle* column_family,
|
||||
const Slice& key, PinnableSlice* value,
|
||||
std::string* timestamp) override;
|
||||
|
||||
using DB::MultiGet;
|
||||
// Note that CompactedDBImpl::MultiGet is not the optimized version of
|
||||
@@ -43,7 +43,7 @@ class CompactedDBImpl : public DBImpl {
|
||||
const std::vector<Slice>& keys,
|
||||
std::vector<std::string>* values) override;
|
||||
|
||||
std::vector<Status> MultiGet(const ReadOptions& _read_options,
|
||||
std::vector<Status> MultiGet(const ReadOptions& options,
|
||||
const std::vector<ColumnFamilyHandle*>&,
|
||||
const std::vector<Slice>& keys,
|
||||
std::vector<std::string>* values,
|
||||
@@ -118,24 +118,16 @@ class CompactedDBImpl : public DBImpl {
|
||||
const IngestExternalFileOptions& /*ingestion_options*/) override {
|
||||
return Status::NotSupported("Not supported in compacted db mode.");
|
||||
}
|
||||
|
||||
using DB::CreateColumnFamilyWithImport;
|
||||
virtual Status CreateColumnFamilyWithImport(
|
||||
const ColumnFamilyOptions& /*options*/,
|
||||
const std::string& /*column_family_name*/,
|
||||
const ImportColumnFamilyOptions& /*import_options*/,
|
||||
const std::vector<const ExportImportFilesMetaData*>& /*metadatas*/,
|
||||
const ExportImportFilesMetaData& /*metadata*/,
|
||||
ColumnFamilyHandle** /*handle*/) override {
|
||||
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?
|
||||
|
||||
|
||||
+111
-407
@@ -28,7 +28,6 @@
|
||||
#include "db/arena_wrapped_db_iter.h"
|
||||
#include "db/builder.h"
|
||||
#include "db/compaction/compaction_job.h"
|
||||
#include "db/convenience_impl.h"
|
||||
#include "db/db_info_dumper.h"
|
||||
#include "db/db_iter.h"
|
||||
#include "db/dbformat.h"
|
||||
@@ -75,6 +74,7 @@
|
||||
#include "port/port.h"
|
||||
#include "rocksdb/cache.h"
|
||||
#include "rocksdb/compaction_filter.h"
|
||||
#include "rocksdb/convenience.h"
|
||||
#include "rocksdb/db.h"
|
||||
#include "rocksdb/env.h"
|
||||
#include "rocksdb/merge_operator.h"
|
||||
@@ -149,24 +149,6 @@ void DumpSupportInfo(Logger* logger) {
|
||||
|
||||
ROCKS_LOG_HEADER(logger, "DMutex implementation: %s", DMutex::kName());
|
||||
}
|
||||
|
||||
// `start` is the inclusive lower user key bound without user-defined timestamp
|
||||
// `limit` is the exclusive upper user key bound without user-defined timestamp
|
||||
std::tuple<Slice, Slice> MaybeAddTimestampsToRange(const Slice& start,
|
||||
const Slice& limit,
|
||||
size_t ts_sz,
|
||||
std::string* start_with_ts,
|
||||
std::string* limit_with_ts) {
|
||||
if (ts_sz == 0) {
|
||||
return std::make_tuple(start, limit);
|
||||
}
|
||||
// Maximum timestamp means including all key with any timestamp
|
||||
AppendKeyWithMaxTimestamp(start_with_ts, start, ts_sz);
|
||||
// Append a maximum timestamp as the range limit is exclusive:
|
||||
// [start, limit)
|
||||
AppendKeyWithMaxTimestamp(limit_with_ts, limit, ts_sz);
|
||||
return std::make_tuple(Slice(*start_with_ts), Slice(*limit_with_ts));
|
||||
}
|
||||
} // namespace
|
||||
|
||||
DBImpl::DBImpl(const DBOptions& options, const std::string& dbname,
|
||||
@@ -201,7 +183,6 @@ DBImpl::DBImpl(const DBOptions& options, const std::string& dbname,
|
||||
batch_per_txn_(batch_per_txn),
|
||||
next_job_id_(1),
|
||||
shutting_down_(false),
|
||||
reject_new_background_jobs_(false),
|
||||
db_lock_(nullptr),
|
||||
manual_compaction_paused_(false),
|
||||
bg_cv_(&mutex_),
|
||||
@@ -277,9 +258,6 @@ 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());
|
||||
@@ -349,9 +327,6 @@ 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;
|
||||
@@ -393,7 +368,7 @@ Status DBImpl::ResumeImpl(DBRecoverContext context) {
|
||||
assert(cfh);
|
||||
ColumnFamilyData* cfd = cfh->cfd();
|
||||
const MutableCFOptions& cf_opts = *cfd->GetLatestMutableCFOptions();
|
||||
s = versions_->LogAndApply(cfd, cf_opts, read_options, &edit, &mutex_,
|
||||
s = versions_->LogAndApply(cfd, cf_opts, &edit, &mutex_,
|
||||
directories_.GetDbDir());
|
||||
if (!s.ok()) {
|
||||
io_s = versions_->io_status();
|
||||
@@ -411,7 +386,22 @@ Status DBImpl::ResumeImpl(DBRecoverContext context) {
|
||||
FlushOptions flush_opts;
|
||||
// We allow flush to stall write since we are trying to resume from error.
|
||||
flush_opts.allow_write_stall = true;
|
||||
s = FlushAllColumnFamilies(flush_opts, context.flush_reason);
|
||||
if (immutable_db_options_.atomic_flush) {
|
||||
mutex_.Unlock();
|
||||
s = AtomicFlushMemTables(flush_opts, context.flush_reason);
|
||||
mutex_.Lock();
|
||||
} else {
|
||||
for (auto cfd : versions_->GetRefedColumnFamilySet()) {
|
||||
if (cfd->IsDropped()) {
|
||||
continue;
|
||||
}
|
||||
InstrumentedMutexUnlock u(&mutex_);
|
||||
s = FlushMemTable(cfd, flush_opts, context.flush_reason);
|
||||
if (!s.ok()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!s.ok()) {
|
||||
ROCKS_LOG_INFO(immutable_db_options_.info_log,
|
||||
"DB resume requested but failed due to Flush failure [%s]",
|
||||
@@ -471,28 +461,6 @@ Status DBImpl::ResumeImpl(DBRecoverContext context) {
|
||||
if (shutdown_initiated_) {
|
||||
s = Status::ShutdownInProgress();
|
||||
}
|
||||
if (s.ok() && context.flush_after_recovery) {
|
||||
// Since we drop all non-recovery flush requests during recovery,
|
||||
// and new memtable may fill up during recovery,
|
||||
// schedule one more round of flush.
|
||||
FlushOptions flush_opts;
|
||||
flush_opts.allow_write_stall = false;
|
||||
flush_opts.wait = false;
|
||||
Status status = FlushAllColumnFamilies(
|
||||
flush_opts, FlushReason::kCatchUpAfterErrorRecovery);
|
||||
if (!status.ok()) {
|
||||
// FlushAllColumnFamilies internally should take care of setting
|
||||
// background error if needed.
|
||||
ROCKS_LOG_INFO(immutable_db_options_.info_log,
|
||||
"The catch up flush after successful recovery failed [%s]",
|
||||
s.ToString().c_str());
|
||||
}
|
||||
// FlushAllColumnFamilies releases and re-acquires mutex.
|
||||
if (shutdown_initiated_) {
|
||||
s = Status::ShutdownInProgress();
|
||||
}
|
||||
}
|
||||
|
||||
if (s.ok()) {
|
||||
for (auto cfd : *versions_->GetColumnFamilySet()) {
|
||||
SchedulePendingCompaction(cfd);
|
||||
@@ -520,15 +488,36 @@ void DBImpl::WaitForBackgroundWork() {
|
||||
void DBImpl::CancelAllBackgroundWork(bool wait) {
|
||||
ROCKS_LOG_INFO(immutable_db_options_.info_log,
|
||||
"Shutdown: canceling all background work");
|
||||
Status s = CancelPeriodicTaskScheduler();
|
||||
s.PermitUncheckedError();
|
||||
|
||||
for (uint8_t task_type = 0;
|
||||
task_type < static_cast<uint8_t>(PeriodicTaskType::kMax); task_type++) {
|
||||
Status s = periodic_task_scheduler_.Unregister(
|
||||
static_cast<PeriodicTaskType>(task_type));
|
||||
if (!s.ok()) {
|
||||
ROCKS_LOG_WARN(immutable_db_options_.info_log,
|
||||
"Failed to unregister periodic task %d, status: %s",
|
||||
task_type, s.ToString().c_str());
|
||||
}
|
||||
}
|
||||
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
if (!shutting_down_.load(std::memory_order_acquire) &&
|
||||
has_unpersisted_data_.load(std::memory_order_relaxed) &&
|
||||
!mutable_db_options_.avoid_flush_during_shutdown) {
|
||||
s = DBImpl::FlushAllColumnFamilies(FlushOptions(), FlushReason::kShutDown);
|
||||
s.PermitUncheckedError(); //**TODO: What to do on error?
|
||||
if (immutable_db_options_.atomic_flush) {
|
||||
mutex_.Unlock();
|
||||
Status s = AtomicFlushMemTables(FlushOptions(), FlushReason::kShutDown);
|
||||
s.PermitUncheckedError(); //**TODO: What to do on error?
|
||||
mutex_.Lock();
|
||||
} else {
|
||||
for (auto cfd : versions_->GetRefedColumnFamilySet()) {
|
||||
if (!cfd->IsDropped() && cfd->initialized() && !cfd->mem()->IsEmpty()) {
|
||||
InstrumentedMutexUnlock u(&mutex_);
|
||||
Status s = FlushMemTable(cfd, FlushOptions(), FlushReason::kShutDown);
|
||||
s.PermitUncheckedError(); //**TODO: What to do on error?
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
shutting_down_.store(true, std::memory_order_release);
|
||||
@@ -877,21 +866,6 @@ Status DBImpl::RegisterRecordSeqnoTimeWorker() {
|
||||
return s;
|
||||
}
|
||||
|
||||
Status DBImpl::CancelPeriodicTaskScheduler() {
|
||||
Status s = Status::OK();
|
||||
for (uint8_t task_type = 0;
|
||||
task_type < static_cast<uint8_t>(PeriodicTaskType::kMax); task_type++) {
|
||||
s = periodic_task_scheduler_.Unregister(
|
||||
static_cast<PeriodicTaskType>(task_type));
|
||||
if (!s.ok()) {
|
||||
ROCKS_LOG_WARN(immutable_db_options_.info_log,
|
||||
"Failed to unregister periodic task %d, status: %s",
|
||||
task_type, s.ToString().c_str());
|
||||
}
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
// esitmate the total size of stats_history_
|
||||
size_t DBImpl::EstimateInMemoryStatsHistorySize() const {
|
||||
size_t size_total =
|
||||
@@ -990,7 +964,7 @@ void DBImpl::PersistStats() {
|
||||
"Storing %" ROCKSDB_PRIszt " stats with timestamp %" PRIu64
|
||||
" to in-memory stats history",
|
||||
stats_slice_.size(), now_seconds);
|
||||
stats_history_[now_seconds] = std::move(stats_delta);
|
||||
stats_history_[now_seconds] = stats_delta;
|
||||
}
|
||||
stats_slice_initialized_ = true;
|
||||
std::swap(stats_slice_, stats_map);
|
||||
@@ -1173,8 +1147,6 @@ 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()) {
|
||||
@@ -1196,8 +1168,8 @@ Status DBImpl::SetOptions(
|
||||
new_options = *cfd->GetLatestMutableCFOptions();
|
||||
// Append new version to recompute compaction score.
|
||||
VersionEdit dummy_edit;
|
||||
s = versions_->LogAndApply(cfd, new_options, read_options, &dummy_edit,
|
||||
&mutex_, directories_.GetDbDir());
|
||||
s = versions_->LogAndApply(cfd, new_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.
|
||||
@@ -1535,9 +1507,7 @@ Status DBImpl::SyncWAL() {
|
||||
}
|
||||
if (status.ok() && synced_wals.IsWalAddition()) {
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
// TODO: plumb Env::IOActivity
|
||||
const ReadOptions read_options;
|
||||
status = ApplyWALToManifest(read_options, &synced_wals);
|
||||
status = ApplyWALToManifest(&synced_wals);
|
||||
}
|
||||
|
||||
TEST_SYNC_POINT("DBImpl::SyncWAL:BeforeMarkLogsSynced:2");
|
||||
@@ -1545,13 +1515,11 @@ Status DBImpl::SyncWAL() {
|
||||
return status;
|
||||
}
|
||||
|
||||
Status DBImpl::ApplyWALToManifest(const ReadOptions& read_options,
|
||||
VersionEdit* synced_wals) {
|
||||
Status DBImpl::ApplyWALToManifest(VersionEdit* synced_wals) {
|
||||
// not empty, write to MANIFEST.
|
||||
mutex_.AssertHeld();
|
||||
|
||||
Status status = versions_->LogAndApplyToDefaultColumnFamily(
|
||||
read_options, synced_wals, &mutex_, directories_.GetDbDir());
|
||||
synced_wals, &mutex_, directories_.GetDbDir());
|
||||
if (!status.ok() && versions_->io_status().IsIOError()) {
|
||||
status = error_handler_.SetBGError(versions_->io_status(),
|
||||
BackgroundErrorReason::kManifestWrite);
|
||||
@@ -1942,43 +1910,15 @@ Status DBImpl::Get(const ReadOptions& read_options,
|
||||
return Get(read_options, column_family, key, value, /*timestamp=*/nullptr);
|
||||
}
|
||||
|
||||
Status DBImpl::GetImpl(const ReadOptions& read_options,
|
||||
ColumnFamilyHandle* column_family, const Slice& key,
|
||||
PinnableSlice* value) {
|
||||
return GetImpl(read_options, column_family, key, value,
|
||||
/*timestamp=*/nullptr);
|
||||
}
|
||||
|
||||
Status DBImpl::Get(const ReadOptions& _read_options,
|
||||
Status DBImpl::Get(const ReadOptions& read_options,
|
||||
ColumnFamilyHandle* column_family, const Slice& key,
|
||||
PinnableSlice* value, std::string* timestamp) {
|
||||
assert(value != nullptr);
|
||||
value->Reset();
|
||||
|
||||
if (_read_options.io_activity != Env::IOActivity::kUnknown &&
|
||||
_read_options.io_activity != Env::IOActivity::kGet) {
|
||||
return Status::InvalidArgument(
|
||||
"Can only call Get with `ReadOptions::io_activity` is "
|
||||
"`Env::IOActivity::kUnknown` or `Env::IOActivity::kGet`");
|
||||
}
|
||||
|
||||
ReadOptions read_options(_read_options);
|
||||
if (read_options.io_activity == Env::IOActivity::kUnknown) {
|
||||
read_options.io_activity = Env::IOActivity::kGet;
|
||||
}
|
||||
|
||||
Status s = GetImpl(read_options, column_family, key, value, timestamp);
|
||||
return s;
|
||||
}
|
||||
|
||||
Status DBImpl::GetImpl(const ReadOptions& read_options,
|
||||
ColumnFamilyHandle* column_family, const Slice& key,
|
||||
PinnableSlice* value, std::string* timestamp) {
|
||||
GetImplOptions get_impl_options;
|
||||
get_impl_options.column_family = column_family;
|
||||
get_impl_options.value = value;
|
||||
get_impl_options.timestamp = timestamp;
|
||||
|
||||
Status s = GetImpl(read_options, key, get_impl_options);
|
||||
return s;
|
||||
}
|
||||
@@ -1996,12 +1936,6 @@ 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;
|
||||
@@ -2047,7 +1981,6 @@ Status DBImpl::GetImpl(const ReadOptions& read_options, const Slice& key,
|
||||
|
||||
assert(get_impl_options.column_family);
|
||||
|
||||
|
||||
if (read_options.timestamp) {
|
||||
const Status s = FailIfTsMismatchCf(get_impl_options.column_family,
|
||||
*(read_options.timestamp),
|
||||
@@ -2340,7 +2273,7 @@ std::vector<Status> DBImpl::MultiGet(
|
||||
}
|
||||
|
||||
std::vector<Status> DBImpl::MultiGet(
|
||||
const ReadOptions& _read_options,
|
||||
const ReadOptions& read_options,
|
||||
const std::vector<ColumnFamilyHandle*>& column_family,
|
||||
const std::vector<Slice>& keys, std::vector<std::string>* values,
|
||||
std::vector<std::string>* timestamps) {
|
||||
@@ -2352,30 +2285,12 @@ std::vector<Status> DBImpl::MultiGet(
|
||||
assert(column_family.size() == num_keys);
|
||||
std::vector<Status> stat_list(num_keys);
|
||||
|
||||
if (_read_options.io_activity != Env::IOActivity::kUnknown &&
|
||||
_read_options.io_activity != Env::IOActivity::kMultiGet) {
|
||||
Status s = Status::InvalidArgument(
|
||||
"Can only call MultiGet with `ReadOptions::io_activity` is "
|
||||
"`Env::IOActivity::kUnknown` or `Env::IOActivity::kMultiGet`");
|
||||
|
||||
for (size_t i = 0; i < num_keys; ++i) {
|
||||
stat_list[i] = s;
|
||||
}
|
||||
return stat_list;
|
||||
}
|
||||
|
||||
ReadOptions read_options(_read_options);
|
||||
if (read_options.io_activity == Env::IOActivity::kUnknown) {
|
||||
read_options.io_activity = Env::IOActivity::kMultiGet;
|
||||
}
|
||||
|
||||
bool should_fail = false;
|
||||
for (size_t i = 0; i < num_keys; ++i) {
|
||||
assert(column_family[i]);
|
||||
if (read_options.timestamp) {
|
||||
stat_list[i] =
|
||||
FailIfTsMismatchCf(column_family[i], *(read_options.timestamp),
|
||||
/*ts_for_read=*/true);
|
||||
stat_list[i] = FailIfTsMismatchCf(
|
||||
column_family[i], *(read_options.timestamp), /*ts_for_read=*/true);
|
||||
if (!stat_list[i].ok()) {
|
||||
should_fail = true;
|
||||
}
|
||||
@@ -2539,6 +2454,7 @@ 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;
|
||||
@@ -2688,26 +2604,10 @@ void DBImpl::MultiGet(const ReadOptions& read_options, const size_t num_keys,
|
||||
/* timestamps */ nullptr, statuses, sorted_input);
|
||||
}
|
||||
|
||||
void DBImpl::MultiGet(const ReadOptions& _read_options, const size_t num_keys,
|
||||
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) {
|
||||
if (_read_options.io_activity != Env::IOActivity::kUnknown &&
|
||||
_read_options.io_activity != Env::IOActivity::kMultiGet) {
|
||||
Status s = Status::InvalidArgument(
|
||||
"Can only call MultiGet with `ReadOptions::io_activity` is "
|
||||
"`Env::IOActivity::kUnknown` or `Env::IOActivity::kMultiGet`");
|
||||
for (size_t i = 0; i < num_keys; ++i) {
|
||||
if (statuses[i].ok()) {
|
||||
statuses[i] = s;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
ReadOptions read_options(_read_options);
|
||||
if (read_options.io_activity == Env::IOActivity::kUnknown) {
|
||||
read_options.io_activity = Env::IOActivity::kMultiGet;
|
||||
}
|
||||
MultiGetCommon(read_options, num_keys, column_families, keys, values,
|
||||
/* columns */ nullptr, timestamps, statuses, sorted_input);
|
||||
}
|
||||
@@ -2722,6 +2622,7 @@ void DBImpl::MultiGetCommon(const ReadOptions& read_options,
|
||||
if (num_keys == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
bool should_fail = false;
|
||||
for (size_t i = 0; i < num_keys; ++i) {
|
||||
ColumnFamilyHandle* cfh = column_families[i];
|
||||
@@ -2904,28 +2805,11 @@ void DBImpl::MultiGet(const ReadOptions& read_options,
|
||||
/* timestamps */ nullptr, statuses, sorted_input);
|
||||
}
|
||||
|
||||
void DBImpl::MultiGet(const ReadOptions& _read_options,
|
||||
void DBImpl::MultiGet(const ReadOptions& read_options,
|
||||
ColumnFamilyHandle* column_family, const size_t num_keys,
|
||||
const Slice* keys, PinnableSlice* values,
|
||||
std::string* timestamps, Status* statuses,
|
||||
const bool sorted_input) {
|
||||
if (_read_options.io_activity != Env::IOActivity::kUnknown &&
|
||||
_read_options.io_activity != Env::IOActivity::kMultiGet) {
|
||||
Status s = Status::InvalidArgument(
|
||||
"Can only call MultiGet with `ReadOptions::io_activity` is "
|
||||
"`Env::IOActivity::kUnknown` or `Env::IOActivity::kMultiGet`");
|
||||
for (size_t i = 0; i < num_keys; ++i) {
|
||||
if (statuses[i].ok()) {
|
||||
statuses[i] = s;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
ReadOptions read_options(_read_options);
|
||||
if (read_options.io_activity == Env::IOActivity::kUnknown) {
|
||||
read_options.io_activity = Env::IOActivity::kMultiGet;
|
||||
}
|
||||
MultiGetCommon(read_options, column_family, num_keys, keys, values,
|
||||
/* columns */ nullptr, timestamps, statuses, sorted_input);
|
||||
}
|
||||
@@ -2974,20 +2858,9 @@ void DBImpl::MultiGetCommon(const ReadOptions& read_options,
|
||||
}
|
||||
|
||||
void DBImpl::MultiGetWithCallback(
|
||||
const ReadOptions& _read_options, ColumnFamilyHandle* column_family,
|
||||
const ReadOptions& read_options, ColumnFamilyHandle* column_family,
|
||||
ReadCallback* callback,
|
||||
autovector<KeyContext*, MultiGetContext::MAX_BATCH_SIZE>* sorted_keys) {
|
||||
if (_read_options.io_activity != Env::IOActivity::kUnknown &&
|
||||
_read_options.io_activity != Env::IOActivity::kMultiGet) {
|
||||
assert(false);
|
||||
return;
|
||||
}
|
||||
|
||||
ReadOptions read_options(_read_options);
|
||||
if (read_options.io_activity == Env::IOActivity::kUnknown) {
|
||||
read_options.io_activity = Env::IOActivity::kMultiGet;
|
||||
}
|
||||
|
||||
std::array<MultiGetColumnFamilyData, 1> multiget_cf_data;
|
||||
multiget_cf_data[0] = MultiGetColumnFamilyData(column_family, nullptr);
|
||||
std::function<MultiGetColumnFamilyData*(
|
||||
@@ -3256,8 +3129,6 @@ 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;
|
||||
|
||||
@@ -3290,8 +3161,6 @@ Status DBImpl::CreateColumnFamilyImpl(const ColumnFamilyOptions& cf_options,
|
||||
edit.SetColumnFamily(new_id);
|
||||
edit.SetLogNumber(logfile_number_);
|
||||
edit.SetComparatorName(cf_options.comparator->Name());
|
||||
edit.SetPersistUserDefinedTimestamps(
|
||||
cf_options.persist_user_defined_timestamps);
|
||||
|
||||
// LogAndApply will both write the creation in MANIFEST and create
|
||||
// ColumnFamilyData object
|
||||
@@ -3300,9 +3169,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),
|
||||
read_options, &edit, &mutex_,
|
||||
directories_.GetDbDir(), false, &cf_options);
|
||||
s = versions_->LogAndApply(nullptr, MutableCFOptions(cf_options), &edit,
|
||||
&mutex_, directories_.GetDbDir(), false,
|
||||
&cf_options);
|
||||
write_thread_.ExitUnbatched(&w);
|
||||
}
|
||||
if (s.ok()) {
|
||||
@@ -3381,8 +3250,6 @@ 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) {
|
||||
@@ -3405,9 +3272,8 @@ 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(),
|
||||
read_options, &edit, &mutex_,
|
||||
directories_.GetDbDir());
|
||||
s = versions_->LogAndApply(cfd, *cfd->GetLatestMutableCFOptions(), &edit,
|
||||
&mutex_, directories_.GetDbDir());
|
||||
write_thread_.ExitUnbatched(&w);
|
||||
}
|
||||
if (s.ok()) {
|
||||
@@ -3458,13 +3324,10 @@ 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;
|
||||
@@ -3482,19 +3345,8 @@ bool DBImpl::KeyMayExist(const ReadOptions& read_options,
|
||||
return s.ok() || s.IsIncomplete();
|
||||
}
|
||||
|
||||
Iterator* DBImpl::NewIterator(const ReadOptions& _read_options,
|
||||
Iterator* DBImpl::NewIterator(const ReadOptions& read_options,
|
||||
ColumnFamilyHandle* column_family) {
|
||||
if (_read_options.io_activity != Env::IOActivity::kUnknown &&
|
||||
_read_options.io_activity != Env::IOActivity::kDBIterator) {
|
||||
return NewErrorIterator(Status::InvalidArgument(
|
||||
"Can only call NewIterator with `ReadOptions::io_activity` is "
|
||||
"`Env::IOActivity::kUnknown` or `Env::IOActivity::kDBIterator`"));
|
||||
}
|
||||
ReadOptions read_options(_read_options);
|
||||
if (read_options.io_activity == Env::IOActivity::kUnknown) {
|
||||
read_options.io_activity = Env::IOActivity::kDBIterator;
|
||||
}
|
||||
|
||||
if (read_options.managed) {
|
||||
return NewErrorIterator(
|
||||
Status::NotSupported("Managed iterator is not supported anymore."));
|
||||
@@ -3504,12 +3356,12 @@ Iterator* DBImpl::NewIterator(const ReadOptions& _read_options,
|
||||
return NewErrorIterator(Status::NotSupported(
|
||||
"ReadTier::kPersistedData is not yet supported in iterators."));
|
||||
}
|
||||
|
||||
assert(column_family);
|
||||
|
||||
if (read_options.timestamp) {
|
||||
const Status s =
|
||||
FailIfTsMismatchCf(column_family, *(read_options.timestamp),
|
||||
/*ts_for_read=*/true);
|
||||
const Status s = FailIfTsMismatchCf(
|
||||
column_family, *(read_options.timestamp), /*ts_for_read=*/true);
|
||||
if (!s.ok()) {
|
||||
return NewErrorIterator(s);
|
||||
}
|
||||
@@ -3629,19 +3481,9 @@ ArenaWrappedDBIter* DBImpl::NewIteratorImpl(const ReadOptions& read_options,
|
||||
}
|
||||
|
||||
Status DBImpl::NewIterators(
|
||||
const ReadOptions& _read_options,
|
||||
const ReadOptions& read_options,
|
||||
const std::vector<ColumnFamilyHandle*>& column_families,
|
||||
std::vector<Iterator*>* iterators) {
|
||||
if (_read_options.io_activity != Env::IOActivity::kUnknown &&
|
||||
_read_options.io_activity != Env::IOActivity::kDBIterator) {
|
||||
return Status::InvalidArgument(
|
||||
"Can only call NewIterators with `ReadOptions::io_activity` is "
|
||||
"`Env::IOActivity::kUnknown` or `Env::IOActivity::kDBIterator`");
|
||||
}
|
||||
ReadOptions read_options(_read_options);
|
||||
if (read_options.io_activity == Env::IOActivity::kUnknown) {
|
||||
read_options.io_activity = Env::IOActivity::kDBIterator;
|
||||
}
|
||||
if (read_options.managed) {
|
||||
return Status::NotSupported("Managed iterator is not supported anymore.");
|
||||
}
|
||||
@@ -3966,9 +3808,7 @@ Status DBImpl::GetPropertiesOfAllTables(ColumnFamilyHandle* column_family,
|
||||
version->Ref();
|
||||
mutex_.Unlock();
|
||||
|
||||
// TODO: plumb Env::IOActivity
|
||||
const ReadOptions read_options;
|
||||
auto s = version->GetPropertiesOfAllTables(read_options, props);
|
||||
auto s = version->GetPropertiesOfAllTables(props);
|
||||
|
||||
// Decrement the ref count
|
||||
mutex_.Lock();
|
||||
@@ -3990,9 +3830,7 @@ Status DBImpl::GetPropertiesOfTablesInRange(ColumnFamilyHandle* column_family,
|
||||
version->Ref();
|
||||
mutex_.Unlock();
|
||||
|
||||
// TODO: plumb Env::IOActivity
|
||||
const ReadOptions read_options;
|
||||
auto s = version->GetPropertiesOfTablesInRange(read_options, range, n, props);
|
||||
auto s = version->GetPropertiesOfTablesInRange(range, n, props);
|
||||
|
||||
// Decrement the ref count
|
||||
mutex_.Lock();
|
||||
@@ -4295,17 +4133,9 @@ void DBImpl::GetApproximateMemTableStats(ColumnFamilyHandle* column_family,
|
||||
ColumnFamilyData* cfd = cfh->cfd();
|
||||
SuperVersion* sv = GetAndRefSuperVersion(cfd);
|
||||
|
||||
const Comparator* const ucmp = column_family->GetComparator();
|
||||
assert(ucmp);
|
||||
size_t ts_sz = ucmp->timestamp_size();
|
||||
|
||||
// Add timestamp if needed
|
||||
std::string start_with_ts, limit_with_ts;
|
||||
auto [start, limit] = MaybeAddTimestampsToRange(
|
||||
range.start, range.limit, ts_sz, &start_with_ts, &limit_with_ts);
|
||||
// Convert user_key into a corresponding internal key.
|
||||
InternalKey k1(start, kMaxSequenceNumber, kValueTypeForSeek);
|
||||
InternalKey k2(limit, kMaxSequenceNumber, kValueTypeForSeek);
|
||||
InternalKey k1(range.start, kMaxSequenceNumber, kValueTypeForSeek);
|
||||
InternalKey k2(range.limit, kMaxSequenceNumber, kValueTypeForSeek);
|
||||
MemTable::MemTableStats memStats =
|
||||
sv->mem->ApproximateStats(k1.Encode(), k2.Encode());
|
||||
MemTable::MemTableStats immStats =
|
||||
@@ -4333,20 +4163,28 @@ 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;
|
||||
|
||||
// Add timestamp if needed
|
||||
std::string start_with_ts, limit_with_ts;
|
||||
auto [start, limit] = MaybeAddTimestampsToRange(
|
||||
range[i].start, range[i].limit, ts_sz, &start_with_ts, &limit_with_ts);
|
||||
if (ts_sz > 0) {
|
||||
// Maximum timestamp means including all key with any timestamp
|
||||
AppendKeyWithMaxTimestamp(&start_with_ts, start, ts_sz);
|
||||
// Append a maximum timestamp as the range limit is exclusive:
|
||||
// [start, limit)
|
||||
AppendKeyWithMaxTimestamp(&limit_with_ts, limit, ts_sz);
|
||||
start = start_with_ts;
|
||||
limit = limit_with_ts;
|
||||
}
|
||||
// Convert user_key into a corresponding internal key.
|
||||
InternalKey k1(start, kMaxSequenceNumber, kValueTypeForSeek);
|
||||
InternalKey k2(limit, kMaxSequenceNumber, kValueTypeForSeek);
|
||||
sizes[i] = 0;
|
||||
if (options.include_files) {
|
||||
sizes[i] += versions_->ApproximateSize(
|
||||
options, read_options, v, k1.Encode(), k2.Encode(), /*start_level=*/0,
|
||||
options, v, k1.Encode(), k2.Encode(), /*start_level=*/0,
|
||||
/*end_level=*/-1, TableReaderCaller::kUserApproximateSize);
|
||||
}
|
||||
if (options.include_memtables) {
|
||||
@@ -4394,8 +4232,6 @@ 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;
|
||||
@@ -4475,8 +4311,7 @@ Status DBImpl::DeleteFile(std::string name) {
|
||||
edit.SetColumnFamily(cfd->GetID());
|
||||
edit.DeleteFile(level, number);
|
||||
status = versions_->LogAndApply(cfd, *cfd->GetLatestMutableCFOptions(),
|
||||
read_options, &edit, &mutex_,
|
||||
directories_.GetDbDir());
|
||||
&edit, &mutex_, directories_.GetDbDir());
|
||||
if (status.ok()) {
|
||||
InstallSuperVersionAndScheduleWork(cfd,
|
||||
&job_context.superversion_contexts[0],
|
||||
@@ -4498,8 +4333,6 @@ 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();
|
||||
@@ -4555,20 +4388,17 @@ Status DBImpl::DeleteFilesInRanges(ColumnFamilyHandle* column_family,
|
||||
deleted_files.insert(level_file);
|
||||
level_file->being_compacted = true;
|
||||
}
|
||||
vstorage->ComputeCompactionScore(*cfd->ioptions(),
|
||||
*cfd->GetLatestMutableCFOptions());
|
||||
}
|
||||
}
|
||||
if (!deleted_files.empty()) {
|
||||
vstorage->ComputeCompactionScore(*cfd->ioptions(),
|
||||
*cfd->GetLatestMutableCFOptions());
|
||||
}
|
||||
if (edit.GetDeletedFiles().empty()) {
|
||||
job_context.Clean();
|
||||
return status;
|
||||
}
|
||||
input_version->Ref();
|
||||
status = versions_->LogAndApply(cfd, *cfd->GetLatestMutableCFOptions(),
|
||||
read_options, &edit, &mutex_,
|
||||
directories_.GetDbDir());
|
||||
&edit, &mutex_, directories_.GetDbDir());
|
||||
if (status.ok()) {
|
||||
InstallSuperVersionAndScheduleWork(cfd,
|
||||
&job_context.superversion_contexts[0],
|
||||
@@ -4634,6 +4464,7 @@ void DBImpl::GetAllColumnFamilyMetaData(
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Status DBImpl::CheckConsistency() {
|
||||
mutex_.AssertHeld();
|
||||
std::vector<LiveFileMetaData> metadata;
|
||||
@@ -5024,15 +4855,6 @@ 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();
|
||||
@@ -5212,7 +5034,6 @@ Status DBImpl::GetLatestSequenceForKey(
|
||||
MergeContext merge_context;
|
||||
SequenceNumber max_covering_tombstone_seq = 0;
|
||||
|
||||
// TODO: plumb Env::IOActivity
|
||||
ReadOptions read_options;
|
||||
SequenceNumber current_seq = versions_->LastSequence();
|
||||
|
||||
@@ -5368,8 +5189,6 @@ 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");
|
||||
}
|
||||
@@ -5587,9 +5406,9 @@ Status DBImpl::IngestExternalFiles(
|
||||
}
|
||||
assert(0 == num_entries);
|
||||
}
|
||||
status = versions_->LogAndApply(cfds_to_commit, mutable_cf_options_list,
|
||||
read_options, edit_lists, &mutex_,
|
||||
directories_.GetDbDir());
|
||||
status =
|
||||
versions_->LogAndApply(cfds_to_commit, mutable_cf_options_list,
|
||||
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.
|
||||
@@ -5687,24 +5506,12 @@ Status DBImpl::IngestExternalFiles(
|
||||
Status DBImpl::CreateColumnFamilyWithImport(
|
||||
const ColumnFamilyOptions& options, const std::string& column_family_name,
|
||||
const ImportColumnFamilyOptions& import_options,
|
||||
const std::vector<const ExportImportFilesMetaData*>& metadatas,
|
||||
ColumnFamilyHandle** handle) {
|
||||
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();
|
||||
|
||||
size_t total_file_num = 0;
|
||||
std::vector<std::vector<LiveFileMetaData*>> metadata_files(metadatas.size());
|
||||
for (size_t i = 0; i < metadatas.size(); i++) {
|
||||
if (cf_comparator_name != metadatas[i]->db_comparator_name) {
|
||||
return Status::InvalidArgument("Comparator name mismatch");
|
||||
}
|
||||
for (auto& file : metadatas[i]->files) {
|
||||
metadata_files[i].push_back((LiveFileMetaData*)&file);
|
||||
}
|
||||
total_file_num += metadatas[i]->files.size();
|
||||
if (cf_comparator_name != metadata.db_comparator_name) {
|
||||
return Status::InvalidArgument("Comparator name mismatch");
|
||||
}
|
||||
|
||||
// Create column family.
|
||||
@@ -5718,7 +5525,7 @@ Status DBImpl::CreateColumnFamilyWithImport(
|
||||
auto cfd = cfh->cfd();
|
||||
ImportColumnFamilyJob import_job(versions_.get(), cfd, immutable_db_options_,
|
||||
file_options_, import_options,
|
||||
metadata_files, io_tracer_);
|
||||
metadata.files, io_tracer_);
|
||||
|
||||
SuperVersionContext dummy_sv_ctx(/* create_superversion */ true);
|
||||
VersionEdit dummy_edit;
|
||||
@@ -5741,11 +5548,10 @@ Status DBImpl::CreateColumnFamilyWithImport(
|
||||
// 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.
|
||||
next_file_number = versions_->FetchAddFileNumber(total_file_num);
|
||||
next_file_number = versions_->FetchAddFileNumber(metadata.files.size());
|
||||
auto cf_options = cfd->GetLatestMutableCFOptions();
|
||||
status =
|
||||
versions_->LogAndApply(cfd, *cf_options, read_options, &dummy_edit,
|
||||
&mutex_, directories_.GetDbDir());
|
||||
status = versions_->LogAndApply(cfd, *cf_options, &dummy_edit, &mutex_,
|
||||
directories_.GetDbDir());
|
||||
if (status.ok()) {
|
||||
InstallSuperVersionAndScheduleWork(cfd, &dummy_sv_ctx, *cf_options);
|
||||
}
|
||||
@@ -5781,9 +5587,8 @@ 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, read_options,
|
||||
import_job.edit(), &mutex_,
|
||||
directories_.GetDbDir());
|
||||
status = versions_->LogAndApply(cfd, *cf_options, import_job.edit(),
|
||||
&mutex_, directories_.GetDbDir());
|
||||
if (status.ok()) {
|
||||
InstallSuperVersionAndScheduleWork(cfd, &sv_context, *cf_options);
|
||||
}
|
||||
@@ -5826,110 +5631,12 @@ 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);
|
||||
}
|
||||
|
||||
Status DBImpl::VerifyFileChecksums(const ReadOptions& _read_options) {
|
||||
if (_read_options.io_activity != Env::IOActivity::kUnknown &&
|
||||
_read_options.io_activity != Env::IOActivity::kVerifyFileChecksums) {
|
||||
return Status::InvalidArgument(
|
||||
"Can only call VerifyFileChecksums with `ReadOptions::io_activity` is "
|
||||
"`Env::IOActivity::kUnknown` or "
|
||||
"`Env::IOActivity::kVerifyFileChecksums`");
|
||||
}
|
||||
ReadOptions read_options(_read_options);
|
||||
if (read_options.io_activity == Env::IOActivity::kUnknown) {
|
||||
read_options.io_activity = Env::IOActivity::kVerifyFileChecksums;
|
||||
}
|
||||
return VerifyChecksumInternal(read_options,
|
||||
/*use_file_checksum=*/true);
|
||||
}
|
||||
|
||||
Status DBImpl::VerifyChecksum(const ReadOptions& _read_options) {
|
||||
if (_read_options.io_activity != Env::IOActivity::kUnknown &&
|
||||
_read_options.io_activity != Env::IOActivity::kVerifyDBChecksum) {
|
||||
return Status::InvalidArgument(
|
||||
"Can only call VerifyChecksum with `ReadOptions::io_activity` is "
|
||||
"`Env::IOActivity::kUnknown` or `Env::IOActivity::kVerifyDBChecksum`");
|
||||
}
|
||||
ReadOptions read_options(_read_options);
|
||||
if (read_options.io_activity == Env::IOActivity::kUnknown) {
|
||||
read_options.io_activity = Env::IOActivity::kVerifyDBChecksum;
|
||||
}
|
||||
return VerifyChecksumInternal(read_options,
|
||||
/*use_file_checksum=*/false);
|
||||
Status DBImpl::VerifyChecksum(const ReadOptions& read_options) {
|
||||
return VerifyChecksumInternal(read_options, /*use_file_checksum=*/false);
|
||||
}
|
||||
|
||||
Status DBImpl::VerifyChecksumInternal(const ReadOptions& read_options,
|
||||
@@ -5951,7 +5658,6 @@ Status DBImpl::VerifyChecksumInternal(const ReadOptions& read_options,
|
||||
return s;
|
||||
}
|
||||
}
|
||||
// FIXME? What does it mean if read_options.verify_checksums == false?
|
||||
|
||||
// TODO: simplify using GetRefedColumnFamilySet?
|
||||
std::vector<ColumnFamilyData*> cfd_list;
|
||||
@@ -5992,7 +5698,7 @@ Status DBImpl::VerifyChecksumInternal(const ReadOptions& read_options,
|
||||
fmeta->file_checksum_func_name, fname,
|
||||
read_options);
|
||||
} else {
|
||||
s = ROCKSDB_NAMESPACE::VerifySstFileChecksumInternal(
|
||||
s = ROCKSDB_NAMESPACE::VerifySstFileChecksum(
|
||||
opts, file_options_, read_options, fname, fd.largest_seqno);
|
||||
}
|
||||
RecordTick(stats_, VERIFY_CHECKSUM_READ_BYTES,
|
||||
@@ -6065,8 +5771,8 @@ Status DBImpl::VerifyFullFileChecksum(const std::string& file_checksum_expected,
|
||||
fs_.get(), fname, immutable_db_options_.file_checksum_gen_factory.get(),
|
||||
func_name_expected, &file_checksum, &func_name,
|
||||
read_options.readahead_size, immutable_db_options_.allow_mmap_reads,
|
||||
io_tracer_, immutable_db_options_.rate_limiter.get(), read_options,
|
||||
immutable_db_options_.stats, immutable_db_options_.clock);
|
||||
io_tracer_, immutable_db_options_.rate_limiter.get(),
|
||||
read_options.rate_limiter_priority);
|
||||
if (s.ok()) {
|
||||
assert(func_name_expected == func_name);
|
||||
if (file_checksum != file_checksum_expected) {
|
||||
@@ -6187,8 +5893,6 @@ 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);
|
||||
@@ -6206,8 +5910,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, read_options, &dummy_edit,
|
||||
&mutex_, directories_.GetDbDir());
|
||||
s = versions_->LogAndApply(cfd, *cf_options, &dummy_edit, &mutex_,
|
||||
directories_.GetDbDir());
|
||||
if (s.ok()) {
|
||||
InstallSuperVersionAndScheduleWork(cfd, &dummy_sv_ctx, *cf_options);
|
||||
}
|
||||
|
||||
+22
-63
@@ -234,7 +234,7 @@ class DBImpl : public DB {
|
||||
virtual Status Get(const ReadOptions& options,
|
||||
ColumnFamilyHandle* column_family, const Slice& key,
|
||||
PinnableSlice* value) override;
|
||||
virtual Status Get(const ReadOptions& _read_options,
|
||||
virtual Status Get(const ReadOptions& options,
|
||||
ColumnFamilyHandle* column_family, const Slice& key,
|
||||
PinnableSlice* value, std::string* timestamp) override;
|
||||
|
||||
@@ -265,7 +265,7 @@ class DBImpl : public DB {
|
||||
const std::vector<Slice>& keys,
|
||||
std::vector<std::string>* values) override;
|
||||
virtual std::vector<Status> MultiGet(
|
||||
const ReadOptions& _read_options,
|
||||
const ReadOptions& options,
|
||||
const std::vector<ColumnFamilyHandle*>& column_family,
|
||||
const std::vector<Slice>& keys, std::vector<std::string>* values,
|
||||
std::vector<std::string>* timestamps) override;
|
||||
@@ -280,9 +280,8 @@ class DBImpl : public DB {
|
||||
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& _read_options,
|
||||
ColumnFamilyHandle* column_family, const size_t num_keys,
|
||||
const Slice* keys, PinnableSlice* values,
|
||||
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;
|
||||
|
||||
@@ -290,13 +289,13 @@ class DBImpl : public DB {
|
||||
ColumnFamilyHandle** column_families, const Slice* keys,
|
||||
PinnableSlice* values, Status* statuses,
|
||||
const bool sorted_input = false) override;
|
||||
void MultiGet(const ReadOptions& _read_options, const size_t num_keys,
|
||||
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 MultiGetWithCallback(
|
||||
const ReadOptions& _read_options, ColumnFamilyHandle* column_family,
|
||||
const ReadOptions& options, ColumnFamilyHandle* column_family,
|
||||
ReadCallback* callback,
|
||||
autovector<KeyContext*, MultiGetContext::MAX_BATCH_SIZE>* sorted_keys);
|
||||
|
||||
@@ -337,10 +336,10 @@ class DBImpl : public DB {
|
||||
bool* value_found = nullptr) override;
|
||||
|
||||
using DB::NewIterator;
|
||||
virtual Iterator* NewIterator(const ReadOptions& _read_options,
|
||||
virtual Iterator* NewIterator(const ReadOptions& options,
|
||||
ColumnFamilyHandle* column_family) override;
|
||||
virtual Status NewIterators(
|
||||
const ReadOptions& _read_options,
|
||||
const ReadOptions& options,
|
||||
const std::vector<ColumnFamilyHandle*>& column_families,
|
||||
std::vector<Iterator*>* iterators) override;
|
||||
|
||||
@@ -532,14 +531,9 @@ class DBImpl : public DB {
|
||||
virtual Status CreateColumnFamilyWithImport(
|
||||
const ColumnFamilyOptions& options, const std::string& column_family_name,
|
||||
const ImportColumnFamilyOptions& import_options,
|
||||
const std::vector<const ExportImportFilesMetaData*>& metadatas,
|
||||
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;
|
||||
|
||||
@@ -628,14 +622,6 @@ class DBImpl : public DB {
|
||||
int* number_of_operands = nullptr;
|
||||
};
|
||||
|
||||
Status GetImpl(const ReadOptions& read_options,
|
||||
ColumnFamilyHandle* column_family, const Slice& key,
|
||||
PinnableSlice* value);
|
||||
|
||||
Status GetImpl(const ReadOptions& read_options,
|
||||
ColumnFamilyHandle* column_family, const Slice& key,
|
||||
PinnableSlice* value, std::string* timestamp);
|
||||
|
||||
// Function that Get and KeyMayExist call with no_io true or false
|
||||
// Note: 'value_found' from KeyMayExist propagates here
|
||||
// This function is also called by GetMergeOperands
|
||||
@@ -748,17 +734,13 @@ 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,
|
||||
int* final_output_level = nullptr);
|
||||
const std::string& trim_ts);
|
||||
|
||||
// Return an internal iterator over the current state of the database.
|
||||
// The keys of this iterator are internal keys (see format.h).
|
||||
@@ -820,8 +802,6 @@ class DBImpl : public DB {
|
||||
// being deleted.
|
||||
uint64_t MinObsoleteSstNumberToKeep();
|
||||
|
||||
uint64_t GetObsoleteSstFilesSize();
|
||||
|
||||
// Returns the list of live files in 'live' and the list
|
||||
// of all files in the filesystem in 'candidate_files'.
|
||||
// If force == false and the last call was less than
|
||||
@@ -1069,8 +1049,10 @@ class DBImpl : public DB {
|
||||
|
||||
VersionSet* GetVersionSet() const { return versions_.get(); }
|
||||
|
||||
Status WaitForCompact(
|
||||
const WaitForCompactOptions& wait_for_compact_options) override;
|
||||
// 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);
|
||||
|
||||
#ifndef NDEBUG
|
||||
// Compact any files in the named level that overlap [*begin, *end]
|
||||
@@ -1109,9 +1091,10 @@ class DBImpl : public DB {
|
||||
// Wait for memtable compaction
|
||||
Status TEST_WaitForFlushMemTable(ColumnFamilyHandle* column_family = nullptr);
|
||||
|
||||
Status TEST_WaitForCompact();
|
||||
Status TEST_WaitForCompact(
|
||||
const WaitForCompactOptions& wait_for_compact_options);
|
||||
// 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);
|
||||
|
||||
// Wait for any background purge
|
||||
Status TEST_WaitForPurge();
|
||||
@@ -1349,11 +1332,6 @@ class DBImpl : public DB {
|
||||
|
||||
std::atomic<bool> shutting_down_;
|
||||
|
||||
// No new background jobs can be queued if true. This is used to prevent new
|
||||
// background jobs from being queued after WaitForCompact() completes waiting
|
||||
// all background jobs then attempts to close when close_db_ option is true.
|
||||
bool reject_new_background_jobs_;
|
||||
|
||||
// RecoveryContext struct stores the context about version edits along
|
||||
// with corresponding column_family_data and column_family_options.
|
||||
class RecoveryContext {
|
||||
@@ -1426,9 +1404,6 @@ class DBImpl : public DB {
|
||||
void NotifyOnExternalFileIngested(
|
||||
ColumnFamilyData* cfd, const ExternalSstFileIngestionJob& ingestion_job);
|
||||
|
||||
Status FlushAllColumnFamilies(const FlushOptions& flush_options,
|
||||
FlushReason flush_reason);
|
||||
|
||||
virtual Status FlushForGetLiveFiles();
|
||||
|
||||
void NewThreadStatusCfInfo(ColumnFamilyData* cfd) const;
|
||||
@@ -1580,14 +1555,12 @@ class DBImpl : public DB {
|
||||
friend class ForwardIterator;
|
||||
friend struct SuperVersion;
|
||||
friend class CompactedDBImpl;
|
||||
#ifndef NDEBUG
|
||||
friend class DBTest_ConcurrentFlushWAL_Test;
|
||||
friend class DBTest_MixedSlowdownOptionsStop_Test;
|
||||
friend class DBCompactionTest_CompactBottomLevelFilesWithDeletions_Test;
|
||||
friend class DBCompactionTest_CompactionDuringShutdown_Test;
|
||||
friend class DBCompactionTest_DelayCompactBottomLevelFilesWithDeletions_Test;
|
||||
friend class DBCompactionTest_DisableCompactBottomLevelFiles_Test;
|
||||
friend class StatsHistoryTest_PersistentStatsCreateColumnFamilies_Test;
|
||||
#ifndef NDEBUG
|
||||
friend class DBTest2_ReadCallbackTest_Test;
|
||||
friend class WriteCallbackPTest_WriteWithCallbackTest_Test;
|
||||
friend class XFTransactionWriteHandler;
|
||||
@@ -2079,10 +2052,6 @@ class DBImpl : public DB {
|
||||
// flush is considered complete.
|
||||
std::unordered_map<ColumnFamilyData*, uint64_t>
|
||||
cfd_to_max_mem_id_to_persist;
|
||||
|
||||
#ifndef NDEBUG
|
||||
int reschedule_count = 1;
|
||||
#endif /* !NDEBUG */
|
||||
};
|
||||
|
||||
void GenerateFlushRequest(const autovector<ColumnFamilyData*>& cfds,
|
||||
@@ -2111,7 +2080,6 @@ class DBImpl : public DB {
|
||||
Env::Priority thread_pri);
|
||||
Status BackgroundFlush(bool* madeProgress, JobContext* job_context,
|
||||
LogBuffer* log_buffer, FlushReason* reason,
|
||||
bool* flush_rescheduled_to_retain_udt,
|
||||
Env::Priority thread_pri);
|
||||
|
||||
bool EnoughRoomForCompaction(ColumnFamilyData* cfd,
|
||||
@@ -2124,18 +2092,9 @@ class DBImpl : public DB {
|
||||
std::unique_ptr<TaskLimiterToken>* token,
|
||||
LogBuffer* log_buffer);
|
||||
|
||||
// Return true if the `FlushRequest` can be rescheduled to retain the UDT.
|
||||
// Only true if there are user-defined timestamps in the involved MemTables
|
||||
// with newer than cutoff timestamp `full_history_ts_low` and not flushing
|
||||
// immediately will not cause entering write stall mode.
|
||||
bool ShouldRescheduleFlushRequestToRetainUDT(const FlushRequest& flush_req);
|
||||
|
||||
// Schedule background tasks
|
||||
Status StartPeriodicTaskScheduler();
|
||||
|
||||
// Cancel scheduled periodic tasks
|
||||
Status CancelPeriodicTaskScheduler();
|
||||
|
||||
Status RegisterRecordSeqnoTimeWorker();
|
||||
|
||||
void PrintStatistics();
|
||||
@@ -2164,7 +2123,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(const ReadOptions& read_options, VersionEdit* edit);
|
||||
Status ApplyWALToManifest(VersionEdit* edit);
|
||||
// WALs with log number up to up_to are not synced successfully.
|
||||
void MarkLogsNotSynced(uint64_t up_to);
|
||||
|
||||
@@ -2219,7 +2178,7 @@ class DBImpl : public DB {
|
||||
void BuildCompactionJobInfo(const ColumnFamilyData* cfd, Compaction* c,
|
||||
const Status& st,
|
||||
const CompactionJobStats& compaction_job_stats,
|
||||
const int job_id,
|
||||
const int job_id, const Version* current,
|
||||
CompactionJobInfo* compaction_job_info) const;
|
||||
// Reserve the next 'num' file numbers for to-be-ingested external SST files,
|
||||
// and return the current file_number in 'next_file_number'.
|
||||
@@ -2347,7 +2306,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 deadlock, if needed to acquire both log_write_mutex_ and
|
||||
// Note: to avoid dealock, 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_;
|
||||
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -178,12 +178,9 @@ Status DBImpl::TEST_WaitForFlushMemTable(ColumnFamilyHandle* column_family) {
|
||||
return WaitForFlushMemTable(cfd, nullptr, false);
|
||||
}
|
||||
|
||||
Status DBImpl::TEST_WaitForCompact() {
|
||||
return WaitForCompact(WaitForCompactOptions());
|
||||
}
|
||||
Status DBImpl::TEST_WaitForCompact(
|
||||
const WaitForCompactOptions& wait_for_compact_options) {
|
||||
return WaitForCompact(wait_for_compact_options);
|
||||
Status DBImpl::TEST_WaitForCompact(bool wait_unscheduled) {
|
||||
// Wait until the compaction completes
|
||||
return WaitForCompact(wait_unscheduled);
|
||||
}
|
||||
|
||||
Status DBImpl::TEST_WaitForPurge() {
|
||||
|
||||
@@ -61,8 +61,7 @@ Status DBImpl::PromoteL0(ColumnFamilyHandle* column_family, int target_level) {
|
||||
"PromoteL0 FAILED. Invalid target level %d\n", target_level);
|
||||
return Status::InvalidArgument("Invalid target level");
|
||||
}
|
||||
// TODO: plumb Env::IOActivity
|
||||
const ReadOptions read_options;
|
||||
|
||||
Status status;
|
||||
VersionEdit edit;
|
||||
JobContext job_context(next_job_id_.fetch_add(1), true);
|
||||
@@ -138,13 +137,11 @@ Status DBImpl::PromoteL0(ColumnFamilyHandle* column_family, int target_level) {
|
||||
f->oldest_blob_file_number, f->oldest_ancester_time,
|
||||
f->file_creation_time, f->epoch_number, f->file_checksum,
|
||||
f->file_checksum_func_name, f->unique_id,
|
||||
f->compensated_range_deletion_size, f->tail_size,
|
||||
f->user_defined_timestamps_persisted);
|
||||
f->compensated_range_deletion_size);
|
||||
}
|
||||
|
||||
status = versions_->LogAndApply(cfd, *cfd->GetLatestMutableCFOptions(),
|
||||
read_options, &edit, &mutex_,
|
||||
directories_.GetDbDir());
|
||||
&edit, &mutex_, directories_.GetDbDir());
|
||||
if (status.ok()) {
|
||||
InstallSuperVersionAndScheduleWork(cfd,
|
||||
&job_context.superversion_contexts[0],
|
||||
|
||||
@@ -35,11 +35,6 @@ uint64_t DBImpl::MinObsoleteSstNumberToKeep() {
|
||||
return std::numeric_limits<uint64_t>::max();
|
||||
}
|
||||
|
||||
uint64_t DBImpl::GetObsoleteSstFilesSize() {
|
||||
mutex_.AssertHeld();
|
||||
return versions_->GetObsoleteSstFilesSize();
|
||||
}
|
||||
|
||||
Status DBImpl::DisableFileDeletions() {
|
||||
Status s;
|
||||
int my_disable_delete_obsolete_files;
|
||||
@@ -291,7 +286,6 @@ void DBImpl::FindObsoleteFiles(JobContext* job_context, bool force,
|
||||
return;
|
||||
}
|
||||
|
||||
bool mutex_unlocked = false;
|
||||
if (!alive_log_files_.empty() && !logs_.empty()) {
|
||||
uint64_t min_log_number = job_context->log_number;
|
||||
size_t num_alive_log_files = alive_log_files_.size();
|
||||
@@ -321,7 +315,6 @@ void DBImpl::FindObsoleteFiles(JobContext* job_context, bool force,
|
||||
}
|
||||
log_write_mutex_.Unlock();
|
||||
mutex_.Unlock();
|
||||
mutex_unlocked = true;
|
||||
TEST_SYNC_POINT_CALLBACK("FindObsoleteFiles::PostMutexUnlock", nullptr);
|
||||
log_write_mutex_.Lock();
|
||||
while (!logs_.empty() && logs_.front().number < min_log_number) {
|
||||
@@ -344,9 +337,7 @@ void DBImpl::FindObsoleteFiles(JobContext* job_context, bool force,
|
||||
|
||||
logs_to_free_.clear();
|
||||
log_write_mutex_.Unlock();
|
||||
if (mutex_unlocked) {
|
||||
mutex_.Lock();
|
||||
}
|
||||
mutex_.Lock();
|
||||
job_context->log_recycle_files.assign(log_recycle_files_.begin(),
|
||||
log_recycle_files_.end());
|
||||
}
|
||||
|
||||
+21
-158
@@ -19,13 +19,11 @@
|
||||
#include "file/writable_file_writer.h"
|
||||
#include "logging/logging.h"
|
||||
#include "monitoring/persistent_stats_history.h"
|
||||
#include "monitoring/thread_status_util.h"
|
||||
#include "options/options_helper.h"
|
||||
#include "rocksdb/table.h"
|
||||
#include "rocksdb/wal_filter.h"
|
||||
#include "test_util/sync_point.h"
|
||||
#include "util/rate_limiter_impl.h"
|
||||
#include "util/udt_util.h"
|
||||
#include "util/rate_limiter.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
Options SanitizeOptions(const std::string& dbname, const Options& src,
|
||||
@@ -143,6 +141,11 @@ DBOptions SanitizeOptions(const std::string& dbname, const DBOptions& src,
|
||||
result.wal_dir = result.wal_dir.substr(0, result.wal_dir.size() - 1);
|
||||
}
|
||||
|
||||
if (result.use_direct_reads && result.compaction_readahead_size == 0) {
|
||||
TEST_SYNC_POINT_CALLBACK("SanitizeOptions:direct_io", nullptr);
|
||||
result.compaction_readahead_size = 1024 * 1024 * 2;
|
||||
}
|
||||
|
||||
// Force flush on DB open if 2PC is enabled, since with 2PC we have no
|
||||
// guarantee that consecutive log files have consecutive sequence id, which
|
||||
// make recovery complicated.
|
||||
@@ -530,104 +533,6 @@ Status DBImpl::Recover(
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
if (s.ok() && !read_only) {
|
||||
for (auto cfd : *versions_->GetColumnFamilySet()) {
|
||||
// Try to trivially move files down the LSM tree to start from bottommost
|
||||
// level when level_compaction_dynamic_level_bytes is enabled. This should
|
||||
// only be useful when user is migrating to turning on this option.
|
||||
// If a user is migrating from Level Compaction with a smaller level
|
||||
// multiplier or from Universal Compaction, there may be too many
|
||||
// non-empty levels and the trivial moves here are not sufficed for
|
||||
// migration. Additional compactions are needed to drain unnecessary
|
||||
// levels.
|
||||
//
|
||||
// Note that this step moves files down LSM without consulting
|
||||
// SSTPartitioner. Further compactions are still needed if
|
||||
// the user wants to partition SST files.
|
||||
// Note that files moved in this step may not respect the compression
|
||||
// option in target level.
|
||||
if (cfd->ioptions()->compaction_style ==
|
||||
CompactionStyle::kCompactionStyleLevel &&
|
||||
cfd->ioptions()->level_compaction_dynamic_level_bytes &&
|
||||
!cfd->GetLatestMutableCFOptions()->disable_auto_compactions) {
|
||||
int to_level = cfd->ioptions()->num_levels - 1;
|
||||
// last level is reserved
|
||||
// allow_ingest_behind does not support Level Compaction,
|
||||
// and per_key_placement can have infinite compaction loop for Level
|
||||
// Compaction. Adjust to_level here just to be safe.
|
||||
if (cfd->ioptions()->allow_ingest_behind ||
|
||||
cfd->ioptions()->preclude_last_level_data_seconds > 0) {
|
||||
to_level -= 1;
|
||||
}
|
||||
// Whether this column family has a level trivially moved
|
||||
bool moved = false;
|
||||
// Fill the LSM starting from to_level and going up one level at a time.
|
||||
// Some loop invariants (when last level is not reserved):
|
||||
// - levels in (from_level, to_level] are empty, and
|
||||
// - levels in (to_level, last_level] are non-empty.
|
||||
for (int from_level = to_level; from_level >= 0; --from_level) {
|
||||
const std::vector<FileMetaData*>& level_files =
|
||||
cfd->current()->storage_info()->LevelFiles(from_level);
|
||||
if (level_files.empty() || from_level == 0) {
|
||||
continue;
|
||||
}
|
||||
assert(from_level <= to_level);
|
||||
// Trivial move files from `from_level` to `to_level`
|
||||
if (from_level < to_level) {
|
||||
if (!moved) {
|
||||
// lsm_state will look like "[1,2,3,4,5,6,0]" for an LSM with
|
||||
// 7 levels
|
||||
std::string lsm_state = "[";
|
||||
for (int i = 0; i < cfd->ioptions()->num_levels; ++i) {
|
||||
lsm_state += std::to_string(
|
||||
cfd->current()->storage_info()->NumLevelFiles(i));
|
||||
if (i < cfd->ioptions()->num_levels - 1) {
|
||||
lsm_state += ",";
|
||||
}
|
||||
}
|
||||
lsm_state += "]";
|
||||
ROCKS_LOG_WARN(immutable_db_options_.info_log,
|
||||
"[%s] Trivially move files down the LSM when open "
|
||||
"with level_compaction_dynamic_level_bytes=true,"
|
||||
" lsm_state: %s (Files are moved only if DB "
|
||||
"Recovery is successful).",
|
||||
cfd->GetName().c_str(), lsm_state.c_str());
|
||||
moved = true;
|
||||
}
|
||||
ROCKS_LOG_WARN(
|
||||
immutable_db_options_.info_log,
|
||||
"[%s] Moving %zu files from from_level-%d to from_level-%d",
|
||||
cfd->GetName().c_str(), level_files.size(), from_level,
|
||||
to_level);
|
||||
VersionEdit edit;
|
||||
edit.SetColumnFamily(cfd->GetID());
|
||||
for (const FileMetaData* f : level_files) {
|
||||
edit.DeleteFile(from_level, f->fd.GetNumber());
|
||||
edit.AddFile(to_level, f->fd.GetNumber(), f->fd.GetPathId(),
|
||||
f->fd.GetFileSize(), f->smallest, f->largest,
|
||||
f->fd.smallest_seqno, f->fd.largest_seqno,
|
||||
f->marked_for_compaction,
|
||||
f->temperature, // this can be different from
|
||||
// `last_level_temperature`
|
||||
f->oldest_blob_file_number, f->oldest_ancester_time,
|
||||
f->file_creation_time, f->epoch_number,
|
||||
f->file_checksum, f->file_checksum_func_name,
|
||||
f->unique_id, f->compensated_range_deletion_size,
|
||||
f->tail_size, f->user_defined_timestamps_persisted);
|
||||
ROCKS_LOG_WARN(immutable_db_options_.info_log,
|
||||
"[%s] Moving #%" PRIu64
|
||||
" from from_level-%d to from_level-%d %" PRIu64
|
||||
" bytes\n",
|
||||
cfd->GetName().c_str(), f->fd.GetNumber(),
|
||||
from_level, to_level, f->fd.GetFileSize());
|
||||
}
|
||||
recovery_ctx->UpdateVersionEdits(cfd, edit);
|
||||
}
|
||||
--to_level;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
s = SetupDBId(read_only, recovery_ctx);
|
||||
ROCKS_LOG_INFO(immutable_db_options_.info_log, "DB ID: %s\n", db_id_.c_str());
|
||||
if (s.ok() && !read_only) {
|
||||
@@ -921,9 +826,8 @@ Status DBImpl::InitPersistStatsColumnFamily() {
|
||||
Status DBImpl::LogAndApplyForRecovery(const RecoveryContext& recovery_ctx) {
|
||||
mutex_.AssertHeld();
|
||||
assert(versions_->descriptor_log_ == nullptr);
|
||||
const ReadOptions read_options(Env::IOActivity::kDBOpen);
|
||||
Status s = versions_->LogAndApply(
|
||||
recovery_ctx.cfds_, recovery_ctx.mutable_cf_opts_, read_options,
|
||||
recovery_ctx.cfds_, recovery_ctx.mutable_cf_opts_,
|
||||
recovery_ctx.edit_lists_, &mutex_, directories_.GetDbDir());
|
||||
if (s.ok() && !(recovery_ctx.files_to_delete_.empty())) {
|
||||
mutex_.Unlock();
|
||||
@@ -1182,9 +1086,6 @@ Status DBImpl::RecoverLogFiles(const std::vector<uint64_t>& wal_numbers,
|
||||
std::string scratch;
|
||||
Slice record;
|
||||
|
||||
const UnorderedMap<uint32_t, size_t>& running_ts_sz =
|
||||
versions_->GetRunningColumnFamiliesTimestampSize();
|
||||
|
||||
TEST_SYNC_POINT_CALLBACK("DBImpl::RecoverLogFiles:BeforeReadWal",
|
||||
/*arg=*/nullptr);
|
||||
uint64_t record_checksum;
|
||||
@@ -1198,41 +1099,27 @@ Status DBImpl::RecoverLogFiles(const std::vector<uint64_t>& wal_numbers,
|
||||
Status::Corruption("log record too small"));
|
||||
continue;
|
||||
}
|
||||
|
||||
// We create a new batch and initialize with a valid prot_info_ to store
|
||||
// the data checksums
|
||||
WriteBatch batch;
|
||||
std::unique_ptr<WriteBatch> new_batch;
|
||||
|
||||
status = WriteBatchInternal::SetContents(&batch, record);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
|
||||
const UnorderedMap<uint32_t, size_t>& record_ts_sz =
|
||||
reader.GetRecordedTimestampSize();
|
||||
status = HandleWriteBatchTimestampSizeDifference(
|
||||
&batch, running_ts_sz, record_ts_sz,
|
||||
TimestampSizeConsistencyMode::kReconcileInconsistency, &new_batch);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
|
||||
bool batch_updated = new_batch != nullptr;
|
||||
WriteBatch* batch_to_use = batch_updated ? new_batch.get() : &batch;
|
||||
TEST_SYNC_POINT_CALLBACK(
|
||||
"DBImpl::RecoverLogFiles:BeforeUpdateProtectionInfo:batch",
|
||||
batch_to_use);
|
||||
"DBImpl::RecoverLogFiles:BeforeUpdateProtectionInfo:batch", &batch);
|
||||
TEST_SYNC_POINT_CALLBACK(
|
||||
"DBImpl::RecoverLogFiles:BeforeUpdateProtectionInfo:checksum",
|
||||
&record_checksum);
|
||||
status = WriteBatchInternal::UpdateProtectionInfo(
|
||||
batch_to_use, 8 /* bytes_per_key */,
|
||||
batch_updated ? nullptr : &record_checksum);
|
||||
&batch, 8 /* bytes_per_key */, &record_checksum);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
|
||||
SequenceNumber sequence = WriteBatchInternal::Sequence(batch_to_use);
|
||||
SequenceNumber sequence = WriteBatchInternal::Sequence(&batch);
|
||||
|
||||
if (immutable_db_options_.wal_recovery_mode ==
|
||||
WALRecoveryMode::kPointInTimeRecovery) {
|
||||
@@ -1253,7 +1140,7 @@ Status DBImpl::RecoverLogFiles(const std::vector<uint64_t>& wal_numbers,
|
||||
// and returns true.
|
||||
if (!InvokeWalFilterIfNeededOnWalRecord(wal_number, fname, reporter,
|
||||
status, stop_replay_by_wal_filter,
|
||||
*batch_to_use)) {
|
||||
batch)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1264,7 +1151,7 @@ Status DBImpl::RecoverLogFiles(const std::vector<uint64_t>& wal_numbers,
|
||||
// That's why we set ignore missing column families to true
|
||||
bool has_valid_writes = false;
|
||||
status = WriteBatchInternal::InsertInto(
|
||||
batch_to_use, column_family_memtables_.get(), &flush_scheduler_,
|
||||
&batch, column_family_memtables_.get(), &flush_scheduler_,
|
||||
&trim_history_scheduler_, true, wal_number, this,
|
||||
false /* concurrent_memtable_writes */, next_sequence,
|
||||
&has_valid_writes, seq_per_batch_, batch_per_txn_);
|
||||
@@ -1467,7 +1354,7 @@ Status DBImpl::RecoverLogFiles(const std::vector<uint64_t>& wal_numbers,
|
||||
recovery_ctx->UpdateVersionEdits(cfd, iter->second);
|
||||
}
|
||||
|
||||
if (flushed || !data_seen) {
|
||||
if (flushed) {
|
||||
VersionEdit wal_deletion;
|
||||
if (immutable_db_options_.track_and_verify_wals_in_manifest) {
|
||||
wal_deletion.DeleteWalsBefore(max_wal_number + 1);
|
||||
@@ -1593,7 +1480,6 @@ Status DBImpl::WriteLevel0TableForRecovery(int job_id, ColumnFamilyData* cfd,
|
||||
meta.fd = FileDescriptor(versions_->NewFileNumber(), 0, 0);
|
||||
ReadOptions ro;
|
||||
ro.total_order_seek = true;
|
||||
ro.io_activity = Env::IOActivity::kDBOpen;
|
||||
Arena arena;
|
||||
Status s;
|
||||
TableProperties table_properties;
|
||||
@@ -1652,19 +1538,16 @@ Status DBImpl::WriteLevel0TableForRecovery(int job_id, ColumnFamilyData* cfd,
|
||||
SeqnoToTimeMapping empty_seqno_time_mapping;
|
||||
Version* version = cfd->current();
|
||||
version->Ref();
|
||||
const ReadOptions read_option(Env::IOActivity::kDBOpen);
|
||||
uint64_t num_input_entries = 0;
|
||||
s = BuildTable(
|
||||
dbname_, versions_.get(), immutable_db_options_, tboptions,
|
||||
file_options_for_compaction_, read_option, cfd->table_cache(),
|
||||
iter.get(), std::move(range_del_iters), &meta, &blob_file_additions,
|
||||
file_options_for_compaction_, cfd->table_cache(), iter.get(),
|
||||
std::move(range_del_iters), &meta, &blob_file_additions,
|
||||
snapshot_seqs, earliest_write_conflict_snapshot, kMaxSequenceNumber,
|
||||
snapshot_checker, paranoid_file_checks, cfd->internal_stats(), &io_s,
|
||||
io_tracer_, BlobFileCreationReason::kRecovery,
|
||||
empty_seqno_time_mapping, &event_logger_, job_id, Env::IO_HIGH,
|
||||
nullptr /* table_properties */, write_hint,
|
||||
nullptr /*full_history_ts_low*/, &blob_callback_, version,
|
||||
&num_input_entries);
|
||||
nullptr /*full_history_ts_low*/, &blob_callback_, version);
|
||||
version->Unref();
|
||||
LogFlush(immutable_db_options_.info_log);
|
||||
ROCKS_LOG_DEBUG(immutable_db_options_.info_log,
|
||||
@@ -1678,19 +1561,6 @@ Status DBImpl::WriteLevel0TableForRecovery(int job_id, ColumnFamilyData* cfd,
|
||||
if (!io_s.ok() && s.ok()) {
|
||||
s = io_s;
|
||||
}
|
||||
|
||||
uint64_t total_num_entries = mem->num_entries();
|
||||
if (s.ok() && total_num_entries != num_input_entries) {
|
||||
std::string msg = "Expected " + std::to_string(total_num_entries) +
|
||||
" entries in memtable, but read " +
|
||||
std::to_string(num_input_entries);
|
||||
ROCKS_LOG_WARN(immutable_db_options_.info_log,
|
||||
"[%s] [JOB %d] Level-0 flush during recover: %s",
|
||||
cfd->GetName().c_str(), job_id, msg.c_str());
|
||||
if (immutable_db_options_.flush_verify_memtable_count) {
|
||||
s = Status::Corruption(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ReleaseFileNumberFromPendingOutputs(pending_outputs_inserted_elem);
|
||||
@@ -1709,8 +1579,7 @@ Status DBImpl::WriteLevel0TableForRecovery(int job_id, ColumnFamilyData* cfd,
|
||||
meta.oldest_blob_file_number, meta.oldest_ancester_time,
|
||||
meta.file_creation_time, meta.epoch_number,
|
||||
meta.file_checksum, meta.file_checksum_func_name,
|
||||
meta.unique_id, meta.compensated_range_deletion_size,
|
||||
meta.tail_size, meta.user_defined_timestamps_persisted);
|
||||
meta.unique_id, meta.compensated_range_deletion_size);
|
||||
|
||||
for (const auto& blob : blob_file_additions) {
|
||||
edit->AddBlobFile(blob);
|
||||
@@ -1773,12 +1642,8 @@ Status DB::Open(const DBOptions& db_options, const std::string& dbname,
|
||||
std::vector<ColumnFamilyHandle*>* handles, DB** dbptr) {
|
||||
const bool kSeqPerBatch = true;
|
||||
const bool kBatchPerTxn = true;
|
||||
ThreadStatusUtil::SetEnableTracking(db_options.enable_thread_tracking);
|
||||
ThreadStatusUtil::SetThreadOperation(ThreadStatus::OperationType::OP_DBOPEN);
|
||||
Status s = DBImpl::Open(db_options, dbname, column_families, handles, dbptr,
|
||||
!kSeqPerBatch, kBatchPerTxn);
|
||||
ThreadStatusUtil::ResetThreadStatus();
|
||||
return s;
|
||||
return DBImpl::Open(db_options, dbname, column_families, handles, dbptr,
|
||||
!kSeqPerBatch, kBatchPerTxn);
|
||||
}
|
||||
|
||||
// TODO: Implement the trimming in flush code path.
|
||||
@@ -1963,9 +1828,7 @@ Status DBImpl::Open(const DBOptions& db_options, const std::string& dbname,
|
||||
|
||||
// Handles create_if_missing, error_if_exists
|
||||
uint64_t recovered_seq(kMaxSequenceNumber);
|
||||
s = impl->Recover(column_families, false /* read_only */,
|
||||
false /* error_if_wal_file_exists */,
|
||||
false /* error_if_data_exists_in_wals */, &recovered_seq,
|
||||
s = impl->Recover(column_families, false, false, false, &recovered_seq,
|
||||
&recovery_ctx);
|
||||
if (s.ok()) {
|
||||
uint64_t new_log_number = impl->versions_->NewFileNumber();
|
||||
|
||||
@@ -36,30 +36,18 @@ Status DBImplReadOnly::Get(const ReadOptions& read_options,
|
||||
/*timestamp*/ nullptr);
|
||||
}
|
||||
|
||||
Status DBImplReadOnly::Get(const ReadOptions& _read_options,
|
||||
Status DBImplReadOnly::Get(const ReadOptions& read_options,
|
||||
ColumnFamilyHandle* column_family, const Slice& key,
|
||||
PinnableSlice* pinnable_val,
|
||||
std::string* timestamp) {
|
||||
if (_read_options.io_activity != Env::IOActivity::kUnknown &&
|
||||
_read_options.io_activity != Env::IOActivity::kGet) {
|
||||
return Status::InvalidArgument(
|
||||
"Can only call Get with `ReadOptions::io_activity` is "
|
||||
"`Env::IOActivity::kUnknown` or `Env::IOActivity::kGet`");
|
||||
}
|
||||
ReadOptions read_options(_read_options);
|
||||
if (read_options.io_activity == Env::IOActivity::kUnknown) {
|
||||
read_options.io_activity = Env::IOActivity::kGet;
|
||||
}
|
||||
assert(pinnable_val != nullptr);
|
||||
PERF_CPU_TIMER_GUARD(get_cpu_nanos, immutable_db_options_.clock);
|
||||
StopWatch sw(immutable_db_options_.clock, stats_, DB_GET);
|
||||
// TODO: stopwatch DB_GET needed?, perf timer needed?
|
||||
PERF_TIMER_GUARD(get_snapshot_time);
|
||||
|
||||
assert(column_family);
|
||||
if (read_options.timestamp) {
|
||||
const Status s =
|
||||
FailIfTsMismatchCf(column_family, *(read_options.timestamp),
|
||||
/*ts_for_read=*/true);
|
||||
const Status s = FailIfTsMismatchCf(
|
||||
column_family, *(read_options.timestamp), /*ts_for_read=*/true);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
@@ -122,23 +110,12 @@ Status DBImplReadOnly::Get(const ReadOptions& _read_options,
|
||||
return s;
|
||||
}
|
||||
|
||||
Iterator* DBImplReadOnly::NewIterator(const ReadOptions& _read_options,
|
||||
Iterator* DBImplReadOnly::NewIterator(const ReadOptions& read_options,
|
||||
ColumnFamilyHandle* column_family) {
|
||||
if (_read_options.io_activity != Env::IOActivity::kUnknown &&
|
||||
_read_options.io_activity != Env::IOActivity::kDBIterator) {
|
||||
return NewErrorIterator(Status::InvalidArgument(
|
||||
"Can only call NewIterator with `ReadOptions::io_activity` is "
|
||||
"`Env::IOActivity::kUnknown` or `Env::IOActivity::kDBIterator`"));
|
||||
}
|
||||
ReadOptions read_options(_read_options);
|
||||
if (read_options.io_activity == Env::IOActivity::kUnknown) {
|
||||
read_options.io_activity = Env::IOActivity::kDBIterator;
|
||||
}
|
||||
assert(column_family);
|
||||
if (read_options.timestamp) {
|
||||
const Status s =
|
||||
FailIfTsMismatchCf(column_family, *(read_options.timestamp),
|
||||
/*ts_for_read=*/true);
|
||||
const Status s = FailIfTsMismatchCf(
|
||||
column_family, *(read_options.timestamp), /*ts_for_read=*/true);
|
||||
if (!s.ok()) {
|
||||
return NewErrorIterator(s);
|
||||
}
|
||||
|
||||
@@ -28,14 +28,14 @@ class DBImplReadOnly : public DBImpl {
|
||||
virtual Status Get(const ReadOptions& options,
|
||||
ColumnFamilyHandle* column_family, const Slice& key,
|
||||
PinnableSlice* value) override;
|
||||
Status Get(const ReadOptions& _read_options,
|
||||
ColumnFamilyHandle* column_family, const Slice& key,
|
||||
PinnableSlice* value, std::string* timestamp) override;
|
||||
Status Get(const ReadOptions& options, ColumnFamilyHandle* column_family,
|
||||
const Slice& key, PinnableSlice* value,
|
||||
std::string* timestamp) override;
|
||||
|
||||
// TODO: Implement ReadOnly MultiGet?
|
||||
|
||||
using DBImpl::NewIterator;
|
||||
virtual Iterator* NewIterator(const ReadOptions& _read_options,
|
||||
virtual Iterator* NewIterator(const ReadOptions&,
|
||||
ColumnFamilyHandle* column_family) override;
|
||||
|
||||
virtual Status NewIterators(
|
||||
@@ -142,22 +142,6 @@ class DBImplReadOnly : public DBImpl {
|
||||
return Status::NotSupported("Not supported operation in read only mode.");
|
||||
}
|
||||
|
||||
virtual Status CreateColumnFamilyWithImport(
|
||||
const ColumnFamilyOptions& /*options*/,
|
||||
const std::string& /*column_family_name*/,
|
||||
const ImportColumnFamilyOptions& /*import_options*/,
|
||||
const std::vector<const ExportImportFilesMetaData*>& /*metadatas*/,
|
||||
ColumnFamilyHandle** /*handle*/) override {
|
||||
return Status::NotSupported("Not supported operation in read only mode.");
|
||||
}
|
||||
|
||||
using DB::ClipColumnFamily;
|
||||
virtual Status ClipColumnFamily(ColumnFamilyHandle* /*column_family*/,
|
||||
const Slice& /*begin*/,
|
||||
const Slice& /*end*/) override {
|
||||
return Status::NotSupported("Not supported operation in read only mode.");
|
||||
}
|
||||
|
||||
// FIXME: some missing overrides for more "write" functions
|
||||
|
||||
protected:
|
||||
@@ -179,3 +163,4 @@ class DBImplReadOnly : public DBImpl {
|
||||
friend class DB;
|
||||
};
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
#include "monitoring/perf_context_imp.h"
|
||||
#include "rocksdb/configurable.h"
|
||||
#include "util/cast_util.h"
|
||||
#include "util/write_batch_util.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
@@ -198,9 +197,6 @@ Status DBImplSecondary::RecoverLogFiles(
|
||||
}
|
||||
assert(reader != nullptr);
|
||||
}
|
||||
|
||||
const UnorderedMap<uint32_t, size_t>& running_ts_sz =
|
||||
versions_->GetRunningColumnFamiliesTimestampSize();
|
||||
for (auto log_number : log_numbers) {
|
||||
auto it = log_readers_.find(log_number);
|
||||
assert(it != log_readers_.end());
|
||||
@@ -228,14 +224,6 @@ Status DBImplSecondary::RecoverLogFiles(
|
||||
if (!status.ok()) {
|
||||
break;
|
||||
}
|
||||
const UnorderedMap<uint32_t, size_t>& record_ts_sz =
|
||||
reader->GetRecordedTimestampSize();
|
||||
status = HandleWriteBatchTimestampSizeDifference(
|
||||
&batch, running_ts_sz, record_ts_sz,
|
||||
TimestampSizeConsistencyMode::kVerifyConsistency);
|
||||
if (!status.ok()) {
|
||||
break;
|
||||
}
|
||||
SequenceNumber seq_of_batch = WriteBatchInternal::Sequence(&batch);
|
||||
std::vector<uint32_t> column_family_ids;
|
||||
status = CollectColumnFamilyIdsFromWriteBatch(batch, &column_family_ids);
|
||||
@@ -340,36 +328,16 @@ Status DBImplSecondary::RecoverLogFiles(
|
||||
}
|
||||
|
||||
// Implementation of the DB interface
|
||||
Status DBImplSecondary::Get(const ReadOptions& _read_options,
|
||||
Status DBImplSecondary::Get(const ReadOptions& read_options,
|
||||
ColumnFamilyHandle* column_family, const Slice& key,
|
||||
PinnableSlice* value) {
|
||||
if (_read_options.io_activity != Env::IOActivity::kUnknown &&
|
||||
_read_options.io_activity != Env::IOActivity::kGet) {
|
||||
return Status::InvalidArgument(
|
||||
"Can only call Get with `ReadOptions::io_activity` is "
|
||||
"`Env::IOActivity::kUnknown` or `Env::IOActivity::kGet`");
|
||||
}
|
||||
ReadOptions read_options(_read_options);
|
||||
if (read_options.io_activity == Env::IOActivity::kUnknown) {
|
||||
read_options.io_activity = Env::IOActivity::kGet;
|
||||
}
|
||||
return GetImpl(read_options, column_family, key, value,
|
||||
/*timestamp*/ nullptr);
|
||||
}
|
||||
|
||||
Status DBImplSecondary::Get(const ReadOptions& _read_options,
|
||||
Status DBImplSecondary::Get(const ReadOptions& read_options,
|
||||
ColumnFamilyHandle* column_family, const Slice& key,
|
||||
PinnableSlice* value, std::string* timestamp) {
|
||||
if (_read_options.io_activity != Env::IOActivity::kUnknown &&
|
||||
_read_options.io_activity != Env::IOActivity::kGet) {
|
||||
return Status::InvalidArgument(
|
||||
"Can only call Get with `ReadOptions::io_activity` is "
|
||||
"`Env::IOActivity::kUnknown` or `Env::IOActivity::kGet`");
|
||||
}
|
||||
ReadOptions read_options(_read_options);
|
||||
if (read_options.io_activity == Env::IOActivity::kUnknown) {
|
||||
read_options.io_activity = Env::IOActivity::kGet;
|
||||
}
|
||||
return GetImpl(read_options, column_family, key, value, timestamp);
|
||||
}
|
||||
|
||||
@@ -467,18 +435,8 @@ Status DBImplSecondary::GetImpl(const ReadOptions& read_options,
|
||||
return s;
|
||||
}
|
||||
|
||||
Iterator* DBImplSecondary::NewIterator(const ReadOptions& _read_options,
|
||||
Iterator* DBImplSecondary::NewIterator(const ReadOptions& read_options,
|
||||
ColumnFamilyHandle* column_family) {
|
||||
if (_read_options.io_activity != Env::IOActivity::kUnknown &&
|
||||
_read_options.io_activity != Env::IOActivity::kDBIterator) {
|
||||
return NewErrorIterator(Status::InvalidArgument(
|
||||
"Can only call NewIterator with `ReadOptions::io_activity` is "
|
||||
"`Env::IOActivity::kUnknown` or `Env::IOActivity::kDBIterator`"));
|
||||
}
|
||||
ReadOptions read_options(_read_options);
|
||||
if (read_options.io_activity == Env::IOActivity::kUnknown) {
|
||||
read_options.io_activity = Env::IOActivity::kDBIterator;
|
||||
}
|
||||
if (read_options.managed) {
|
||||
return NewErrorIterator(
|
||||
Status::NotSupported("Managed iterator is not supported anymore."));
|
||||
@@ -490,9 +448,8 @@ Iterator* DBImplSecondary::NewIterator(const ReadOptions& _read_options,
|
||||
|
||||
assert(column_family);
|
||||
if (read_options.timestamp) {
|
||||
const Status s =
|
||||
FailIfTsMismatchCf(column_family, *(read_options.timestamp),
|
||||
/*ts_for_read=*/true);
|
||||
const Status s = FailIfTsMismatchCf(
|
||||
column_family, *(read_options.timestamp), /*ts_for_read=*/true);
|
||||
if (!s.ok()) {
|
||||
return NewErrorIterator(s);
|
||||
}
|
||||
@@ -544,19 +501,9 @@ ArenaWrappedDBIter* DBImplSecondary::NewIteratorImpl(
|
||||
}
|
||||
|
||||
Status DBImplSecondary::NewIterators(
|
||||
const ReadOptions& _read_options,
|
||||
const ReadOptions& read_options,
|
||||
const std::vector<ColumnFamilyHandle*>& column_families,
|
||||
std::vector<Iterator*>* iterators) {
|
||||
if (_read_options.io_activity != Env::IOActivity::kUnknown &&
|
||||
_read_options.io_activity != Env::IOActivity::kDBIterator) {
|
||||
return Status::InvalidArgument(
|
||||
"Can only call NewIterators with `ReadOptions::io_activity` is "
|
||||
"`Env::IOActivity::kUnknown` or `Env::IOActivity::kDBIterator`");
|
||||
}
|
||||
ReadOptions read_options(_read_options);
|
||||
if (read_options.io_activity == Env::IOActivity::kUnknown) {
|
||||
read_options.io_activity = Env::IOActivity::kDBIterator;
|
||||
}
|
||||
if (read_options.managed) {
|
||||
return Status::NotSupported("Managed iterator is not supported anymore.");
|
||||
}
|
||||
@@ -986,8 +933,6 @@ Status DB::OpenAndCompact(
|
||||
delete db;
|
||||
if (s.ok()) {
|
||||
return serialization_status;
|
||||
} else {
|
||||
serialization_status.PermitUncheckedError();
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
@@ -96,13 +96,12 @@ class DBImplSecondary : public DBImpl {
|
||||
// workaround, the secondaries can be opened with `max_open_files=-1` so that
|
||||
// it eagerly keeps all talbe files open and is able to access the contents of
|
||||
// deleted files via prior open fd.
|
||||
Status Get(const ReadOptions& _read_options,
|
||||
ColumnFamilyHandle* column_family, const Slice& key,
|
||||
PinnableSlice* value) override;
|
||||
Status Get(const ReadOptions& options, ColumnFamilyHandle* column_family,
|
||||
const Slice& key, PinnableSlice* value) override;
|
||||
|
||||
Status Get(const ReadOptions& _read_options,
|
||||
ColumnFamilyHandle* column_family, const Slice& key,
|
||||
PinnableSlice* value, std::string* timestamp) override;
|
||||
Status Get(const ReadOptions& options, ColumnFamilyHandle* column_family,
|
||||
const Slice& key, PinnableSlice* value,
|
||||
std::string* timestamp) override;
|
||||
|
||||
Status GetImpl(const ReadOptions& options, ColumnFamilyHandle* column_family,
|
||||
const Slice& key, PinnableSlice* value,
|
||||
@@ -118,7 +117,7 @@ class DBImplSecondary : public DBImpl {
|
||||
// deleted. As a partial hacky workaround, the secondaries can be opened with
|
||||
// `max_open_files=-1` so that it eagerly keeps all talbe files open and is
|
||||
// able to access the contents of deleted files via prior open fd.
|
||||
Iterator* NewIterator(const ReadOptions& _read_options,
|
||||
Iterator* NewIterator(const ReadOptions&,
|
||||
ColumnFamilyHandle* column_family) override;
|
||||
|
||||
ArenaWrappedDBIter* NewIteratorImpl(const ReadOptions& read_options,
|
||||
@@ -128,7 +127,7 @@ class DBImplSecondary : public DBImpl {
|
||||
bool expose_blob_index = false,
|
||||
bool allow_refresh = true);
|
||||
|
||||
Status NewIterators(const ReadOptions& _read_options,
|
||||
Status NewIterators(const ReadOptions& options,
|
||||
const std::vector<ColumnFamilyHandle*>& column_families,
|
||||
std::vector<Iterator*>* iterators) override;
|
||||
|
||||
@@ -274,6 +273,85 @@ class DBImplSecondary : public DBImpl {
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
// ColumnFamilyCollector is a write batch handler which does nothing
|
||||
// except recording unique column family IDs
|
||||
class ColumnFamilyCollector : public WriteBatch::Handler {
|
||||
std::unordered_set<uint32_t> column_family_ids_;
|
||||
|
||||
Status AddColumnFamilyId(uint32_t column_family_id) {
|
||||
if (column_family_ids_.find(column_family_id) ==
|
||||
column_family_ids_.end()) {
|
||||
column_family_ids_.insert(column_family_id);
|
||||
}
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
public:
|
||||
explicit ColumnFamilyCollector() {}
|
||||
|
||||
~ColumnFamilyCollector() override {}
|
||||
|
||||
Status PutCF(uint32_t column_family_id, const Slice&,
|
||||
const Slice&) override {
|
||||
return AddColumnFamilyId(column_family_id);
|
||||
}
|
||||
|
||||
Status DeleteCF(uint32_t column_family_id, const Slice&) override {
|
||||
return AddColumnFamilyId(column_family_id);
|
||||
}
|
||||
|
||||
Status SingleDeleteCF(uint32_t column_family_id, const Slice&) override {
|
||||
return AddColumnFamilyId(column_family_id);
|
||||
}
|
||||
|
||||
Status DeleteRangeCF(uint32_t column_family_id, const Slice&,
|
||||
const Slice&) override {
|
||||
return AddColumnFamilyId(column_family_id);
|
||||
}
|
||||
|
||||
Status MergeCF(uint32_t column_family_id, const Slice&,
|
||||
const Slice&) override {
|
||||
return AddColumnFamilyId(column_family_id);
|
||||
}
|
||||
|
||||
Status PutBlobIndexCF(uint32_t column_family_id, const Slice&,
|
||||
const Slice&) override {
|
||||
return AddColumnFamilyId(column_family_id);
|
||||
}
|
||||
|
||||
Status MarkBeginPrepare(bool) override { return Status::OK(); }
|
||||
|
||||
Status MarkEndPrepare(const Slice&) override { return Status::OK(); }
|
||||
|
||||
Status MarkRollback(const Slice&) override { return Status::OK(); }
|
||||
|
||||
Status MarkCommit(const Slice&) override { return Status::OK(); }
|
||||
|
||||
Status MarkCommitWithTimestamp(const Slice&, const Slice&) override {
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status MarkNoop(bool) override { return Status::OK(); }
|
||||
|
||||
const std::unordered_set<uint32_t>& column_families() const {
|
||||
return column_family_ids_;
|
||||
}
|
||||
};
|
||||
|
||||
Status CollectColumnFamilyIdsFromWriteBatch(
|
||||
const WriteBatch& batch, std::vector<uint32_t>* column_family_ids) {
|
||||
assert(column_family_ids != nullptr);
|
||||
column_family_ids->clear();
|
||||
ColumnFamilyCollector handler;
|
||||
Status s = batch.Iterate(&handler);
|
||||
if (s.ok()) {
|
||||
for (const auto& cf : handler.column_families()) {
|
||||
column_family_ids->push_back(cf);
|
||||
}
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
bool OwnTablesAndLogs() const override {
|
||||
// Currently, the secondary instance does not own the database files. It
|
||||
// simply opens the files of the primary instance and tracks their file
|
||||
@@ -325,3 +403,4 @@ class DBImplSecondary : public DBImpl {
|
||||
};
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
@@ -606,9 +606,7 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options,
|
||||
log_write_mutex_.Unlock();
|
||||
if (status.ok() && synced_wals.IsWalAddition()) {
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
// TODO: plumb Env::IOActivity
|
||||
const ReadOptions read_options;
|
||||
status = ApplyWALToManifest(read_options, &synced_wals);
|
||||
status = ApplyWALToManifest(&synced_wals);
|
||||
}
|
||||
|
||||
// Requesting sync with two_write_queues_ is expected to be very rare. We
|
||||
@@ -769,9 +767,7 @@ Status DBImpl::PipelinedWriteImpl(const WriteOptions& write_options,
|
||||
}
|
||||
if (w.status.ok() && synced_wals.IsWalAddition()) {
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
// TODO: plumb Env::IOActivity
|
||||
const ReadOptions read_options;
|
||||
w.status = ApplyWALToManifest(read_options, &synced_wals);
|
||||
w.status = ApplyWALToManifest(&synced_wals);
|
||||
}
|
||||
write_thread_.ExitAsBatchGroupLeader(wal_write_group, w.status);
|
||||
}
|
||||
@@ -1328,13 +1324,7 @@ IOStatus DBImpl::WriteToWAL(const WriteBatch& merged_batch,
|
||||
if (UNLIKELY(needs_locking)) {
|
||||
log_write_mutex_.Lock();
|
||||
}
|
||||
IOStatus io_s = log_writer->MaybeAddUserDefinedTimestampSizeRecord(
|
||||
versions_->GetColumnFamiliesTimestampSizeForRecord(),
|
||||
rate_limiter_priority);
|
||||
if (!io_s.ok()) {
|
||||
return io_s;
|
||||
}
|
||||
io_s = log_writer->AddRecord(log_entry, rate_limiter_priority);
|
||||
IOStatus io_s = log_writer->AddRecord(log_entry, rate_limiter_priority);
|
||||
|
||||
if (UNLIKELY(needs_locking)) {
|
||||
log_write_mutex_.Unlock();
|
||||
@@ -1815,7 +1805,7 @@ Status DBImpl::DelayWrite(uint64_t num_bytes, WriteThread& write_thread,
|
||||
bool delayed = false;
|
||||
{
|
||||
StopWatch sw(immutable_db_options_.clock, stats_, WRITE_STALL,
|
||||
Histograms::HISTOGRAM_ENUM_MAX, &time_delayed);
|
||||
&time_delayed);
|
||||
// To avoid parallel timed delays (bad throttling), only support them
|
||||
// on the primary write queue.
|
||||
uint64_t delay;
|
||||
@@ -2096,8 +2086,6 @@ void DBImpl::NotifyOnMemTableSealed(ColumnFamilyData* /*cfd*/,
|
||||
// two_write_queues_ is true (This is to simplify the reasoning.)
|
||||
Status DBImpl::SwitchMemtable(ColumnFamilyData* cfd, WriteContext* context) {
|
||||
mutex_.AssertHeld();
|
||||
// TODO: plumb Env::IOActivity
|
||||
const ReadOptions read_options;
|
||||
log::Writer* new_log = nullptr;
|
||||
MemTable* new_mem = nullptr;
|
||||
IOStatus io_s;
|
||||
@@ -2249,8 +2237,8 @@ Status DBImpl::SwitchMemtable(ColumnFamilyData* cfd, WriteContext* context) {
|
||||
|
||||
VersionEdit wal_deletion;
|
||||
wal_deletion.DeleteWalsBefore(min_wal_number_to_keep);
|
||||
s = versions_->LogAndApplyToDefaultColumnFamily(
|
||||
read_options, &wal_deletion, &mutex_, directories_.GetDbDir());
|
||||
s = versions_->LogAndApplyToDefaultColumnFamily(&wal_deletion, &mutex_,
|
||||
directories_.GetDbDir());
|
||||
if (!s.ok() && versions_->io_status().IsIOError()) {
|
||||
s = error_handler_.SetBGError(versions_->io_status(),
|
||||
BackgroundErrorReason::kManifestWrite);
|
||||
|
||||
+9
-8
@@ -77,7 +77,6 @@ DBIter::DBIter(Env* _env, const ReadOptions& read_options,
|
||||
expose_blob_index_(expose_blob_index),
|
||||
is_blob_(false),
|
||||
arena_mode_(arena_mode),
|
||||
io_activity_(read_options.io_activity),
|
||||
db_impl_(db_impl),
|
||||
cfd_(cfd),
|
||||
timestamp_ub_(read_options.timestamp),
|
||||
@@ -132,7 +131,6 @@ void DBIter::Next() {
|
||||
assert(valid_);
|
||||
assert(status_.ok());
|
||||
|
||||
PERF_COUNTER_ADD(iter_next_count, 1);
|
||||
PERF_CPU_TIMER_GUARD(iter_next_cpu_nanos, clock_);
|
||||
// Release temporarily pinned blocks from last operation
|
||||
ReleaseTempPinnedData();
|
||||
@@ -201,7 +199,7 @@ bool DBIter::SetBlobValueIfNeeded(const Slice& user_key,
|
||||
read_options.read_tier = read_tier_;
|
||||
read_options.fill_cache = fill_cache_;
|
||||
read_options.verify_checksums = verify_checksums_;
|
||||
read_options.io_activity = io_activity_;
|
||||
|
||||
constexpr FilePrefetchBuffer* prefetch_buffer = nullptr;
|
||||
constexpr uint64_t* bytes_read = nullptr;
|
||||
|
||||
@@ -639,7 +637,6 @@ void DBIter::Prev() {
|
||||
assert(valid_);
|
||||
assert(status_.ok());
|
||||
|
||||
PERF_COUNTER_ADD(iter_prev_count, 1);
|
||||
PERF_CPU_TIMER_GUARD(iter_prev_cpu_nanos, clock_);
|
||||
ReleaseTempPinnedData();
|
||||
ResetBlobValue();
|
||||
@@ -964,6 +961,9 @@ bool DBIter::FindValueForCurrentKey() {
|
||||
assert(last_key_entry_type == ikey_.type);
|
||||
}
|
||||
|
||||
Status s;
|
||||
s.PermitUncheckedError();
|
||||
|
||||
switch (last_key_entry_type) {
|
||||
case kTypeDeletion:
|
||||
case kTypeDeletionWithTimestamp:
|
||||
@@ -1040,6 +1040,11 @@ bool DBIter::FindValueForCurrentKey() {
|
||||
std::to_string(static_cast<unsigned int>(last_key_entry_type)));
|
||||
return false;
|
||||
}
|
||||
if (!s.ok()) {
|
||||
valid_ = false;
|
||||
status_ = s;
|
||||
return false;
|
||||
}
|
||||
valid_ = true;
|
||||
return true;
|
||||
}
|
||||
@@ -1439,7 +1444,6 @@ void DBIter::SetSavedKeyToSeekForPrevTarget(const Slice& target) {
|
||||
}
|
||||
|
||||
void DBIter::Seek(const Slice& target) {
|
||||
PERF_COUNTER_ADD(iter_seek_count, 1);
|
||||
PERF_CPU_TIMER_GUARD(iter_seek_cpu_nanos, clock_);
|
||||
StopWatch sw(clock_, statistics_, DB_SEEK);
|
||||
|
||||
@@ -1513,7 +1517,6 @@ void DBIter::Seek(const Slice& target) {
|
||||
}
|
||||
|
||||
void DBIter::SeekForPrev(const Slice& target) {
|
||||
PERF_COUNTER_ADD(iter_seek_count, 1);
|
||||
PERF_CPU_TIMER_GUARD(iter_seek_cpu_nanos, clock_);
|
||||
StopWatch sw(clock_, statistics_, DB_SEEK);
|
||||
|
||||
@@ -1587,7 +1590,6 @@ void DBIter::SeekToFirst() {
|
||||
Seek(*iterate_lower_bound_);
|
||||
return;
|
||||
}
|
||||
PERF_COUNTER_ADD(iter_seek_count, 1);
|
||||
PERF_CPU_TIMER_GUARD(iter_seek_cpu_nanos, clock_);
|
||||
// Don't use iter_::Seek() if we set a prefix extractor
|
||||
// because prefix seek will be used.
|
||||
@@ -1650,7 +1652,6 @@ void DBIter::SeekToLast() {
|
||||
return;
|
||||
}
|
||||
|
||||
PERF_COUNTER_ADD(iter_seek_count, 1);
|
||||
PERF_CPU_TIMER_GUARD(iter_seek_cpu_nanos, clock_);
|
||||
// Don't use iter_::Seek() if we set a prefix extractor
|
||||
// because prefix seek will be used.
|
||||
|
||||
Loaded 100 of 645 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user