mirror of
https://github.com/facebook/rocksdb.git
synced 2026-07-07 22:55:23 +08:00
Compare commits
217 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 76a56d89a7 | |||
| dd757ffde0 | |||
| fdc56e2b73 | |||
| cfd6eb3e68 | |||
| 9623a66b24 | |||
| 47f0d5fdd0 | |||
| 3542f7ef00 | |||
| 5c12a474f3 | |||
| 6d113fc066 | |||
| 11afcbe7ba | |||
| b6e554e698 | |||
| a1f08cc953 | |||
| 0d9bfa6e4d | |||
| 9feb730c6e | |||
| 70dca18c96 | |||
| 5f703af1ea | |||
| 8baa66acf6 | |||
| 570d490a3d | |||
| 55320dedab | |||
| 03c7ae24c2 | |||
| a5debd7ed8 | |||
| b02d0c238d | |||
| 25810ca9c7 | |||
| 210b49cac9 | |||
| 18864567c8 | |||
| 35e6ba734e | |||
| 3548e4220d | |||
| 8c7eb59838 | |||
| 506e8448be | |||
| 6eb317bb4c | |||
| 084a3c697c | |||
| da96f2fe00 | |||
| 66d8360beb | |||
| cd77d3c558 | |||
| aa56b7e74a | |||
| 1c8cbf315f | |||
| a7d103198e | |||
| 990b2f4cb3 | |||
| 72c8533f2c | |||
| 78a6e07c83 | |||
| 47fd574829 | |||
| 8272a6de57 | |||
| df38c1ce66 | |||
| de76909464 | |||
| dc64c2f5cc | |||
| c77aab584e | |||
| 5265c5709e | |||
| 7a73adda9c | |||
| efa948741c | |||
| 3bdce20e2b | |||
| d6862b3f51 | |||
| 01cfea6637 | |||
| 992dfc7811 | |||
| 6c2bf9e916 | |||
| 392f6d49e5 | |||
| 5b7e09bd6f | |||
| 248b6b551e | |||
| baa5302447 | |||
| d9280ff2d2 | |||
| beb44ec3eb | |||
| f1239d5f10 | |||
| 71a82a0abe | |||
| 3e63e553b4 | |||
| b70967aac7 | |||
| b5cad5c986 | |||
| 8295d364e2 | |||
| 29111e92b4 | |||
| cca141ecf8 | |||
| d655a3aab7 | |||
| fe642cbee6 | |||
| 1966a7c055 | |||
| 85b2bde3dd | |||
| 3189398c00 | |||
| f49e12b892 | |||
| fefd4b98c5 | |||
| ed9f5e21aa | |||
| d3d20dcdca | |||
| ef0fc1b461 | |||
| 313e877285 | |||
| 0bb555630f | |||
| de00f28132 | |||
| 39c6c5fc1b | |||
| 479c566771 | |||
| 8d1e52165d | |||
| 59ef2ba559 | |||
| 306b9adfd8 | |||
| c06c4c01c5 | |||
| f0edf9d575 | |||
| 75e8b6dfcf | |||
| 7441a0ecba | |||
| ebb9b2ed16 | |||
| e8480d4d9d | |||
| 5234fc1b70 | |||
| 26015f3b48 | |||
| 14b3f683a1 | |||
| d9d3cacaf5 | |||
| ebcc8ae1d3 | |||
| fa1b558299 | |||
| 120bc4715b | |||
| 127a850beb | |||
| a703f16da9 | |||
| f29dc1b906 | |||
| 09957ded1d | |||
| d77476ef55 | |||
| dae3b5545c | |||
| a98317f555 | |||
| 106a94af15 | |||
| d1edf4eced | |||
| 89ab1381f8 | |||
| 1f7f5a5a79 | |||
| 5f6adf3f6a | |||
| 8c072044d2 | |||
| d69241586e | |||
| 2b4d5ceb47 | |||
| 9358178edc | |||
| 2a5463ae84 | |||
| 01e6badbb6 | |||
| 4774a9409b | |||
| fe2bd190a5 | |||
| 75133b1b6b | |||
| 7ca9eb7542 | |||
| 3c5eed5ebe | |||
| 48e7effa79 | |||
| 52e6404e0f | |||
| a4396f9218 | |||
| 88d85b6820 | |||
| c84fad7a19 | |||
| 34f8ac0c99 | |||
| 1721635f76 | |||
| 959f86e5f8 | |||
| a291f3a1e5 | |||
| e50326f327 | |||
| 36c2a7cfb1 | |||
| f2f6acbef3 | |||
| b45b1cde3e | |||
| 20d49da90c | |||
| b4fa51dfaf | |||
| 2263f86901 | |||
| dcde292c3b | |||
| fdc72a5c5d | |||
| 5a5c0492db | |||
| 8a1ecd1982 | |||
| c4e00cf10a | |||
| 05ebfebc17 | |||
| 62eb2c23aa | |||
| 04d3ac4e63 | |||
| 79b6ab43ce | |||
| 04a2631dbe | |||
| 703f1375c2 | |||
| 186b3afaa8 | |||
| a283800616 | |||
| 9a31b8dd2c | |||
| 0920bf4e68 | |||
| 18d2e4beb7 | |||
| aef763b6d6 | |||
| 916e524134 | |||
| 0b80f6b380 | |||
| 77ebc82b92 | |||
| 68a2f94d5d | |||
| 06ea73d60b | |||
| f83eecff99 | |||
| b157d3d128 | |||
| 5e298f865b | |||
| a661c0d208 | |||
| bb474e9a02 | |||
| 06f378d75e | |||
| c4f5d0aa15 | |||
| 48c8d8445e | |||
| cf98df34c1 | |||
| 0f4244fe00 | |||
| bcdc8c8b19 | |||
| 93f7e7a450 | |||
| 7d23210226 | |||
| 3c5d1b16b1 | |||
| ed995c6a69 | |||
| c2affccc18 | |||
| 26a33ee5bd | |||
| 4db46aa2e6 | |||
| 3231a2e581 | |||
| ca89ac2ba9 | |||
| c8c8104d7e | |||
| 4fc442029a | |||
| 34b55dd8c0 | |||
| 51a90415e2 | |||
| 5af9446ee6 | |||
| a69d4deefb | |||
| 33b33235ff | |||
| 49ddd7ec4f | |||
| eafb09a380 | |||
| c5a64cffd2 | |||
| 62f70f6d14 | |||
| 79496d71ed | |||
| d6b9b3b884 | |||
| 576d2d6c60 | |||
| 1218704b61 | |||
| 9144d1f186 | |||
| 2d049ab7e8 | |||
| 5d9a623e2c | |||
| 08809f5e6c | |||
| 10d14693ac | |||
| 39fb88f14e | |||
| 1a761e6a6c | |||
| f48758e939 | |||
| cf3a671733 | |||
| 199fabc197 | |||
| d9c9f3c809 | |||
| 71cae59a99 | |||
| 00ed41daee | |||
| c9a52cbdc8 | |||
| 8fe073324f | |||
| 31221bb7e8 | |||
| dcb73e7735 | |||
| 30468d8eb4 | |||
| 59244447e3 | |||
| 4091597c67 | |||
| 842cdc11dd | |||
| 0ea57115a3 |
+10
-7
@@ -92,7 +92,7 @@ else()
|
||||
endif()
|
||||
|
||||
if(WITH_ZLIB)
|
||||
find_package(zlib REQUIRED)
|
||||
find_package(ZLIB REQUIRED)
|
||||
add_definitions(-DZLIB)
|
||||
if(ZLIB_INCLUDE_DIRS)
|
||||
# CMake 3
|
||||
@@ -317,6 +317,10 @@ if(DISABLE_STALL_NOTIF)
|
||||
add_definitions(-DROCKSDB_DISABLE_STALL_NOTIFICATION)
|
||||
endif()
|
||||
|
||||
option(WITH_DYNAMIC_EXTENSION "build with dynamic extension support" OFF)
|
||||
if(NOT WITH_DYNAMIC_EXTENSION)
|
||||
add_definitions(-DROCKSDB_NO_DYNAMIC_EXTENSION)
|
||||
endif()
|
||||
|
||||
if(DEFINED USE_RTTI)
|
||||
if(USE_RTTI)
|
||||
@@ -416,7 +420,7 @@ if(WITH_FALLOCATE)
|
||||
#include <linux/falloc.h>
|
||||
int main() {
|
||||
int fd = open(\"/dev/null\", 0);
|
||||
fallocate(fd, FALLOC_FL_KEEP_SIZE | FALLOC_FL_PUNCH_HOLE, 0, 1024);
|
||||
fallocate(fd, FALLOC_FL_KEEP_SIZE, 0, 1024);
|
||||
}
|
||||
" HAVE_FALLOCATE)
|
||||
if(HAVE_FALLOCATE)
|
||||
@@ -461,8 +465,6 @@ include_directories(${PROJECT_SOURCE_DIR}/include)
|
||||
include_directories(SYSTEM ${PROJECT_SOURCE_DIR}/third-party/gtest-1.7.0/fused-src)
|
||||
find_package(Threads REQUIRED)
|
||||
|
||||
add_subdirectory(third-party/gtest-1.7.0/fused-src/gtest)
|
||||
|
||||
# Main library source code
|
||||
|
||||
set(SOURCES
|
||||
@@ -489,6 +491,7 @@ set(SOURCES
|
||||
db/db_impl_debug.cc
|
||||
db/db_impl_experimental.cc
|
||||
db/db_impl_readonly.cc
|
||||
db/db_impl_secondary.cc
|
||||
db/db_info_dumper.cc
|
||||
db/db_iter.cc
|
||||
db/dbformat.cc
|
||||
@@ -501,6 +504,7 @@ set(SOURCES
|
||||
db/flush_scheduler.cc
|
||||
db/forward_iterator.cc
|
||||
db/internal_stats.cc
|
||||
db/in_memory_stats_history.cc
|
||||
db/logs_with_prep_tracker.cc
|
||||
db/log_reader.cc
|
||||
db/log_writer.cc
|
||||
@@ -530,7 +534,6 @@ set(SOURCES
|
||||
env/env_hdfs.cc
|
||||
env/mock_env.cc
|
||||
memtable/alloc_tracker.cc
|
||||
memtable/hash_cuckoo_rep.cc
|
||||
memtable/hash_linklist_rep.cc
|
||||
memtable/hash_skiplist_rep.cc
|
||||
memtable/skiplistrep.cc
|
||||
@@ -648,7 +651,6 @@ set(SOURCES
|
||||
utilities/env_mirror.cc
|
||||
utilities/env_timed.cc
|
||||
utilities/leveldb_options/leveldb_options.cc
|
||||
utilities/lua/rocks_lua_compaction_filter.cc
|
||||
utilities/memory/memory_util.cc
|
||||
utilities/merge_operators/bytesxor.cc
|
||||
utilities/merge_operators/max.cc
|
||||
@@ -843,6 +845,7 @@ endif()
|
||||
|
||||
option(WITH_TESTS "build with tests" ON)
|
||||
if(WITH_TESTS)
|
||||
add_subdirectory(third-party/gtest-1.7.0/fused-src/gtest)
|
||||
set(TESTS
|
||||
cache/cache_test.cc
|
||||
cache/lru_cache_test.cc
|
||||
@@ -874,6 +877,7 @@ if(WITH_TESTS)
|
||||
db/db_options_test.cc
|
||||
db/db_properties_test.cc
|
||||
db/db_range_del_test.cc
|
||||
db/db_secondary_test.cc
|
||||
db/db_sst_test.cc
|
||||
db/db_statistics_test.cc
|
||||
db/db_table_properties_test.cc
|
||||
@@ -965,7 +969,6 @@ if(WITH_TESTS)
|
||||
utilities/cassandra/cassandra_row_merge_test.cc
|
||||
utilities/cassandra/cassandra_serialize_test.cc
|
||||
utilities/checkpoint/checkpoint_test.cc
|
||||
utilities/lua/rocks_lua_test.cc
|
||||
utilities/memory/memory_test.cc
|
||||
utilities/merge_operators/string_append/stringappend_test.cc
|
||||
utilities/object_registry_test.cc
|
||||
|
||||
+80
-2
@@ -1,5 +1,71 @@
|
||||
# Rocksdb Change Log
|
||||
## Unreleased
|
||||
## 6.2.4 (9/18/2019)
|
||||
### Bug Fixes
|
||||
* Disable snap_refresh_nanos by default. The feature is to be deprecated in the next release.
|
||||
|
||||
## 6.2.3 (9/3/2019)
|
||||
### Bug Fixes
|
||||
* Fix a bug in file ingestion caused by incorrect file number allocation when the number of column families involved in the ingestion exceeds 2.
|
||||
|
||||
## 6.2.2 (6/7/2019)
|
||||
### Bug Fixes
|
||||
* Disable dynamic extension support by default for CMake.
|
||||
|
||||
## 6.2.1 (6/4/2019)
|
||||
### Bug Fixes
|
||||
* Fix flush's/compaction's merge processing logic which allowed `Put`s covered by range tombstones to reappear. Note `Put`s may exist even if the user only ever called `Merge()` due to an internal conversion during compaction to the bottommost level.
|
||||
|
||||
## 6.2.0 (4/30/2019)
|
||||
### New Features
|
||||
* Add an option `strict_bytes_per_sync` that causes a file-writing thread to block rather than exceed the limit on bytes pending writeback specified by `bytes_per_sync` or `wal_bytes_per_sync`.
|
||||
* Improve range scan performance by avoiding per-key upper bound check in BlockBasedTableIterator.
|
||||
* Introduce Periodic Compaction for Level style compaction. Files are re-compacted periodically and put in the same level.
|
||||
* Block-based table index now contains exact highest key in the file, rather than an upper bound. This may improve Get() and iterator Seek() performance in some situations, especially when direct IO is enabled and block cache is disabled. A setting BlockBasedTableOptions::index_shortening is introduced to control this behavior. Set it to kShortenSeparatorsAndSuccessor to get the old behavior.
|
||||
* When reading from option file/string/map, customized envs can be filled according to object registry.
|
||||
* Add an option `snap_refresh_nanos` (default to 0.5s) to periodically refresh the snapshot list in compaction jobs. Assign to 0 to disable the feature.
|
||||
* Improve range scan performance when using explicit user readahead by not creating new table readers for every iterator.
|
||||
|
||||
### Public API Change
|
||||
* Change the behavior of OptimizeForPointLookup(): move away from hash-based block-based-table index, and use whole key memtable filtering.
|
||||
* Change the behavior of OptimizeForSmallDb(): use a 16MB block cache, put index and filter blocks into it, and cost the memtable size to it. DBOptions.OptimizeForSmallDb() and ColumnFamilyOptions.OptimizeForSmallDb() start to take an optional cache object.
|
||||
* Added BottommostLevelCompaction::kForceOptimized to avoid double compacting newly compacted files in the bottommost level compaction of manual compaction. Note this option may prohibit the manual compaction to produce a single file in the bottommost level.
|
||||
|
||||
### Bug Fixes
|
||||
* Adjust WriteBufferManager's dummy entry size to block cache from 1MB to 256KB.
|
||||
* Fix a race condition between WritePrepared::Get and ::Put with duplicate keys.
|
||||
* Fix crash when memtable prefix bloom is enabled and read/write a key out of domain of prefix extractor.
|
||||
* Close a WAL file before another thread deletes it.
|
||||
* Fix an assertion failure `IsFlushPending() == true` caused by one bg thread releasing the db mutex in ~ColumnFamilyData and another thread clearing `flush_requested_` flag.
|
||||
|
||||
## 6.1.1 (4/9/2019)
|
||||
### New Features
|
||||
* When reading from option file/string/map, customized comparators and/or merge operators can be filled according to object registry.
|
||||
|
||||
### Public API Change
|
||||
|
||||
### Bug Fixes
|
||||
* Fix a bug in 2PC where a sequence of txn prepare, memtable flush, and crash could result in losing the prepared transaction.
|
||||
* Fix a bug in Encryption Env which could cause encrypted files to be read beyond file boundaries.
|
||||
|
||||
## 6.1.0 (3/27/2019)
|
||||
### New Features
|
||||
* Introduce two more stats levels, kExceptHistogramOrTimers and kExceptTimers.
|
||||
* Added a feature to perform data-block sampling for compressibility, and report stats to user.
|
||||
* Add support for trace filtering.
|
||||
* Add DBOptions.avoid_unnecessary_blocking_io. If true, we avoid file deletion when destorying ColumnFamilyHandle and Iterator. Instead, a job is scheduled to delete the files in background.
|
||||
|
||||
### Public API Change
|
||||
* Remove bundled fbson library.
|
||||
* statistics.stats_level_ becomes atomic. It is preferred to use statistics.set_stats_level() and statistics.get_stats_level() to access it.
|
||||
* Introduce a new IOError subcode, PathNotFound, to indicate trying to open a nonexistent file or directory for read.
|
||||
* Add initial support for multiple db instances sharing the same data in single-writer, multi-reader mode.
|
||||
* Removed some "using std::xxx" from public headers.
|
||||
|
||||
### Bug Fixes
|
||||
* Fix JEMALLOC_CXX_THROW macro missing from older Jemalloc versions, causing build failures on some platforms.
|
||||
* Fix SstFileReader not able to open file ingested with write_glbal_seqno=true.
|
||||
|
||||
## 6.0.0 (2/19/2019)
|
||||
### New Features
|
||||
* Enabled checkpoint on readonly db (DBImplReadOnly).
|
||||
* Make DB ignore dropped column families while committing results of atomic flush.
|
||||
@@ -7,8 +73,16 @@
|
||||
* For users of dictionary compression with ZSTD v0.7.0+, we now reuse the same digested dictionary when compressing each of an SST file's data blocks for faster compression speeds.
|
||||
* For all users of dictionary compression who set `cache_index_and_filter_blocks == true`, we now store dictionary data used for decompression in the block cache for better control over memory usage. For users of ZSTD v1.1.4+ who compile with -DZSTD_STATIC_LINKING_ONLY, this includes a digested dictionary, which is used to increase decompression speed.
|
||||
* Add support for block checksums verification for external SST files before ingestion.
|
||||
* Introduce stats history which periodically saves Statistics snapshots and added `GetStatsHistory` API to retrieve these snapshots.
|
||||
* Add a place holder in manifest which indicate a record from future that can be safely ignored.
|
||||
* Add support for trace sampling.
|
||||
* Enable properties block checksum verification for block-based tables.
|
||||
* For all users of dictionary compression, we now generate a separate dictionary for compressing each bottom-level SST file. Previously we reused a single dictionary for a whole compaction to bottom level. The new approach achieves better compression ratios; however, it uses more memory and CPU for buffering/sampling data blocks and training dictionaries.
|
||||
* Add whole key bloom filter support in memtable.
|
||||
* Files written by `SstFileWriter` will now use dictionary compression if it is configured in the file writer's `CompressionOptions`.
|
||||
|
||||
### Public API Change
|
||||
* Disallow CompactionFilter::IgnoreSnapshots() = false, because it is not very useful and the behavior is confusing. The filter will filter everything if there is no snapshot declared by the time the compaction starts. However, users can define a snapshot after the compaction starts and before it finishes and this new snapshot won't be repeatable, because after the compaction finishes, some keys may be dropped.
|
||||
* CompactionPri = kMinOverlappingRatio also uses compensated file size, which boosts file with lots of tombstones to be compacted first.
|
||||
* Transaction::GetForUpdate is extended with a do_validate parameter with default value of true. If false it skips validating the snapshot before doing the read. Similarly ::Merge, ::Put, ::Delete, and ::SingleDelete are extended with assume_tracked with default value of false. If true it indicates that call is assumed to be after a ::GetForUpdate.
|
||||
* `TableProperties::num_entries` and `TableProperties::num_deletions` now also account for number of range tombstones.
|
||||
@@ -16,7 +90,11 @@
|
||||
* With "ldb ----try_load_options", when wal_dir specified by the option file doesn't exist, ignore it.
|
||||
* Change time resolution in FileOperationInfo.
|
||||
* Deleting Blob files also go through SStFileManager.
|
||||
* Remove PlainTable's store_index_in_file feature. When opening an existing DB with index in SST files, the index and bloom filter will still be rebuild while SST files are opened, in the same way as there is no index in the file.
|
||||
* Remove CuckooHash memtable.
|
||||
* The counter stat `number.block.not_compressed` now also counts blocks not compressed due to poor compression ratio.
|
||||
* Remove ttl option from `CompactionOptionsFIFO`. The option has been deprecated and ttl in `ColumnFamilyOptions` is used instead.
|
||||
* Support SST file ingestion across multiple column families via DB::IngestExternalFiles. See the function's comment about atomicity.
|
||||
* Remove Lua compaction filter.
|
||||
|
||||
### Bug Fixes
|
||||
* Fix a deadlock caused by compaction and file ingestion waiting for each other in the event of write stalls.
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
This is the list of all known third-party language bindings for RocksDB. If something is missing, please open a pull request to add it.
|
||||
|
||||
* Java - https://github.com/facebook/rocksdb/tree/master/java
|
||||
* Python - http://pyrocksdb.readthedocs.org/en/latest/
|
||||
* Python
|
||||
* http://python-rocksdb.readthedocs.io/en/latest/
|
||||
* http://pyrocksdb.readthedocs.org/en/latest/ (unmaintained)
|
||||
* Perl - https://metacpan.org/pod/RocksDB
|
||||
* Node.js - https://npmjs.org/package/rocksdb
|
||||
* Go - https://github.com/tecbot/gorocksdb
|
||||
|
||||
@@ -137,6 +137,12 @@ CFLAGS += -DHAVE_POWER8
|
||||
HAVE_POWER8=1
|
||||
endif
|
||||
|
||||
ifeq (,$(shell $(CXX) -fsyntax-only -march=armv8-a+crc -xc /dev/null 2>&1))
|
||||
CXXFLAGS += -march=armv8-a+crc
|
||||
CFLAGS += -march=armv8-a+crc
|
||||
ARMCRC_SOURCE=1
|
||||
endif
|
||||
|
||||
# if we're compiling for release, compile without debug code (-DNDEBUG)
|
||||
ifeq ($(DEBUG_LEVEL),0)
|
||||
OPT += -DNDEBUG
|
||||
@@ -443,6 +449,7 @@ TESTS = \
|
||||
db_merge_operator_test \
|
||||
db_options_test \
|
||||
db_range_del_test \
|
||||
db_secondary_test \
|
||||
db_sst_test \
|
||||
db_tailing_iter_test \
|
||||
db_io_failure_test \
|
||||
@@ -535,7 +542,6 @@ TESTS = \
|
||||
ldb_cmd_test \
|
||||
persistent_cache_test \
|
||||
statistics_test \
|
||||
lua_test \
|
||||
lru_cache_test \
|
||||
object_registry_test \
|
||||
repair_test \
|
||||
@@ -548,6 +554,7 @@ TESTS = \
|
||||
range_tombstone_fragmenter_test \
|
||||
range_del_aggregator_test \
|
||||
sst_file_reader_test \
|
||||
db_secondary_test \
|
||||
|
||||
PARALLEL_TEST = \
|
||||
backupable_db_test \
|
||||
@@ -687,7 +694,8 @@ endif # PLATFORM_SHARED_EXT
|
||||
.PHONY: blackbox_crash_test check clean coverage crash_test ldb_tests package \
|
||||
release tags tags0 valgrind_check whitebox_crash_test format static_lib shared_lib all \
|
||||
dbg rocksdbjavastatic rocksdbjava install install-static install-shared uninstall \
|
||||
analyze tools tools_lib
|
||||
analyze tools tools_lib \
|
||||
blackbox_crash_test_with_atomic_flush whitebox_crash_test_with_atomic_flush
|
||||
|
||||
|
||||
all: $(LIBRARY) $(BENCHMARKS) tools tools_lib test_libs $(TESTS)
|
||||
@@ -895,23 +903,29 @@ ldb_tests: ldb
|
||||
|
||||
crash_test: whitebox_crash_test blackbox_crash_test
|
||||
|
||||
crash_test_with_atomic_flush: whitebox_crash_test_with_atomic_flush blackbox_crash_test_with_atomic_flush
|
||||
|
||||
blackbox_crash_test: db_stress
|
||||
python -u tools/db_crashtest.py --simple blackbox $(CRASH_TEST_EXT_ARGS)
|
||||
python -u tools/db_crashtest.py --enable_atomic_flush blackbox $(CRASH_TEST_EXT_ARGS)
|
||||
python -u tools/db_crashtest.py blackbox $(CRASH_TEST_EXT_ARGS)
|
||||
|
||||
blackbox_crash_test_with_atomic_flush: db_stress
|
||||
python -u tools/db_crashtest.py --enable_atomic_flush blackbox $(CRASH_TEST_EXT_ARGS)
|
||||
|
||||
ifeq ($(CRASH_TEST_KILL_ODD),)
|
||||
CRASH_TEST_KILL_ODD=888887
|
||||
endif
|
||||
|
||||
whitebox_crash_test: db_stress
|
||||
python -u tools/db_crashtest.py --simple whitebox --random_kill_odd \
|
||||
$(CRASH_TEST_KILL_ODD) $(CRASH_TEST_EXT_ARGS)
|
||||
python -u tools/db_crashtest.py --enable_atomic_flush whitebox --random_kill_odd \
|
||||
$(CRASH_TEST_KILL_ODD) $(CRASH_TEST_EXT_ARGS)
|
||||
python -u tools/db_crashtest.py whitebox --random_kill_odd \
|
||||
$(CRASH_TEST_KILL_ODD) $(CRASH_TEST_EXT_ARGS)
|
||||
|
||||
whitebox_crash_test_with_atomic_flush: db_stress
|
||||
python -u tools/db_crashtest.py --enable_atomic_flush whitebox --random_kill_odd \
|
||||
$(CRASH_TEST_KILL_ODD) $(CRASH_TEST_EXT_ARGS)
|
||||
|
||||
asan_check:
|
||||
$(MAKE) clean
|
||||
COMPILE_WITH_ASAN=1 $(MAKE) check -j32
|
||||
@@ -922,6 +936,11 @@ asan_crash_test:
|
||||
COMPILE_WITH_ASAN=1 $(MAKE) crash_test
|
||||
$(MAKE) clean
|
||||
|
||||
asan_crash_test_with_atomic_flush:
|
||||
$(MAKE) clean
|
||||
COMPILE_WITH_ASAN=1 $(MAKE) crash_test_with_atomic_flush
|
||||
$(MAKE) clean
|
||||
|
||||
ubsan_check:
|
||||
$(MAKE) clean
|
||||
COMPILE_WITH_UBSAN=1 $(MAKE) check -j32
|
||||
@@ -932,6 +951,11 @@ ubsan_crash_test:
|
||||
COMPILE_WITH_UBSAN=1 $(MAKE) crash_test
|
||||
$(MAKE) clean
|
||||
|
||||
ubsan_crash_test_with_atomic_flush:
|
||||
$(MAKE) clean
|
||||
COMPILE_WITH_UBSAN=1 $(MAKE) crash_test_with_atomic_flush
|
||||
$(MAKE) clean
|
||||
|
||||
valgrind_test:
|
||||
ROCKSDB_VALGRIND_RUN=1 DISABLE_JEMALLOC=1 $(MAKE) valgrind_check
|
||||
|
||||
@@ -1537,9 +1561,6 @@ statistics_test: monitoring/statistics_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
lru_cache_test: cache/lru_cache_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
lua_test: utilities/lua/rocks_lua_test.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
range_del_aggregator_test: db/range_del_aggregator_test.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
@@ -1558,6 +1579,9 @@ range_tombstone_fragmenter_test: db/range_tombstone_fragmenter_test.o db/db_test
|
||||
sst_file_reader_test: table/sst_file_reader_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
db_secondary_test: db/db_secondary_test.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
#-------------------------------------------------
|
||||
# make install related stuff
|
||||
INSTALL_PATH ?= /usr/local
|
||||
@@ -1713,7 +1737,7 @@ endif
|
||||
fi
|
||||
tar xvzf snappy-$(SNAPPY_VER).tar.gz
|
||||
mkdir snappy-$(SNAPPY_VER)/build
|
||||
cd snappy-$(SNAPPY_VER)/build && CFLAGS='${EXTRA_CFLAGS}' CXXFLAGS='${EXTRA_CXXFLAGS}' LDFLAGS='${EXTRA_LDFLAGS}' cmake .. && $(MAKE) ${SNAPPY_MAKE_TARGET}
|
||||
cd snappy-$(SNAPPY_VER)/build && CFLAGS='${EXTRA_CFLAGS}' CXXFLAGS='${EXTRA_CXXFLAGS}' LDFLAGS='${EXTRA_LDFLAGS}' cmake -DCMAKE_POSITION_INDEPENDENT_CODE=ON .. && $(MAKE) ${SNAPPY_MAKE_TARGET}
|
||||
cp snappy-$(SNAPPY_VER)/build/libsnappy.a .
|
||||
|
||||
liblz4.a:
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
[](http://140.211.168.68:8080/job/Rocksdb)
|
||||
|
||||
RocksDB is developed and maintained by Facebook Database Engineering Team.
|
||||
It is built on earlier work on LevelDB by Sanjay Ghemawat (sanjay@google.com)
|
||||
It is built on earlier work on [LevelDB](https://github.com/google/leveldb) by Sanjay Ghemawat (sanjay@google.com)
|
||||
and Jeff Dean (jeff@google.com)
|
||||
|
||||
This code is a library that forms the core building block for a fast
|
||||
|
||||
@@ -98,6 +98,7 @@ cpp_library(
|
||||
"db/db_impl_files.cc",
|
||||
"db/db_impl_open.cc",
|
||||
"db/db_impl_readonly.cc",
|
||||
"db/db_impl_secondary.cc",
|
||||
"db/db_impl_write.cc",
|
||||
"db/db_info_dumper.cc",
|
||||
"db/db_iter.cc",
|
||||
@@ -110,6 +111,7 @@ cpp_library(
|
||||
"db/flush_job.cc",
|
||||
"db/flush_scheduler.cc",
|
||||
"db/forward_iterator.cc",
|
||||
"db/in_memory_stats_history.cc",
|
||||
"db/internal_stats.cc",
|
||||
"db/log_reader.cc",
|
||||
"db/log_writer.cc",
|
||||
@@ -142,7 +144,6 @@ cpp_library(
|
||||
"env/io_posix.cc",
|
||||
"env/mock_env.cc",
|
||||
"memtable/alloc_tracker.cc",
|
||||
"memtable/hash_cuckoo_rep.cc",
|
||||
"memtable/hash_linklist_rep.cc",
|
||||
"memtable/hash_skiplist_rep.cc",
|
||||
"memtable/skiplistrep.cc",
|
||||
@@ -261,7 +262,6 @@ cpp_library(
|
||||
"utilities/env_mirror.cc",
|
||||
"utilities/env_timed.cc",
|
||||
"utilities/leveldb_options/leveldb_options.cc",
|
||||
"utilities/lua/rocks_lua_compaction_filter.cc",
|
||||
"utilities/memory/memory_util.cc",
|
||||
"utilities/merge_operators/bytesxor.cc",
|
||||
"utilities/merge_operators/max.cc",
|
||||
@@ -606,6 +606,11 @@ ROCKS_TESTS = [
|
||||
"db/db_range_del_test.cc",
|
||||
"serial",
|
||||
],
|
||||
[
|
||||
"db_secondary_test",
|
||||
"db/db_secondary_test.cc",
|
||||
"serial",
|
||||
],
|
||||
[
|
||||
"db_sst_test",
|
||||
"db/db_sst_test.cc",
|
||||
|
||||
+1
-1
@@ -43,7 +43,7 @@ We plan to use this port for our business purposes here at Bing and this provide
|
||||
|
||||
* Certain headers that are not present and not necessary on Windows were simply `#ifndef OS_WIN` in a few places (`unistd.h`)
|
||||
* All posix specific headers were replaced to port/port.h which worked well
|
||||
* Replaced `dirent.h` for `port/dirent.h` (very few places) with the implementation of the relevant interfaces within `rocksdb::port` namespace
|
||||
* Replaced `dirent.h` for `port/port_dirent.h` (very few places) with the implementation of the relevant interfaces within `rocksdb::port` namespace
|
||||
* Replaced `sys/time.h` to `port/sys_time.h` (few places) implemented equivalents within `rocksdb::port`
|
||||
* `printf %z` specification is not supported on Windows. To imitate existing standards we came up with a string macro `ROCKSDB_PRIszt` which expands to `zu` on posix systems and to `Iu` on windows.
|
||||
* in class member initialization were moved to a __ctors in some cases
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
@@ -81,7 +82,7 @@ def get_tests(repo_path):
|
||||
else:
|
||||
# we consumed all the parallel tests
|
||||
break
|
||||
|
||||
|
||||
return tests
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
|
||||
# Create a tmp directory for the test to use
|
||||
TEST_DIR=$(mktemp -d /dev/shm/fbcode_rocksdb_XXXXXXX)
|
||||
# shellcheck disable=SC2068
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
from __future__ import unicode_literals
|
||||
rocksdb_target_header = """load("@fbcode_macros//build_defs:auto_headers.bzl", "AutoHeaders")
|
||||
load("@fbcode_macros//build_defs:cpp_library.bzl", "cpp_library")
|
||||
load(":defs.bzl", "test_binary")
|
||||
|
||||
REPO_PATH = package_name() + "/"
|
||||
@@ -23,6 +25,7 @@ ROCKSDB_COMPILER_FLAGS = [
|
||||
"-DBZIP2",
|
||||
"-DLZ4",
|
||||
"-DZSTD",
|
||||
"-DZSTD_STATIC_LINKING_ONLY",
|
||||
"-DGFLAGS=gflags",
|
||||
"-DNUMA",
|
||||
"-DTBB",
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
|
||||
"""
|
||||
This module keeps commonly used components.
|
||||
"""
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#!/usr/bin/python
|
||||
# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
|
||||
|
||||
# amalgamate.py creates an amalgamation from a unity build.
|
||||
# It can be run with either Python 2 or 3.
|
||||
|
||||
@@ -234,7 +234,7 @@ else
|
||||
#include <linux/falloc.h>
|
||||
int main() {
|
||||
int fd = open("/dev/null", 0);
|
||||
fallocate(fd, FALLOC_FL_KEEP_SIZE | FALLOC_FL_PUNCH_HOLE, 0, 1024);
|
||||
fallocate(fd, FALLOC_FL_KEEP_SIZE, 0, 1024);
|
||||
}
|
||||
EOF
|
||||
if [ "$?" = 0 ]; then
|
||||
@@ -518,8 +518,8 @@ if test "$USE_HDFS"; then
|
||||
echo "JAVA_HOME has to be set for HDFS usage."
|
||||
exit 1
|
||||
fi
|
||||
HDFS_CCFLAGS="$HDFS_CCFLAGS -I$JAVA_HOME/include -I$JAVA_HOME/include/linux -DUSE_HDFS"
|
||||
HDFS_LDFLAGS="$HDFS_LDFLAGS -lhdfs -L$JAVA_HOME/jre/lib/amd64"
|
||||
HDFS_CCFLAGS="$HDFS_CCFLAGS -I$JAVA_HOME/include -I$JAVA_HOME/include/linux -DUSE_HDFS -I$HADOOP_HOME/include"
|
||||
HDFS_LDFLAGS="$HDFS_LDFLAGS -lhdfs -L$JAVA_HOME/jre/lib/amd64 -L$HADOOP_HOME/lib/native"
|
||||
HDFS_LDFLAGS="$HDFS_LDFLAGS -L$JAVA_HOME/jre/lib/amd64/server -L$GLIBC_RUNTIME_PATH/lib"
|
||||
HDFS_LDFLAGS="$HDFS_LDFLAGS -ldl -lverify -ljava -ljvm"
|
||||
COMMON_FLAGS="$COMMON_FLAGS $HDFS_CCFLAGS"
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
|
||||
GCC_BASE=/mnt/gvfs/third-party2/gcc/112ec378fec7002ad3e09afde022e656049f7191/5.x/centos7-native/c447969
|
||||
CLANG_BASE=/mnt/gvfs/third-party2/llvm-fb/04999bdb3ce81a11073535dcb00b5e13dc1cbaf5/stable/centos7-native/c9f9104
|
||||
LIBGCC_BASE=/mnt/gvfs/third-party2/libgcc/92b0c8e5c8eecc71eb042594ce1ab3413799b385/5.x/gcc-5-glibc-2.23/339d858
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
# shellcheck disable=SC2148
|
||||
# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
|
||||
GCC_BASE=/mnt/gvfs/third-party2/gcc/cf7d14c625ce30bae1a4661c2319c5a283e4dd22/4.8.1/centos6-native/cc6c9dc
|
||||
CLANG_BASE=/mnt/gvfs/third-party2/llvm-fb/8598c375b0e94e1448182eb3df034704144a838d/stable/centos6-native/3f16ddd
|
||||
LIBGCC_BASE=/mnt/gvfs/third-party2/libgcc/d6e0a7da6faba45f5e5b1638f9edd7afc2f34e7d/4.8.1/gcc-4.8.1-glibc-2.17/8aac7fc
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
|
||||
GCC_BASE=/mnt/gvfs/third-party2/gcc/6e8e715624fd15256a7970073387793dfcf79b46/7.x/centos7-native/b2ef2b6
|
||||
CLANG_BASE=/mnt/gvfs/third-party2/llvm-fb/ef37e1faa1c29782abfac1ae65a291b9b7966f6d/stable/centos7-native/c9f9104
|
||||
LIBGCC_BASE=/mnt/gvfs/third-party2/libgcc/c67031f0f739ac61575a061518d6ef5038f99f90/7.x/platform007/5620abc
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
#!/usr/bin/env bash
|
||||
# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
|
||||
docker run -v $PWD:/rocks -w /rocks buildpack-deps make
|
||||
|
||||
@@ -132,11 +132,14 @@ _TEST_NAME_TO_PARSERS = {
|
||||
'lite': [CompilerErrorParser],
|
||||
'lite_test': [CompilerErrorParser, GTestErrorParser],
|
||||
'stress_crash': [CompilerErrorParser, DbCrashErrorParser],
|
||||
'stress_crash_with_atomic_flush': [CompilerErrorParser, DbCrashErrorParser],
|
||||
'write_stress': [CompilerErrorParser, WriteStressErrorParser],
|
||||
'asan': [CompilerErrorParser, GTestErrorParser, AsanErrorParser],
|
||||
'asan_crash': [CompilerErrorParser, AsanErrorParser, DbCrashErrorParser],
|
||||
'asan_crash_with_atomic_flush': [CompilerErrorParser, AsanErrorParser, DbCrashErrorParser],
|
||||
'ubsan': [CompilerErrorParser, GTestErrorParser, UbsanErrorParser],
|
||||
'ubsan_crash': [CompilerErrorParser, UbsanErrorParser, DbCrashErrorParser],
|
||||
'ubsan_crash_with_atomic_flush': [CompilerErrorParser, UbsanErrorParser, DbCrashErrorParser],
|
||||
'valgrind': [CompilerErrorParser, GTestErrorParser, ValgrindErrorParser],
|
||||
'tsan': [CompilerErrorParser, GTestErrorParser, TsanErrorParser],
|
||||
'format_compatible': [CompilerErrorParser, CompatErrorParser],
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
|
||||
# fail early
|
||||
set -e
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#!/bin/sh
|
||||
# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
|
||||
#
|
||||
# Set environment variables so that we can compile rocksdb using
|
||||
# fbcode settings. It uses the latest g++ and clang compilers and also
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#!/bin/sh
|
||||
# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
|
||||
#
|
||||
# Set environment variables so that we can compile rocksdb using
|
||||
# fbcode settings. It uses the latest g++ compiler and also
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#!/bin/sh
|
||||
# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
|
||||
#
|
||||
# Set environment variables so that we can compile rocksdb using
|
||||
# fbcode settings. It uses the latest g++ and clang compilers and also
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
|
||||
# If clang_format_diff.py command is not specfied, we assume we are able to
|
||||
# access directly without any path.
|
||||
if [ -z $CLANG_FORMAT_DIFF ]
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
# shellcheck disable=SC1113
|
||||
#/usr/bin/env bash
|
||||
# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
|
||||
|
||||
set -e
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#!/usr/local/fbcode/gcc-4.9-glibc-2.20-fb/bin/python2.7
|
||||
# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
|
||||
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
|
||||
|
||||
set -e
|
||||
|
||||
|
||||
@@ -400,6 +400,35 @@ STRESS_CRASH_TEST_COMMANDS="[
|
||||
}
|
||||
]"
|
||||
|
||||
#
|
||||
# RocksDB stress/crash test with atomic flush
|
||||
#
|
||||
STRESS_CRASH_TEST_WITH_ATOMIC_FLUSH_COMMANDS="[
|
||||
{
|
||||
'name':'Rocksdb Stress/Crash Test (atomic flush)',
|
||||
'oncall':'$ONCALL',
|
||||
'timeout': 86400,
|
||||
'steps': [
|
||||
$CLEANUP_ENV,
|
||||
{
|
||||
'name':'Build and run RocksDB debug stress tests',
|
||||
'shell':'$SHM $DEBUG $NON_TSAN_CRASH make J=1 db_stress || $CONTRUN_NAME=db_stress $TASK_CREATION_TOOL',
|
||||
'user':'root',
|
||||
$PARSER
|
||||
},
|
||||
{
|
||||
'name':'Build and run RocksDB debug crash tests with atomic flush',
|
||||
'timeout': 86400,
|
||||
'shell':'$SHM $DEBUG $NON_TSAN_CRASH make J=1 crash_test_with_atomic_flush || $CONTRUN_NAME=crash_test_with_atomic_flush $TASK_CREATION_TOOL',
|
||||
'user':'root',
|
||||
$PARSER
|
||||
}
|
||||
],
|
||||
$ARTIFACTS,
|
||||
$REPORT
|
||||
}
|
||||
]"
|
||||
|
||||
# RocksDB write stress test.
|
||||
# We run on disk device on purpose (i.e. no $SHM)
|
||||
# because we want to add some randomness to fsync commands
|
||||
@@ -464,6 +493,28 @@ ASAN_CRASH_TEST_COMMANDS="[
|
||||
}
|
||||
]"
|
||||
|
||||
#
|
||||
# RocksDB crash testing with atomic flush under address sanitizer
|
||||
#
|
||||
ASAN_CRASH_TEST_WITH_ATOMIC_FLUSH_COMMANDS="[
|
||||
{
|
||||
'name':'Rocksdb crash test (atomic flush) under ASAN',
|
||||
'oncall':'$ONCALL',
|
||||
'timeout': 86400,
|
||||
'steps': [
|
||||
$CLEANUP_ENV,
|
||||
{
|
||||
'name':'Build and run RocksDB debug asan_crash_test_with_atomic_flush',
|
||||
'timeout': 86400,
|
||||
'shell':'$SHM $DEBUG $NON_TSAN_CRASH make J=1 asan_crash_test_with_atomic_flush || $CONTRUN_NAME=asan_crash_test_with_atomic_flush $TASK_CREATION_TOOL',
|
||||
'user':'root',
|
||||
$PARSER
|
||||
},
|
||||
],
|
||||
$REPORT
|
||||
}
|
||||
]"
|
||||
|
||||
#
|
||||
# RocksDB test under undefined behavior sanitizer
|
||||
#
|
||||
@@ -506,6 +557,28 @@ UBSAN_CRASH_TEST_COMMANDS="[
|
||||
}
|
||||
]"
|
||||
|
||||
#
|
||||
# RocksDB crash testing with atomic flush under undefined behavior sanitizer
|
||||
#
|
||||
UBSAN_CRASH_TEST_WITH_ATOMIC_FLUSH_COMMANDS="[
|
||||
{
|
||||
'name':'Rocksdb crash test (atomic flush) under UBSAN',
|
||||
'oncall':'$ONCALL',
|
||||
'timeout': 86400,
|
||||
'steps': [
|
||||
$CLEANUP_ENV,
|
||||
{
|
||||
'name':'Build and run RocksDB debug ubsan_crash_test_with_atomic_flush',
|
||||
'timeout': 86400,
|
||||
'shell':'$SHM $DEBUG $NON_TSAN_CRASH make J=1 ubsan_crash_test_with_atomic_flush || $CONTRUN_NAME=ubsan_crash_test_with_atomic_flush $TASK_CREATION_TOOL',
|
||||
'user':'root',
|
||||
$PARSER
|
||||
},
|
||||
],
|
||||
$REPORT
|
||||
}
|
||||
]"
|
||||
|
||||
#
|
||||
# RocksDB unit test under valgrind
|
||||
#
|
||||
@@ -572,6 +645,28 @@ TSAN_CRASH_TEST_COMMANDS="[
|
||||
}
|
||||
]"
|
||||
|
||||
#
|
||||
# RocksDB crash test with atomic flush under TSAN
|
||||
#
|
||||
TSAN_CRASH_TEST_WITH_ATOMIC_FLUSH_COMMANDS="[
|
||||
{
|
||||
'name':'Rocksdb Crash Test with atomic flush under TSAN',
|
||||
'oncall':'$ONCALL',
|
||||
'timeout': 86400,
|
||||
'steps': [
|
||||
$CLEANUP_ENV,
|
||||
{
|
||||
'name':'Compile and run',
|
||||
'timeout': 86400,
|
||||
'shell':'set -o pipefail && $SHM $DEBUG $TSAN $TSAN_CRASH CRASH_TEST_KILL_ODD=1887 make J=1 crash_test_with_atomic_flush || $CONTRUN_NAME=tsan_crash_test_with_atomic_flush $TASK_CREATION_TOOL',
|
||||
'user':'root',
|
||||
$PARSER
|
||||
},
|
||||
],
|
||||
$REPORT
|
||||
}
|
||||
]"
|
||||
|
||||
#
|
||||
# RocksDB format compatible
|
||||
#
|
||||
@@ -611,7 +706,7 @@ run_no_compression()
|
||||
rm -rf /dev/shm/rocksdb
|
||||
mkdir /dev/shm/rocksdb
|
||||
make clean
|
||||
cat build_tools/fbcode_config.sh | grep -iv dzlib | grep -iv dlz4 | grep -iv dsnappy | grep -iv dbzip2 > .tmp.fbcode_config.sh
|
||||
cat build_tools/fbcode_config.sh | grep -iv dzstd | grep -iv dzlib | grep -iv dlz4 | grep -iv dsnappy | grep -iv dbzip2 > .tmp.fbcode_config.sh
|
||||
mv .tmp.fbcode_config.sh build_tools/fbcode_config.sh
|
||||
cat Makefile | grep -v tools/ldb_test.py > .tmp.Makefile
|
||||
mv .tmp.Makefile Makefile
|
||||
@@ -753,6 +848,9 @@ case $1 in
|
||||
stress_crash)
|
||||
echo $STRESS_CRASH_TEST_COMMANDS
|
||||
;;
|
||||
stress_crash_with_atomic_flush)
|
||||
echo $STRESS_CRASH_TEST_WITH_ATOMIC_FLUSH_COMMANDS
|
||||
;;
|
||||
write_stress)
|
||||
echo $WRITE_STRESS_COMMANDS
|
||||
;;
|
||||
@@ -762,12 +860,18 @@ case $1 in
|
||||
asan_crash)
|
||||
echo $ASAN_CRASH_TEST_COMMANDS
|
||||
;;
|
||||
asan_crash_with_atomic_flush)
|
||||
echo $ASAN_CRASH_TEST_WITH_ATOMIC_FLUSH_COMMANDS
|
||||
;;
|
||||
ubsan)
|
||||
echo $UBSAN_TEST_COMMANDS
|
||||
;;
|
||||
ubsan_crash)
|
||||
echo $UBSAN_CRASH_TEST_COMMANDS
|
||||
;;
|
||||
ubsan_crash_with_atomic_flush)
|
||||
echo $UBSAN_CRASH_TEST_WITH_ATOMIC_FLUSH_COMMANDS
|
||||
;;
|
||||
valgrind)
|
||||
echo $VALGRIND_TEST_COMMANDS
|
||||
;;
|
||||
@@ -777,6 +881,9 @@ case $1 in
|
||||
tsan_crash)
|
||||
echo $TSAN_CRASH_TEST_COMMANDS
|
||||
;;
|
||||
tsan_crash_with_atomic_flush)
|
||||
echo $TSAN_CRASH_TEST_WITH_ATOMIC_FLUSH_COMMANDS
|
||||
;;
|
||||
format_compatible)
|
||||
echo $FORMAT_COMPATIBLE_COMMANDS
|
||||
;;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
|
||||
# This script enables you running RocksDB tests by running
|
||||
# All the tests concurrently and utilizing all the cores
|
||||
Param(
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#!/bin/bash
|
||||
# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
|
||||
set -e
|
||||
|
||||
ROCKSDB_VERSION="5.10.3"
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#!/bin/sh
|
||||
# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
|
||||
#
|
||||
# Update dependencies.sh file with the latest avaliable versions
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
|
||||
if [ "$#" = "0" ]; then
|
||||
echo "Usage: $0 major|minor|patch|full"
|
||||
exit 1
|
||||
|
||||
Vendored
+9
-9
@@ -73,8 +73,7 @@ class CacheTest : public testing::TestWithParam<std::string> {
|
||||
current_ = this;
|
||||
}
|
||||
|
||||
~CacheTest() {
|
||||
}
|
||||
~CacheTest() override {}
|
||||
|
||||
std::shared_ptr<Cache> NewCache(size_t capacity) {
|
||||
auto type = GetParam();
|
||||
@@ -99,7 +98,7 @@ class CacheTest : public testing::TestWithParam<std::string> {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int Lookup(shared_ptr<Cache> cache, int key) {
|
||||
int Lookup(std::shared_ptr<Cache> cache, int key) {
|
||||
Cache::Handle* handle = cache->Lookup(EncodeKey(key));
|
||||
const int r = (handle == nullptr) ? -1 : DecodeValue(cache->Value(handle));
|
||||
if (handle != nullptr) {
|
||||
@@ -108,16 +107,16 @@ class CacheTest : public testing::TestWithParam<std::string> {
|
||||
return r;
|
||||
}
|
||||
|
||||
void Insert(shared_ptr<Cache> cache, int key, int value, int charge = 1) {
|
||||
void Insert(std::shared_ptr<Cache> cache, int key, int value,
|
||||
int charge = 1) {
|
||||
cache->Insert(EncodeKey(key), EncodeValue(value), charge,
|
||||
&CacheTest::Deleter);
|
||||
}
|
||||
|
||||
void Erase(shared_ptr<Cache> cache, int key) {
|
||||
void Erase(std::shared_ptr<Cache> cache, int key) {
|
||||
cache->Erase(EncodeKey(key));
|
||||
}
|
||||
|
||||
|
||||
int Lookup(int key) {
|
||||
return Lookup(cache_, key);
|
||||
}
|
||||
@@ -307,7 +306,7 @@ TEST_P(CacheTest, EvictionPolicy) {
|
||||
Insert(200, 201);
|
||||
|
||||
// Frequently used entry must be kept around
|
||||
for (int i = 0; i < kCacheSize + 100; i++) {
|
||||
for (int i = 0; i < kCacheSize + 200; i++) {
|
||||
Insert(1000+i, 2000+i);
|
||||
ASSERT_EQ(101, Lookup(100));
|
||||
}
|
||||
@@ -360,7 +359,7 @@ TEST_P(CacheTest, EvictionPolicyRef) {
|
||||
Insert(303, 104);
|
||||
|
||||
// Insert entries much more than Cache capacity
|
||||
for (int i = 0; i < kCacheSize + 100; i++) {
|
||||
for (int i = 0; i < kCacheSize + 200; i++) {
|
||||
Insert(1000 + i, 2000 + i);
|
||||
}
|
||||
|
||||
@@ -688,7 +687,8 @@ TEST_P(CacheTest, DefaultShardBits) {
|
||||
}
|
||||
|
||||
#ifdef SUPPORT_CLOCK_CACHE
|
||||
shared_ptr<Cache> (*new_clock_cache_func)(size_t, int, bool) = NewClockCache;
|
||||
std::shared_ptr<Cache> (*new_clock_cache_func)(size_t, int,
|
||||
bool) = NewClockCache;
|
||||
INSTANTIATE_TEST_CASE_P(CacheTestInstance, CacheTest,
|
||||
testing::Values(kLRU, kClock));
|
||||
#else
|
||||
|
||||
Vendored
+25
-28
@@ -234,38 +234,35 @@ struct CleanupContext {
|
||||
};
|
||||
|
||||
// A cache shard which maintains its own CLOCK cache.
|
||||
class ClockCacheShard : public CacheShard {
|
||||
class ClockCacheShard final : public CacheShard {
|
||||
public:
|
||||
// Hash map type.
|
||||
typedef tbb::concurrent_hash_map<CacheKey, CacheHandle*, CacheKey> HashTable;
|
||||
|
||||
ClockCacheShard();
|
||||
~ClockCacheShard();
|
||||
~ClockCacheShard() override;
|
||||
|
||||
// Interfaces
|
||||
virtual void SetCapacity(size_t capacity) override;
|
||||
virtual void SetStrictCapacityLimit(bool strict_capacity_limit) override;
|
||||
virtual Status Insert(const Slice& key, uint32_t hash, void* value,
|
||||
size_t charge,
|
||||
void (*deleter)(const Slice& key, void* value),
|
||||
Cache::Handle** handle,
|
||||
Cache::Priority priority) override;
|
||||
virtual Cache::Handle* Lookup(const Slice& key, uint32_t hash) override;
|
||||
void SetCapacity(size_t capacity) override;
|
||||
void SetStrictCapacityLimit(bool strict_capacity_limit) override;
|
||||
Status Insert(const Slice& key, uint32_t hash, void* value, size_t charge,
|
||||
void (*deleter)(const Slice& key, void* value),
|
||||
Cache::Handle** handle, Cache::Priority priority) override;
|
||||
Cache::Handle* Lookup(const Slice& key, uint32_t hash) override;
|
||||
// If the entry in in cache, increase reference count and return true.
|
||||
// Return false otherwise.
|
||||
//
|
||||
// Not necessary to hold mutex_ before being called.
|
||||
virtual bool Ref(Cache::Handle* handle) override;
|
||||
virtual bool Release(Cache::Handle* handle,
|
||||
bool force_erase = false) override;
|
||||
virtual void Erase(const Slice& key, uint32_t hash) override;
|
||||
bool Ref(Cache::Handle* handle) override;
|
||||
bool Release(Cache::Handle* handle, bool force_erase = false) override;
|
||||
void Erase(const Slice& key, uint32_t hash) override;
|
||||
bool EraseAndConfirm(const Slice& key, uint32_t hash,
|
||||
CleanupContext* context);
|
||||
virtual size_t GetUsage() const override;
|
||||
virtual size_t GetPinnedUsage() const override;
|
||||
virtual void EraseUnRefEntries() override;
|
||||
virtual void ApplyToAllCacheEntries(void (*callback)(void*, size_t),
|
||||
bool thread_safe) override;
|
||||
size_t GetUsage() const override;
|
||||
size_t GetPinnedUsage() const override;
|
||||
void EraseUnRefEntries() override;
|
||||
void ApplyToAllCacheEntries(void (*callback)(void*, size_t),
|
||||
bool thread_safe) override;
|
||||
|
||||
private:
|
||||
static const uint32_t kInCacheBit = 1;
|
||||
@@ -675,7 +672,7 @@ void ClockCacheShard::EraseUnRefEntries() {
|
||||
Cleanup(context);
|
||||
}
|
||||
|
||||
class ClockCache : public ShardedCache {
|
||||
class ClockCache final : public ShardedCache {
|
||||
public:
|
||||
ClockCache(size_t capacity, int num_shard_bits, bool strict_capacity_limit)
|
||||
: ShardedCache(capacity, num_shard_bits, strict_capacity_limit) {
|
||||
@@ -685,31 +682,31 @@ class ClockCache : public ShardedCache {
|
||||
SetStrictCapacityLimit(strict_capacity_limit);
|
||||
}
|
||||
|
||||
virtual ~ClockCache() { delete[] shards_; }
|
||||
~ClockCache() override { delete[] shards_; }
|
||||
|
||||
virtual const char* Name() const override { return "ClockCache"; }
|
||||
const char* Name() const override { return "ClockCache"; }
|
||||
|
||||
virtual CacheShard* GetShard(int shard) override {
|
||||
CacheShard* GetShard(int shard) override {
|
||||
return reinterpret_cast<CacheShard*>(&shards_[shard]);
|
||||
}
|
||||
|
||||
virtual const CacheShard* GetShard(int shard) const override {
|
||||
const CacheShard* GetShard(int shard) const override {
|
||||
return reinterpret_cast<CacheShard*>(&shards_[shard]);
|
||||
}
|
||||
|
||||
virtual void* Value(Handle* handle) override {
|
||||
void* Value(Handle* handle) override {
|
||||
return reinterpret_cast<const CacheHandle*>(handle)->value;
|
||||
}
|
||||
|
||||
virtual size_t GetCharge(Handle* handle) const override {
|
||||
size_t GetCharge(Handle* handle) const override {
|
||||
return reinterpret_cast<const CacheHandle*>(handle)->charge;
|
||||
}
|
||||
|
||||
virtual uint32_t GetHash(Handle* handle) const override {
|
||||
uint32_t GetHash(Handle* handle) const override {
|
||||
return reinterpret_cast<const CacheHandle*>(handle)->hash;
|
||||
}
|
||||
|
||||
virtual void DisownData() override { shards_ = nullptr; }
|
||||
void DisownData() override { shards_ = nullptr; }
|
||||
|
||||
private:
|
||||
ClockCacheShard* shards_;
|
||||
|
||||
Vendored
+14
-7
@@ -100,14 +100,16 @@ void LRUHandleTable::Resize() {
|
||||
}
|
||||
|
||||
LRUCacheShard::LRUCacheShard(size_t capacity, bool strict_capacity_limit,
|
||||
double high_pri_pool_ratio)
|
||||
double high_pri_pool_ratio,
|
||||
bool use_adaptive_mutex)
|
||||
: capacity_(0),
|
||||
high_pri_pool_usage_(0),
|
||||
strict_capacity_limit_(strict_capacity_limit),
|
||||
high_pri_pool_ratio_(high_pri_pool_ratio),
|
||||
high_pri_pool_capacity_(0),
|
||||
usage_(0),
|
||||
lru_usage_(0) {
|
||||
lru_usage_(0),
|
||||
mutex_(use_adaptive_mutex) {
|
||||
// Make empty circular linked list
|
||||
lru_.next = &lru_;
|
||||
lru_.prev = &lru_;
|
||||
@@ -462,7 +464,8 @@ std::string LRUCacheShard::GetPrintableOptions() const {
|
||||
|
||||
LRUCache::LRUCache(size_t capacity, int num_shard_bits,
|
||||
bool strict_capacity_limit, double high_pri_pool_ratio,
|
||||
std::shared_ptr<MemoryAllocator> allocator)
|
||||
std::shared_ptr<MemoryAllocator> allocator,
|
||||
bool use_adaptive_mutex)
|
||||
: ShardedCache(capacity, num_shard_bits, strict_capacity_limit,
|
||||
std::move(allocator)) {
|
||||
num_shards_ = 1 << num_shard_bits;
|
||||
@@ -471,7 +474,8 @@ LRUCache::LRUCache(size_t capacity, int num_shard_bits,
|
||||
size_t per_shard = (capacity + (num_shards_ - 1)) / num_shards_;
|
||||
for (int i = 0; i < num_shards_; i++) {
|
||||
new (&shards_[i])
|
||||
LRUCacheShard(per_shard, strict_capacity_limit, high_pri_pool_ratio);
|
||||
LRUCacheShard(per_shard, strict_capacity_limit, high_pri_pool_ratio,
|
||||
use_adaptive_mutex);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -540,13 +544,15 @@ std::shared_ptr<Cache> NewLRUCache(const LRUCacheOptions& cache_opts) {
|
||||
return NewLRUCache(cache_opts.capacity, cache_opts.num_shard_bits,
|
||||
cache_opts.strict_capacity_limit,
|
||||
cache_opts.high_pri_pool_ratio,
|
||||
cache_opts.memory_allocator);
|
||||
cache_opts.memory_allocator,
|
||||
cache_opts.use_adaptive_mutex);
|
||||
}
|
||||
|
||||
std::shared_ptr<Cache> NewLRUCache(
|
||||
size_t capacity, int num_shard_bits, bool strict_capacity_limit,
|
||||
double high_pri_pool_ratio,
|
||||
std::shared_ptr<MemoryAllocator> memory_allocator) {
|
||||
std::shared_ptr<MemoryAllocator> memory_allocator,
|
||||
bool use_adaptive_mutex) {
|
||||
if (num_shard_bits >= 20) {
|
||||
return nullptr; // the cache cannot be sharded into too many fine pieces
|
||||
}
|
||||
@@ -559,7 +565,8 @@ std::shared_ptr<Cache> NewLRUCache(
|
||||
}
|
||||
return std::make_shared<LRUCache>(capacity, num_shard_bits,
|
||||
strict_capacity_limit, high_pri_pool_ratio,
|
||||
std::move(memory_allocator));
|
||||
std::move(memory_allocator),
|
||||
use_adaptive_mutex);
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
Vendored
+32
-19
@@ -55,10 +55,18 @@ struct LRUHandle {
|
||||
// cache itself is counted as 1
|
||||
|
||||
// Include the following flags:
|
||||
// in_cache: whether this entry is referenced by the hash table.
|
||||
// is_high_pri: whether this entry is high priority entry.
|
||||
// in_high_pri_pool: whether this entry is in high-pri pool.
|
||||
char flags;
|
||||
// IN_CACHE: whether this entry is referenced by the hash table.
|
||||
// IS_HIGH_PRI: whether this entry is high priority entry.
|
||||
// IN_HIGH_PRI_POOL: whether this entry is in high-pri pool.
|
||||
// HAS_HIT: whether this entry has had any lookups (hits).
|
||||
enum Flags : uint8_t {
|
||||
IN_CACHE = (1 << 0),
|
||||
IS_HIGH_PRI = (1 << 1),
|
||||
IN_HIGH_PRI_POOL = (1 << 2),
|
||||
HAS_HIT = (1 << 3),
|
||||
};
|
||||
|
||||
uint8_t flags;
|
||||
|
||||
uint32_t hash; // Hash of key(); used for fast sharding and comparisons
|
||||
|
||||
@@ -74,36 +82,36 @@ struct LRUHandle {
|
||||
}
|
||||
}
|
||||
|
||||
bool InCache() { return flags & 1; }
|
||||
bool IsHighPri() { return flags & 2; }
|
||||
bool InHighPriPool() { return flags & 4; }
|
||||
bool HasHit() { return flags & 8; }
|
||||
bool InCache() const { return flags & IN_CACHE; }
|
||||
bool IsHighPri() const { return flags & IS_HIGH_PRI; }
|
||||
bool InHighPriPool() const { return flags & IN_HIGH_PRI_POOL; }
|
||||
bool HasHit() const { return flags & HAS_HIT; }
|
||||
|
||||
void SetInCache(bool in_cache) {
|
||||
if (in_cache) {
|
||||
flags |= 1;
|
||||
flags |= IN_CACHE;
|
||||
} else {
|
||||
flags &= ~1;
|
||||
flags &= ~IN_CACHE;
|
||||
}
|
||||
}
|
||||
|
||||
void SetPriority(Cache::Priority priority) {
|
||||
if (priority == Cache::Priority::HIGH) {
|
||||
flags |= 2;
|
||||
flags |= IS_HIGH_PRI;
|
||||
} else {
|
||||
flags &= ~2;
|
||||
flags &= ~IS_HIGH_PRI;
|
||||
}
|
||||
}
|
||||
|
||||
void SetInHighPriPool(bool in_high_pri_pool) {
|
||||
if (in_high_pri_pool) {
|
||||
flags |= 4;
|
||||
flags |= IN_HIGH_PRI_POOL;
|
||||
} else {
|
||||
flags &= ~4;
|
||||
flags &= ~IN_HIGH_PRI_POOL;
|
||||
}
|
||||
}
|
||||
|
||||
void SetHit() { flags |= 8; }
|
||||
void SetHit() { flags |= HAS_HIT; }
|
||||
|
||||
void Free() {
|
||||
assert((refs == 1 && InCache()) || (refs == 0 && !InCache()));
|
||||
@@ -157,10 +165,10 @@ class LRUHandleTable {
|
||||
};
|
||||
|
||||
// A single shard of sharded cache.
|
||||
class ALIGN_AS(CACHE_LINE_SIZE) LRUCacheShard : public CacheShard {
|
||||
class ALIGN_AS(CACHE_LINE_SIZE) LRUCacheShard final : public CacheShard {
|
||||
public:
|
||||
LRUCacheShard(size_t capacity, bool strict_capacity_limit,
|
||||
double high_pri_pool_ratio);
|
||||
double high_pri_pool_ratio, bool use_adaptive_mutex);
|
||||
virtual ~LRUCacheShard();
|
||||
|
||||
// Separate from constructor so caller can easily make an array of LRUCache
|
||||
@@ -276,11 +284,16 @@ class ALIGN_AS(CACHE_LINE_SIZE) LRUCacheShard : public CacheShard {
|
||||
mutable port::Mutex mutex_;
|
||||
};
|
||||
|
||||
class LRUCache : public ShardedCache {
|
||||
class LRUCache
|
||||
#ifdef NDEBUG
|
||||
final
|
||||
#endif
|
||||
: public ShardedCache {
|
||||
public:
|
||||
LRUCache(size_t capacity, int num_shard_bits, bool strict_capacity_limit,
|
||||
double high_pri_pool_ratio,
|
||||
std::shared_ptr<MemoryAllocator> memory_allocator = nullptr);
|
||||
std::shared_ptr<MemoryAllocator> memory_allocator = nullptr,
|
||||
bool use_adaptive_mutex = kDefaultToAdaptiveMutex);
|
||||
virtual ~LRUCache();
|
||||
virtual const char* Name() const override { return "LRUCache"; }
|
||||
virtual CacheShard* GetShard(int shard) override;
|
||||
|
||||
Vendored
+4
-3
@@ -15,7 +15,7 @@ namespace rocksdb {
|
||||
class LRUCacheTest : public testing::Test {
|
||||
public:
|
||||
LRUCacheTest() {}
|
||||
~LRUCacheTest() { DeleteCache(); }
|
||||
~LRUCacheTest() override { DeleteCache(); }
|
||||
|
||||
void DeleteCache() {
|
||||
if (cache_ != nullptr) {
|
||||
@@ -25,12 +25,13 @@ class LRUCacheTest : public testing::Test {
|
||||
}
|
||||
}
|
||||
|
||||
void NewCache(size_t capacity, double high_pri_pool_ratio = 0.0) {
|
||||
void NewCache(size_t capacity, double high_pri_pool_ratio = 0.0,
|
||||
bool use_adaptive_mutex = kDefaultToAdaptiveMutex) {
|
||||
DeleteCache();
|
||||
cache_ = reinterpret_cast<LRUCacheShard*>(
|
||||
port::cacheline_aligned_alloc(sizeof(LRUCacheShard)));
|
||||
new (cache_) LRUCacheShard(capacity, false /*strict_capcity_limit*/,
|
||||
high_pri_pool_ratio);
|
||||
high_pri_pool_ratio, use_adaptive_mutex);
|
||||
}
|
||||
|
||||
void Insert(const std::string& key,
|
||||
|
||||
Vendored
+1
-1
@@ -83,7 +83,7 @@ class ShardedCache : public Cache {
|
||||
|
||||
private:
|
||||
static inline uint32_t HashSlice(const Slice& s) {
|
||||
return Hash(s.data(), s.size(), 0);
|
||||
return static_cast<uint32_t>(GetSliceNPHash64(s));
|
||||
}
|
||||
|
||||
uint32_t Shard(uint32_t hash) {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
|
||||
|
||||
# Exit on error.
|
||||
set -e
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
|
||||
import re
|
||||
import sys
|
||||
|
||||
|
||||
+23
-14
@@ -47,18 +47,20 @@ TableBuilder* NewTableBuilder(
|
||||
int_tbl_prop_collector_factories,
|
||||
uint32_t column_family_id, const std::string& column_family_name,
|
||||
WritableFileWriter* file, const CompressionType compression_type,
|
||||
const CompressionOptions& compression_opts, int level,
|
||||
const std::string* compression_dict, const bool skip_filters,
|
||||
const uint64_t creation_time, const uint64_t oldest_key_time) {
|
||||
uint64_t sample_for_compression, const CompressionOptions& compression_opts,
|
||||
int level, const bool skip_filters, const uint64_t creation_time,
|
||||
const uint64_t oldest_key_time, const uint64_t target_file_size,
|
||||
const uint64_t file_creation_time) {
|
||||
assert((column_family_id ==
|
||||
TablePropertiesCollectorFactory::Context::kUnknownColumnFamily) ==
|
||||
column_family_name.empty());
|
||||
return ioptions.table_factory->NewTableBuilder(
|
||||
TableBuilderOptions(ioptions, moptions, internal_comparator,
|
||||
int_tbl_prop_collector_factories, compression_type,
|
||||
compression_opts, compression_dict, skip_filters,
|
||||
column_family_name, level, creation_time,
|
||||
oldest_key_time),
|
||||
sample_for_compression, compression_opts,
|
||||
skip_filters, column_family_name, level,
|
||||
creation_time, oldest_key_time, target_file_size,
|
||||
file_creation_time),
|
||||
column_family_id, file);
|
||||
}
|
||||
|
||||
@@ -75,11 +77,12 @@ Status BuildTable(
|
||||
std::vector<SequenceNumber> snapshots,
|
||||
SequenceNumber earliest_write_conflict_snapshot,
|
||||
SnapshotChecker* snapshot_checker, const CompressionType compression,
|
||||
const CompressionOptions& compression_opts, bool paranoid_file_checks,
|
||||
InternalStats* internal_stats, TableFileCreationReason reason,
|
||||
EventLogger* event_logger, int job_id, const Env::IOPriority io_priority,
|
||||
TableProperties* table_properties, int level, const uint64_t creation_time,
|
||||
const uint64_t oldest_key_time, Env::WriteLifeTimeHint write_hint) {
|
||||
uint64_t sample_for_compression, const CompressionOptions& compression_opts,
|
||||
bool paranoid_file_checks, InternalStats* internal_stats,
|
||||
TableFileCreationReason reason, EventLogger* event_logger, int job_id,
|
||||
const Env::IOPriority io_priority, TableProperties* table_properties,
|
||||
int level, const uint64_t creation_time, const uint64_t oldest_key_time,
|
||||
Env::WriteLifeTimeHint write_hint, const uint64_t file_creation_time) {
|
||||
assert((column_family_id ==
|
||||
TablePropertiesCollectorFactory::Context::kUnknownColumnFamily) ==
|
||||
column_family_name.empty());
|
||||
@@ -105,6 +108,11 @@ Status BuildTable(
|
||||
if (iter->Valid() || !range_del_agg->IsEmpty()) {
|
||||
TableBuilder* builder;
|
||||
std::unique_ptr<WritableFileWriter> file_writer;
|
||||
// Currently we only enable dictionary compression during compaction to the
|
||||
// bottommost level.
|
||||
CompressionOptions compression_opts_for_flush(compression_opts);
|
||||
compression_opts_for_flush.max_dict_bytes = 0;
|
||||
compression_opts_for_flush.zstd_max_train_bytes = 0;
|
||||
{
|
||||
std::unique_ptr<WritableFile> file;
|
||||
#ifndef NDEBUG
|
||||
@@ -127,9 +135,10 @@ Status BuildTable(
|
||||
builder = NewTableBuilder(
|
||||
ioptions, mutable_cf_options, internal_comparator,
|
||||
int_tbl_prop_collector_factories, column_family_id,
|
||||
column_family_name, file_writer.get(), compression, compression_opts,
|
||||
level, nullptr /* compression_dict */, false /* skip_filters */,
|
||||
creation_time, oldest_key_time);
|
||||
column_family_name, file_writer.get(), compression,
|
||||
sample_for_compression, compression_opts_for_flush, level,
|
||||
false /* skip_filters */, creation_time, oldest_key_time,
|
||||
0 /*target_file_size*/, file_creation_time);
|
||||
}
|
||||
|
||||
MergeHelper merge(env, internal_comparator.user_comparator(),
|
||||
|
||||
+6
-5
@@ -40,8 +40,6 @@ class InternalStats;
|
||||
// @param column_family_name Name of the column family that is also identified
|
||||
// by column_family_id, or empty string if unknown. It must outlive the
|
||||
// TableBuilder returned by this function.
|
||||
// @param compression_dict Data for presetting the compression library's
|
||||
// dictionary, or nullptr.
|
||||
TableBuilder* NewTableBuilder(
|
||||
const ImmutableCFOptions& options, const MutableCFOptions& moptions,
|
||||
const InternalKeyComparator& internal_comparator,
|
||||
@@ -49,10 +47,11 @@ TableBuilder* NewTableBuilder(
|
||||
int_tbl_prop_collector_factories,
|
||||
uint32_t column_family_id, const std::string& column_family_name,
|
||||
WritableFileWriter* file, const CompressionType compression_type,
|
||||
const uint64_t sample_for_compression,
|
||||
const CompressionOptions& compression_opts, int level,
|
||||
const std::string* compression_dict = nullptr,
|
||||
const bool skip_filters = false, const uint64_t creation_time = 0,
|
||||
const uint64_t oldest_key_time = 0);
|
||||
const uint64_t oldest_key_time = 0, const uint64_t target_file_size = 0,
|
||||
const uint64_t file_creation_time = 0);
|
||||
|
||||
// Build a Table file from the contents of *iter. The generated file
|
||||
// will be named according to number specified in meta. On success, the rest of
|
||||
@@ -75,12 +74,14 @@ extern Status BuildTable(
|
||||
std::vector<SequenceNumber> snapshots,
|
||||
SequenceNumber earliest_write_conflict_snapshot,
|
||||
SnapshotChecker* snapshot_checker, const CompressionType compression,
|
||||
const uint64_t sample_for_compression,
|
||||
const CompressionOptions& compression_opts, bool paranoid_file_checks,
|
||||
InternalStats* internal_stats, TableFileCreationReason reason,
|
||||
EventLogger* event_logger = nullptr, int job_id = 0,
|
||||
const Env::IOPriority io_priority = Env::IO_HIGH,
|
||||
TableProperties* table_properties = nullptr, int level = -1,
|
||||
const uint64_t creation_time = 0, const uint64_t oldest_key_time = 0,
|
||||
Env::WriteLifeTimeHint write_hint = Env::WLTH_NOT_SET);
|
||||
Env::WriteLifeTimeHint write_hint = Env::WLTH_NOT_SET,
|
||||
const uint64_t file_creation_time = 0);
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
@@ -208,13 +208,10 @@ struct rocksdb_compactionfilter_t : public CompactionFilter {
|
||||
const char* (*name_)(void*);
|
||||
unsigned char ignore_snapshots_;
|
||||
|
||||
virtual ~rocksdb_compactionfilter_t() {
|
||||
(*destructor_)(state_);
|
||||
}
|
||||
~rocksdb_compactionfilter_t() override { (*destructor_)(state_); }
|
||||
|
||||
virtual bool Filter(int level, const Slice& key, const Slice& existing_value,
|
||||
std::string* new_value,
|
||||
bool* value_changed) const override {
|
||||
bool Filter(int level, const Slice& key, const Slice& existing_value,
|
||||
std::string* new_value, bool* value_changed) const override {
|
||||
char* c_new_value = nullptr;
|
||||
size_t new_value_length = 0;
|
||||
unsigned char c_value_changed = 0;
|
||||
@@ -231,9 +228,9 @@ struct rocksdb_compactionfilter_t : public CompactionFilter {
|
||||
return result;
|
||||
}
|
||||
|
||||
virtual const char* Name() const override { return (*name_)(state_); }
|
||||
const char* Name() const override { return (*name_)(state_); }
|
||||
|
||||
virtual bool IgnoreSnapshots() const override { return ignore_snapshots_; }
|
||||
bool IgnoreSnapshots() const override { return ignore_snapshots_; }
|
||||
};
|
||||
|
||||
struct rocksdb_compactionfilterfactory_t : public CompactionFilterFactory {
|
||||
@@ -243,9 +240,9 @@ struct rocksdb_compactionfilterfactory_t : public CompactionFilterFactory {
|
||||
void*, rocksdb_compactionfiltercontext_t* context);
|
||||
const char* (*name_)(void*);
|
||||
|
||||
virtual ~rocksdb_compactionfilterfactory_t() { (*destructor_)(state_); }
|
||||
~rocksdb_compactionfilterfactory_t() override { (*destructor_)(state_); }
|
||||
|
||||
virtual std::unique_ptr<CompactionFilter> CreateCompactionFilter(
|
||||
std::unique_ptr<CompactionFilter> CreateCompactionFilter(
|
||||
const CompactionFilter::Context& context) override {
|
||||
rocksdb_compactionfiltercontext_t ccontext;
|
||||
ccontext.rep = context;
|
||||
@@ -253,7 +250,7 @@ struct rocksdb_compactionfilterfactory_t : public CompactionFilterFactory {
|
||||
return std::unique_ptr<CompactionFilter>(cf);
|
||||
}
|
||||
|
||||
virtual const char* Name() const override { return (*name_)(state_); }
|
||||
const char* Name() const override { return (*name_)(state_); }
|
||||
};
|
||||
|
||||
struct rocksdb_comparator_t : public Comparator {
|
||||
@@ -265,20 +262,17 @@ struct rocksdb_comparator_t : public Comparator {
|
||||
const char* b, size_t blen);
|
||||
const char* (*name_)(void*);
|
||||
|
||||
virtual ~rocksdb_comparator_t() {
|
||||
(*destructor_)(state_);
|
||||
}
|
||||
~rocksdb_comparator_t() override { (*destructor_)(state_); }
|
||||
|
||||
virtual int Compare(const Slice& a, const Slice& b) const override {
|
||||
int Compare(const Slice& a, const Slice& b) const override {
|
||||
return (*compare_)(state_, a.data(), a.size(), b.data(), b.size());
|
||||
}
|
||||
|
||||
virtual const char* Name() const override { return (*name_)(state_); }
|
||||
const char* Name() const override { return (*name_)(state_); }
|
||||
|
||||
// No-ops since the C binding does not support key shortening methods.
|
||||
virtual void FindShortestSeparator(std::string*,
|
||||
const Slice&) const override {}
|
||||
virtual void FindShortSuccessor(std::string* /*key*/) const override {}
|
||||
void FindShortestSeparator(std::string*, const Slice&) const override {}
|
||||
void FindShortSuccessor(std::string* /*key*/) const override {}
|
||||
};
|
||||
|
||||
struct rocksdb_filterpolicy_t : public FilterPolicy {
|
||||
@@ -298,14 +292,11 @@ struct rocksdb_filterpolicy_t : public FilterPolicy {
|
||||
void*,
|
||||
const char* filter, size_t filter_length);
|
||||
|
||||
virtual ~rocksdb_filterpolicy_t() {
|
||||
(*destructor_)(state_);
|
||||
}
|
||||
~rocksdb_filterpolicy_t() override { (*destructor_)(state_); }
|
||||
|
||||
virtual const char* Name() const override { return (*name_)(state_); }
|
||||
const char* Name() const override { return (*name_)(state_); }
|
||||
|
||||
virtual void CreateFilter(const Slice* keys, int n,
|
||||
std::string* dst) const override {
|
||||
void CreateFilter(const Slice* keys, int n, std::string* dst) const override {
|
||||
std::vector<const char*> key_pointers(n);
|
||||
std::vector<size_t> key_sizes(n);
|
||||
for (int i = 0; i < n; i++) {
|
||||
@@ -323,8 +314,7 @@ struct rocksdb_filterpolicy_t : public FilterPolicy {
|
||||
}
|
||||
}
|
||||
|
||||
virtual bool KeyMayMatch(const Slice& key,
|
||||
const Slice& filter) const override {
|
||||
bool KeyMayMatch(const Slice& key, const Slice& filter) const override {
|
||||
return (*key_match_)(state_, key.data(), key.size(),
|
||||
filter.data(), filter.size());
|
||||
}
|
||||
@@ -349,14 +339,12 @@ struct rocksdb_mergeoperator_t : public MergeOperator {
|
||||
void*,
|
||||
const char* value, size_t value_length);
|
||||
|
||||
virtual ~rocksdb_mergeoperator_t() {
|
||||
(*destructor_)(state_);
|
||||
}
|
||||
~rocksdb_mergeoperator_t() override { (*destructor_)(state_); }
|
||||
|
||||
virtual const char* Name() const override { return (*name_)(state_); }
|
||||
const char* Name() const override { return (*name_)(state_); }
|
||||
|
||||
virtual bool FullMergeV2(const MergeOperationInput& merge_in,
|
||||
MergeOperationOutput* merge_out) const override {
|
||||
bool FullMergeV2(const MergeOperationInput& merge_in,
|
||||
MergeOperationOutput* merge_out) const override {
|
||||
size_t n = merge_in.operand_list.size();
|
||||
std::vector<const char*> operand_pointers(n);
|
||||
std::vector<size_t> operand_sizes(n);
|
||||
@@ -390,10 +378,10 @@ struct rocksdb_mergeoperator_t : public MergeOperator {
|
||||
return success;
|
||||
}
|
||||
|
||||
virtual bool PartialMergeMulti(const Slice& key,
|
||||
const std::deque<Slice>& operand_list,
|
||||
std::string* new_value,
|
||||
Logger* /*logger*/) const override {
|
||||
bool PartialMergeMulti(const Slice& key,
|
||||
const std::deque<Slice>& operand_list,
|
||||
std::string* new_value,
|
||||
Logger* /*logger*/) const override {
|
||||
size_t operand_count = operand_list.size();
|
||||
std::vector<const char*> operand_pointers(operand_count);
|
||||
std::vector<size_t> operand_sizes(operand_count);
|
||||
@@ -444,23 +432,21 @@ struct rocksdb_slicetransform_t : public SliceTransform {
|
||||
void*,
|
||||
const char* key, size_t length);
|
||||
|
||||
virtual ~rocksdb_slicetransform_t() {
|
||||
(*destructor_)(state_);
|
||||
}
|
||||
~rocksdb_slicetransform_t() override { (*destructor_)(state_); }
|
||||
|
||||
virtual const char* Name() const override { return (*name_)(state_); }
|
||||
const char* Name() const override { return (*name_)(state_); }
|
||||
|
||||
virtual Slice Transform(const Slice& src) const override {
|
||||
Slice Transform(const Slice& src) const override {
|
||||
size_t len;
|
||||
char* dst = (*transform_)(state_, src.data(), src.size(), &len);
|
||||
return Slice(dst, len);
|
||||
}
|
||||
|
||||
virtual bool InDomain(const Slice& src) const override {
|
||||
bool InDomain(const Slice& src) const override {
|
||||
return (*in_domain_)(state_, src.data(), src.size());
|
||||
}
|
||||
|
||||
virtual bool InRange(const Slice& src) const override {
|
||||
bool InRange(const Slice& src) const override {
|
||||
return (*in_range_)(state_, src.data(), src.size());
|
||||
}
|
||||
};
|
||||
@@ -1084,6 +1070,18 @@ int rocksdb_property_int(
|
||||
}
|
||||
}
|
||||
|
||||
int rocksdb_property_int_cf(
|
||||
rocksdb_t* db,
|
||||
rocksdb_column_family_handle_t* column_family,
|
||||
const char* propname,
|
||||
uint64_t *out_val) {
|
||||
if (db->rep->GetIntProperty(column_family->rep, Slice(propname), out_val)) {
|
||||
return 0;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
char* rocksdb_property_value_cf(
|
||||
rocksdb_t* db,
|
||||
rocksdb_column_family_handle_t* column_family,
|
||||
@@ -1197,6 +1195,14 @@ void rocksdb_flush(
|
||||
SaveError(errptr, db->rep->Flush(options->rep));
|
||||
}
|
||||
|
||||
void rocksdb_flush_cf(
|
||||
rocksdb_t* db,
|
||||
const rocksdb_flushoptions_t* options,
|
||||
rocksdb_column_family_handle_t* column_family,
|
||||
char** errptr) {
|
||||
SaveError(errptr, db->rep->Flush(options->rep, column_family->rep));
|
||||
}
|
||||
|
||||
void rocksdb_disable_file_deletions(
|
||||
rocksdb_t* db,
|
||||
char** errptr) {
|
||||
@@ -1495,10 +1501,10 @@ class H : public WriteBatch::Handler {
|
||||
void* state_;
|
||||
void (*put_)(void*, const char* k, size_t klen, const char* v, size_t vlen);
|
||||
void (*deleted_)(void*, const char* k, size_t klen);
|
||||
virtual void Put(const Slice& key, const Slice& value) override {
|
||||
void Put(const Slice& key, const Slice& value) override {
|
||||
(*put_)(state_, key.data(), key.size(), value.data(), value.size());
|
||||
}
|
||||
virtual void Delete(const Slice& key) override {
|
||||
void Delete(const Slice& key) override {
|
||||
(*deleted_)(state_, key.data(), key.size());
|
||||
}
|
||||
};
|
||||
@@ -2220,6 +2226,11 @@ void rocksdb_options_set_max_bytes_for_level_base(
|
||||
opt->rep.max_bytes_for_level_base = n;
|
||||
}
|
||||
|
||||
void rocksdb_options_set_snap_refresh_nanos(rocksdb_options_t* opt,
|
||||
uint64_t n) {
|
||||
opt->rep.snap_refresh_nanos = n;
|
||||
}
|
||||
|
||||
void rocksdb_options_set_level_compaction_dynamic_level_bytes(
|
||||
rocksdb_options_t* opt, unsigned char v) {
|
||||
opt->rep.level_compaction_dynamic_level_bytes = v;
|
||||
@@ -2866,7 +2877,7 @@ rocksdb_compactionfilter_t* rocksdb_compactionfilter_create(
|
||||
result->state_ = state;
|
||||
result->destructor_ = destructor;
|
||||
result->filter_ = filter;
|
||||
result->ignore_snapshots_ = false;
|
||||
result->ignore_snapshots_ = true;
|
||||
result->name_ = name;
|
||||
return result;
|
||||
}
|
||||
@@ -2966,7 +2977,7 @@ rocksdb_filterpolicy_t* rocksdb_filterpolicy_create_bloom_format(int bits_per_ke
|
||||
// supplied C functions.
|
||||
struct Wrapper : public rocksdb_filterpolicy_t {
|
||||
const FilterPolicy* rep_;
|
||||
~Wrapper() { delete rep_; }
|
||||
~Wrapper() override { delete rep_; }
|
||||
const char* Name() const override { return rep_->Name(); }
|
||||
void CreateFilter(const Slice* keys, int n,
|
||||
std::string* dst) const override {
|
||||
@@ -3421,7 +3432,7 @@ void rocksdb_slicetransform_destroy(rocksdb_slicetransform_t* st) {
|
||||
|
||||
struct Wrapper : public rocksdb_slicetransform_t {
|
||||
const SliceTransform* rep_;
|
||||
~Wrapper() { delete rep_; }
|
||||
~Wrapper() override { delete rep_; }
|
||||
const char* Name() const override { return rep_->Name(); }
|
||||
Slice Transform(const Slice& src) const override {
|
||||
return rep_->Transform(src);
|
||||
@@ -3716,6 +3727,38 @@ rocksdb_transactiondb_t* rocksdb_transactiondb_open(
|
||||
return result;
|
||||
}
|
||||
|
||||
rocksdb_transactiondb_t* rocksdb_transactiondb_open_column_families(
|
||||
const rocksdb_options_t* options,
|
||||
const rocksdb_transactiondb_options_t* txn_db_options, const char* name,
|
||||
int num_column_families, const char** column_family_names,
|
||||
const rocksdb_options_t** column_family_options,
|
||||
rocksdb_column_family_handle_t** column_family_handles, char** errptr) {
|
||||
std::vector<ColumnFamilyDescriptor> column_families;
|
||||
for (int i = 0; i < num_column_families; i++) {
|
||||
column_families.push_back(ColumnFamilyDescriptor(
|
||||
std::string(column_family_names[i]),
|
||||
ColumnFamilyOptions(column_family_options[i]->rep)));
|
||||
}
|
||||
|
||||
TransactionDB* txn_db;
|
||||
std::vector<ColumnFamilyHandle*> handles;
|
||||
if (SaveError(errptr, TransactionDB::Open(options->rep, txn_db_options->rep,
|
||||
std::string(name), column_families,
|
||||
&handles, &txn_db))) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < handles.size(); i++) {
|
||||
rocksdb_column_family_handle_t* c_handle =
|
||||
new rocksdb_column_family_handle_t;
|
||||
c_handle->rep = handles[i];
|
||||
column_family_handles[i] = c_handle;
|
||||
}
|
||||
rocksdb_transactiondb_t* result = new rocksdb_transactiondb_t;
|
||||
result->rep = txn_db;
|
||||
return result;
|
||||
}
|
||||
|
||||
const rocksdb_snapshot_t* rocksdb_transactiondb_create_snapshot(
|
||||
rocksdb_transactiondb_t* txn_db) {
|
||||
rocksdb_snapshot_t* result = new rocksdb_snapshot_t;
|
||||
@@ -3836,6 +3879,26 @@ char* rocksdb_transaction_get_for_update(rocksdb_transaction_t* txn,
|
||||
return result;
|
||||
}
|
||||
|
||||
char* rocksdb_transaction_get_for_update_cf(
|
||||
rocksdb_transaction_t* txn, const rocksdb_readoptions_t* options,
|
||||
rocksdb_column_family_handle_t* column_family, const char* key, size_t klen,
|
||||
size_t* vlen, unsigned char exclusive, char** errptr) {
|
||||
char* result = nullptr;
|
||||
std::string tmp;
|
||||
Status s = txn->rep->GetForUpdate(options->rep, column_family->rep,
|
||||
Slice(key, klen), &tmp, exclusive);
|
||||
if (s.ok()) {
|
||||
*vlen = tmp.size();
|
||||
result = CopyString(tmp);
|
||||
} else {
|
||||
*vlen = 0;
|
||||
if (!s.IsNotFound()) {
|
||||
SaveError(errptr, s);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Read a key outside a transaction
|
||||
char* rocksdb_transactiondb_get(
|
||||
rocksdb_transactiondb_t* txn_db,
|
||||
@@ -3928,6 +3991,14 @@ void rocksdb_transaction_merge(rocksdb_transaction_t* txn, const char* key,
|
||||
SaveError(errptr, txn->rep->Merge(Slice(key, klen), Slice(val, vlen)));
|
||||
}
|
||||
|
||||
void rocksdb_transaction_merge_cf(rocksdb_transaction_t* txn,
|
||||
rocksdb_column_family_handle_t* column_family,
|
||||
const char* key, size_t klen, const char* val,
|
||||
size_t vlen, char** errptr) {
|
||||
SaveError(errptr, txn->rep->Merge(column_family->rep, Slice(key, klen),
|
||||
Slice(val, vlen)));
|
||||
}
|
||||
|
||||
// Merge a key outside a transaction
|
||||
void rocksdb_transactiondb_merge(rocksdb_transactiondb_t* txn_db,
|
||||
const rocksdb_writeoptions_t* options,
|
||||
@@ -3937,6 +4008,14 @@ void rocksdb_transactiondb_merge(rocksdb_transactiondb_t* txn_db,
|
||||
Slice(val, vlen)));
|
||||
}
|
||||
|
||||
void rocksdb_transactiondb_merge_cf(
|
||||
rocksdb_transactiondb_t* txn_db, const rocksdb_writeoptions_t* options,
|
||||
rocksdb_column_family_handle_t* column_family, const char* key, size_t klen,
|
||||
const char* val, size_t vlen, char** errptr) {
|
||||
SaveError(errptr, txn_db->rep->Merge(options->rep, column_family->rep,
|
||||
Slice(key, klen), Slice(val, vlen)));
|
||||
}
|
||||
|
||||
// Delete a key inside a transaction
|
||||
void rocksdb_transaction_delete(rocksdb_transaction_t* txn, const char* key,
|
||||
size_t klen, char** errptr) {
|
||||
@@ -3989,6 +4068,14 @@ rocksdb_iterator_t* rocksdb_transactiondb_create_iterator(
|
||||
return result;
|
||||
}
|
||||
|
||||
rocksdb_iterator_t* rocksdb_transactiondb_create_iterator_cf(
|
||||
rocksdb_transactiondb_t* txn_db, const rocksdb_readoptions_t* options,
|
||||
rocksdb_column_family_handle_t* column_family) {
|
||||
rocksdb_iterator_t* result = new rocksdb_iterator_t;
|
||||
result->rep = txn_db->rep->NewIterator(options->rep, column_family->rep);
|
||||
return result;
|
||||
}
|
||||
|
||||
void rocksdb_transactiondb_close(rocksdb_transactiondb_t* txn_db) {
|
||||
delete txn_db->rep;
|
||||
delete txn_db;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/* Copyright (c) 2011 The LevelDB Authors. All rights reserved.
|
||||
Use of this source code is governed by a BSD-style license that can be
|
||||
found in the LICENSE file. See the AUTHORS file for names of contributors. */
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
@@ -1187,6 +1188,12 @@ int main(int argc, char** argv) {
|
||||
rocksdb_put_cf(db, woptions, handles[1], "foo", 3, "hello", 5, &err);
|
||||
CheckNoError(err);
|
||||
|
||||
rocksdb_flushoptions_t *flush_options = rocksdb_flushoptions_create();
|
||||
rocksdb_flushoptions_set_wait(flush_options, 1);
|
||||
rocksdb_flush_cf(db, flush_options, handles[1], &err);
|
||||
CheckNoError(err)
|
||||
rocksdb_flushoptions_destroy(flush_options);
|
||||
|
||||
CheckGetCF(db, roptions, handles[1], "foo", "hello");
|
||||
CheckPinGetCF(db, roptions, handles[1], "foo", "hello");
|
||||
|
||||
|
||||
+19
-6
@@ -63,12 +63,24 @@ ColumnFamilyHandleImpl::~ColumnFamilyHandleImpl() {
|
||||
JobContext job_context(0);
|
||||
mutex_->Lock();
|
||||
if (cfd_->Unref()) {
|
||||
bool dropped = cfd_->IsDropped();
|
||||
|
||||
delete cfd_;
|
||||
|
||||
if (dropped) {
|
||||
db_->FindObsoleteFiles(&job_context, false, true);
|
||||
}
|
||||
}
|
||||
db_->FindObsoleteFiles(&job_context, false, true);
|
||||
mutex_->Unlock();
|
||||
if (job_context.HaveSomethingToDelete()) {
|
||||
db_->PurgeObsoleteFiles(job_context);
|
||||
bool defer_purge =
|
||||
db_->immutable_db_options().avoid_unnecessary_blocking_io;
|
||||
db_->PurgeObsoleteFiles(job_context, defer_purge);
|
||||
if (defer_purge) {
|
||||
mutex_->Lock();
|
||||
db_->SchedulePurge();
|
||||
mutex_->Unlock();
|
||||
}
|
||||
}
|
||||
job_context.Clean();
|
||||
}
|
||||
@@ -982,13 +994,14 @@ const int ColumnFamilyData::kCompactToBaseLevel = -2;
|
||||
|
||||
Compaction* ColumnFamilyData::CompactRange(
|
||||
const MutableCFOptions& mutable_cf_options, int input_level,
|
||||
int output_level, uint32_t output_path_id, uint32_t max_subcompactions,
|
||||
int output_level, const CompactRangeOptions& compact_range_options,
|
||||
const InternalKey* begin, const InternalKey* end,
|
||||
InternalKey** compaction_end, bool* conflict) {
|
||||
InternalKey** compaction_end, bool* conflict,
|
||||
uint64_t max_file_num_to_ignore) {
|
||||
auto* result = compaction_picker_->CompactRange(
|
||||
GetName(), mutable_cf_options, current_->storage_info(), input_level,
|
||||
output_level, output_path_id, max_subcompactions, begin, end,
|
||||
compaction_end, conflict);
|
||||
output_level, compact_range_options, begin, end, compaction_end, conflict,
|
||||
max_file_num_to_ignore);
|
||||
if (result != nullptr) {
|
||||
result->SetInputVersion(current_);
|
||||
}
|
||||
|
||||
+3
-2
@@ -296,9 +296,10 @@ class ColumnFamilyData {
|
||||
// REQUIRES: DB mutex held
|
||||
Compaction* CompactRange(const MutableCFOptions& mutable_cf_options,
|
||||
int input_level, int output_level,
|
||||
uint32_t output_path_id, uint32_t max_subcompactions,
|
||||
const CompactRangeOptions& compact_range_options,
|
||||
const InternalKey* begin, const InternalKey* end,
|
||||
InternalKey** compaction_end, bool* manual_conflict);
|
||||
InternalKey** compaction_end, bool* manual_conflict,
|
||||
uint64_t max_file_num_to_ignore);
|
||||
|
||||
CompactionPicker* compaction_picker() { return compaction_picker_.get(); }
|
||||
// thread-safe
|
||||
|
||||
+26
-22
@@ -14,6 +14,7 @@
|
||||
|
||||
#include "db/db_impl.h"
|
||||
#include "db/db_test_util.h"
|
||||
#include "memtable/hash_skiplist_rep.h"
|
||||
#include "options/options_parser.h"
|
||||
#include "port/port.h"
|
||||
#include "rocksdb/db.h"
|
||||
@@ -68,7 +69,7 @@ class ColumnFamilyTestBase : public testing::Test {
|
||||
DestroyDB(dbname_, Options(db_options_, column_family_options_));
|
||||
}
|
||||
|
||||
virtual ~ColumnFamilyTestBase() {
|
||||
~ColumnFamilyTestBase() override {
|
||||
std::vector<ColumnFamilyDescriptor> column_families;
|
||||
for (auto h : handles_) {
|
||||
ColumnFamilyDescriptor cfdescriptor;
|
||||
@@ -1206,29 +1207,32 @@ TEST_P(ColumnFamilyTest, DifferentWriteBufferSizes) {
|
||||
}
|
||||
#endif // !ROCKSDB_LITE
|
||||
|
||||
#ifndef ROCKSDB_LITE // Cuckoo is not supported in lite
|
||||
TEST_P(ColumnFamilyTest, MemtableNotSupportSnapshot) {
|
||||
db_options_.allow_concurrent_memtable_write = false;
|
||||
Open();
|
||||
auto* s1 = dbfull()->GetSnapshot();
|
||||
ASSERT_TRUE(s1 != nullptr);
|
||||
dbfull()->ReleaseSnapshot(s1);
|
||||
// The test is commented out because we want to test that snapshot is
|
||||
// not created for memtables not supported it, but There isn't a memtable
|
||||
// that doesn't support snapshot right now. If we have one later, we can
|
||||
// re-enable the test.
|
||||
//
|
||||
// #ifndef ROCKSDB_LITE // Cuckoo is not supported in lite
|
||||
// TEST_P(ColumnFamilyTest, MemtableNotSupportSnapshot) {
|
||||
// db_options_.allow_concurrent_memtable_write = false;
|
||||
// Open();
|
||||
// auto* s1 = dbfull()->GetSnapshot();
|
||||
// ASSERT_TRUE(s1 != nullptr);
|
||||
// dbfull()->ReleaseSnapshot(s1);
|
||||
|
||||
// Add a column family that doesn't support snapshot
|
||||
ColumnFamilyOptions first;
|
||||
first.memtable_factory.reset(NewHashCuckooRepFactory(1024 * 1024));
|
||||
CreateColumnFamilies({"first"}, {first});
|
||||
auto* s2 = dbfull()->GetSnapshot();
|
||||
ASSERT_TRUE(s2 == nullptr);
|
||||
// // Add a column family that doesn't support snapshot
|
||||
// ColumnFamilyOptions first;
|
||||
// first.memtable_factory.reset(new DummyMemtableNotSupportingSnapshot());
|
||||
// CreateColumnFamilies({"first"}, {first});
|
||||
// auto* s2 = dbfull()->GetSnapshot();
|
||||
// ASSERT_TRUE(s2 == nullptr);
|
||||
|
||||
// Add a column family that supports snapshot. Snapshot stays not supported.
|
||||
ColumnFamilyOptions second;
|
||||
CreateColumnFamilies({"second"}, {second});
|
||||
auto* s3 = dbfull()->GetSnapshot();
|
||||
ASSERT_TRUE(s3 == nullptr);
|
||||
Close();
|
||||
}
|
||||
#endif // !ROCKSDB_LITE
|
||||
// // Add a column family that supports snapshot. Snapshot stays not
|
||||
// supported. ColumnFamilyOptions second; CreateColumnFamilies({"second"},
|
||||
// {second}); auto* s3 = dbfull()->GetSnapshot(); ASSERT_TRUE(s3 == nullptr);
|
||||
// Close();
|
||||
// }
|
||||
// #endif // !ROCKSDB_LITE
|
||||
|
||||
class TestComparator : public Comparator {
|
||||
int Compare(const rocksdb::Slice& /*a*/,
|
||||
|
||||
@@ -35,9 +35,9 @@ class CompactFilesTest : public testing::Test {
|
||||
class FlushedFileCollector : public EventListener {
|
||||
public:
|
||||
FlushedFileCollector() {}
|
||||
~FlushedFileCollector() {}
|
||||
~FlushedFileCollector() override {}
|
||||
|
||||
virtual void OnFlushCompleted(DB* /*db*/, const FlushJobInfo& info) override {
|
||||
void OnFlushCompleted(DB* /*db*/, const FlushJobInfo& info) override {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
flushed_files_.push_back(info.file_path);
|
||||
}
|
||||
@@ -256,9 +256,9 @@ TEST_F(CompactFilesTest, CapturingPendingFiles) {
|
||||
TEST_F(CompactFilesTest, CompactionFilterWithGetSv) {
|
||||
class FilterWithGet : public CompactionFilter {
|
||||
public:
|
||||
virtual bool Filter(int /*level*/, const Slice& /*key*/,
|
||||
const Slice& /*value*/, std::string* /*new_value*/,
|
||||
bool* /*value_changed*/) const override {
|
||||
bool Filter(int /*level*/, const Slice& /*key*/, const Slice& /*value*/,
|
||||
std::string* /*new_value*/,
|
||||
bool* /*value_changed*/) const override {
|
||||
if (db_ == nullptr) {
|
||||
return true;
|
||||
}
|
||||
@@ -271,7 +271,7 @@ TEST_F(CompactFilesTest, CompactionFilterWithGetSv) {
|
||||
db_ = db;
|
||||
}
|
||||
|
||||
virtual const char* Name() const override { return "FilterWithGet"; }
|
||||
const char* Name() const override { return "FilterWithGet"; }
|
||||
|
||||
private:
|
||||
DB* db_;
|
||||
|
||||
@@ -250,6 +250,12 @@ Compaction::Compaction(VersionStorageInfo* vstorage,
|
||||
if (max_subcompactions_ == 0) {
|
||||
max_subcompactions_ = immutable_cf_options_.max_subcompactions;
|
||||
}
|
||||
if (!bottommost_level_) {
|
||||
// Currently we only enable dictionary compression during compaction to the
|
||||
// bottommost level.
|
||||
output_compression_opts_.max_dict_bytes = 0;
|
||||
output_compression_opts_.zstd_max_train_bytes = 0;
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
for (size_t i = 1; i < inputs_.size(); ++i) {
|
||||
|
||||
+45
-37
@@ -38,14 +38,16 @@ CompactionIterator::CompactionIterator(
|
||||
CompactionRangeDelAggregator* range_del_agg, const Compaction* compaction,
|
||||
const CompactionFilter* compaction_filter,
|
||||
const std::atomic<bool>* shutting_down,
|
||||
const SequenceNumber preserve_deletes_seqnum)
|
||||
const SequenceNumber preserve_deletes_seqnum,
|
||||
SnapshotListFetchCallback* snap_list_callback)
|
||||
: CompactionIterator(
|
||||
input, cmp, merge_helper, last_sequence, snapshots,
|
||||
earliest_write_conflict_snapshot, snapshot_checker, env,
|
||||
report_detailed_time, expect_valid_internal_key, range_del_agg,
|
||||
std::unique_ptr<CompactionProxy>(
|
||||
compaction ? new CompactionProxy(compaction) : nullptr),
|
||||
compaction_filter, shutting_down, preserve_deletes_seqnum) {}
|
||||
compaction_filter, shutting_down, preserve_deletes_seqnum,
|
||||
snap_list_callback) {}
|
||||
|
||||
CompactionIterator::CompactionIterator(
|
||||
InternalIterator* input, const Comparator* cmp, MergeHelper* merge_helper,
|
||||
@@ -57,7 +59,8 @@ CompactionIterator::CompactionIterator(
|
||||
std::unique_ptr<CompactionProxy> compaction,
|
||||
const CompactionFilter* compaction_filter,
|
||||
const std::atomic<bool>* shutting_down,
|
||||
const SequenceNumber preserve_deletes_seqnum)
|
||||
const SequenceNumber preserve_deletes_seqnum,
|
||||
SnapshotListFetchCallback* snap_list_callback)
|
||||
: input_(input),
|
||||
cmp_(cmp),
|
||||
merge_helper_(merge_helper),
|
||||
@@ -72,11 +75,11 @@ CompactionIterator::CompactionIterator(
|
||||
compaction_filter_(compaction_filter),
|
||||
shutting_down_(shutting_down),
|
||||
preserve_deletes_seqnum_(preserve_deletes_seqnum),
|
||||
ignore_snapshots_(false),
|
||||
current_user_key_sequence_(0),
|
||||
current_user_key_snapshot_(0),
|
||||
merge_out_iter_(merge_helper_),
|
||||
current_key_committed_(false) {
|
||||
current_key_committed_(false),
|
||||
snap_list_callback_(snap_list_callback) {
|
||||
assert(compaction_filter_ == nullptr || compaction_ != nullptr);
|
||||
assert(snapshots_ != nullptr);
|
||||
bottommost_level_ =
|
||||
@@ -84,31 +87,7 @@ CompactionIterator::CompactionIterator(
|
||||
if (compaction_ != nullptr) {
|
||||
level_ptrs_ = std::vector<size_t>(compaction_->number_levels(), 0);
|
||||
}
|
||||
if (snapshots_->size() == 0) {
|
||||
// optimize for fast path if there are no snapshots
|
||||
visible_at_tip_ = true;
|
||||
earliest_snapshot_iter_ = snapshots_->end();
|
||||
earliest_snapshot_ = kMaxSequenceNumber;
|
||||
latest_snapshot_ = 0;
|
||||
} else {
|
||||
visible_at_tip_ = false;
|
||||
earliest_snapshot_iter_ = snapshots_->begin();
|
||||
earliest_snapshot_ = snapshots_->at(0);
|
||||
latest_snapshot_ = snapshots_->back();
|
||||
}
|
||||
#ifndef NDEBUG
|
||||
// findEarliestVisibleSnapshot assumes this ordering.
|
||||
for (size_t i = 1; i < snapshots_->size(); ++i) {
|
||||
assert(snapshots_->at(i - 1) < snapshots_->at(i));
|
||||
}
|
||||
#endif
|
||||
if (compaction_filter_ != nullptr) {
|
||||
if (compaction_filter_->IgnoreSnapshots()) {
|
||||
ignore_snapshots_ = true;
|
||||
}
|
||||
} else {
|
||||
ignore_snapshots_ = false;
|
||||
}
|
||||
ProcessSnapshotList();
|
||||
input_->SetPinnedItersMgr(&pinned_iters_mgr_);
|
||||
TEST_SYNC_POINT_CALLBACK("CompactionIterator:AfterInit", compaction_.get());
|
||||
}
|
||||
@@ -180,9 +159,7 @@ void CompactionIterator::Next() {
|
||||
void CompactionIterator::InvokeFilterIfNeeded(bool* need_skip,
|
||||
Slice* skip_until) {
|
||||
if (compaction_filter_ != nullptr &&
|
||||
(ikey_.type == kTypeValue || ikey_.type == kTypeBlobIndex) &&
|
||||
(visible_at_tip_ || ignore_snapshots_ ||
|
||||
DEFINITELY_NOT_IN_SNAPSHOT(ikey_.sequence, latest_snapshot_))) {
|
||||
(ikey_.type == kTypeValue || ikey_.type == kTypeBlobIndex)) {
|
||||
// If the user has specified a compaction filter and the sequence
|
||||
// number is greater than any external snapshot, then invoke the
|
||||
// filter. If the return value of the compaction filter is true,
|
||||
@@ -232,6 +209,28 @@ void CompactionIterator::InvokeFilterIfNeeded(bool* need_skip,
|
||||
}
|
||||
}
|
||||
|
||||
void CompactionIterator::ProcessSnapshotList() {
|
||||
#ifndef NDEBUG
|
||||
// findEarliestVisibleSnapshot assumes this ordering.
|
||||
for (size_t i = 1; i < snapshots_->size(); ++i) {
|
||||
assert(snapshots_->at(i - 1) < snapshots_->at(i));
|
||||
}
|
||||
#endif
|
||||
if (snapshots_->size() == 0) {
|
||||
// optimize for fast path if there are no snapshots
|
||||
visible_at_tip_ = true;
|
||||
earliest_snapshot_iter_ = snapshots_->end();
|
||||
earliest_snapshot_ = kMaxSequenceNumber;
|
||||
latest_snapshot_ = 0;
|
||||
} else {
|
||||
visible_at_tip_ = false;
|
||||
earliest_snapshot_iter_ = snapshots_->begin();
|
||||
earliest_snapshot_ = snapshots_->at(0);
|
||||
latest_snapshot_ = snapshots_->back();
|
||||
}
|
||||
released_snapshots_.clear();
|
||||
}
|
||||
|
||||
void CompactionIterator::NextFromInput() {
|
||||
at_next_ = false;
|
||||
valid_ = false;
|
||||
@@ -279,6 +278,13 @@ void CompactionIterator::NextFromInput() {
|
||||
// compaction filter). ikey_.user_key is pointing to the copy.
|
||||
if (!has_current_user_key_ ||
|
||||
!cmp_->Equal(ikey_.user_key, current_user_key_)) {
|
||||
num_keys_++;
|
||||
// Use num_keys_ to reduce the overhead of reading current time
|
||||
if (snap_list_callback_ && snapshots_->size() &&
|
||||
snap_list_callback_->TimeToRefresh(num_keys_)) {
|
||||
snap_list_callback_->Refresh(snapshots_, latest_snapshot_);
|
||||
ProcessSnapshotList();
|
||||
}
|
||||
// First occurrence of this user key
|
||||
// Copy key for output
|
||||
key_ = current_key_.SetInternalKey(key_, &ikey_);
|
||||
@@ -492,11 +498,14 @@ void CompactionIterator::NextFromInput() {
|
||||
// this value, and findEarliestVisibleSnapshot returns the next snapshot
|
||||
// as current_user_key_snapshot. In this case last value and current
|
||||
// value are both in current_user_key_snapshot currently.
|
||||
// Although last_snapshot is released we might still get a definitive
|
||||
// response when key sequence number changes, e.g., when seq is determined
|
||||
// too old and visible in all snapshots.
|
||||
assert(last_snapshot == current_user_key_snapshot_ ||
|
||||
(snapshot_checker_ != nullptr &&
|
||||
snapshot_checker_->CheckInSnapshot(current_user_key_sequence_,
|
||||
last_snapshot) ==
|
||||
SnapshotCheckerResult::kSnapshotReleased));
|
||||
last_snapshot) !=
|
||||
SnapshotCheckerResult::kNotInSnapshot));
|
||||
|
||||
++iter_stats_.num_record_drop_hidden; // (A)
|
||||
input_->Next();
|
||||
@@ -634,8 +643,7 @@ void CompactionIterator::PrepareOutput() {
|
||||
// KeyNotExistsBeyondOutputLevel() return true?
|
||||
if ((compaction_ != nullptr && !compaction_->allow_ingest_behind()) &&
|
||||
ikeyNotNeededForIncrementalSnapshot() && bottommost_level_ && valid_ &&
|
||||
IN_EARLIEST_SNAPSHOT(ikey_.sequence) && ikey_.type != kTypeMerge &&
|
||||
!cmp_->Equal(compaction_->GetLargestUserKey(), ikey_.user_key)) {
|
||||
IN_EARLIEST_SNAPSHOT(ikey_.sequence) && ikey_.type != kTypeMerge) {
|
||||
assert(ikey_.type != kTypeDeletion && ikey_.type != kTypeSingleDeletion);
|
||||
ikey_.sequence = 0;
|
||||
current_key_.UpdateInternalKey(0, ikey_.type);
|
||||
|
||||
@@ -21,6 +21,54 @@
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
// This callback can be used to refresh the snapshot list from the db. It
|
||||
// includes logics to exponentially decrease the refresh rate to limit the
|
||||
// overhead of refresh.
|
||||
class SnapshotListFetchCallback {
|
||||
public:
|
||||
SnapshotListFetchCallback(Env* env, uint64_t snap_refresh_nanos,
|
||||
size_t every_nth_key = 1024)
|
||||
: timer_(env, /*auto restart*/ true),
|
||||
snap_refresh_nanos_(snap_refresh_nanos),
|
||||
every_nth_key_minus_one_(every_nth_key - 1) {
|
||||
assert(every_nth_key > 0);
|
||||
assert((ceil(log2(every_nth_key)) == floor(log2(every_nth_key))));
|
||||
}
|
||||
// Refresh the snapshot list. snapshots will bre replacted with the new list.
|
||||
// max is the upper bound. Note: this function will acquire the db_mutex_.
|
||||
virtual void Refresh(std::vector<SequenceNumber>* snapshots,
|
||||
SequenceNumber max) = 0;
|
||||
inline bool TimeToRefresh(const size_t key_index) {
|
||||
assert(snap_refresh_nanos_ != 0);
|
||||
// skip the key if key_index % every_nth_key (which is of power 2) is not 0.
|
||||
if ((key_index & every_nth_key_minus_one_) != 0) {
|
||||
return false;
|
||||
}
|
||||
const uint64_t elapsed = timer_.ElapsedNanos();
|
||||
auto ret = elapsed > snap_refresh_nanos_;
|
||||
// pre-compute the next time threshold
|
||||
if (ret) {
|
||||
// inc next refresh period exponentially (by x4)
|
||||
auto next_refresh_threshold = snap_refresh_nanos_ << 2;
|
||||
// make sure the shift has not overflown the highest 1 bit
|
||||
snap_refresh_nanos_ =
|
||||
std::max(snap_refresh_nanos_, next_refresh_threshold);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
static constexpr SnapshotListFetchCallback* kDisabled = nullptr;
|
||||
|
||||
virtual ~SnapshotListFetchCallback() {}
|
||||
|
||||
private:
|
||||
// Time since the callback was created
|
||||
StopWatchNano timer_;
|
||||
// The delay before calling ::Refresh. To be increased exponentially.
|
||||
uint64_t snap_refresh_nanos_;
|
||||
// Skip evey nth key. Number n if of power 2. The math will require n-1.
|
||||
const uint64_t every_nth_key_minus_one_;
|
||||
};
|
||||
|
||||
class CompactionIterator {
|
||||
public:
|
||||
// A wrapper around Compaction. Has a much smaller interface, only what
|
||||
@@ -69,7 +117,8 @@ class CompactionIterator {
|
||||
const Compaction* compaction = nullptr,
|
||||
const CompactionFilter* compaction_filter = nullptr,
|
||||
const std::atomic<bool>* shutting_down = nullptr,
|
||||
const SequenceNumber preserve_deletes_seqnum = 0);
|
||||
const SequenceNumber preserve_deletes_seqnum = 0,
|
||||
SnapshotListFetchCallback* snap_list_callback = nullptr);
|
||||
|
||||
// Constructor with custom CompactionProxy, used for tests.
|
||||
CompactionIterator(InternalIterator* input, const Comparator* cmp,
|
||||
@@ -82,7 +131,8 @@ class CompactionIterator {
|
||||
std::unique_ptr<CompactionProxy> compaction,
|
||||
const CompactionFilter* compaction_filter = nullptr,
|
||||
const std::atomic<bool>* shutting_down = nullptr,
|
||||
const SequenceNumber preserve_deletes_seqnum = 0);
|
||||
const SequenceNumber preserve_deletes_seqnum = 0,
|
||||
SnapshotListFetchCallback* snap_list_callback = nullptr);
|
||||
|
||||
~CompactionIterator();
|
||||
|
||||
@@ -110,6 +160,8 @@ class CompactionIterator {
|
||||
private:
|
||||
// Processes the input stream to find the next output
|
||||
void NextFromInput();
|
||||
// Process snapshots_ and assign related variables
|
||||
void ProcessSnapshotList();
|
||||
|
||||
// Do last preparations before presenting the output to the callee. At this
|
||||
// point this only zeroes out the sequence number if possible for better
|
||||
@@ -144,7 +196,7 @@ class CompactionIterator {
|
||||
InternalIterator* input_;
|
||||
const Comparator* cmp_;
|
||||
MergeHelper* merge_helper_;
|
||||
const std::vector<SequenceNumber>* snapshots_;
|
||||
std::vector<SequenceNumber>* snapshots_;
|
||||
// List of snapshots released during compaction.
|
||||
// findEarliestVisibleSnapshot() find them out from return of
|
||||
// snapshot_checker, and make sure they will not be returned as
|
||||
@@ -168,8 +220,6 @@ class CompactionIterator {
|
||||
SequenceNumber earliest_snapshot_;
|
||||
SequenceNumber latest_snapshot_;
|
||||
|
||||
bool ignore_snapshots_;
|
||||
|
||||
// State
|
||||
//
|
||||
// Points to a copy of the current compaction iterator output (current_key_)
|
||||
@@ -221,6 +271,9 @@ class CompactionIterator {
|
||||
// Used to avoid purging uncommitted values. The application can specify
|
||||
// uncommitted values by providing a SnapshotChecker object.
|
||||
bool current_key_committed_;
|
||||
SnapshotListFetchCallback* snap_list_callback_;
|
||||
// number of distinct keys processed
|
||||
size_t num_keys_ = 0;
|
||||
|
||||
bool IsShuttingDown() {
|
||||
// This is a best-effort facility, so memory_order_relaxed is sufficient.
|
||||
|
||||
@@ -112,39 +112,39 @@ class LoggingForwardVectorIterator : public InternalIterator {
|
||||
assert(keys_.size() == values_.size());
|
||||
}
|
||||
|
||||
virtual bool Valid() const override { return current_ < keys_.size(); }
|
||||
bool Valid() const override { return current_ < keys_.size(); }
|
||||
|
||||
virtual void SeekToFirst() override {
|
||||
void SeekToFirst() override {
|
||||
log.emplace_back(Action::Type::SEEK_TO_FIRST);
|
||||
current_ = 0;
|
||||
}
|
||||
virtual void SeekToLast() override { assert(false); }
|
||||
void SeekToLast() override { assert(false); }
|
||||
|
||||
virtual void Seek(const Slice& target) override {
|
||||
void Seek(const Slice& target) override {
|
||||
log.emplace_back(Action::Type::SEEK, target.ToString());
|
||||
current_ = std::lower_bound(keys_.begin(), keys_.end(), target.ToString()) -
|
||||
keys_.begin();
|
||||
}
|
||||
|
||||
virtual void SeekForPrev(const Slice& /*target*/) override { assert(false); }
|
||||
void SeekForPrev(const Slice& /*target*/) override { assert(false); }
|
||||
|
||||
virtual void Next() override {
|
||||
void Next() override {
|
||||
assert(Valid());
|
||||
log.emplace_back(Action::Type::NEXT);
|
||||
current_++;
|
||||
}
|
||||
virtual void Prev() override { assert(false); }
|
||||
void Prev() override { assert(false); }
|
||||
|
||||
virtual Slice key() const override {
|
||||
Slice key() const override {
|
||||
assert(Valid());
|
||||
return Slice(keys_[current_]);
|
||||
}
|
||||
virtual Slice value() const override {
|
||||
Slice value() const override {
|
||||
assert(Valid());
|
||||
return Slice(values_[current_]);
|
||||
}
|
||||
|
||||
virtual Status status() const override { return Status::OK(); }
|
||||
Status status() const override { return Status::OK(); }
|
||||
|
||||
std::vector<Action> log;
|
||||
|
||||
@@ -158,22 +158,20 @@ class FakeCompaction : public CompactionIterator::CompactionProxy {
|
||||
public:
|
||||
FakeCompaction() = default;
|
||||
|
||||
virtual int level(size_t /*compaction_input_level*/) const override {
|
||||
return 0;
|
||||
}
|
||||
virtual bool KeyNotExistsBeyondOutputLevel(
|
||||
int level(size_t /*compaction_input_level*/) const override { return 0; }
|
||||
bool KeyNotExistsBeyondOutputLevel(
|
||||
const Slice& /*user_key*/,
|
||||
std::vector<size_t>* /*level_ptrs*/) const override {
|
||||
return is_bottommost_level || key_not_exists_beyond_output_level;
|
||||
}
|
||||
virtual bool bottommost_level() const override { return is_bottommost_level; }
|
||||
virtual int number_levels() const override { return 1; }
|
||||
virtual Slice GetLargestUserKey() const override {
|
||||
bool bottommost_level() const override { return is_bottommost_level; }
|
||||
int number_levels() const override { return 1; }
|
||||
Slice GetLargestUserKey() const override {
|
||||
return "\xff\xff\xff\xff\xff\xff\xff\xff\xff";
|
||||
}
|
||||
virtual bool allow_ingest_behind() const override { return false; }
|
||||
bool allow_ingest_behind() const override { return false; }
|
||||
|
||||
virtual bool preserve_deletes() const override { return false; }
|
||||
bool preserve_deletes() const override { return false; }
|
||||
|
||||
bool key_not_exists_beyond_output_level = false;
|
||||
|
||||
@@ -377,10 +375,9 @@ TEST_P(CompactionIteratorTest, RangeDeletionWithSnapshots) {
|
||||
|
||||
TEST_P(CompactionIteratorTest, CompactionFilterSkipUntil) {
|
||||
class Filter : public CompactionFilter {
|
||||
virtual Decision FilterV2(int /*level*/, const Slice& key, ValueType t,
|
||||
const Slice& existing_value,
|
||||
std::string* /*new_value*/,
|
||||
std::string* skip_until) const override {
|
||||
Decision FilterV2(int /*level*/, const Slice& key, ValueType t,
|
||||
const Slice& existing_value, std::string* /*new_value*/,
|
||||
std::string* skip_until) const override {
|
||||
std::string k = key.ToString();
|
||||
std::string v = existing_value.ToString();
|
||||
// See InitIterators() call below for the sequence of keys and their
|
||||
@@ -560,10 +557,9 @@ TEST_P(CompactionIteratorTest, ShuttingDownInMerge) {
|
||||
|
||||
TEST_P(CompactionIteratorTest, SingleMergeOperand) {
|
||||
class Filter : public CompactionFilter {
|
||||
virtual Decision FilterV2(int /*level*/, const Slice& key, ValueType t,
|
||||
const Slice& existing_value,
|
||||
std::string* /*new_value*/,
|
||||
std::string* /*skip_until*/) const override {
|
||||
Decision FilterV2(int /*level*/, const Slice& key, ValueType t,
|
||||
const Slice& existing_value, std::string* /*new_value*/,
|
||||
std::string* /*skip_until*/) const override {
|
||||
std::string k = key.ToString();
|
||||
std::string v = existing_value.ToString();
|
||||
|
||||
|
||||
+75
-145
@@ -97,6 +97,8 @@ const char* GetCompactionReasonString(CompactionReason compaction_reason) {
|
||||
return "Flush";
|
||||
case CompactionReason::kExternalSstIngestion:
|
||||
return "ExternalSstIngestion";
|
||||
case CompactionReason::kPeriodicCompaction:
|
||||
return "PeriodicCompaction";
|
||||
case CompactionReason::kNumOfReasons:
|
||||
// fall through
|
||||
default:
|
||||
@@ -157,7 +159,6 @@ struct CompactionJob::SubcompactionState {
|
||||
uint64_t overlapped_bytes = 0;
|
||||
// A flag determine whether the key has been seen in ShouldStopBefore()
|
||||
bool seen_key = false;
|
||||
std::string compression_dict;
|
||||
|
||||
SubcompactionState(Compaction* c, Slice* _start, Slice* _end,
|
||||
uint64_t size = 0)
|
||||
@@ -173,8 +174,7 @@ struct CompactionJob::SubcompactionState {
|
||||
approx_size(size),
|
||||
grandparent_index(0),
|
||||
overlapped_bytes(0),
|
||||
seen_key(false),
|
||||
compression_dict() {
|
||||
seen_key(false) {
|
||||
assert(compaction != nullptr);
|
||||
}
|
||||
|
||||
@@ -197,7 +197,6 @@ struct CompactionJob::SubcompactionState {
|
||||
grandparent_index = std::move(o.grandparent_index);
|
||||
overlapped_bytes = std::move(o.overlapped_bytes);
|
||||
seen_key = std::move(o.seen_key);
|
||||
compression_dict = std::move(o.compression_dict);
|
||||
return *this;
|
||||
}
|
||||
|
||||
@@ -315,7 +314,8 @@ CompactionJob::CompactionJob(
|
||||
SequenceNumber earliest_write_conflict_snapshot,
|
||||
const SnapshotChecker* snapshot_checker, std::shared_ptr<Cache> table_cache,
|
||||
EventLogger* event_logger, bool paranoid_file_checks, bool measure_io_stats,
|
||||
const std::string& dbname, CompactionJobStats* compaction_job_stats)
|
||||
const std::string& dbname, CompactionJobStats* compaction_job_stats,
|
||||
Env::Priority thread_pri, SnapshotListFetchCallback* snap_list_callback)
|
||||
: job_id_(job_id),
|
||||
compact_(new CompactionState(compaction)),
|
||||
compaction_job_stats_(compaction_job_stats),
|
||||
@@ -336,6 +336,7 @@ CompactionJob::CompactionJob(
|
||||
db_mutex_(db_mutex),
|
||||
db_error_handler_(db_error_handler),
|
||||
existing_snapshots_(std::move(existing_snapshots)),
|
||||
snap_list_callback_(snap_list_callback),
|
||||
earliest_write_conflict_snapshot_(earliest_write_conflict_snapshot),
|
||||
snapshot_checker_(snapshot_checker),
|
||||
table_cache_(std::move(table_cache)),
|
||||
@@ -343,7 +344,8 @@ CompactionJob::CompactionJob(
|
||||
bottommost_level_(false),
|
||||
paranoid_file_checks_(paranoid_file_checks),
|
||||
measure_io_stats_(measure_io_stats),
|
||||
write_hint_(Env::WLTH_NOT_SET) {
|
||||
write_hint_(Env::WLTH_NOT_SET),
|
||||
thread_pri_(thread_pri) {
|
||||
assert(log_buffer_ != nullptr);
|
||||
const auto* cfd = compact_->compaction->column_family_data();
|
||||
ThreadStatusUtil::SetColumnFamily(cfd, cfd->ioptions()->env,
|
||||
@@ -417,11 +419,10 @@ void CompactionJob::Prepare() {
|
||||
bottommost_level_ = c->bottommost_level();
|
||||
|
||||
if (c->ShouldFormSubcompactions()) {
|
||||
const uint64_t start_micros = env_->NowMicros();
|
||||
GenSubcompactionBoundaries();
|
||||
MeasureTime(stats_, SUBCOMPACTION_SETUP_TIME,
|
||||
env_->NowMicros() - start_micros);
|
||||
|
||||
{
|
||||
StopWatch sw(env_, stats_, SUBCOMPACTION_SETUP_TIME);
|
||||
GenSubcompactionBoundaries();
|
||||
}
|
||||
assert(sizes_.size() == boundaries_.size() + 1);
|
||||
|
||||
for (size_t i = 0; i <= boundaries_.size(); i++) {
|
||||
@@ -429,8 +430,8 @@ void CompactionJob::Prepare() {
|
||||
Slice* end = i == boundaries_.size() ? nullptr : &boundaries_[i];
|
||||
compact_->sub_compact_states.emplace_back(c, start, end, sizes_[i]);
|
||||
}
|
||||
MeasureTime(stats_, NUM_SUBCOMPACTIONS_SCHEDULED,
|
||||
compact_->sub_compact_states.size());
|
||||
RecordInHistogram(stats_, NUM_SUBCOMPACTIONS_SCHEDULED,
|
||||
compact_->sub_compact_states.size());
|
||||
} else {
|
||||
compact_->sub_compact_states.emplace_back(c, nullptr, nullptr);
|
||||
}
|
||||
@@ -610,8 +611,9 @@ Status CompactionJob::Run() {
|
||||
compact_->sub_compact_states[i].compaction_job_stats.cpu_micros;
|
||||
}
|
||||
|
||||
MeasureTime(stats_, COMPACTION_TIME, compaction_stats_.micros);
|
||||
MeasureTime(stats_, COMPACTION_CPU_TIME, compaction_stats_.cpu_micros);
|
||||
RecordTimeToHistogram(stats_, COMPACTION_TIME, compaction_stats_.micros);
|
||||
RecordTimeToHistogram(stats_, COMPACTION_CPU_TIME,
|
||||
compaction_stats_.cpu_micros);
|
||||
|
||||
TEST_SYNC_POINT("CompactionJob::Run:BeforeVerify");
|
||||
|
||||
@@ -720,7 +722,7 @@ Status CompactionJob::Install(const MutableCFOptions& mutable_cf_options) {
|
||||
Status status = compact_->status;
|
||||
ColumnFamilyData* cfd = compact_->compaction->column_family_data();
|
||||
cfd->internal_stats()->AddCompactionStats(
|
||||
compact_->compaction->output_level(), compaction_stats_);
|
||||
compact_->compaction->output_level(), thread_pri_, compaction_stats_);
|
||||
|
||||
if (status.ok()) {
|
||||
status = InstallCompactionResults(mutable_cf_options);
|
||||
@@ -816,6 +818,24 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
|
||||
uint64_t prev_cpu_micros = env_->NowCPUNanos() / 1000;
|
||||
|
||||
ColumnFamilyData* cfd = sub_compact->compaction->column_family_data();
|
||||
|
||||
// Create compaction filter and fail the compaction if
|
||||
// IgnoreSnapshots() = false because it is not supported anymore
|
||||
const CompactionFilter* compaction_filter =
|
||||
cfd->ioptions()->compaction_filter;
|
||||
std::unique_ptr<CompactionFilter> compaction_filter_from_factory = nullptr;
|
||||
if (compaction_filter == nullptr) {
|
||||
compaction_filter_from_factory =
|
||||
sub_compact->compaction->CreateCompactionFilter();
|
||||
compaction_filter = compaction_filter_from_factory.get();
|
||||
}
|
||||
if (compaction_filter != nullptr && !compaction_filter->IgnoreSnapshots()) {
|
||||
sub_compact->status = Status::NotSupported(
|
||||
"CompactionFilter::IgnoreSnapshots() = false is not supported "
|
||||
"anymore.");
|
||||
return;
|
||||
}
|
||||
|
||||
CompactionRangeDelAggregator range_del_agg(&cfd->internal_comparator(),
|
||||
existing_snapshots_);
|
||||
|
||||
@@ -847,49 +867,6 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
|
||||
prev_cpu_read_nanos = IOSTATS(cpu_read_nanos);
|
||||
}
|
||||
|
||||
const MutableCFOptions* mutable_cf_options =
|
||||
sub_compact->compaction->mutable_cf_options();
|
||||
|
||||
// To build compression dictionary, we sample the first output file, assuming
|
||||
// it'll reach the maximum length. We optionally pass these samples through
|
||||
// zstd's dictionary trainer, or just use them directly. Then, the dictionary
|
||||
// is used for compressing subsequent output files in the same subcompaction.
|
||||
const bool kUseZstdTrainer =
|
||||
sub_compact->compaction->output_compression_opts().zstd_max_train_bytes >
|
||||
0;
|
||||
const size_t kSampleBytes =
|
||||
kUseZstdTrainer
|
||||
? sub_compact->compaction->output_compression_opts()
|
||||
.zstd_max_train_bytes
|
||||
: sub_compact->compaction->output_compression_opts().max_dict_bytes;
|
||||
const int kSampleLenShift = 6; // 2^6 = 64-byte samples
|
||||
std::set<size_t> sample_begin_offsets;
|
||||
if (bottommost_level_ && kSampleBytes > 0) {
|
||||
const size_t kMaxSamples = kSampleBytes >> kSampleLenShift;
|
||||
const size_t kOutFileLen =
|
||||
static_cast<size_t>(MaxFileSizeForLevel(*mutable_cf_options,
|
||||
compact_->compaction->output_level(),
|
||||
cfd->ioptions()->compaction_style,
|
||||
compact_->compaction->GetInputBaseLevel(),
|
||||
cfd->ioptions()->level_compaction_dynamic_level_bytes));
|
||||
if (kOutFileLen != port::kMaxSizet) {
|
||||
const size_t kOutFileNumSamples = kOutFileLen >> kSampleLenShift;
|
||||
Random64 generator{versions_->NewFileNumber()};
|
||||
for (size_t i = 0; i < kMaxSamples; ++i) {
|
||||
sample_begin_offsets.insert(
|
||||
static_cast<size_t>(generator.Uniform(kOutFileNumSamples))
|
||||
<< kSampleLenShift);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto compaction_filter = cfd->ioptions()->compaction_filter;
|
||||
std::unique_ptr<CompactionFilter> compaction_filter_from_factory = nullptr;
|
||||
if (compaction_filter == nullptr) {
|
||||
compaction_filter_from_factory =
|
||||
sub_compact->compaction->CreateCompactionFilter();
|
||||
compaction_filter = compaction_filter_from_factory.get();
|
||||
}
|
||||
MergeHelper merge(
|
||||
env_, cfd->user_comparator(), cfd->ioptions()->merge_operator,
|
||||
compaction_filter, db_options_.info_log.get(),
|
||||
@@ -916,7 +893,9 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
|
||||
&existing_snapshots_, earliest_write_conflict_snapshot_,
|
||||
snapshot_checker_, env_, ShouldReportDetailedTime(env_, stats_), false,
|
||||
&range_del_agg, sub_compact->compaction, compaction_filter,
|
||||
shutting_down_, preserve_deletes_seqnum_));
|
||||
shutting_down_, preserve_deletes_seqnum_,
|
||||
// Currently range_del_agg is incompatible with snapshot refresh feature.
|
||||
range_del_agg.IsEmpty() ? snap_list_callback_ : nullptr));
|
||||
auto c_iter = sub_compact->c_iter.get();
|
||||
c_iter->SeekToFirst();
|
||||
if (c_iter->Valid() && sub_compact->compaction->output_level() != 0) {
|
||||
@@ -927,12 +906,6 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
|
||||
sub_compact->current_output_file_size);
|
||||
}
|
||||
const auto& c_iter_stats = c_iter->iter_stats();
|
||||
auto sample_begin_offset_iter = sample_begin_offsets.cbegin();
|
||||
// data_begin_offset and dict_sample_data are only valid while generating
|
||||
// dictionary from the first output file.
|
||||
size_t data_begin_offset = 0;
|
||||
std::string dict_sample_data;
|
||||
dict_sample_data.reserve(kSampleBytes);
|
||||
|
||||
while (status.ok() && !cfd->IsDropped() && c_iter->Valid()) {
|
||||
// Invariant: c_iter.status() is guaranteed to be OK if c_iter->Valid()
|
||||
@@ -968,55 +941,6 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
|
||||
key, c_iter->ikey().sequence);
|
||||
sub_compact->num_output_records++;
|
||||
|
||||
if (sub_compact->outputs.size() == 1) { // first output file
|
||||
// Check if this key/value overlaps any sample intervals; if so, appends
|
||||
// overlapping portions to the dictionary.
|
||||
for (const auto& data_elmt : {key, value}) {
|
||||
size_t data_end_offset = data_begin_offset + data_elmt.size();
|
||||
while (sample_begin_offset_iter != sample_begin_offsets.cend() &&
|
||||
*sample_begin_offset_iter < data_end_offset) {
|
||||
size_t sample_end_offset =
|
||||
*sample_begin_offset_iter + (1 << kSampleLenShift);
|
||||
// Invariant: Because we advance sample iterator while processing the
|
||||
// data_elmt containing the sample's last byte, the current sample
|
||||
// cannot end before the current data_elmt.
|
||||
assert(data_begin_offset < sample_end_offset);
|
||||
|
||||
size_t data_elmt_copy_offset, data_elmt_copy_len;
|
||||
if (*sample_begin_offset_iter <= data_begin_offset) {
|
||||
// The sample starts before data_elmt starts, so take bytes starting
|
||||
// at the beginning of data_elmt.
|
||||
data_elmt_copy_offset = 0;
|
||||
} else {
|
||||
// data_elmt starts before the sample starts, so take bytes starting
|
||||
// at the below offset into data_elmt.
|
||||
data_elmt_copy_offset =
|
||||
*sample_begin_offset_iter - data_begin_offset;
|
||||
}
|
||||
if (sample_end_offset <= data_end_offset) {
|
||||
// The sample ends before data_elmt ends, so take as many bytes as
|
||||
// needed.
|
||||
data_elmt_copy_len =
|
||||
sample_end_offset - (data_begin_offset + data_elmt_copy_offset);
|
||||
} else {
|
||||
// data_elmt ends before the sample ends, so take all remaining
|
||||
// bytes in data_elmt.
|
||||
data_elmt_copy_len =
|
||||
data_end_offset - (data_begin_offset + data_elmt_copy_offset);
|
||||
}
|
||||
dict_sample_data.append(&data_elmt.data()[data_elmt_copy_offset],
|
||||
data_elmt_copy_len);
|
||||
if (sample_end_offset > data_end_offset) {
|
||||
// Didn't finish sample. Try to finish it with the next data_elmt.
|
||||
break;
|
||||
}
|
||||
// Next sample may require bytes from same data_elmt.
|
||||
sample_begin_offset_iter++;
|
||||
}
|
||||
data_begin_offset = data_end_offset;
|
||||
}
|
||||
}
|
||||
|
||||
// Close output file if it is big enough. Two possibilities determine it's
|
||||
// time to close it: (1) the current key should be this file's last key, (2)
|
||||
// the next key should not be in this file.
|
||||
@@ -1058,18 +982,6 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
|
||||
&range_del_out_stats, next_key);
|
||||
RecordDroppedKeys(range_del_out_stats,
|
||||
&sub_compact->compaction_job_stats);
|
||||
if (sub_compact->outputs.size() == 1) {
|
||||
// Use samples from first output file to create dictionary for
|
||||
// compression of subsequent files.
|
||||
if (kUseZstdTrainer) {
|
||||
sub_compact->compression_dict = ZSTD_TrainDictionary(
|
||||
dict_sample_data, kSampleLenShift,
|
||||
sub_compact->compaction->output_compression_opts()
|
||||
.max_dict_bytes);
|
||||
} else {
|
||||
sub_compact->compression_dict = std::move(dict_sample_data);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1228,10 +1140,19 @@ Status CompactionJob::FinishCompactionOutputFile(
|
||||
lower_bound = nullptr;
|
||||
}
|
||||
if (next_table_min_key != nullptr) {
|
||||
// This isn't the last file in the subcompaction, so extend until the next
|
||||
// file starts.
|
||||
// This may be the last file in the subcompaction in some cases, so we
|
||||
// need to compare the end key of subcompaction with the next file start
|
||||
// key. When the end key is chosen by the subcompaction, we know that
|
||||
// it must be the biggest key in output file. Therefore, it is safe to
|
||||
// use the smaller key as the upper bound of the output file, to ensure
|
||||
// that there is no overlapping between different output files.
|
||||
upper_bound_guard = ExtractUserKey(*next_table_min_key);
|
||||
upper_bound = &upper_bound_guard;
|
||||
if (sub_compact->end != nullptr &&
|
||||
ucmp->Compare(upper_bound_guard, *sub_compact->end) >= 0) {
|
||||
upper_bound = sub_compact->end;
|
||||
} else {
|
||||
upper_bound = &upper_bound_guard;
|
||||
}
|
||||
} else {
|
||||
// This is the last file in the subcompaction, so extend until the
|
||||
// subcompaction ends.
|
||||
@@ -1249,6 +1170,13 @@ Status CompactionJob::FinishCompactionOutputFile(
|
||||
has_overlapping_endpoints = false;
|
||||
}
|
||||
|
||||
// The end key of the subcompaction must be bigger or equal to the upper
|
||||
// bound. If the end of subcompaction is null or the upper bound is null,
|
||||
// it means that this file is the last file in the compaction. So there
|
||||
// will be no overlapping between this file and others.
|
||||
assert(sub_compact->end == nullptr ||
|
||||
upper_bound == nullptr ||
|
||||
ucmp->Compare(*upper_bound , *sub_compact->end) <= 0);
|
||||
auto it = range_del_agg->NewIterator(lower_bound, upper_bound,
|
||||
has_overlapping_endpoints);
|
||||
// Position the range tombstone output iterator. There may be tombstone
|
||||
@@ -1557,20 +1485,20 @@ Status CompactionJob::OpenCompactionOutputFile(
|
||||
bool skip_filters =
|
||||
cfd->ioptions()->optimize_filters_for_hits && bottommost_level_;
|
||||
|
||||
uint64_t output_file_creation_time =
|
||||
int64_t temp_current_time = 0;
|
||||
auto get_time_status = env_->GetCurrentTime(&temp_current_time);
|
||||
// Safe to proceed even if GetCurrentTime fails. So, log and proceed.
|
||||
if (!get_time_status.ok()) {
|
||||
ROCKS_LOG_WARN(db_options_.info_log,
|
||||
"Failed to get current time. Status: %s",
|
||||
get_time_status.ToString().c_str());
|
||||
}
|
||||
uint64_t current_time = static_cast<uint64_t>(temp_current_time);
|
||||
|
||||
uint64_t latest_key_time =
|
||||
sub_compact->compaction->MaxInputFileCreationTime();
|
||||
if (output_file_creation_time == 0) {
|
||||
int64_t _current_time = 0;
|
||||
auto status = db_options_.env->GetCurrentTime(&_current_time);
|
||||
// Safe to proceed even if GetCurrentTime fails. So, log and proceed.
|
||||
if (!status.ok()) {
|
||||
ROCKS_LOG_WARN(
|
||||
db_options_.info_log,
|
||||
"Failed to get current time to populate creation_time property. "
|
||||
"Status: %s",
|
||||
status.ToString().c_str());
|
||||
}
|
||||
output_file_creation_time = static_cast<uint64_t>(_current_time);
|
||||
if (latest_key_time == 0) {
|
||||
latest_key_time = current_time;
|
||||
}
|
||||
|
||||
sub_compact->builder.reset(NewTableBuilder(
|
||||
@@ -1578,9 +1506,11 @@ Status CompactionJob::OpenCompactionOutputFile(
|
||||
cfd->internal_comparator(), cfd->int_tbl_prop_collector_factories(),
|
||||
cfd->GetID(), cfd->GetName(), sub_compact->outfile.get(),
|
||||
sub_compact->compaction->output_compression(),
|
||||
0 /*sample_for_compression */,
|
||||
sub_compact->compaction->output_compression_opts(),
|
||||
sub_compact->compaction->output_level(), &sub_compact->compression_dict,
|
||||
skip_filters, output_file_creation_time));
|
||||
sub_compact->compaction->output_level(), skip_filters, latest_key_time,
|
||||
0 /* oldest_key_time */, sub_compact->compaction->max_output_file_size(),
|
||||
current_time));
|
||||
LogFlush(db_options_.info_log);
|
||||
return s;
|
||||
}
|
||||
|
||||
+16
-16
@@ -57,22 +57,20 @@ class VersionSet;
|
||||
|
||||
class CompactionJob {
|
||||
public:
|
||||
CompactionJob(int job_id, Compaction* compaction,
|
||||
const ImmutableDBOptions& db_options,
|
||||
const EnvOptions env_options, VersionSet* versions,
|
||||
const std::atomic<bool>* shutting_down,
|
||||
const SequenceNumber preserve_deletes_seqnum,
|
||||
LogBuffer* log_buffer,
|
||||
Directory* db_directory, Directory* output_directory,
|
||||
Statistics* stats, InstrumentedMutex* db_mutex,
|
||||
ErrorHandler* db_error_handler,
|
||||
std::vector<SequenceNumber> existing_snapshots,
|
||||
SequenceNumber earliest_write_conflict_snapshot,
|
||||
const SnapshotChecker* snapshot_checker,
|
||||
std::shared_ptr<Cache> table_cache, EventLogger* event_logger,
|
||||
bool paranoid_file_checks, bool measure_io_stats,
|
||||
const std::string& dbname,
|
||||
CompactionJobStats* compaction_job_stats);
|
||||
CompactionJob(
|
||||
int job_id, Compaction* compaction, const ImmutableDBOptions& db_options,
|
||||
const EnvOptions env_options, VersionSet* versions,
|
||||
const std::atomic<bool>* shutting_down,
|
||||
const SequenceNumber preserve_deletes_seqnum, LogBuffer* log_buffer,
|
||||
Directory* db_directory, Directory* output_directory, Statistics* stats,
|
||||
InstrumentedMutex* db_mutex, ErrorHandler* db_error_handler,
|
||||
std::vector<SequenceNumber> existing_snapshots,
|
||||
SequenceNumber earliest_write_conflict_snapshot,
|
||||
const SnapshotChecker* snapshot_checker,
|
||||
std::shared_ptr<Cache> table_cache, EventLogger* event_logger,
|
||||
bool paranoid_file_checks, bool measure_io_stats,
|
||||
const std::string& dbname, CompactionJobStats* compaction_job_stats,
|
||||
Env::Priority thread_pri, SnapshotListFetchCallback* snap_list_callback);
|
||||
|
||||
~CompactionJob();
|
||||
|
||||
@@ -152,6 +150,7 @@ class CompactionJob {
|
||||
// entirely within s1 and s2, then the earlier version of k1 can be safely
|
||||
// deleted because that version is not visible in any snapshot.
|
||||
std::vector<SequenceNumber> existing_snapshots_;
|
||||
SnapshotListFetchCallback* snap_list_callback_;
|
||||
|
||||
// This is the earliest snapshot that could be used for write-conflict
|
||||
// checking by a transaction. For any user-key newer than this snapshot, we
|
||||
@@ -172,6 +171,7 @@ class CompactionJob {
|
||||
// Stores the approx size of keys covered in the range of each subcompaction
|
||||
std::vector<uint64_t> sizes_;
|
||||
Env::WriteLifeTimeHint write_hint_;
|
||||
Env::Priority thread_pri_;
|
||||
};
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
@@ -113,7 +113,7 @@ class CompactionJobStatsTest : public testing::Test,
|
||||
Reopen(options);
|
||||
}
|
||||
|
||||
~CompactionJobStatsTest() {
|
||||
~CompactionJobStatsTest() override {
|
||||
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
||||
rocksdb::SyncPoint::GetInstance()->LoadDependency({});
|
||||
rocksdb::SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
@@ -426,7 +426,7 @@ class CompactionJobStatsChecker : public EventListener {
|
||||
// Once a compaction completed, this function will verify the returned
|
||||
// CompactionJobInfo with the oldest CompactionJobInfo added earlier
|
||||
// in "expected_stats_" which has not yet being used for verification.
|
||||
virtual void OnCompactionCompleted(DB* /*db*/, const CompactionJobInfo& ci) {
|
||||
void OnCompactionCompleted(DB* /*db*/, const CompactionJobInfo& ci) override {
|
||||
if (verify_next_comp_io_stats_) {
|
||||
ASSERT_GT(ci.stats.file_write_nanos, 0);
|
||||
ASSERT_GT(ci.stats.file_range_sync_nanos, 0);
|
||||
@@ -523,7 +523,7 @@ class CompactionJobDeletionStatsChecker : public CompactionJobStatsChecker {
|
||||
public:
|
||||
// Verifies whether two CompactionJobStats match.
|
||||
void Verify(const CompactionJobStats& current_stats,
|
||||
const CompactionJobStats& stats) {
|
||||
const CompactionJobStats& stats) override {
|
||||
ASSERT_EQ(
|
||||
current_stats.num_input_deletion_records,
|
||||
stats.num_input_deletion_records);
|
||||
|
||||
+137
-19
@@ -5,7 +5,13 @@
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
|
||||
#ifndef __STDC_FORMAT_MACROS
|
||||
#define __STDC_FORMAT_MACROS
|
||||
#endif
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
@@ -172,7 +178,7 @@ class CompactionJobTest : public testing::Test {
|
||||
// This is how the key will look like once it's written in bottommost
|
||||
// file
|
||||
InternalKey bottommost_internal_key(
|
||||
key, (key == "9999") ? sequence_number : 0, kTypeValue);
|
||||
key, 0, kTypeValue);
|
||||
|
||||
if (corrupt_id(k)) {
|
||||
test::CorruptKeyType(&internal_key);
|
||||
@@ -194,6 +200,13 @@ class CompactionJobTest : public testing::Test {
|
||||
}
|
||||
|
||||
void NewDB() {
|
||||
DestroyDB(dbname_, Options());
|
||||
EXPECT_OK(env_->CreateDirIfMissing(dbname_));
|
||||
versions_.reset(new VersionSet(dbname_, &db_options_, env_options_,
|
||||
table_cache_.get(), &write_buffer_manager_,
|
||||
&write_controller_));
|
||||
compaction_job_stats_.Reset();
|
||||
|
||||
VersionEdit new_db;
|
||||
new_db.SetLogNumber(0);
|
||||
new_db.SetNextFile(2);
|
||||
@@ -230,7 +243,10 @@ class CompactionJobTest : public testing::Test {
|
||||
const std::vector<std::vector<FileMetaData*>>& input_files,
|
||||
const stl_wrappers::KVMap& expected_results,
|
||||
const std::vector<SequenceNumber>& snapshots = {},
|
||||
SequenceNumber earliest_write_conflict_snapshot = kMaxSequenceNumber) {
|
||||
SequenceNumber earliest_write_conflict_snapshot = kMaxSequenceNumber,
|
||||
int output_level = 1, bool verify = true,
|
||||
SnapshotListFetchCallback* snapshot_fetcher =
|
||||
SnapshotListFetchCallback::kDisabled) {
|
||||
auto cfd = versions_->GetColumnFamilySet()->GetDefault();
|
||||
|
||||
size_t num_input_files = 0;
|
||||
@@ -247,7 +263,7 @@ class CompactionJobTest : public testing::Test {
|
||||
|
||||
Compaction compaction(cfd->current()->storage_info(), *cfd->ioptions(),
|
||||
*cfd->GetLatestMutableCFOptions(),
|
||||
compaction_input_files, 1, 1024 * 1024,
|
||||
compaction_input_files, output_level, 1024 * 1024,
|
||||
10 * 1024 * 1024, 0, kNoCompression,
|
||||
cfd->ioptions()->compression_opts, 0, {}, true);
|
||||
compaction.SetInputVersion(cfd->current());
|
||||
@@ -262,7 +278,8 @@ class CompactionJobTest : public testing::Test {
|
||||
&shutting_down_, preserve_deletes_seqnum_, &log_buffer, nullptr,
|
||||
nullptr, nullptr, &mutex_, &error_handler_, snapshots,
|
||||
earliest_write_conflict_snapshot, snapshot_checker, table_cache_,
|
||||
&event_logger, false, false, dbname_, &compaction_job_stats_);
|
||||
&event_logger, false, false, dbname_, &compaction_job_stats_,
|
||||
Env::Priority::USER, snapshot_fetcher);
|
||||
VerifyInitializationOfCompactionJobStats(compaction_job_stats_);
|
||||
|
||||
compaction_job.Prepare();
|
||||
@@ -274,15 +291,17 @@ class CompactionJobTest : public testing::Test {
|
||||
ASSERT_OK(compaction_job.Install(*cfd->GetLatestMutableCFOptions()));
|
||||
mutex_.Unlock();
|
||||
|
||||
if (expected_results.size() == 0) {
|
||||
ASSERT_GE(compaction_job_stats_.elapsed_micros, 0U);
|
||||
ASSERT_EQ(compaction_job_stats_.num_input_files, num_input_files);
|
||||
ASSERT_EQ(compaction_job_stats_.num_output_files, 0U);
|
||||
} else {
|
||||
ASSERT_GE(compaction_job_stats_.elapsed_micros, 0U);
|
||||
ASSERT_EQ(compaction_job_stats_.num_input_files, num_input_files);
|
||||
ASSERT_EQ(compaction_job_stats_.num_output_files, 1U);
|
||||
mock_table_factory_->AssertLatestFile(expected_results);
|
||||
if (verify) {
|
||||
if (expected_results.size() == 0) {
|
||||
ASSERT_GE(compaction_job_stats_.elapsed_micros, 0U);
|
||||
ASSERT_EQ(compaction_job_stats_.num_input_files, num_input_files);
|
||||
ASSERT_EQ(compaction_job_stats_.num_output_files, 0U);
|
||||
} else {
|
||||
ASSERT_GE(compaction_job_stats_.elapsed_micros, 0U);
|
||||
ASSERT_EQ(compaction_job_stats_.num_input_files, num_input_files);
|
||||
ASSERT_EQ(compaction_job_stats_.num_output_files, 1U);
|
||||
mock_table_factory_->AssertLatestFile(expected_results);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -379,7 +398,7 @@ TEST_F(CompactionJobTest, SimpleOverwrite) {
|
||||
|
||||
auto expected_results =
|
||||
mock::MakeMockFile({{KeyStr("a", 0U, kTypeValue), "val2"},
|
||||
{KeyStr("b", 4U, kTypeValue), "val3"}});
|
||||
{KeyStr("b", 0U, kTypeValue), "val3"}});
|
||||
|
||||
SetLastSequence(4U);
|
||||
auto files = cfd_->current()->storage_info()->LevelFiles(0);
|
||||
@@ -432,7 +451,7 @@ TEST_F(CompactionJobTest, SimpleMerge) {
|
||||
|
||||
auto expected_results =
|
||||
mock::MakeMockFile({{KeyStr("a", 0U, kTypeValue), "3,4,5"},
|
||||
{KeyStr("b", 2U, kTypeValue), "1,2"}});
|
||||
{KeyStr("b", 0U, kTypeValue), "1,2"}});
|
||||
|
||||
SetLastSequence(5U);
|
||||
auto files = cfd_->current()->storage_info()->LevelFiles(0);
|
||||
@@ -456,7 +475,7 @@ TEST_F(CompactionJobTest, NonAssocMerge) {
|
||||
|
||||
auto expected_results =
|
||||
mock::MakeMockFile({{KeyStr("a", 0U, kTypeValue), "3,4,5"},
|
||||
{KeyStr("b", 2U, kTypeValue), "1,2"}});
|
||||
{KeyStr("b", 0U, kTypeValue), "1,2"}});
|
||||
|
||||
SetLastSequence(5U);
|
||||
auto files = cfd_->current()->storage_info()->LevelFiles(0);
|
||||
@@ -483,7 +502,7 @@ TEST_F(CompactionJobTest, MergeOperandFilter) {
|
||||
|
||||
auto expected_results =
|
||||
mock::MakeMockFile({{KeyStr("a", 0U, kTypeValue), test::EncodeInt(8U)},
|
||||
{KeyStr("b", 2U, kTypeValue), test::EncodeInt(2U)}});
|
||||
{KeyStr("b", 0U, kTypeValue), test::EncodeInt(2U)}});
|
||||
|
||||
SetLastSequence(5U);
|
||||
auto files = cfd_->current()->storage_info()->LevelFiles(0);
|
||||
@@ -746,7 +765,7 @@ TEST_F(CompactionJobTest, SingleDeleteZeroSeq) {
|
||||
AddMockFile(file2);
|
||||
|
||||
auto expected_results = mock::MakeMockFile({
|
||||
{KeyStr("dummy", 5U, kTypeValue), "val2"},
|
||||
{KeyStr("dummy", 0U, kTypeValue), "val2"},
|
||||
});
|
||||
|
||||
SetLastSequence(22U);
|
||||
@@ -930,13 +949,112 @@ TEST_F(CompactionJobTest, CorruptionAfterDeletion) {
|
||||
mock::MakeMockFile({{test::KeyStr("A", 0U, kTypeValue), "val3"},
|
||||
{test::KeyStr("a", 0U, kTypeValue, true), "val"},
|
||||
{test::KeyStr("b", 0U, kTypeValue, true), "val"},
|
||||
{test::KeyStr("c", 1U, kTypeValue), "val2"}});
|
||||
{test::KeyStr("c", 0U, kTypeValue), "val2"}});
|
||||
|
||||
SetLastSequence(6U);
|
||||
auto files = cfd_->current()->storage_info()->LevelFiles(0);
|
||||
RunCompaction({files}, expected_results);
|
||||
}
|
||||
|
||||
// Test the snapshot fetcher in compaction
|
||||
TEST_F(CompactionJobTest, SnapshotRefresh) {
|
||||
uint64_t time_seed = env_->NowMicros();
|
||||
printf("time_seed is %" PRIu64 "\n", time_seed); // would help to reproduce
|
||||
Random64 rand(time_seed);
|
||||
std::vector<SequenceNumber> db_snapshots;
|
||||
class SnapshotListFetchCallbackTest : public SnapshotListFetchCallback {
|
||||
public:
|
||||
SnapshotListFetchCallbackTest(Env* env, Random64& rand,
|
||||
std::vector<SequenceNumber>* snapshots)
|
||||
: SnapshotListFetchCallback(env, 1 /*short time delay*/,
|
||||
1 /*fetch after each key*/),
|
||||
rand_(rand),
|
||||
snapshots_(snapshots) {}
|
||||
virtual void Refresh(std::vector<SequenceNumber>* snapshots,
|
||||
SequenceNumber) override {
|
||||
assert(snapshots->size());
|
||||
assert(snapshots_->size());
|
||||
assert(snapshots_->size() == snapshots->size());
|
||||
if (rand_.OneIn(2)) {
|
||||
uint64_t release_index = rand_.Uniform(snapshots_->size());
|
||||
snapshots_->erase(snapshots_->begin() + release_index);
|
||||
*snapshots = *snapshots_;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
Random64 rand_;
|
||||
std::vector<SequenceNumber>* snapshots_;
|
||||
} snapshot_fetcher(env_, rand, &db_snapshots);
|
||||
|
||||
std::vector<std::pair<const std::string, std::string>> file1_kvs, file2_kvs;
|
||||
std::array<ValueType, 4> types = {kTypeValue, kTypeDeletion,
|
||||
kTypeSingleDeletion};
|
||||
SequenceNumber last_seq = 0;
|
||||
for (int i = 1; i < 100; i++) {
|
||||
SequenceNumber seq = last_seq + 1;
|
||||
last_seq = seq;
|
||||
if (rand.OneIn(2)) {
|
||||
auto type = types[rand.Uniform(types.size())];
|
||||
file1_kvs.push_back(
|
||||
{test::KeyStr("k" + ToString(i), seq, type), "v" + ToString(i)});
|
||||
}
|
||||
}
|
||||
auto file1 = mock::MakeMockFile(file1_kvs);
|
||||
for (int i = 1; i < 100; i++) {
|
||||
SequenceNumber seq = last_seq + 1;
|
||||
last_seq++;
|
||||
if (rand.OneIn(2)) {
|
||||
auto type = types[rand.Uniform(types.size())];
|
||||
file2_kvs.push_back(
|
||||
{test::KeyStr("k" + ToString(i), seq, type), "v" + ToString(i)});
|
||||
}
|
||||
}
|
||||
auto file2 = mock::MakeMockFile(file2_kvs);
|
||||
for (SequenceNumber i = 1; i < last_seq + 1; i++) {
|
||||
if (rand.OneIn(5)) {
|
||||
db_snapshots.push_back(i);
|
||||
}
|
||||
}
|
||||
|
||||
const bool kVerify = true;
|
||||
const int output_level_0 = 0;
|
||||
NewDB();
|
||||
AddMockFile(file1);
|
||||
AddMockFile(file2);
|
||||
SetLastSequence(last_seq);
|
||||
auto files = cfd_->current()->storage_info()->LevelFiles(0);
|
||||
// put the output on L0 since it is easier to feed them again to the 2nd
|
||||
// compaction
|
||||
RunCompaction({files}, file1, db_snapshots, kMaxSequenceNumber,
|
||||
output_level_0, !kVerify, &snapshot_fetcher);
|
||||
|
||||
// Now db_snapshots are changed. Run the compaction again without snapshot
|
||||
// fetcher but with the updated snapshot list.
|
||||
compaction_job_stats_.Reset();
|
||||
files = cfd_->current()->storage_info()->LevelFiles(0);
|
||||
RunCompaction({files}, file1, db_snapshots, kMaxSequenceNumber,
|
||||
output_level_0 + 1, !kVerify);
|
||||
// The result should be what we get if we run compaction without snapshot
|
||||
// fetcher on the updated list of snapshots
|
||||
auto expected = mock_table_factory_->output();
|
||||
|
||||
NewDB();
|
||||
AddMockFile(file1);
|
||||
AddMockFile(file2);
|
||||
SetLastSequence(last_seq);
|
||||
files = cfd_->current()->storage_info()->LevelFiles(0);
|
||||
RunCompaction({files}, expected, db_snapshots, kMaxSequenceNumber,
|
||||
output_level_0, !kVerify);
|
||||
// The 2nd compaction above would get rid of useless delete markers. To get
|
||||
// the output here exactly as what we got above after two compactions, we also
|
||||
// run the compaction for 2nd time.
|
||||
compaction_job_stats_.Reset();
|
||||
files = cfd_->current()->storage_info()->LevelFiles(0);
|
||||
RunCompaction({files}, expected, db_snapshots, kMaxSequenceNumber,
|
||||
output_level_0 + 1, !kVerify);
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
+101
-10
@@ -543,9 +543,9 @@ void CompactionPicker::GetGrandparents(
|
||||
Compaction* CompactionPicker::CompactRange(
|
||||
const std::string& cf_name, const MutableCFOptions& mutable_cf_options,
|
||||
VersionStorageInfo* vstorage, int input_level, int output_level,
|
||||
uint32_t output_path_id, uint32_t max_subcompactions,
|
||||
const InternalKey* begin, const InternalKey* end,
|
||||
InternalKey** compaction_end, bool* manual_conflict) {
|
||||
const CompactRangeOptions& compact_range_options, const InternalKey* begin,
|
||||
const InternalKey* end, InternalKey** compaction_end, bool* manual_conflict,
|
||||
uint64_t max_file_num_to_ignore) {
|
||||
// CompactionPickerFIFO has its own implementation of compact range
|
||||
assert(ioptions_.compaction_style != kCompactionStyleFIFO);
|
||||
|
||||
@@ -609,11 +609,13 @@ Compaction* CompactionPicker::CompactRange(
|
||||
output_level,
|
||||
MaxFileSizeForLevel(mutable_cf_options, output_level,
|
||||
ioptions_.compaction_style),
|
||||
/* max_compaction_bytes */ LLONG_MAX, output_path_id,
|
||||
/* max_compaction_bytes */ LLONG_MAX,
|
||||
compact_range_options.target_path_id,
|
||||
GetCompressionType(ioptions_, vstorage, mutable_cf_options,
|
||||
output_level, 1),
|
||||
GetCompressionOptions(ioptions_, vstorage, output_level),
|
||||
max_subcompactions, /* grandparents */ {}, /* is manual */ true);
|
||||
compact_range_options.max_subcompactions, /* grandparents */ {},
|
||||
/* is manual */ true);
|
||||
RegisterCompaction(c);
|
||||
return c;
|
||||
}
|
||||
@@ -658,7 +660,43 @@ Compaction* CompactionPicker::CompactRange(
|
||||
}
|
||||
}
|
||||
}
|
||||
assert(output_path_id < static_cast<uint32_t>(ioptions_.cf_paths.size()));
|
||||
assert(compact_range_options.target_path_id <
|
||||
static_cast<uint32_t>(ioptions_.cf_paths.size()));
|
||||
|
||||
// for BOTTOM LEVEL compaction only, use max_file_num_to_ignore to filter out
|
||||
// files that are created during the current compaction.
|
||||
if (compact_range_options.bottommost_level_compaction ==
|
||||
BottommostLevelCompaction::kForceOptimized &&
|
||||
max_file_num_to_ignore != port::kMaxUint64) {
|
||||
assert(input_level == output_level);
|
||||
// inputs_shrunk holds a continuous subset of input files which were all
|
||||
// created before the current manual compaction
|
||||
std::vector<FileMetaData*> inputs_shrunk;
|
||||
size_t skip_input_index = inputs.size();
|
||||
for (size_t i = 0; i < inputs.size(); ++i) {
|
||||
if (inputs[i]->fd.GetNumber() < max_file_num_to_ignore) {
|
||||
inputs_shrunk.push_back(inputs[i]);
|
||||
} else if (!inputs_shrunk.empty()) {
|
||||
// inputs[i] was created during the current manual compaction and
|
||||
// need to be skipped
|
||||
skip_input_index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (inputs_shrunk.empty()) {
|
||||
return nullptr;
|
||||
}
|
||||
if (inputs.size() != inputs_shrunk.size()) {
|
||||
inputs.files.swap(inputs_shrunk);
|
||||
}
|
||||
// set covering_the_whole_range to false if there is any file that need to
|
||||
// be compacted in the range of inputs[skip_input_index+1, inputs.size())
|
||||
for (size_t i = skip_input_index + 1; i < inputs.size(); ++i) {
|
||||
if (inputs[i]->fd.GetNumber() < max_file_num_to_ignore) {
|
||||
covering_the_whole_range = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
InternalKey key_storage;
|
||||
InternalKey* next_smallest = &key_storage;
|
||||
@@ -724,11 +762,12 @@ Compaction* CompactionPicker::CompactRange(
|
||||
MaxFileSizeForLevel(mutable_cf_options, output_level,
|
||||
ioptions_.compaction_style, vstorage->base_level(),
|
||||
ioptions_.level_compaction_dynamic_level_bytes),
|
||||
mutable_cf_options.max_compaction_bytes, output_path_id,
|
||||
mutable_cf_options.max_compaction_bytes,
|
||||
compact_range_options.target_path_id,
|
||||
GetCompressionType(ioptions_, vstorage, mutable_cf_options, output_level,
|
||||
vstorage->base_level()),
|
||||
GetCompressionOptions(ioptions_, vstorage, output_level),
|
||||
max_subcompactions, std::move(grandparents),
|
||||
compact_range_options.max_subcompactions, std::move(grandparents),
|
||||
/* is manual compaction */ true);
|
||||
|
||||
TEST_SYNC_POINT_CALLBACK("CompactionPicker::CompactRange:Return", compaction);
|
||||
@@ -1073,6 +1112,9 @@ bool LevelCompactionPicker::NeedsCompaction(
|
||||
if (!vstorage->ExpiredTtlFiles().empty()) {
|
||||
return true;
|
||||
}
|
||||
if (!vstorage->FilesMarkedForPeriodicCompaction().empty()) {
|
||||
return true;
|
||||
}
|
||||
if (!vstorage->BottommostFilesMarkedForCompaction().empty()) {
|
||||
return true;
|
||||
}
|
||||
@@ -1141,6 +1183,8 @@ class LevelCompactionBuilder {
|
||||
|
||||
void PickExpiredTtlFiles();
|
||||
|
||||
void PickFilesMarkedForPeriodicCompaction();
|
||||
|
||||
const std::string& cf_name_;
|
||||
VersionStorageInfo* vstorage_;
|
||||
CompactionPicker* compaction_picker_;
|
||||
@@ -1203,6 +1247,39 @@ void LevelCompactionBuilder::PickExpiredTtlFiles() {
|
||||
start_level_inputs_.files.clear();
|
||||
}
|
||||
|
||||
void LevelCompactionBuilder::PickFilesMarkedForPeriodicCompaction() {
|
||||
if (vstorage_->FilesMarkedForPeriodicCompaction().empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto continuation = [&](std::pair<int, FileMetaData*> level_file) {
|
||||
// If it's being compacted it has nothing to do here.
|
||||
// If this assert() fails that means that some function marked some
|
||||
// files as being_compacted, but didn't call ComputeCompactionScore()
|
||||
assert(!level_file.second->being_compacted);
|
||||
output_level_ = start_level_ = level_file.first;
|
||||
|
||||
if (start_level_ == 0 &&
|
||||
!compaction_picker_->level0_compactions_in_progress()->empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
start_level_inputs_.files = {level_file.second};
|
||||
start_level_inputs_.level = start_level_;
|
||||
return compaction_picker_->ExpandInputsToCleanCut(cf_name_, vstorage_,
|
||||
&start_level_inputs_);
|
||||
};
|
||||
|
||||
for (auto& level_file : vstorage_->FilesMarkedForPeriodicCompaction()) {
|
||||
if (continuation(level_file)) {
|
||||
// found the compaction!
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
start_level_inputs_.files.clear();
|
||||
}
|
||||
|
||||
void LevelCompactionBuilder::SetupInitialFiles() {
|
||||
// Find the compactions by size on all levels.
|
||||
bool skipped_l0_to_base = false;
|
||||
@@ -1256,7 +1333,6 @@ void LevelCompactionBuilder::SetupInitialFiles() {
|
||||
if (start_level_inputs_.empty()) {
|
||||
parent_index_ = base_index_ = -1;
|
||||
|
||||
// PickFilesMarkedForCompaction();
|
||||
compaction_picker_->PickFilesMarkedForCompaction(
|
||||
cf_name_, vstorage_, &start_level_, &output_level_, &start_level_inputs_);
|
||||
if (!start_level_inputs_.empty()) {
|
||||
@@ -1264,7 +1340,10 @@ void LevelCompactionBuilder::SetupInitialFiles() {
|
||||
compaction_reason_ = CompactionReason::kFilesMarkedForCompaction;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Bottommost Files Compaction on deleting tombstones
|
||||
if (start_level_inputs_.empty()) {
|
||||
size_t i;
|
||||
for (i = 0; i < vstorage_->BottommostFilesMarkedForCompaction().size();
|
||||
++i) {
|
||||
@@ -1285,11 +1364,23 @@ void LevelCompactionBuilder::SetupInitialFiles() {
|
||||
compaction_reason_ = CompactionReason::kBottommostFiles;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
assert(start_level_inputs_.empty());
|
||||
// TTL Compaction
|
||||
if (start_level_inputs_.empty()) {
|
||||
PickExpiredTtlFiles();
|
||||
if (!start_level_inputs_.empty()) {
|
||||
compaction_reason_ = CompactionReason::kTtl;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Periodic Compaction
|
||||
if (start_level_inputs_.empty()) {
|
||||
PickFilesMarkedForPeriodicCompaction();
|
||||
if (!start_level_inputs_.empty()) {
|
||||
compaction_reason_ = CompactionReason::kPeriodicCompaction;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,9 +58,10 @@ class CompactionPicker {
|
||||
virtual Compaction* CompactRange(
|
||||
const std::string& cf_name, const MutableCFOptions& mutable_cf_options,
|
||||
VersionStorageInfo* vstorage, int input_level, int output_level,
|
||||
uint32_t output_path_id, uint32_t max_subcompactions,
|
||||
const CompactRangeOptions& compact_range_options,
|
||||
const InternalKey* begin, const InternalKey* end,
|
||||
InternalKey** compaction_end, bool* manual_conflict);
|
||||
InternalKey** compaction_end, bool* manual_conflict,
|
||||
uint64_t max_file_num_to_ignore);
|
||||
|
||||
// The maximum allowed output level. Default value is NumberLevels() - 1.
|
||||
virtual int MaxOutputLevel() const { return NumberLevels() - 1; }
|
||||
@@ -255,12 +256,12 @@ class NullCompactionPicker : public CompactionPicker {
|
||||
const MutableCFOptions& /*mutable_cf_options*/,
|
||||
VersionStorageInfo* /*vstorage*/,
|
||||
int /*input_level*/, int /*output_level*/,
|
||||
uint32_t /*output_path_id*/,
|
||||
uint32_t /*max_subcompactions*/,
|
||||
const CompactRangeOptions& /*compact_range_options*/,
|
||||
const InternalKey* /*begin*/,
|
||||
const InternalKey* /*end*/,
|
||||
InternalKey** /*compaction_end*/,
|
||||
bool* /*manual_conflict*/) override {
|
||||
bool* /*manual_conflict*/,
|
||||
uint64_t /*max_file_num_to_ignore*/) override {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ bool FIFOCompactionPicker::NeedsCompaction(
|
||||
Compaction* FIFOCompactionPicker::PickTTLCompaction(
|
||||
const std::string& cf_name, const MutableCFOptions& mutable_cf_options,
|
||||
VersionStorageInfo* vstorage, LogBuffer* log_buffer) {
|
||||
assert(mutable_cf_options.compaction_options_fifo.ttl > 0);
|
||||
assert(mutable_cf_options.ttl > 0);
|
||||
|
||||
const int kLevel0 = 0;
|
||||
const std::vector<FileMetaData*>& level_files = vstorage->LevelFiles(kLevel0);
|
||||
@@ -63,7 +63,7 @@ Compaction* FIFOCompactionPicker::PickTTLCompaction(
|
||||
inputs[0].level = 0;
|
||||
|
||||
// avoid underflow
|
||||
if (current_time > mutable_cf_options.compaction_options_fifo.ttl) {
|
||||
if (current_time > mutable_cf_options.ttl) {
|
||||
for (auto ritr = level_files.rbegin(); ritr != level_files.rend(); ++ritr) {
|
||||
auto f = *ritr;
|
||||
if (f->fd.table_reader != nullptr &&
|
||||
@@ -71,8 +71,7 @@ Compaction* FIFOCompactionPicker::PickTTLCompaction(
|
||||
auto creation_time =
|
||||
f->fd.table_reader->GetTableProperties()->creation_time;
|
||||
if (creation_time == 0 ||
|
||||
creation_time >= (current_time -
|
||||
mutable_cf_options.compaction_options_fifo.ttl)) {
|
||||
creation_time >= (current_time - mutable_cf_options.ttl)) {
|
||||
break;
|
||||
}
|
||||
total_size -= f->compensated_file_size;
|
||||
@@ -201,7 +200,7 @@ Compaction* FIFOCompactionPicker::PickCompaction(
|
||||
assert(vstorage->num_levels() == 1);
|
||||
|
||||
Compaction* c = nullptr;
|
||||
if (mutable_cf_options.compaction_options_fifo.ttl > 0) {
|
||||
if (mutable_cf_options.ttl > 0) {
|
||||
c = PickTTLCompaction(cf_name, mutable_cf_options, vstorage, log_buffer);
|
||||
}
|
||||
if (c == nullptr) {
|
||||
@@ -214,9 +213,10 @@ Compaction* FIFOCompactionPicker::PickCompaction(
|
||||
Compaction* FIFOCompactionPicker::CompactRange(
|
||||
const std::string& cf_name, const MutableCFOptions& mutable_cf_options,
|
||||
VersionStorageInfo* vstorage, int input_level, int output_level,
|
||||
uint32_t /*output_path_id*/, uint32_t /*max_subcompactions*/,
|
||||
const CompactRangeOptions& /*compact_range_options*/,
|
||||
const InternalKey* /*begin*/, const InternalKey* /*end*/,
|
||||
InternalKey** compaction_end, bool* /*manual_conflict*/) {
|
||||
InternalKey** compaction_end, bool* /*manual_conflict*/,
|
||||
uint64_t /*max_file_num_to_ignore*/) {
|
||||
#ifdef NDEBUG
|
||||
(void)input_level;
|
||||
(void)output_level;
|
||||
|
||||
@@ -27,9 +27,10 @@ class FIFOCompactionPicker : public CompactionPicker {
|
||||
virtual Compaction* CompactRange(
|
||||
const std::string& cf_name, const MutableCFOptions& mutable_cf_options,
|
||||
VersionStorageInfo* vstorage, int input_level, int output_level,
|
||||
uint32_t output_path_id, uint32_t max_subcompactions,
|
||||
const CompactRangeOptions& compact_range_options,
|
||||
const InternalKey* begin, const InternalKey* end,
|
||||
InternalKey** compaction_end, bool* manual_conflict) override;
|
||||
InternalKey** compaction_end, bool* manual_conflict,
|
||||
uint64_t max_file_num_to_ignore) override;
|
||||
|
||||
// The maximum allowed output level. Always returns 0.
|
||||
virtual int MaxOutputLevel() const override { return 0; }
|
||||
|
||||
@@ -22,9 +22,7 @@ namespace rocksdb {
|
||||
class CountingLogger : public Logger {
|
||||
public:
|
||||
using Logger::Logv;
|
||||
virtual void Logv(const char* /*format*/, va_list /*ap*/) override {
|
||||
log_count++;
|
||||
}
|
||||
void Logv(const char* /*format*/, va_list /*ap*/) override { log_count++; }
|
||||
size_t log_count;
|
||||
};
|
||||
|
||||
@@ -68,8 +66,7 @@ class CompactionPickerTest : public testing::Test {
|
||||
std::numeric_limits<uint64_t>::max());
|
||||
}
|
||||
|
||||
~CompactionPickerTest() {
|
||||
}
|
||||
~CompactionPickerTest() override {}
|
||||
|
||||
void NewVersionStorage(int num_levels, CompactionStyle style) {
|
||||
DeleteVersionStorage();
|
||||
|
||||
@@ -395,8 +395,8 @@ Compaction* UniversalCompactionPicker::PickCompaction(
|
||||
}
|
||||
#endif
|
||||
// update statistics
|
||||
MeasureTime(ioptions_.statistics, NUM_FILES_IN_SINGLE_COMPACTION,
|
||||
c->inputs(0)->size());
|
||||
RecordInHistogram(ioptions_.statistics, NUM_FILES_IN_SINGLE_COMPACTION,
|
||||
c->inputs(0)->size());
|
||||
|
||||
RegisterCompaction(c);
|
||||
vstorage->ComputeCompactionScore(ioptions_, mutable_cf_options);
|
||||
|
||||
+26
-26
@@ -27,24 +27,24 @@ class KVIter : public Iterator {
|
||||
public:
|
||||
explicit KVIter(const stl_wrappers::KVMap* map)
|
||||
: map_(map), iter_(map_->end()) {}
|
||||
virtual bool Valid() const override { return iter_ != map_->end(); }
|
||||
virtual void SeekToFirst() override { iter_ = map_->begin(); }
|
||||
virtual void SeekToLast() override {
|
||||
bool Valid() const override { return iter_ != map_->end(); }
|
||||
void SeekToFirst() override { iter_ = map_->begin(); }
|
||||
void SeekToLast() override {
|
||||
if (map_->empty()) {
|
||||
iter_ = map_->end();
|
||||
} else {
|
||||
iter_ = map_->find(map_->rbegin()->first);
|
||||
}
|
||||
}
|
||||
virtual void Seek(const Slice& k) override {
|
||||
void Seek(const Slice& k) override {
|
||||
iter_ = map_->lower_bound(k.ToString());
|
||||
}
|
||||
virtual void SeekForPrev(const Slice& k) override {
|
||||
void SeekForPrev(const Slice& k) override {
|
||||
iter_ = map_->upper_bound(k.ToString());
|
||||
Prev();
|
||||
}
|
||||
virtual void Next() override { ++iter_; }
|
||||
virtual void Prev() override {
|
||||
void Next() override { ++iter_; }
|
||||
void Prev() override {
|
||||
if (iter_ == map_->begin()) {
|
||||
iter_ = map_->end();
|
||||
return;
|
||||
@@ -52,9 +52,9 @@ class KVIter : public Iterator {
|
||||
--iter_;
|
||||
}
|
||||
|
||||
virtual Slice key() const override { return iter_->first; }
|
||||
virtual Slice value() const override { return iter_->second; }
|
||||
virtual Status status() const override { return Status::OK(); }
|
||||
Slice key() const override { return iter_->first; }
|
||||
Slice value() const override { return iter_->second; }
|
||||
Status status() const override { return Status::OK(); }
|
||||
|
||||
private:
|
||||
const stl_wrappers::KVMap* const map_;
|
||||
@@ -171,9 +171,9 @@ class DoubleComparator : public Comparator {
|
||||
public:
|
||||
DoubleComparator() {}
|
||||
|
||||
virtual const char* Name() const override { return "DoubleComparator"; }
|
||||
const char* Name() const override { return "DoubleComparator"; }
|
||||
|
||||
virtual int Compare(const Slice& a, const Slice& b) const override {
|
||||
int Compare(const Slice& a, const Slice& b) const override {
|
||||
#ifndef CYGWIN
|
||||
double da = std::stod(a.ToString());
|
||||
double db = std::stod(b.ToString());
|
||||
@@ -189,19 +189,19 @@ class DoubleComparator : public Comparator {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
virtual void FindShortestSeparator(std::string* /*start*/,
|
||||
const Slice& /*limit*/) const override {}
|
||||
void FindShortestSeparator(std::string* /*start*/,
|
||||
const Slice& /*limit*/) const override {}
|
||||
|
||||
virtual void FindShortSuccessor(std::string* /*key*/) const override {}
|
||||
void FindShortSuccessor(std::string* /*key*/) const override {}
|
||||
};
|
||||
|
||||
class HashComparator : public Comparator {
|
||||
public:
|
||||
HashComparator() {}
|
||||
|
||||
virtual const char* Name() const override { return "HashComparator"; }
|
||||
const char* Name() const override { return "HashComparator"; }
|
||||
|
||||
virtual int Compare(const Slice& a, const Slice& b) const override {
|
||||
int Compare(const Slice& a, const Slice& b) const override {
|
||||
uint32_t ha = Hash(a.data(), a.size(), 66);
|
||||
uint32_t hb = Hash(b.data(), b.size(), 66);
|
||||
if (ha == hb) {
|
||||
@@ -212,19 +212,19 @@ class HashComparator : public Comparator {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
virtual void FindShortestSeparator(std::string* /*start*/,
|
||||
const Slice& /*limit*/) const override {}
|
||||
void FindShortestSeparator(std::string* /*start*/,
|
||||
const Slice& /*limit*/) const override {}
|
||||
|
||||
virtual void FindShortSuccessor(std::string* /*key*/) const override {}
|
||||
void FindShortSuccessor(std::string* /*key*/) const override {}
|
||||
};
|
||||
|
||||
class TwoStrComparator : public Comparator {
|
||||
public:
|
||||
TwoStrComparator() {}
|
||||
|
||||
virtual const char* Name() const override { return "TwoStrComparator"; }
|
||||
const char* Name() const override { return "TwoStrComparator"; }
|
||||
|
||||
virtual int Compare(const Slice& a, const Slice& b) const override {
|
||||
int Compare(const Slice& a, const Slice& b) const override {
|
||||
assert(a.size() >= 2);
|
||||
assert(b.size() >= 2);
|
||||
size_t size_a1 = static_cast<size_t>(a[0]);
|
||||
@@ -244,10 +244,10 @@ class TwoStrComparator : public Comparator {
|
||||
}
|
||||
return a2.compare(b2);
|
||||
}
|
||||
virtual void FindShortestSeparator(std::string* /*start*/,
|
||||
const Slice& /*limit*/) const override {}
|
||||
void FindShortestSeparator(std::string* /*start*/,
|
||||
const Slice& /*limit*/) const override {}
|
||||
|
||||
virtual void FindShortSuccessor(std::string* /*key*/) const override {}
|
||||
void FindShortSuccessor(std::string* /*key*/) const override {}
|
||||
};
|
||||
} // namespace
|
||||
|
||||
@@ -272,7 +272,7 @@ class ComparatorDBTest
|
||||
EXPECT_OK(DestroyDB(dbname_, last_options_));
|
||||
}
|
||||
|
||||
~ComparatorDBTest() {
|
||||
~ComparatorDBTest() override {
|
||||
delete db_;
|
||||
EXPECT_OK(DestroyDB(dbname_, last_options_));
|
||||
comparator = BytewiseComparator();
|
||||
|
||||
@@ -62,9 +62,9 @@ class CorruptionTest : public testing::Test {
|
||||
options_.create_if_missing = false;
|
||||
}
|
||||
|
||||
~CorruptionTest() {
|
||||
delete db_;
|
||||
DestroyDB(dbname_, Options());
|
||||
~CorruptionTest() override {
|
||||
delete db_;
|
||||
DestroyDB(dbname_, Options());
|
||||
}
|
||||
|
||||
void CloseDb() {
|
||||
|
||||
@@ -31,7 +31,7 @@ class CuckooTableDBTest : public testing::Test {
|
||||
Reopen();
|
||||
}
|
||||
|
||||
~CuckooTableDBTest() {
|
||||
~CuckooTableDBTest() override {
|
||||
delete db_;
|
||||
EXPECT_OK(DestroyDB(dbname_, Options()));
|
||||
}
|
||||
|
||||
+177
-28
@@ -215,11 +215,11 @@ TEST_F(DBBasicTest, PutSingleDeleteGet) {
|
||||
ASSERT_EQ("v2", Get(1, "foo2"));
|
||||
ASSERT_OK(SingleDelete(1, "foo"));
|
||||
ASSERT_EQ("NOT_FOUND", Get(1, "foo"));
|
||||
// Skip HashCuckooRep as it does not support single delete. FIFO and
|
||||
// universal compaction do not apply to the test case. Skip MergePut
|
||||
// because single delete does not get removed when it encounters a merge.
|
||||
} while (ChangeOptions(kSkipHashCuckoo | kSkipFIFOCompaction |
|
||||
kSkipUniversalCompaction | kSkipMergePut));
|
||||
// Ski FIFO and universal compaction because they do not apply to the test
|
||||
// case. Skip MergePut because single delete does not get removed when it
|
||||
// encounters a merge.
|
||||
} while (ChangeOptions(kSkipFIFOCompaction | kSkipUniversalCompaction |
|
||||
kSkipMergePut));
|
||||
}
|
||||
|
||||
TEST_F(DBBasicTest, EmptyFlush) {
|
||||
@@ -237,11 +237,11 @@ TEST_F(DBBasicTest, EmptyFlush) {
|
||||
ASSERT_OK(Flush(1));
|
||||
|
||||
ASSERT_EQ("[ ]", AllEntriesFor("a", 1));
|
||||
// Skip HashCuckooRep as it does not support single delete. FIFO and
|
||||
// universal compaction do not apply to the test case. Skip MergePut
|
||||
// because merges cannot be combined with single deletions.
|
||||
} while (ChangeOptions(kSkipHashCuckoo | kSkipFIFOCompaction |
|
||||
kSkipUniversalCompaction | kSkipMergePut));
|
||||
// Skip FIFO and universal compaction as they do not apply to the test
|
||||
// case. Skip MergePut because merges cannot be combined with single
|
||||
// deletions.
|
||||
} while (ChangeOptions(kSkipFIFOCompaction | kSkipUniversalCompaction |
|
||||
kSkipMergePut));
|
||||
}
|
||||
|
||||
TEST_F(DBBasicTest, GetFromVersions) {
|
||||
@@ -265,11 +265,6 @@ TEST_F(DBBasicTest, GetSnapshot) {
|
||||
std::string key = (i == 0) ? std::string("foo") : std::string(200, 'x');
|
||||
ASSERT_OK(Put(1, key, "v1"));
|
||||
const Snapshot* s1 = db_->GetSnapshot();
|
||||
if (option_config_ == kHashCuckoo) {
|
||||
// Unsupported case.
|
||||
ASSERT_TRUE(s1 == nullptr);
|
||||
break;
|
||||
}
|
||||
ASSERT_OK(Put(1, key, "v2"));
|
||||
ASSERT_EQ("v2", Get(1, key));
|
||||
ASSERT_EQ("v1", Get(1, key, s1));
|
||||
@@ -510,7 +505,7 @@ TEST_F(DBBasicTest, Snapshot) {
|
||||
ASSERT_EQ(0U, GetNumSnapshots());
|
||||
ASSERT_EQ("0v4", Get(0, "foo"));
|
||||
ASSERT_EQ("1v4", Get(1, "foo"));
|
||||
} while (ChangeOptions(kSkipHashCuckoo));
|
||||
} while (ChangeOptions());
|
||||
}
|
||||
|
||||
#endif // ROCKSDB_LITE
|
||||
@@ -566,8 +561,7 @@ TEST_F(DBBasicTest, CompactBetweenSnapshots) {
|
||||
nullptr);
|
||||
ASSERT_EQ("sixth", Get(1, "foo"));
|
||||
ASSERT_EQ(AllEntriesFor("foo", 1), "[ sixth ]");
|
||||
// skip HashCuckooRep as it does not support snapshot
|
||||
} while (ChangeOptions(kSkipHashCuckoo | kSkipFIFOCompaction));
|
||||
} while (ChangeOptions(kSkipFIFOCompaction));
|
||||
}
|
||||
|
||||
TEST_F(DBBasicTest, DBOpen_Options) {
|
||||
@@ -858,18 +852,17 @@ class TestEnv : public EnvWrapper {
|
||||
public:
|
||||
using Logger::Logv;
|
||||
TestLogger(TestEnv *env_ptr) : Logger() { env = env_ptr; }
|
||||
~TestLogger() {
|
||||
~TestLogger() override {
|
||||
if (!closed_) {
|
||||
CloseHelper();
|
||||
}
|
||||
}
|
||||
virtual void Logv(const char* /*format*/, va_list /*ap*/) override{};
|
||||
void Logv(const char* /*format*/, va_list /*ap*/) override{};
|
||||
|
||||
protected:
|
||||
virtual Status CloseImpl() override {
|
||||
return CloseHelper();
|
||||
}
|
||||
private:
|
||||
Status CloseImpl() override { return CloseHelper(); }
|
||||
|
||||
private:
|
||||
Status CloseHelper() {
|
||||
env->CloseCountInc();;
|
||||
return Status::IOError();
|
||||
@@ -881,8 +874,8 @@ class TestEnv : public EnvWrapper {
|
||||
|
||||
int GetCloseCount() { return close_count; }
|
||||
|
||||
virtual Status NewLogger(const std::string& /*fname*/,
|
||||
std::shared_ptr<Logger>* result) {
|
||||
Status NewLogger(const std::string& /*fname*/,
|
||||
std::shared_ptr<Logger>* result) override {
|
||||
result->reset(new TestLogger(this));
|
||||
return Status::OK();
|
||||
}
|
||||
@@ -1001,7 +994,7 @@ TEST_F(DBBasicTest, MultiGetMultiCF) {
|
||||
keys.push_back("cf" + std::to_string(i) + "_key");
|
||||
}
|
||||
|
||||
values = MultiGet(cfs, keys);
|
||||
values = MultiGet(cfs, keys, nullptr);
|
||||
ASSERT_EQ(values.size(), 8);
|
||||
for (unsigned int j = 0; j < values.size(); ++j) {
|
||||
ASSERT_EQ(values[j], "cf" + std::to_string(j) + "_val2");
|
||||
@@ -1061,7 +1054,7 @@ TEST_F(DBBasicTest, MultiGetMultiCFMutex) {
|
||||
keys.push_back("cf" + std::to_string(i) + "_key");
|
||||
}
|
||||
|
||||
values = MultiGet(cfs, keys);
|
||||
values = MultiGet(cfs, keys, nullptr);
|
||||
ASSERT_TRUE(last_try);
|
||||
ASSERT_EQ(values.size(), 8);
|
||||
for (unsigned int j = 0; j < values.size(); ++j) {
|
||||
@@ -1135,6 +1128,162 @@ TEST_F(DBBasicTest, MultiGetMultiCFSnapshot) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBBasicTest, MultiGetBatchedSimpleUnsorted) {
|
||||
do {
|
||||
CreateAndReopenWithCF({"pikachu"}, CurrentOptions());
|
||||
SetPerfLevel(kEnableCount);
|
||||
ASSERT_OK(Put(1, "k1", "v1"));
|
||||
ASSERT_OK(Put(1, "k2", "v2"));
|
||||
ASSERT_OK(Put(1, "k3", "v3"));
|
||||
ASSERT_OK(Put(1, "k4", "v4"));
|
||||
ASSERT_OK(Delete(1, "k4"));
|
||||
ASSERT_OK(Put(1, "k5", "v5"));
|
||||
ASSERT_OK(Delete(1, "no_key"));
|
||||
|
||||
get_perf_context()->Reset();
|
||||
|
||||
std::vector<Slice> keys({"no_key", "k5", "k4", "k3", "k2", "k1"});
|
||||
std::vector<PinnableSlice> values(keys.size());
|
||||
std::vector<ColumnFamilyHandle*> cfs(keys.size(), handles_[1]);
|
||||
std::vector<Status> s(keys.size());
|
||||
|
||||
db_->MultiGet(ReadOptions(), handles_[1], keys.size(), keys.data(),
|
||||
values.data(), s.data(), false);
|
||||
|
||||
ASSERT_EQ(values.size(), keys.size());
|
||||
ASSERT_EQ(std::string(values[5].data(), values[5].size()), "v1");
|
||||
ASSERT_EQ(std::string(values[4].data(), values[4].size()), "v2");
|
||||
ASSERT_EQ(std::string(values[3].data(), values[3].size()), "v3");
|
||||
ASSERT_EQ(std::string(values[1].data(), values[1].size()), "v5");
|
||||
// four kv pairs * two bytes per value
|
||||
ASSERT_EQ(8, (int)get_perf_context()->multiget_read_bytes);
|
||||
|
||||
ASSERT_TRUE(s[0].IsNotFound());
|
||||
ASSERT_OK(s[1]);
|
||||
ASSERT_TRUE(s[2].IsNotFound());
|
||||
ASSERT_OK(s[3]);
|
||||
ASSERT_OK(s[4]);
|
||||
ASSERT_OK(s[5]);
|
||||
|
||||
SetPerfLevel(kDisable);
|
||||
} while (ChangeCompactOptions());
|
||||
}
|
||||
|
||||
TEST_F(DBBasicTest, MultiGetBatchedSimpleSorted) {
|
||||
do {
|
||||
CreateAndReopenWithCF({"pikachu"}, CurrentOptions());
|
||||
SetPerfLevel(kEnableCount);
|
||||
ASSERT_OK(Put(1, "k1", "v1"));
|
||||
ASSERT_OK(Put(1, "k2", "v2"));
|
||||
ASSERT_OK(Put(1, "k3", "v3"));
|
||||
ASSERT_OK(Put(1, "k4", "v4"));
|
||||
ASSERT_OK(Delete(1, "k4"));
|
||||
ASSERT_OK(Put(1, "k5", "v5"));
|
||||
ASSERT_OK(Delete(1, "no_key"));
|
||||
|
||||
get_perf_context()->Reset();
|
||||
|
||||
std::vector<Slice> keys({"k1", "k2", "k3", "k4", "k5", "no_key"});
|
||||
std::vector<PinnableSlice> values(keys.size());
|
||||
std::vector<ColumnFamilyHandle*> cfs(keys.size(), handles_[1]);
|
||||
std::vector<Status> s(keys.size());
|
||||
|
||||
db_->MultiGet(ReadOptions(), handles_[1], keys.size(), keys.data(),
|
||||
values.data(), s.data(), true);
|
||||
|
||||
ASSERT_EQ(values.size(), keys.size());
|
||||
ASSERT_EQ(std::string(values[0].data(), values[0].size()), "v1");
|
||||
ASSERT_EQ(std::string(values[1].data(), values[1].size()), "v2");
|
||||
ASSERT_EQ(std::string(values[2].data(), values[2].size()), "v3");
|
||||
ASSERT_EQ(std::string(values[4].data(), values[4].size()), "v5");
|
||||
// four kv pairs * two bytes per value
|
||||
ASSERT_EQ(8, (int)get_perf_context()->multiget_read_bytes);
|
||||
|
||||
ASSERT_OK(s[0]);
|
||||
ASSERT_OK(s[1]);
|
||||
ASSERT_OK(s[2]);
|
||||
ASSERT_TRUE(s[3].IsNotFound());
|
||||
ASSERT_OK(s[4]);
|
||||
ASSERT_TRUE(s[5].IsNotFound());
|
||||
|
||||
SetPerfLevel(kDisable);
|
||||
} while (ChangeCompactOptions());
|
||||
}
|
||||
|
||||
TEST_F(DBBasicTest, MultiGetBatchedMultiLevel) {
|
||||
Options options = CurrentOptions();
|
||||
options.disable_auto_compactions = true;
|
||||
Reopen(options);
|
||||
int num_keys = 0;
|
||||
|
||||
for (int i = 0; i < 128; ++i) {
|
||||
ASSERT_OK(Put("key_" + std::to_string(i), "val_l2_" + std::to_string(i)));
|
||||
num_keys++;
|
||||
if (num_keys == 8) {
|
||||
Flush();
|
||||
num_keys = 0;
|
||||
}
|
||||
}
|
||||
if (num_keys > 0) {
|
||||
Flush();
|
||||
num_keys = 0;
|
||||
}
|
||||
MoveFilesToLevel(2);
|
||||
|
||||
for (int i = 0; i < 128; i += 3) {
|
||||
ASSERT_OK(Put("key_" + std::to_string(i), "val_l1_" + std::to_string(i)));
|
||||
num_keys++;
|
||||
if (num_keys == 8) {
|
||||
Flush();
|
||||
num_keys = 0;
|
||||
}
|
||||
}
|
||||
if (num_keys > 0) {
|
||||
Flush();
|
||||
num_keys = 0;
|
||||
}
|
||||
MoveFilesToLevel(1);
|
||||
|
||||
for (int i = 0; i < 128; i += 5) {
|
||||
ASSERT_OK(Put("key_" + std::to_string(i), "val_l0_" + std::to_string(i)));
|
||||
num_keys++;
|
||||
if (num_keys == 8) {
|
||||
Flush();
|
||||
num_keys = 0;
|
||||
}
|
||||
}
|
||||
if (num_keys > 0) {
|
||||
Flush();
|
||||
num_keys = 0;
|
||||
}
|
||||
ASSERT_EQ(0, num_keys);
|
||||
|
||||
for (int i = 0; i < 128; i += 9) {
|
||||
ASSERT_OK(Put("key_" + std::to_string(i), "val_mem_" + std::to_string(i)));
|
||||
}
|
||||
|
||||
std::vector<std::string> keys;
|
||||
std::vector<std::string> values;
|
||||
|
||||
for (int i = 64; i < 80; ++i) {
|
||||
keys.push_back("key_" + std::to_string(i));
|
||||
}
|
||||
|
||||
values = MultiGet(keys, nullptr);
|
||||
ASSERT_EQ(values.size(), 16);
|
||||
for (unsigned int j = 0; j < values.size(); ++j) {
|
||||
int key = j + 64;
|
||||
if (key % 9 == 0) {
|
||||
ASSERT_EQ(values[j], "val_mem_" + std::to_string(key));
|
||||
} else if (key % 5 == 0) {
|
||||
ASSERT_EQ(values[j], "val_l0_" + std::to_string(key));
|
||||
} else if (key % 3 == 0) {
|
||||
ASSERT_EQ(values[j], "val_l1_" + std::to_string(key));
|
||||
} else {
|
||||
ASSERT_EQ(values[j], "val_l2_" + std::to_string(key));
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace rocksdb
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
@@ -346,14 +346,14 @@ TEST_F(DBBlockCacheTest, IndexAndFilterBlocksStats) {
|
||||
options.statistics = rocksdb::CreateDBStatistics();
|
||||
BlockBasedTableOptions table_options;
|
||||
table_options.cache_index_and_filter_blocks = true;
|
||||
// 200 bytes are enough to hold the first two blocks
|
||||
std::shared_ptr<Cache> cache = NewLRUCache(200, 0, false);
|
||||
// 500 bytes are enough to hold the first two blocks
|
||||
std::shared_ptr<Cache> cache = NewLRUCache(500, 0, false);
|
||||
table_options.block_cache = cache;
|
||||
table_options.filter_policy.reset(NewBloomFilterPolicy(20, true));
|
||||
options.table_factory.reset(new BlockBasedTableFactory(table_options));
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
|
||||
ASSERT_OK(Put(1, "key", "val"));
|
||||
ASSERT_OK(Put(1, "longer_key", "val"));
|
||||
// Create a new table
|
||||
ASSERT_OK(Flush(1));
|
||||
size_t index_bytes_insert =
|
||||
@@ -367,7 +367,9 @@ TEST_F(DBBlockCacheTest, IndexAndFilterBlocksStats) {
|
||||
cache->SetCapacity(index_bytes_insert + filter_bytes_insert);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOCK_CACHE_INDEX_BYTES_EVICT), 0);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOCK_CACHE_FILTER_BYTES_EVICT), 0);
|
||||
ASSERT_OK(Put(1, "key2", "val"));
|
||||
// Note that the second key needs to be no longer than the first one.
|
||||
// Otherwise the second index block may not fit in cache.
|
||||
ASSERT_OK(Put(1, "key", "val"));
|
||||
// Create a new table
|
||||
ASSERT_OK(Flush(1));
|
||||
// cache evicted old index and block entries
|
||||
@@ -395,9 +397,9 @@ class MockCache : public LRUCache {
|
||||
false /*strict_capacity_limit*/, 0.0 /*high_pri_pool_ratio*/) {
|
||||
}
|
||||
|
||||
virtual Status Insert(const Slice& key, void* value, size_t charge,
|
||||
void (*deleter)(const Slice& key, void* value),
|
||||
Handle** handle, Priority priority) override {
|
||||
Status Insert(const Slice& key, void* value, size_t charge,
|
||||
void (*deleter)(const Slice& key, void* value), Handle** handle,
|
||||
Priority priority) override {
|
||||
if (priority == Priority::LOW) {
|
||||
low_pri_insert_count++;
|
||||
} else {
|
||||
@@ -633,7 +635,7 @@ TEST_F(DBBlockCacheTest, CompressedCache) {
|
||||
|
||||
TEST_F(DBBlockCacheTest, CacheCompressionDict) {
|
||||
const int kNumFiles = 4;
|
||||
const int kNumEntriesPerFile = 32;
|
||||
const int kNumEntriesPerFile = 128;
|
||||
const int kNumBytesPerEntry = 1024;
|
||||
|
||||
// Try all the available libraries that support dictionary compression
|
||||
|
||||
@@ -32,7 +32,7 @@ class DBBloomFilterTestWithParam
|
||||
public:
|
||||
DBBloomFilterTestWithParam() : DBTestBase("/db_bloom_filter_tests") {}
|
||||
|
||||
~DBBloomFilterTestWithParam() {}
|
||||
~DBBloomFilterTestWithParam() override {}
|
||||
|
||||
void SetUp() override {
|
||||
use_block_based_filter_ = std::get<0>(GetParam());
|
||||
@@ -642,7 +642,7 @@ class WrappedBloom : public FilterPolicy {
|
||||
explicit WrappedBloom(int bits_per_key)
|
||||
: filter_(NewBloomFilterPolicy(bits_per_key)), counter_(0) {}
|
||||
|
||||
~WrappedBloom() { delete filter_; }
|
||||
~WrappedBloom() override { delete filter_; }
|
||||
|
||||
const char* Name() const override { return "WrappedRocksDbFilterPolicy"; }
|
||||
|
||||
@@ -786,6 +786,75 @@ TEST_F(DBBloomFilterTest, PrefixExtractorBlockFilter) {
|
||||
delete iter;
|
||||
}
|
||||
|
||||
TEST_F(DBBloomFilterTest, MemtableWholeKeyBloomFilter) {
|
||||
// regression test for #2743. the range delete tombstones in memtable should
|
||||
// be added even when Get() skips searching due to its prefix bloom filter
|
||||
const int kMemtableSize = 1 << 20; // 1MB
|
||||
const int kMemtablePrefixFilterSize = 1 << 13; // 8KB
|
||||
const int kPrefixLen = 4;
|
||||
Options options = CurrentOptions();
|
||||
options.memtable_prefix_bloom_size_ratio =
|
||||
static_cast<double>(kMemtablePrefixFilterSize) / kMemtableSize;
|
||||
options.prefix_extractor.reset(rocksdb::NewFixedPrefixTransform(kPrefixLen));
|
||||
options.write_buffer_size = kMemtableSize;
|
||||
options.memtable_whole_key_filtering = false;
|
||||
Reopen(options);
|
||||
std::string key1("AAAABBBB");
|
||||
std::string key2("AAAACCCC"); // not in DB
|
||||
std::string key3("AAAADDDD");
|
||||
std::string key4("AAAAEEEE");
|
||||
std::string value1("Value1");
|
||||
std::string value3("Value3");
|
||||
std::string value4("Value4");
|
||||
|
||||
ASSERT_OK(Put(key1, value1, WriteOptions()));
|
||||
|
||||
// check memtable bloom stats
|
||||
ASSERT_EQ("NOT_FOUND", Get(key2));
|
||||
ASSERT_EQ(0, get_perf_context()->bloom_memtable_miss_count);
|
||||
// same prefix, bloom filter false positive
|
||||
ASSERT_EQ(1, get_perf_context()->bloom_memtable_hit_count);
|
||||
|
||||
// enable whole key bloom filter
|
||||
options.memtable_whole_key_filtering = true;
|
||||
Reopen(options);
|
||||
// check memtable bloom stats
|
||||
ASSERT_OK(Put(key3, value3, WriteOptions()));
|
||||
ASSERT_EQ("NOT_FOUND", Get(key2));
|
||||
// whole key bloom filter kicks in and determines it's a miss
|
||||
ASSERT_EQ(1, get_perf_context()->bloom_memtable_miss_count);
|
||||
ASSERT_EQ(1, get_perf_context()->bloom_memtable_hit_count);
|
||||
|
||||
// verify whole key filtering does not depend on prefix_extractor
|
||||
options.prefix_extractor.reset();
|
||||
Reopen(options);
|
||||
// check memtable bloom stats
|
||||
ASSERT_OK(Put(key4, value4, WriteOptions()));
|
||||
ASSERT_EQ("NOT_FOUND", Get(key2));
|
||||
// whole key bloom filter kicks in and determines it's a miss
|
||||
ASSERT_EQ(2, get_perf_context()->bloom_memtable_miss_count);
|
||||
ASSERT_EQ(1, get_perf_context()->bloom_memtable_hit_count);
|
||||
}
|
||||
|
||||
TEST_F(DBBloomFilterTest, MemtablePrefixBloomOutOfDomain) {
|
||||
constexpr size_t kPrefixSize = 8;
|
||||
const std::string kKey = "key";
|
||||
assert(kKey.size() < kPrefixSize);
|
||||
Options options = CurrentOptions();
|
||||
options.prefix_extractor.reset(NewFixedPrefixTransform(kPrefixSize));
|
||||
options.memtable_prefix_bloom_size_ratio = 0.25;
|
||||
Reopen(options);
|
||||
ASSERT_OK(Put(kKey, "v"));
|
||||
ASSERT_EQ("v", Get(kKey));
|
||||
std::unique_ptr<Iterator> iter(dbfull()->NewIterator(ReadOptions()));
|
||||
iter->Seek(kKey);
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ(kKey, iter->key());
|
||||
iter->SeekForPrev(kKey);
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ(kKey, iter->key());
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
class BloomStatsTestWithParam
|
||||
: public DBBloomFilterTest,
|
||||
@@ -823,7 +892,7 @@ class BloomStatsTestWithParam
|
||||
DestroyAndReopen(options_);
|
||||
}
|
||||
|
||||
~BloomStatsTestWithParam() {
|
||||
~BloomStatsTestWithParam() override {
|
||||
get_perf_context()->Reset();
|
||||
Destroy(options_);
|
||||
}
|
||||
@@ -935,13 +1004,16 @@ TEST_P(BloomStatsTestWithParam, BloomStatsTestWithIter) {
|
||||
ASSERT_OK(iter->status());
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ(value3, iter->value().ToString());
|
||||
ASSERT_EQ(2, get_perf_context()->bloom_sst_hit_count);
|
||||
// The seek doesn't check block-based bloom filter because last index key
|
||||
// starts with the same prefix we're seeking to.
|
||||
uint64_t expected_hits = use_block_based_builder_ ? 1 : 2;
|
||||
ASSERT_EQ(expected_hits, get_perf_context()->bloom_sst_hit_count);
|
||||
|
||||
iter->Seek(key2);
|
||||
ASSERT_OK(iter->status());
|
||||
ASSERT_TRUE(!iter->Valid());
|
||||
ASSERT_EQ(1, get_perf_context()->bloom_sst_miss_count);
|
||||
ASSERT_EQ(2, get_perf_context()->bloom_sst_hit_count);
|
||||
ASSERT_EQ(expected_hits, get_perf_context()->bloom_sst_hit_count);
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(BloomStatsTestWithParam, BloomStatsTestWithParam,
|
||||
@@ -1266,6 +1338,8 @@ TEST_F(DBBloomFilterTest, DynamicBloomFilterUpperBound) {
|
||||
table_options.cache_index_and_filter_blocks = true;
|
||||
table_options.filter_policy.reset(
|
||||
NewBloomFilterPolicy(10, use_block_based_builder));
|
||||
table_options.index_shortening = BlockBasedTableOptions::
|
||||
IndexShorteningMode::kShortenSeparatorsAndSuccessor;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
DestroyAndReopen(options);
|
||||
|
||||
|
||||
@@ -63,33 +63,33 @@ INSTANTIATE_TEST_CASE_P(DBTestCompactionFilterWithCompactOption,
|
||||
|
||||
class KeepFilter : public CompactionFilter {
|
||||
public:
|
||||
virtual bool Filter(int /*level*/, const Slice& /*key*/,
|
||||
const Slice& /*value*/, std::string* /*new_value*/,
|
||||
bool* /*value_changed*/) const override {
|
||||
bool Filter(int /*level*/, const Slice& /*key*/, const Slice& /*value*/,
|
||||
std::string* /*new_value*/,
|
||||
bool* /*value_changed*/) const override {
|
||||
cfilter_count++;
|
||||
return false;
|
||||
}
|
||||
|
||||
virtual const char* Name() const override { return "KeepFilter"; }
|
||||
const char* Name() const override { return "KeepFilter"; }
|
||||
};
|
||||
|
||||
class DeleteFilter : public CompactionFilter {
|
||||
public:
|
||||
virtual bool Filter(int /*level*/, const Slice& /*key*/,
|
||||
const Slice& /*value*/, std::string* /*new_value*/,
|
||||
bool* /*value_changed*/) const override {
|
||||
bool Filter(int /*level*/, const Slice& /*key*/, const Slice& /*value*/,
|
||||
std::string* /*new_value*/,
|
||||
bool* /*value_changed*/) const override {
|
||||
cfilter_count++;
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual const char* Name() const override { return "DeleteFilter"; }
|
||||
const char* Name() const override { return "DeleteFilter"; }
|
||||
};
|
||||
|
||||
class DeleteISFilter : public CompactionFilter {
|
||||
public:
|
||||
virtual bool Filter(int /*level*/, const Slice& key, const Slice& /*value*/,
|
||||
std::string* /*new_value*/,
|
||||
bool* /*value_changed*/) const override {
|
||||
bool Filter(int /*level*/, const Slice& key, const Slice& /*value*/,
|
||||
std::string* /*new_value*/,
|
||||
bool* /*value_changed*/) const override {
|
||||
cfilter_count++;
|
||||
int i = std::stoi(key.ToString());
|
||||
if (i > 5 && i <= 105) {
|
||||
@@ -98,20 +98,18 @@ class DeleteISFilter : public CompactionFilter {
|
||||
return false;
|
||||
}
|
||||
|
||||
virtual bool IgnoreSnapshots() const override { return true; }
|
||||
bool IgnoreSnapshots() const override { return true; }
|
||||
|
||||
virtual const char* Name() const override { return "DeleteFilter"; }
|
||||
const char* Name() const override { return "DeleteFilter"; }
|
||||
};
|
||||
|
||||
// Skip x if floor(x/10) is even, use range skips. Requires that keys are
|
||||
// zero-padded to length 10.
|
||||
class SkipEvenFilter : public CompactionFilter {
|
||||
public:
|
||||
virtual Decision FilterV2(int /*level*/, const Slice& key,
|
||||
ValueType /*value_type*/,
|
||||
const Slice& /*existing_value*/,
|
||||
std::string* /*new_value*/,
|
||||
std::string* skip_until) const override {
|
||||
Decision FilterV2(int /*level*/, const Slice& key, ValueType /*value_type*/,
|
||||
const Slice& /*existing_value*/, std::string* /*new_value*/,
|
||||
std::string* skip_until) const override {
|
||||
cfilter_count++;
|
||||
int i = std::stoi(key.ToString());
|
||||
if (i / 10 % 2 == 0) {
|
||||
@@ -124,22 +122,22 @@ class SkipEvenFilter : public CompactionFilter {
|
||||
return Decision::kKeep;
|
||||
}
|
||||
|
||||
virtual bool IgnoreSnapshots() const override { return true; }
|
||||
bool IgnoreSnapshots() const override { return true; }
|
||||
|
||||
virtual const char* Name() const override { return "DeleteFilter"; }
|
||||
const char* Name() const override { return "DeleteFilter"; }
|
||||
};
|
||||
|
||||
class DelayFilter : public CompactionFilter {
|
||||
public:
|
||||
explicit DelayFilter(DBTestBase* d) : db_test(d) {}
|
||||
virtual bool Filter(int /*level*/, const Slice& /*key*/,
|
||||
const Slice& /*value*/, std::string* /*new_value*/,
|
||||
bool* /*value_changed*/) const override {
|
||||
bool Filter(int /*level*/, const Slice& /*key*/, const Slice& /*value*/,
|
||||
std::string* /*new_value*/,
|
||||
bool* /*value_changed*/) const override {
|
||||
db_test->env_->addon_time_.fetch_add(1000);
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual const char* Name() const override { return "DelayFilter"; }
|
||||
const char* Name() const override { return "DelayFilter"; }
|
||||
|
||||
private:
|
||||
DBTestBase* db_test;
|
||||
@@ -149,13 +147,13 @@ class ConditionalFilter : public CompactionFilter {
|
||||
public:
|
||||
explicit ConditionalFilter(const std::string* filtered_value)
|
||||
: filtered_value_(filtered_value) {}
|
||||
virtual bool Filter(int /*level*/, const Slice& /*key*/, const Slice& value,
|
||||
std::string* /*new_value*/,
|
||||
bool* /*value_changed*/) const override {
|
||||
bool Filter(int /*level*/, const Slice& /*key*/, const Slice& value,
|
||||
std::string* /*new_value*/,
|
||||
bool* /*value_changed*/) const override {
|
||||
return value.ToString() == *filtered_value_;
|
||||
}
|
||||
|
||||
virtual const char* Name() const override { return "ConditionalFilter"; }
|
||||
const char* Name() const override { return "ConditionalFilter"; }
|
||||
|
||||
private:
|
||||
const std::string* filtered_value_;
|
||||
@@ -165,16 +163,15 @@ class ChangeFilter : public CompactionFilter {
|
||||
public:
|
||||
explicit ChangeFilter() {}
|
||||
|
||||
virtual bool Filter(int /*level*/, const Slice& /*key*/,
|
||||
const Slice& /*value*/, std::string* new_value,
|
||||
bool* value_changed) const override {
|
||||
bool Filter(int /*level*/, const Slice& /*key*/, const Slice& /*value*/,
|
||||
std::string* new_value, bool* value_changed) const override {
|
||||
assert(new_value != nullptr);
|
||||
*new_value = NEW_VALUE;
|
||||
*value_changed = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
virtual const char* Name() const override { return "ChangeFilter"; }
|
||||
const char* Name() const override { return "ChangeFilter"; }
|
||||
};
|
||||
|
||||
class KeepFilterFactory : public CompactionFilterFactory {
|
||||
@@ -185,7 +182,7 @@ class KeepFilterFactory : public CompactionFilterFactory {
|
||||
check_context_cf_id_(check_context_cf_id),
|
||||
compaction_filter_created_(false) {}
|
||||
|
||||
virtual std::unique_ptr<CompactionFilter> CreateCompactionFilter(
|
||||
std::unique_ptr<CompactionFilter> CreateCompactionFilter(
|
||||
const CompactionFilter::Context& context) override {
|
||||
if (check_context_) {
|
||||
EXPECT_EQ(expect_full_compaction_.load(), context.is_full_compaction);
|
||||
@@ -200,7 +197,7 @@ class KeepFilterFactory : public CompactionFilterFactory {
|
||||
|
||||
bool compaction_filter_created() const { return compaction_filter_created_; }
|
||||
|
||||
virtual const char* Name() const override { return "KeepFilterFactory"; }
|
||||
const char* Name() const override { return "KeepFilterFactory"; }
|
||||
bool check_context_;
|
||||
bool check_context_cf_id_;
|
||||
std::atomic_bool expect_full_compaction_;
|
||||
@@ -211,7 +208,7 @@ class KeepFilterFactory : public CompactionFilterFactory {
|
||||
|
||||
class DeleteFilterFactory : public CompactionFilterFactory {
|
||||
public:
|
||||
virtual std::unique_ptr<CompactionFilter> CreateCompactionFilter(
|
||||
std::unique_ptr<CompactionFilter> CreateCompactionFilter(
|
||||
const CompactionFilter::Context& context) override {
|
||||
if (context.is_manual_compaction) {
|
||||
return std::unique_ptr<CompactionFilter>(new DeleteFilter());
|
||||
@@ -220,13 +217,13 @@ class DeleteFilterFactory : public CompactionFilterFactory {
|
||||
}
|
||||
}
|
||||
|
||||
virtual const char* Name() const override { return "DeleteFilterFactory"; }
|
||||
const char* Name() const override { return "DeleteFilterFactory"; }
|
||||
};
|
||||
|
||||
// Delete Filter Factory which ignores snapshots
|
||||
class DeleteISFilterFactory : public CompactionFilterFactory {
|
||||
public:
|
||||
virtual std::unique_ptr<CompactionFilter> CreateCompactionFilter(
|
||||
std::unique_ptr<CompactionFilter> CreateCompactionFilter(
|
||||
const CompactionFilter::Context& context) override {
|
||||
if (context.is_manual_compaction) {
|
||||
return std::unique_ptr<CompactionFilter>(new DeleteISFilter());
|
||||
@@ -235,12 +232,12 @@ class DeleteISFilterFactory : public CompactionFilterFactory {
|
||||
}
|
||||
}
|
||||
|
||||
virtual const char* Name() const override { return "DeleteFilterFactory"; }
|
||||
const char* Name() const override { return "DeleteFilterFactory"; }
|
||||
};
|
||||
|
||||
class SkipEvenFilterFactory : public CompactionFilterFactory {
|
||||
public:
|
||||
virtual std::unique_ptr<CompactionFilter> CreateCompactionFilter(
|
||||
std::unique_ptr<CompactionFilter> CreateCompactionFilter(
|
||||
const CompactionFilter::Context& context) override {
|
||||
if (context.is_manual_compaction) {
|
||||
return std::unique_ptr<CompactionFilter>(new SkipEvenFilter());
|
||||
@@ -249,18 +246,18 @@ class SkipEvenFilterFactory : public CompactionFilterFactory {
|
||||
}
|
||||
}
|
||||
|
||||
virtual const char* Name() const override { return "SkipEvenFilterFactory"; }
|
||||
const char* Name() const override { return "SkipEvenFilterFactory"; }
|
||||
};
|
||||
|
||||
class DelayFilterFactory : public CompactionFilterFactory {
|
||||
public:
|
||||
explicit DelayFilterFactory(DBTestBase* d) : db_test(d) {}
|
||||
virtual std::unique_ptr<CompactionFilter> CreateCompactionFilter(
|
||||
std::unique_ptr<CompactionFilter> CreateCompactionFilter(
|
||||
const CompactionFilter::Context& /*context*/) override {
|
||||
return std::unique_ptr<CompactionFilter>(new DelayFilter(db_test));
|
||||
}
|
||||
|
||||
virtual const char* Name() const override { return "DelayFilterFactory"; }
|
||||
const char* Name() const override { return "DelayFilterFactory"; }
|
||||
|
||||
private:
|
||||
DBTestBase* db_test;
|
||||
@@ -271,15 +268,13 @@ class ConditionalFilterFactory : public CompactionFilterFactory {
|
||||
explicit ConditionalFilterFactory(const Slice& filtered_value)
|
||||
: filtered_value_(filtered_value.ToString()) {}
|
||||
|
||||
virtual std::unique_ptr<CompactionFilter> CreateCompactionFilter(
|
||||
std::unique_ptr<CompactionFilter> CreateCompactionFilter(
|
||||
const CompactionFilter::Context& /*context*/) override {
|
||||
return std::unique_ptr<CompactionFilter>(
|
||||
new ConditionalFilter(&filtered_value_));
|
||||
}
|
||||
|
||||
virtual const char* Name() const override {
|
||||
return "ConditionalFilterFactory";
|
||||
}
|
||||
const char* Name() const override { return "ConditionalFilterFactory"; }
|
||||
|
||||
private:
|
||||
std::string filtered_value_;
|
||||
@@ -289,12 +284,12 @@ class ChangeFilterFactory : public CompactionFilterFactory {
|
||||
public:
|
||||
explicit ChangeFilterFactory() {}
|
||||
|
||||
virtual std::unique_ptr<CompactionFilter> CreateCompactionFilter(
|
||||
std::unique_ptr<CompactionFilter> CreateCompactionFilter(
|
||||
const CompactionFilter::Context& /*context*/) override {
|
||||
return std::unique_ptr<CompactionFilter>(new ChangeFilter());
|
||||
}
|
||||
|
||||
virtual const char* Name() const override { return "ChangeFilterFactory"; }
|
||||
const char* Name() const override { return "ChangeFilterFactory"; }
|
||||
};
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
@@ -357,7 +352,7 @@ TEST_F(DBTestCompactionFilter, CompactionFilter) {
|
||||
}
|
||||
}
|
||||
ASSERT_EQ(total, 100000);
|
||||
ASSERT_EQ(count, 1);
|
||||
ASSERT_EQ(count, 0);
|
||||
|
||||
// overwrite all the 100K keys once again.
|
||||
for (int i = 0; i < 100000; i++) {
|
||||
@@ -664,7 +659,7 @@ TEST_F(DBTestCompactionFilter, CompactionFilterContextManual) {
|
||||
iter->Next();
|
||||
}
|
||||
ASSERT_EQ(total, 700);
|
||||
ASSERT_EQ(count, 1);
|
||||
ASSERT_EQ(count, 0);
|
||||
}
|
||||
}
|
||||
#endif // ROCKSDB_LITE
|
||||
@@ -699,44 +694,7 @@ TEST_F(DBTestCompactionFilter, CompactionFilterContextCfId) {
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
// Compaction filters should only be applied to records that are newer than the
|
||||
// latest snapshot. This test inserts records and applies a delete filter.
|
||||
TEST_F(DBTestCompactionFilter, CompactionFilterSnapshot) {
|
||||
Options options = CurrentOptions();
|
||||
options.compaction_filter_factory = std::make_shared<DeleteFilterFactory>();
|
||||
options.disable_auto_compactions = true;
|
||||
options.create_if_missing = true;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
// Put some data.
|
||||
const Snapshot* snapshot = nullptr;
|
||||
for (int table = 0; table < 4; ++table) {
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
Put(ToString(table * 100 + i), "val");
|
||||
}
|
||||
Flush();
|
||||
|
||||
if (table == 0) {
|
||||
snapshot = db_->GetSnapshot();
|
||||
}
|
||||
}
|
||||
assert(snapshot != nullptr);
|
||||
|
||||
cfilter_count = 0;
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
||||
// The filter should delete 10 records.
|
||||
ASSERT_EQ(30U, cfilter_count);
|
||||
|
||||
// Release the snapshot and compact again -> now all records should be
|
||||
// removed.
|
||||
db_->ReleaseSnapshot(snapshot);
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
||||
ASSERT_EQ(0U, CountLiveFiles());
|
||||
}
|
||||
|
||||
// Compaction filters should only be applied to records that are newer than the
|
||||
// latest snapshot. However, if the compaction filter asks to ignore snapshots
|
||||
// records newer than the snapshot will also be processed
|
||||
// Compaction filters aplies to all records, regardless snapshots.
|
||||
TEST_F(DBTestCompactionFilter, CompactionFilterIgnoreSnapshot) {
|
||||
std::string five = ToString(5);
|
||||
Options options = CurrentOptions();
|
||||
@@ -874,6 +832,38 @@ TEST_F(DBTestCompactionFilter, SkipUntilWithBloomFilter) {
|
||||
EXPECT_EQ("v50", val);
|
||||
}
|
||||
|
||||
class TestNotSupportedFilter : public CompactionFilter {
|
||||
public:
|
||||
bool Filter(int /*level*/, const Slice& /*key*/, const Slice& /*value*/,
|
||||
std::string* /*new_value*/,
|
||||
bool* /*value_changed*/) const override {
|
||||
return true;
|
||||
}
|
||||
|
||||
const char* Name() const override { return "NotSupported"; }
|
||||
bool IgnoreSnapshots() const override { return false; }
|
||||
};
|
||||
|
||||
TEST_F(DBTestCompactionFilter, IgnoreSnapshotsFalse) {
|
||||
Options options = CurrentOptions();
|
||||
options.compaction_filter = new TestNotSupportedFilter();
|
||||
DestroyAndReopen(options);
|
||||
|
||||
Put("a", "v10");
|
||||
Put("z", "v20");
|
||||
Flush();
|
||||
|
||||
Put("a", "v10");
|
||||
Put("z", "v20");
|
||||
Flush();
|
||||
|
||||
// Comapction should fail because IgnoreSnapshots() = false
|
||||
EXPECT_TRUE(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr)
|
||||
.IsNotSupported());
|
||||
|
||||
delete options.compaction_filter;
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
+301
-17
@@ -55,9 +55,9 @@ namespace {
|
||||
class FlushedFileCollector : public EventListener {
|
||||
public:
|
||||
FlushedFileCollector() {}
|
||||
~FlushedFileCollector() {}
|
||||
~FlushedFileCollector() override {}
|
||||
|
||||
virtual void OnFlushCompleted(DB* /*db*/, const FlushJobInfo& info) override {
|
||||
void OnFlushCompleted(DB* /*db*/, const FlushJobInfo& info) override {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
flushed_files_.push_back(info.file_path);
|
||||
}
|
||||
@@ -87,24 +87,23 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
~CompactionStatsCollector() {}
|
||||
~CompactionStatsCollector() override {}
|
||||
|
||||
virtual void OnCompactionCompleted(DB* /* db */,
|
||||
const CompactionJobInfo& info) override {
|
||||
void OnCompactionCompleted(DB* /* db */,
|
||||
const CompactionJobInfo& info) override {
|
||||
int k = static_cast<int>(info.compaction_reason);
|
||||
int num_of_reasons = static_cast<int>(CompactionReason::kNumOfReasons);
|
||||
assert(k >= 0 && k < num_of_reasons);
|
||||
compaction_completed_[k]++;
|
||||
}
|
||||
|
||||
virtual void OnExternalFileIngested(DB* /* db */,
|
||||
const ExternalFileIngestionInfo& /* info */) override {
|
||||
void OnExternalFileIngested(
|
||||
DB* /* db */, const ExternalFileIngestionInfo& /* info */) override {
|
||||
int k = static_cast<int>(CompactionReason::kExternalSstIngestion);
|
||||
compaction_completed_[k]++;
|
||||
}
|
||||
|
||||
virtual void OnFlushCompleted(DB* /* db */,
|
||||
const FlushJobInfo& /* info */) override {
|
||||
void OnFlushCompleted(DB* /* db */, const FlushJobInfo& /* info */) override {
|
||||
int k = static_cast<int>(CompactionReason::kFlush);
|
||||
compaction_completed_[k]++;
|
||||
}
|
||||
@@ -354,7 +353,8 @@ TEST_P(DBCompactionTestWithParam, CompactionsPreserveDeletes) {
|
||||
CompactRangeOptions cro;
|
||||
cro.change_level = true;
|
||||
cro.target_level = 2;
|
||||
cro.bottommost_level_compaction = BottommostLevelCompaction::kForce;
|
||||
cro.bottommost_level_compaction =
|
||||
BottommostLevelCompaction::kForceOptimized;
|
||||
|
||||
dbfull()->TEST_WaitForFlushMemTable();
|
||||
dbfull()->CompactRange(cro, nullptr, nullptr);
|
||||
@@ -512,7 +512,7 @@ TEST_F(DBCompactionTest, TestTableReaderForCompaction) {
|
||||
CompactRangeOptions cro;
|
||||
cro.change_level = true;
|
||||
cro.target_level = 2;
|
||||
cro.bottommost_level_compaction = BottommostLevelCompaction::kForce;
|
||||
cro.bottommost_level_compaction = BottommostLevelCompaction::kForceOptimized;
|
||||
db_->CompactRange(cro, nullptr, nullptr);
|
||||
// Only verifying compaction outputs issues one table cache lookup
|
||||
// for both data block and range deletion block).
|
||||
@@ -2261,6 +2261,8 @@ TEST_P(DBCompactionTestWithParam, ConvertCompactionStyle) {
|
||||
CompactRangeOptions compact_options;
|
||||
compact_options.change_level = true;
|
||||
compact_options.target_level = 0;
|
||||
// cannot use kForceOptimized here because the compaction here is expected
|
||||
// to generate one output file
|
||||
compact_options.bottommost_level_compaction =
|
||||
BottommostLevelCompaction::kForce;
|
||||
compact_options.exclusive_manual_compaction = exclusive_manual_compaction_;
|
||||
@@ -3040,7 +3042,7 @@ TEST_P(DBCompactionTestWithParam, ForceBottommostLevelCompaction) {
|
||||
// then compacte the bottommost level L3=>L3 (non trivial move)
|
||||
compact_options = CompactRangeOptions();
|
||||
compact_options.bottommost_level_compaction =
|
||||
BottommostLevelCompaction::kForce;
|
||||
BottommostLevelCompaction::kForceOptimized;
|
||||
ASSERT_OK(db_->CompactRange(compact_options, nullptr, nullptr));
|
||||
ASSERT_EQ("0,0,0,1", FilesPerLevel(0));
|
||||
ASSERT_EQ(trivial_move, 4);
|
||||
@@ -3349,7 +3351,7 @@ TEST_F(DBCompactionTest, CompactBottomLevelFilesWithDeletions) {
|
||||
options.level0_file_num_compaction_trigger = kNumLevelFiles;
|
||||
// inflate it a bit to account for key/metadata overhead
|
||||
options.target_file_size_base = 120 * kNumKeysPerFile * kValueSize / 100;
|
||||
Reopen(options);
|
||||
CreateAndReopenWithCF({"one"}, options);
|
||||
|
||||
Random rnd(301);
|
||||
const Snapshot* snapshot = nullptr;
|
||||
@@ -3380,10 +3382,12 @@ TEST_F(DBCompactionTest, CompactBottomLevelFilesWithDeletions) {
|
||||
// just need to bump seqnum so ReleaseSnapshot knows the newest key in the SST
|
||||
// files does not need to be preserved in case of a future snapshot.
|
||||
ASSERT_OK(Put(Key(0), "val"));
|
||||
ASSERT_NE(kMaxSequenceNumber, dbfull()->bottommost_files_mark_threshold_);
|
||||
// release snapshot and wait for compactions to finish. Single-file
|
||||
// compactions should be triggered, which reduce the size of each bottom-level
|
||||
// file without changing file count.
|
||||
db_->ReleaseSnapshot(snapshot);
|
||||
ASSERT_EQ(kMaxSequenceNumber, dbfull()->bottommost_files_mark_threshold_);
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"LevelCompactionPicker::PickCompaction:Return", [&](void* arg) {
|
||||
Compaction* compaction = reinterpret_cast<Compaction*>(arg);
|
||||
@@ -3516,6 +3520,234 @@ TEST_F(DBCompactionTest, LevelCompactExpiredTtlFiles) {
|
||||
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
|
||||
TEST_F(DBCompactionTest, LevelPeriodicCompaction) {
|
||||
const int kNumKeysPerFile = 32;
|
||||
const int kNumLevelFiles = 2;
|
||||
const int kValueSize = 100;
|
||||
|
||||
Options options = CurrentOptions();
|
||||
options.periodic_compaction_seconds = 48 * 60 * 60; // 2 days
|
||||
options.max_open_files = -1; // needed for ttl compaction
|
||||
env_->time_elapse_only_sleep_ = false;
|
||||
options.env = env_;
|
||||
|
||||
env_->addon_time_.store(0);
|
||||
DestroyAndReopen(options);
|
||||
|
||||
int periodic_compactions = 0;
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"LevelCompactionPicker::PickCompaction:Return", [&](void* arg) {
|
||||
Compaction* compaction = reinterpret_cast<Compaction*>(arg);
|
||||
auto compaction_reason = compaction->compaction_reason();
|
||||
if (compaction_reason == CompactionReason::kPeriodicCompaction) {
|
||||
periodic_compactions++;
|
||||
}
|
||||
});
|
||||
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
Random rnd(301);
|
||||
for (int i = 0; i < kNumLevelFiles; ++i) {
|
||||
for (int j = 0; j < kNumKeysPerFile; ++j) {
|
||||
ASSERT_OK(
|
||||
Put(Key(i * kNumKeysPerFile + j), RandomString(&rnd, kValueSize)));
|
||||
}
|
||||
Flush();
|
||||
}
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
|
||||
ASSERT_EQ("2", FilesPerLevel());
|
||||
ASSERT_EQ(0, periodic_compactions);
|
||||
|
||||
// Add 50 hours and do a write
|
||||
env_->addon_time_.fetch_add(50 * 60 * 60);
|
||||
ASSERT_OK(Put("a", "1"));
|
||||
Flush();
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
// Assert that the files stay in the same level
|
||||
ASSERT_EQ("3", FilesPerLevel());
|
||||
// The two old files go through the periodic compaction process
|
||||
ASSERT_EQ(2, periodic_compactions);
|
||||
|
||||
MoveFilesToLevel(1);
|
||||
ASSERT_EQ("0,3", FilesPerLevel());
|
||||
|
||||
// Add another 50 hours and do another write
|
||||
env_->addon_time_.fetch_add(50 * 60 * 60);
|
||||
ASSERT_OK(Put("b", "2"));
|
||||
Flush();
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
ASSERT_EQ("1,3", FilesPerLevel());
|
||||
// The three old files now go through the periodic compaction process. 2 + 3.
|
||||
ASSERT_EQ(5, periodic_compactions);
|
||||
|
||||
// Add another 50 hours and do another write
|
||||
env_->addon_time_.fetch_add(50 * 60 * 60);
|
||||
ASSERT_OK(Put("c", "3"));
|
||||
Flush();
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
ASSERT_EQ("2,3", FilesPerLevel());
|
||||
// The four old files now go through the periodic compaction process. 5 + 4.
|
||||
ASSERT_EQ(9, periodic_compactions);
|
||||
|
||||
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
|
||||
TEST_F(DBCompactionTest, LevelPeriodicCompactionWithOldDB) {
|
||||
// This test makes sure that periodic compactions are working with a DB
|
||||
// where file_creation_time of some files is 0.
|
||||
// After compactions the new files are created with a valid file_creation_time
|
||||
|
||||
const int kNumKeysPerFile = 32;
|
||||
const int kNumFiles = 4;
|
||||
const int kValueSize = 100;
|
||||
|
||||
Options options = CurrentOptions();
|
||||
options.max_open_files = -1; // needed for ttl compaction
|
||||
env_->time_elapse_only_sleep_ = false;
|
||||
options.env = env_;
|
||||
|
||||
env_->addon_time_.store(0);
|
||||
DestroyAndReopen(options);
|
||||
|
||||
int periodic_compactions = 0;
|
||||
bool set_file_creation_time_to_zero = true;
|
||||
bool set_creation_time_to_zero = true;
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"LevelCompactionPicker::PickCompaction:Return", [&](void* arg) {
|
||||
Compaction* compaction = reinterpret_cast<Compaction*>(arg);
|
||||
auto compaction_reason = compaction->compaction_reason();
|
||||
if (compaction_reason == CompactionReason::kPeriodicCompaction) {
|
||||
periodic_compactions++;
|
||||
}
|
||||
});
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"PropertyBlockBuilder::AddTableProperty:Start", [&](void* arg) {
|
||||
TableProperties* props = reinterpret_cast<TableProperties*>(arg);
|
||||
if (set_file_creation_time_to_zero) {
|
||||
props->file_creation_time = 0;
|
||||
}
|
||||
if (set_creation_time_to_zero) {
|
||||
props->creation_time = 0;
|
||||
}
|
||||
});
|
||||
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
Random rnd(301);
|
||||
for (int i = 0; i < kNumFiles; ++i) {
|
||||
for (int j = 0; j < kNumKeysPerFile; ++j) {
|
||||
ASSERT_OK(
|
||||
Put(Key(i * kNumKeysPerFile + j), RandomString(&rnd, kValueSize)));
|
||||
}
|
||||
Flush();
|
||||
// Move the first two files to L2.
|
||||
if (i == 1) {
|
||||
MoveFilesToLevel(2);
|
||||
set_creation_time_to_zero = false;
|
||||
}
|
||||
}
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
|
||||
ASSERT_EQ("2,0,2", FilesPerLevel());
|
||||
ASSERT_EQ(0, periodic_compactions);
|
||||
|
||||
Close();
|
||||
|
||||
set_file_creation_time_to_zero = false;
|
||||
// Forward the clock by 2 days.
|
||||
env_->addon_time_.fetch_add(2 * 24 * 60 * 60);
|
||||
options.periodic_compaction_seconds = 1 * 24 * 60 * 60; // 1 day
|
||||
|
||||
Reopen(options);
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
ASSERT_EQ("2,0,2", FilesPerLevel());
|
||||
// Make sure that all files go through periodic compaction.
|
||||
ASSERT_EQ(kNumFiles, periodic_compactions);
|
||||
|
||||
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
|
||||
TEST_F(DBCompactionTest, LevelPeriodicAndTtlCompaction) {
|
||||
const int kNumKeysPerFile = 32;
|
||||
const int kNumLevelFiles = 2;
|
||||
const int kValueSize = 100;
|
||||
|
||||
Options options = CurrentOptions();
|
||||
options.ttl = 10 * 60 * 60; // 10 hours
|
||||
options.periodic_compaction_seconds = 48 * 60 * 60; // 2 days
|
||||
options.max_open_files = -1; // needed for both periodic and ttl compactions
|
||||
env_->time_elapse_only_sleep_ = false;
|
||||
options.env = env_;
|
||||
|
||||
env_->addon_time_.store(0);
|
||||
DestroyAndReopen(options);
|
||||
|
||||
int periodic_compactions = 0;
|
||||
int ttl_compactions = 0;
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"LevelCompactionPicker::PickCompaction:Return", [&](void* arg) {
|
||||
Compaction* compaction = reinterpret_cast<Compaction*>(arg);
|
||||
auto compaction_reason = compaction->compaction_reason();
|
||||
if (compaction_reason == CompactionReason::kPeriodicCompaction) {
|
||||
periodic_compactions++;
|
||||
} else if (compaction_reason == CompactionReason::kTtl) {
|
||||
ttl_compactions++;
|
||||
}
|
||||
});
|
||||
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
Random rnd(301);
|
||||
for (int i = 0; i < kNumLevelFiles; ++i) {
|
||||
for (int j = 0; j < kNumKeysPerFile; ++j) {
|
||||
ASSERT_OK(
|
||||
Put(Key(i * kNumKeysPerFile + j), RandomString(&rnd, kValueSize)));
|
||||
}
|
||||
Flush();
|
||||
}
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
|
||||
MoveFilesToLevel(3);
|
||||
|
||||
ASSERT_EQ("0,0,0,2", FilesPerLevel());
|
||||
ASSERT_EQ(0, periodic_compactions);
|
||||
ASSERT_EQ(0, ttl_compactions);
|
||||
|
||||
// Add some time greater than periodic_compaction_time.
|
||||
env_->addon_time_.fetch_add(50 * 60 * 60);
|
||||
ASSERT_OK(Put("a", "1"));
|
||||
Flush();
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
// Files in the bottom level go through periodic compactions.
|
||||
ASSERT_EQ("1,0,0,2", FilesPerLevel());
|
||||
ASSERT_EQ(2, periodic_compactions);
|
||||
ASSERT_EQ(0, ttl_compactions);
|
||||
|
||||
// Add a little more time than ttl
|
||||
env_->addon_time_.fetch_add(11 * 60 * 60);
|
||||
ASSERT_OK(Put("b", "1"));
|
||||
Flush();
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
// Notice that the previous file in level 1 falls down to the bottom level
|
||||
// due to ttl compactions, one level at a time.
|
||||
// And bottom level files don't get picked up for ttl compactions.
|
||||
ASSERT_EQ("1,0,0,3", FilesPerLevel());
|
||||
ASSERT_EQ(2, periodic_compactions);
|
||||
ASSERT_EQ(3, ttl_compactions);
|
||||
|
||||
// Add some time greater than periodic_compaction_time.
|
||||
env_->addon_time_.fetch_add(50 * 60 * 60);
|
||||
ASSERT_OK(Put("c", "1"));
|
||||
Flush();
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
// Previous L0 file falls one level at a time to bottom level due to ttl.
|
||||
// And all 4 bottom files go through periodic compactions.
|
||||
ASSERT_EQ("1,0,0,4", FilesPerLevel());
|
||||
ASSERT_EQ(6, periodic_compactions);
|
||||
ASSERT_EQ(6, ttl_compactions);
|
||||
|
||||
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
|
||||
|
||||
TEST_F(DBCompactionTest, CompactRangeDelayedByL0FileCount) {
|
||||
// Verify that, when `CompactRangeOptions::allow_write_stall == false`, manual
|
||||
// compaction only triggers flush after it's sure stall won't be triggered for
|
||||
@@ -4187,14 +4419,14 @@ class NoopMergeOperator : public MergeOperator {
|
||||
public:
|
||||
NoopMergeOperator() {}
|
||||
|
||||
virtual bool FullMergeV2(const MergeOperationInput& /*merge_in*/,
|
||||
MergeOperationOutput* merge_out) const override {
|
||||
bool FullMergeV2(const MergeOperationInput& /*merge_in*/,
|
||||
MergeOperationOutput* merge_out) const override {
|
||||
std::string val("bar");
|
||||
merge_out->new_value = val;
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual const char* Name() const override { return "Noop"; }
|
||||
const char* Name() const override { return "Noop"; }
|
||||
};
|
||||
|
||||
TEST_F(DBCompactionTest, PartialManualCompaction) {
|
||||
@@ -4223,7 +4455,7 @@ TEST_F(DBCompactionTest, PartialManualCompaction) {
|
||||
{{"max_compaction_bytes", std::to_string(max_compaction_bytes)}}));
|
||||
|
||||
CompactRangeOptions cro;
|
||||
cro.bottommost_level_compaction = BottommostLevelCompaction::kForce;
|
||||
cro.bottommost_level_compaction = BottommostLevelCompaction::kForceOptimized;
|
||||
dbfull()->CompactRange(cro, nullptr, nullptr);
|
||||
}
|
||||
|
||||
@@ -4267,6 +4499,58 @@ TEST_F(DBCompactionTest, ManualCompactionFailsInReadOnlyMode) {
|
||||
Close();
|
||||
}
|
||||
|
||||
// ManualCompactionBottomLevelOptimization tests the bottom level manual
|
||||
// compaction optimization to skip recompacting files created by Ln-1 to Ln
|
||||
// compaction
|
||||
TEST_F(DBCompactionTest, ManualCompactionBottomLevelOptimized) {
|
||||
Options opts = CurrentOptions();
|
||||
opts.num_levels = 3;
|
||||
opts.level0_file_num_compaction_trigger = 5;
|
||||
opts.compression = kNoCompression;
|
||||
opts.merge_operator.reset(new NoopMergeOperator());
|
||||
opts.target_file_size_base = 1024;
|
||||
opts.max_bytes_for_level_multiplier = 2;
|
||||
opts.disable_auto_compactions = true;
|
||||
DestroyAndReopen(opts);
|
||||
ColumnFamilyHandleImpl* cfh =
|
||||
static_cast<ColumnFamilyHandleImpl*>(dbfull()->DefaultColumnFamily());
|
||||
ColumnFamilyData* cfd = cfh->cfd();
|
||||
InternalStats* internal_stats_ptr = cfd->internal_stats();
|
||||
ASSERT_NE(internal_stats_ptr, nullptr);
|
||||
|
||||
Random rnd(301);
|
||||
for (auto i = 0; i < 8; ++i) {
|
||||
for (auto j = 0; j < 10; ++j) {
|
||||
ASSERT_OK(
|
||||
Put("foo" + std::to_string(i * 10 + j), RandomString(&rnd, 1024)));
|
||||
}
|
||||
Flush();
|
||||
}
|
||||
|
||||
MoveFilesToLevel(2);
|
||||
|
||||
for (auto i = 0; i < 8; ++i) {
|
||||
for (auto j = 0; j < 10; ++j) {
|
||||
ASSERT_OK(
|
||||
Put("bar" + std::to_string(i * 10 + j), RandomString(&rnd, 1024)));
|
||||
}
|
||||
Flush();
|
||||
}
|
||||
const std::vector<InternalStats::CompactionStats>& comp_stats =
|
||||
internal_stats_ptr->TEST_GetCompactionStats();
|
||||
int num = comp_stats[2].num_input_files_in_output_level;
|
||||
ASSERT_EQ(num, 0);
|
||||
|
||||
CompactRangeOptions cro;
|
||||
cro.bottommost_level_compaction = BottommostLevelCompaction::kForceOptimized;
|
||||
dbfull()->CompactRange(cro, nullptr, nullptr);
|
||||
|
||||
const std::vector<InternalStats::CompactionStats>& comp_stats2 =
|
||||
internal_stats_ptr->TEST_GetCompactionStats();
|
||||
num = comp_stats2[2].num_input_files_in_output_level;
|
||||
ASSERT_EQ(num, 0);
|
||||
}
|
||||
|
||||
// FixFileIngestionCompactionDeadlock tests and verifies that compaction and
|
||||
// file ingestion do not cause deadlock in the event of write stall triggered
|
||||
// by number of L0 files reaching level0_stop_writes_trigger.
|
||||
|
||||
@@ -488,6 +488,63 @@ TEST_P(DBAtomicFlushTest,
|
||||
Destroy(options);
|
||||
}
|
||||
|
||||
TEST_P(DBAtomicFlushTest, TriggerFlushAndClose) {
|
||||
bool atomic_flush = GetParam();
|
||||
if (!atomic_flush) {
|
||||
return;
|
||||
}
|
||||
const int kNumKeysTriggerFlush = 4;
|
||||
Options options = CurrentOptions();
|
||||
options.create_if_missing = true;
|
||||
options.atomic_flush = atomic_flush;
|
||||
options.memtable_factory.reset(
|
||||
new SpecialSkipListFactory(kNumKeysTriggerFlush));
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
|
||||
for (int i = 0; i != kNumKeysTriggerFlush; ++i) {
|
||||
ASSERT_OK(Put(0, "key" + std::to_string(i), "value" + std::to_string(i)));
|
||||
}
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
ASSERT_OK(Put(0, "key", "value"));
|
||||
Close();
|
||||
|
||||
ReopenWithColumnFamilies({kDefaultColumnFamilyName, "pikachu"}, options);
|
||||
ASSERT_EQ("value", Get(0, "key"));
|
||||
}
|
||||
|
||||
TEST_P(DBAtomicFlushTest, PickMemtablesRaceWithBackgroundFlush) {
|
||||
bool atomic_flush = GetParam();
|
||||
Options options = CurrentOptions();
|
||||
options.create_if_missing = true;
|
||||
options.atomic_flush = atomic_flush;
|
||||
options.max_write_buffer_number = 4;
|
||||
// Set min_write_buffer_number_to_merge to be greater than 1, so that
|
||||
// a column family with one memtable in the imm will not cause IsFlushPending
|
||||
// to return true when flush_requested_ is false.
|
||||
options.min_write_buffer_number_to_merge = 2;
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
ASSERT_EQ(2, handles_.size());
|
||||
ASSERT_OK(dbfull()->PauseBackgroundWork());
|
||||
ASSERT_OK(Put(0, "key00", "value00"));
|
||||
ASSERT_OK(Put(1, "key10", "value10"));
|
||||
FlushOptions flush_opts;
|
||||
flush_opts.wait = false;
|
||||
ASSERT_OK(dbfull()->Flush(flush_opts, handles_));
|
||||
ASSERT_OK(Put(0, "key01", "value01"));
|
||||
// Since max_write_buffer_number is 4, the following flush won't cause write
|
||||
// stall.
|
||||
ASSERT_OK(dbfull()->Flush(flush_opts));
|
||||
ASSERT_OK(dbfull()->DropColumnFamily(handles_[1]));
|
||||
ASSERT_OK(dbfull()->DestroyColumnFamilyHandle(handles_[1]));
|
||||
handles_[1] = nullptr;
|
||||
ASSERT_OK(dbfull()->ContinueBackgroundWork());
|
||||
ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable(handles_[0]));
|
||||
delete handles_[0];
|
||||
handles_.clear();
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(DBFlushDirectIOTest, DBFlushDirectIOTest,
|
||||
testing::Bool());
|
||||
|
||||
|
||||
+724
-198
File diff suppressed because it is too large
Load Diff
+193
-97
@@ -63,6 +63,7 @@ namespace rocksdb {
|
||||
|
||||
class Arena;
|
||||
class ArenaWrappedDBIter;
|
||||
class InMemoryStatsHistoryIterator;
|
||||
class MemTable;
|
||||
class TableCache;
|
||||
class TaskLimiterToken;
|
||||
@@ -123,6 +124,25 @@ class DBImpl : public DB {
|
||||
const std::vector<Slice>& keys,
|
||||
std::vector<std::string>* values) override;
|
||||
|
||||
// This MultiGet is a batched version, which may be faster than calling Get
|
||||
// multiple times, especially if the keys have some spatial locality that
|
||||
// enables them to be queried in the same SST files/set of files. The larger
|
||||
// the batch size, the more scope for batching and performance improvement
|
||||
// The values and statuses parameters are arrays with number of elements
|
||||
// equal to keys.size(). This allows the storage for those to be alloacted
|
||||
// by the caller on the stack for small batches
|
||||
virtual void MultiGet(const ReadOptions& options,
|
||||
ColumnFamilyHandle* column_family,
|
||||
const size_t num_keys, const Slice* keys,
|
||||
PinnableSlice* values, Status* statuses,
|
||||
const bool sorted_input = false) override;
|
||||
|
||||
void MultiGetImpl(
|
||||
const ReadOptions& options, ColumnFamilyHandle* column_family,
|
||||
autovector<KeyContext, MultiGetContext::MAX_BATCH_SIZE>& key_context,
|
||||
bool sorted_input, ReadCallback* callback = nullptr,
|
||||
bool* is_blob_index = nullptr);
|
||||
|
||||
virtual Status CreateColumnFamily(const ColumnFamilyOptions& cf_options,
|
||||
const std::string& column_family,
|
||||
ColumnFamilyHandle** handle) override;
|
||||
@@ -177,10 +197,9 @@ class DBImpl : public DB {
|
||||
virtual bool GetAggregatedIntProperty(const Slice& property,
|
||||
uint64_t* aggregated_value) override;
|
||||
using DB::GetApproximateSizes;
|
||||
virtual void GetApproximateSizes(ColumnFamilyHandle* column_family,
|
||||
const Range* range, int n, uint64_t* sizes,
|
||||
uint8_t include_flags
|
||||
= INCLUDE_FILES) override;
|
||||
virtual void GetApproximateSizes(
|
||||
ColumnFamilyHandle* column_family, const Range* range, int n,
|
||||
uint64_t* sizes, uint8_t include_flags = INCLUDE_FILES) override;
|
||||
using DB::GetApproximateMemTableStats;
|
||||
virtual void GetApproximateMemTableStats(ColumnFamilyHandle* column_family,
|
||||
const Range& range,
|
||||
@@ -234,8 +253,10 @@ class DBImpl : public DB {
|
||||
const FlushOptions& options,
|
||||
const std::vector<ColumnFamilyHandle*>& column_families) override;
|
||||
virtual Status FlushWAL(bool sync) override;
|
||||
bool TEST_WALBufferIsEmpty();
|
||||
bool TEST_WALBufferIsEmpty(bool lock = true);
|
||||
virtual Status SyncWAL() override;
|
||||
virtual Status LockWAL() override;
|
||||
virtual Status UnlockWAL() override;
|
||||
|
||||
virtual SequenceNumber GetLatestSequenceNumber() const override;
|
||||
virtual SequenceNumber GetLastPublishedSequence() const {
|
||||
@@ -283,9 +304,8 @@ class DBImpl : public DB {
|
||||
// Status::NotFound() will be returned if the current DB does not have
|
||||
// any column family match the specified name.
|
||||
// TODO(yhchiang): output parameter is placed in the end in this codebase.
|
||||
virtual void GetColumnFamilyMetaData(
|
||||
ColumnFamilyHandle* column_family,
|
||||
ColumnFamilyMetaData* metadata) override;
|
||||
virtual void GetColumnFamilyMetaData(ColumnFamilyHandle* column_family,
|
||||
ColumnFamilyMetaData* metadata) override;
|
||||
|
||||
Status SuggestCompactRange(ColumnFamilyHandle* column_family,
|
||||
const Slice* begin, const Slice* end) override;
|
||||
@@ -346,6 +366,10 @@ class DBImpl : public DB {
|
||||
const std::vector<std::string>& external_files,
|
||||
const IngestExternalFileOptions& ingestion_options) override;
|
||||
|
||||
using DB::IngestExternalFiles;
|
||||
virtual Status IngestExternalFiles(
|
||||
const std::vector<IngestExternalFileArg>& args) override;
|
||||
|
||||
virtual Status VerifyChecksum() override;
|
||||
|
||||
using DB::StartTrace;
|
||||
@@ -371,12 +395,15 @@ class DBImpl : public DB {
|
||||
|
||||
virtual Status GetDbIdentity(std::string& identity) const override;
|
||||
|
||||
// max_file_num_to_ignore allows bottom level compaction to filter out newly
|
||||
// compacted SST files. Setting max_file_num_to_ignore to kMaxUint64 will
|
||||
// disable the filtering
|
||||
Status RunManualCompaction(ColumnFamilyData* cfd, int input_level,
|
||||
int output_level, uint32_t output_path_id,
|
||||
uint32_t max_subcompactions,
|
||||
int output_level,
|
||||
const CompactRangeOptions& compact_range_options,
|
||||
const Slice* begin, const Slice* end,
|
||||
bool exclusive,
|
||||
bool disallow_trivial_move = false);
|
||||
bool exclusive, bool disallow_trivial_move,
|
||||
uint64_t max_file_num_to_ignore);
|
||||
|
||||
// Return an internal iterator over the current state of the database.
|
||||
// The keys of this iterator are internal keys (see format.h).
|
||||
@@ -422,8 +449,8 @@ class DBImpl : public DB {
|
||||
|
||||
// Return the maximum overlapping data (in bytes) at next level for any
|
||||
// file at a level >= 1.
|
||||
int64_t TEST_MaxNextLevelOverlappingBytes(ColumnFamilyHandle* column_family =
|
||||
nullptr);
|
||||
int64_t TEST_MaxNextLevelOverlappingBytes(
|
||||
ColumnFamilyHandle* column_family = nullptr);
|
||||
|
||||
// Return the current manifest file no.
|
||||
uint64_t TEST_Current_Manifest_FileNo();
|
||||
@@ -478,7 +505,10 @@ class DBImpl : public DB {
|
||||
int TEST_BGCompactionsAllowed() const;
|
||||
int TEST_BGFlushesAllowed() const;
|
||||
size_t TEST_GetWalPreallocateBlockSize(uint64_t write_buffer_size) const;
|
||||
void TEST_WaitForTimedTaskRun(std::function<void()> callback) const;
|
||||
void TEST_WaitForDumpStatsRun(std::function<void()> callback) const;
|
||||
void TEST_WaitForPersistStatsRun(std::function<void()> callback) const;
|
||||
bool TEST_IsPersistentStatsEnabled() const;
|
||||
size_t TEST_EstiamteStatsHistorySize() const;
|
||||
|
||||
#endif // NDEBUG
|
||||
|
||||
@@ -531,6 +561,13 @@ class DBImpl : public DB {
|
||||
|
||||
const SnapshotList& snapshots() const { return snapshots_; }
|
||||
|
||||
void LoadSnapshots(std::vector<SequenceNumber>* snap_vector,
|
||||
SequenceNumber* oldest_write_conflict_snapshot,
|
||||
const SequenceNumber& max_seq) const {
|
||||
InstrumentedMutexLock l(mutex());
|
||||
snapshots().GetAll(snap_vector, oldest_write_conflict_snapshot, max_seq);
|
||||
}
|
||||
|
||||
const ImmutableDBOptions& immutable_db_options() const {
|
||||
return immutable_db_options_;
|
||||
}
|
||||
@@ -709,7 +746,7 @@ class DBImpl : public DB {
|
||||
// Not thread-safe.
|
||||
void SetRecoverableStatePreReleaseCallback(PreReleaseCallback* callback);
|
||||
|
||||
InstrumentedMutex* mutex() { return &mutex_; }
|
||||
InstrumentedMutex* mutex() const { return &mutex_; }
|
||||
|
||||
Status NewDB();
|
||||
|
||||
@@ -724,6 +761,17 @@ class DBImpl : public DB {
|
||||
static Status CreateAndNewDirectory(Env* env, const std::string& dirname,
|
||||
std::unique_ptr<Directory>* directory);
|
||||
|
||||
// Given a time window, return an iterator for accessing stats history
|
||||
Status GetStatsHistory(
|
||||
uint64_t start_time, uint64_t end_time,
|
||||
std::unique_ptr<StatsHistoryIterator>* stats_iterator) override;
|
||||
|
||||
// find stats map from stats_history_ with smallest timestamp in
|
||||
// the range of [start_time, end_time)
|
||||
bool FindStatsByTime(uint64_t start_time, uint64_t end_time,
|
||||
uint64_t* new_time,
|
||||
std::map<std::string, uint64_t>* stats_map);
|
||||
|
||||
protected:
|
||||
Env* const env_;
|
||||
const std::string dbname_;
|
||||
@@ -739,6 +787,46 @@ class DBImpl : public DB {
|
||||
std::unique_ptr<Tracer> tracer_;
|
||||
InstrumentedMutex trace_mutex_;
|
||||
|
||||
// State below is protected by mutex_
|
||||
// With two_write_queues enabled, some of the variables that accessed during
|
||||
// WriteToWAL need different synchronization: log_empty_, alive_log_files_,
|
||||
// logs_, logfile_number_. Refer to the definition of each variable below for
|
||||
// more description.
|
||||
mutable InstrumentedMutex mutex_;
|
||||
|
||||
ColumnFamilyHandleImpl* default_cf_handle_;
|
||||
InternalStats* default_cf_internal_stats_;
|
||||
|
||||
// only used for dynamically adjusting max_total_wal_size. it is a sum of
|
||||
// [write_buffer_size * max_write_buffer_number] over all column families
|
||||
uint64_t max_total_in_memory_state_;
|
||||
// If true, we have only one (default) column family. We use this to optimize
|
||||
// some code-paths
|
||||
bool single_column_family_mode_;
|
||||
|
||||
// The options to access storage files
|
||||
const EnvOptions env_options_;
|
||||
|
||||
// Additonal options for compaction and flush
|
||||
EnvOptions env_options_for_compaction_;
|
||||
|
||||
std::unique_ptr<ColumnFamilyMemTablesImpl> column_family_memtables_;
|
||||
|
||||
// Increase the sequence number after writing each batch, whether memtable is
|
||||
// disabled for that or not. Otherwise the sequence number is increased after
|
||||
// writing each key into memtable. This implies that when disable_memtable is
|
||||
// set, the seq is not increased at all.
|
||||
//
|
||||
// Default: false
|
||||
const bool seq_per_batch_;
|
||||
// This determines during recovery whether we expect one writebatch per
|
||||
// recovered transaction, or potentially multiple writebatches per
|
||||
// transaction. For WriteUnprepared, this is set to false, since multiple
|
||||
// batches can exist per transaction.
|
||||
//
|
||||
// Default: true
|
||||
const bool batch_per_txn_;
|
||||
|
||||
// Except in DB::Open(), WriteOptionsFile can only be called when:
|
||||
// Persist options to options file.
|
||||
// If need_mutex_lock = false, the method will lock DB mutex.
|
||||
@@ -759,13 +847,12 @@ class DBImpl : public DB {
|
||||
const MutableCFOptions& mutable_cf_options,
|
||||
int job_id, TableProperties prop);
|
||||
|
||||
void NotifyOnCompactionBegin(ColumnFamilyData* cfd,
|
||||
Compaction *c, const Status &st,
|
||||
const CompactionJobStats& job_stats,
|
||||
int job_id);
|
||||
void NotifyOnCompactionBegin(ColumnFamilyData* cfd, Compaction* c,
|
||||
const Status& st,
|
||||
const CompactionJobStats& job_stats, int job_id);
|
||||
|
||||
void NotifyOnCompactionCompleted(ColumnFamilyData* cfd,
|
||||
Compaction *c, const Status &st,
|
||||
void NotifyOnCompactionCompleted(ColumnFamilyData* cfd, Compaction* c,
|
||||
const Status& st,
|
||||
const CompactionJobStats& job_stats,
|
||||
int job_id);
|
||||
void NotifyOnMemTableSealed(ColumnFamilyData* cfd,
|
||||
@@ -826,6 +913,14 @@ class DBImpl : public DB {
|
||||
// Actual implementation of Close()
|
||||
Status CloseImpl();
|
||||
|
||||
// Recover the descriptor from persistent storage. May do a significant
|
||||
// amount of work to recover recently logged updates. Any changes to
|
||||
// be made to the descriptor are added to *edit.
|
||||
virtual Status Recover(
|
||||
const std::vector<ColumnFamilyDescriptor>& column_families,
|
||||
bool read_only = false, bool error_if_log_file_exist = false,
|
||||
bool error_if_data_exists_in_logs = false);
|
||||
|
||||
private:
|
||||
friend class DB;
|
||||
friend class ErrorHandler;
|
||||
@@ -846,6 +941,7 @@ class DBImpl : public DB {
|
||||
friend class CompactedDBImpl;
|
||||
friend class DBTest_ConcurrentFlushWAL_Test;
|
||||
friend class DBTest_MixedSlowdownOptionsStop_Test;
|
||||
friend class DBCompactionTest_CompactBottomLevelFilesWithDeletions_Test;
|
||||
#ifndef NDEBUG
|
||||
friend class DBTest2_ReadCallbackTest_Test;
|
||||
friend class WriteCallbackTest_WriteWithCallbackTest_Test;
|
||||
@@ -873,13 +969,6 @@ class DBImpl : public DB {
|
||||
struct PrepickedCompaction;
|
||||
struct PurgeFileInfo;
|
||||
|
||||
// Recover the descriptor from persistent storage. May do a significant
|
||||
// amount of work to recover recently logged updates. Any changes to
|
||||
// be made to the descriptor are added to *edit.
|
||||
Status Recover(const std::vector<ColumnFamilyDescriptor>& column_families,
|
||||
bool read_only = false, bool error_if_log_file_exist = false,
|
||||
bool error_if_data_exists_in_logs = false);
|
||||
|
||||
Status ResumeImpl();
|
||||
|
||||
void MaybeIgnoreError(Status* s) const;
|
||||
@@ -929,7 +1018,8 @@ class DBImpl : public DB {
|
||||
SuperVersionContext* superversion_context,
|
||||
std::vector<SequenceNumber>& snapshot_seqs,
|
||||
SequenceNumber earliest_write_conflict_snapshot,
|
||||
SnapshotChecker* snapshot_checker, LogBuffer* log_buffer);
|
||||
SnapshotChecker* snapshot_checker, LogBuffer* log_buffer,
|
||||
Env::Priority thread_pri);
|
||||
|
||||
// Argument required by background flush thread.
|
||||
struct BGFlushArg {
|
||||
@@ -952,19 +1042,26 @@ class DBImpl : public DB {
|
||||
SuperVersionContext* superversion_context_;
|
||||
};
|
||||
|
||||
// Argument passed to flush thread.
|
||||
struct FlushThreadArg {
|
||||
DBImpl* db_;
|
||||
|
||||
Env::Priority thread_pri_;
|
||||
};
|
||||
|
||||
// Flush the memtables of (multiple) column families to multiple files on
|
||||
// persistent storage.
|
||||
Status FlushMemTablesToOutputFiles(
|
||||
const autovector<BGFlushArg>& bg_flush_args, bool* made_progress,
|
||||
JobContext* job_context, LogBuffer* log_buffer);
|
||||
JobContext* job_context, LogBuffer* log_buffer, Env::Priority thread_pri);
|
||||
|
||||
Status AtomicFlushMemTablesToOutputFiles(
|
||||
const autovector<BGFlushArg>& bg_flush_args, bool* made_progress,
|
||||
JobContext* job_context, LogBuffer* log_buffer);
|
||||
JobContext* job_context, LogBuffer* log_buffer, Env::Priority thread_pri);
|
||||
|
||||
// REQUIRES: log_numbers are sorted in ascending order
|
||||
Status RecoverLogFiles(const std::vector<uint64_t>& log_numbers,
|
||||
SequenceNumber* next_sequence, bool read_only);
|
||||
virtual Status RecoverLogFiles(const std::vector<uint64_t>& log_numbers,
|
||||
SequenceNumber* next_sequence, bool read_only);
|
||||
|
||||
// The following two methods are used to flush a memtable to
|
||||
// storage. The first one is used at database RecoveryTime (when the
|
||||
@@ -1103,18 +1200,21 @@ class DBImpl : public DB {
|
||||
// Runs a pre-chosen universal compaction involving bottom level in a
|
||||
// separate, bottom-pri thread pool.
|
||||
static void BGWorkBottomCompaction(void* arg);
|
||||
static void BGWorkFlush(void* db);
|
||||
static void BGWorkFlush(void* arg);
|
||||
static void BGWorkPurge(void* arg);
|
||||
static void UnscheduleCallback(void* arg);
|
||||
static void UnscheduleCompactionCallback(void* arg);
|
||||
static void UnscheduleFlushCallback(void* arg);
|
||||
void BackgroundCallCompaction(PrepickedCompaction* prepicked_compaction,
|
||||
Env::Priority bg_thread_pri);
|
||||
void BackgroundCallFlush();
|
||||
Env::Priority thread_pri);
|
||||
void BackgroundCallFlush(Env::Priority thread_pri);
|
||||
void BackgroundCallPurge();
|
||||
Status BackgroundCompaction(bool* madeProgress, JobContext* job_context,
|
||||
LogBuffer* log_buffer,
|
||||
PrepickedCompaction* prepicked_compaction);
|
||||
PrepickedCompaction* prepicked_compaction,
|
||||
Env::Priority thread_pri);
|
||||
Status BackgroundFlush(bool* madeProgress, JobContext* job_context,
|
||||
LogBuffer* log_buffer, FlushReason* reason);
|
||||
LogBuffer* log_buffer, FlushReason* reason,
|
||||
Env::Priority thread_pri);
|
||||
|
||||
bool EnoughRoomForCompaction(ColumnFamilyData* cfd,
|
||||
const std::vector<CompactionInputFiles>& inputs,
|
||||
@@ -1131,13 +1231,19 @@ class DBImpl : public DB {
|
||||
|
||||
void PrintStatistics();
|
||||
|
||||
size_t EstiamteStatsHistorySize() const;
|
||||
|
||||
// persist stats to column family "_persistent_stats"
|
||||
void PersistStats();
|
||||
|
||||
// dump rocksdb.stats to LOG
|
||||
void DumpStats();
|
||||
|
||||
// Return the minimum empty level that could hold the total data in the
|
||||
// input level. Return the input level, if such level could not be found.
|
||||
int FindMinimumEmptyLevelFitting(ColumnFamilyData* cfd,
|
||||
const MutableCFOptions& mutable_cf_options, int level);
|
||||
const MutableCFOptions& mutable_cf_options,
|
||||
int level);
|
||||
|
||||
// Move the files in the input level to the target level.
|
||||
// If target_level < 0, automatically calculate the minimum level that could
|
||||
@@ -1173,17 +1279,13 @@ class DBImpl : public DB {
|
||||
// Lock over the persistent DB state. Non-nullptr iff successfully acquired.
|
||||
FileLock* db_lock_;
|
||||
|
||||
// In addition to mutex_, log_write_mutex_ protected writes to stats_history_
|
||||
InstrumentedMutex stats_history_mutex_;
|
||||
// In addition to mutex_, log_write_mutex_ protected writes to logs_ and
|
||||
// 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.
|
||||
InstrumentedMutex log_write_mutex_;
|
||||
// State below is protected by mutex_
|
||||
// With two_write_queues enabled, some of the variables that accessed during
|
||||
// WriteToWAL need different synchronization: log_empty_, alive_log_files_,
|
||||
// logs_, logfile_number_. Refer to the definition of each variable below for
|
||||
// more description.
|
||||
mutable InstrumentedMutex mutex_;
|
||||
|
||||
std::atomic<bool> shutting_down_;
|
||||
// This condition variable is signaled on these conditions:
|
||||
@@ -1215,12 +1317,9 @@ class DBImpl : public DB {
|
||||
// read and writes are protected by log_write_mutex_ instead. This is to avoid
|
||||
// expesnive mutex_ lock during WAL write, which update log_empty_.
|
||||
bool log_empty_;
|
||||
ColumnFamilyHandleImpl* default_cf_handle_;
|
||||
InternalStats* default_cf_internal_stats_;
|
||||
std::unique_ptr<ColumnFamilyMemTablesImpl> column_family_memtables_;
|
||||
|
||||
struct LogFileNumberSize {
|
||||
explicit LogFileNumberSize(uint64_t _number)
|
||||
: number(_number) {}
|
||||
explicit LogFileNumberSize(uint64_t _number) : number(_number) {}
|
||||
void AddSize(uint64_t new_size) { size += new_size; }
|
||||
uint64_t number;
|
||||
uint64_t size = 0;
|
||||
@@ -1283,18 +1382,19 @@ class DBImpl : public DB {
|
||||
WriteBatch cached_recoverable_state_;
|
||||
std::atomic<bool> cached_recoverable_state_empty_ = {true};
|
||||
std::atomic<uint64_t> total_log_size_;
|
||||
// only used for dynamically adjusting max_total_wal_size. it is a sum of
|
||||
// [write_buffer_size * max_write_buffer_number] over all column families
|
||||
uint64_t max_total_in_memory_state_;
|
||||
// If true, we have only one (default) column family. We use this to optimize
|
||||
// some code-paths
|
||||
bool single_column_family_mode_;
|
||||
|
||||
// If this is non-empty, we need to delete these log files in background
|
||||
// threads. Protected by db mutex.
|
||||
autovector<log::Writer*> logs_to_free_;
|
||||
|
||||
bool is_snapshot_supported_;
|
||||
|
||||
std::map<uint64_t, std::map<std::string, uint64_t>> stats_history_;
|
||||
|
||||
std::map<std::string, uint64_t> stats_slice_;
|
||||
|
||||
bool stats_slice_initialized_ = false;
|
||||
|
||||
// Class to maintain directories for all database paths other than main one.
|
||||
class Directories {
|
||||
public:
|
||||
@@ -1431,15 +1531,15 @@ class DBImpl : public DB {
|
||||
uint32_t output_path_id;
|
||||
Status status;
|
||||
bool done;
|
||||
bool in_progress; // compaction request being processed?
|
||||
bool incomplete; // only part of requested range compacted
|
||||
bool exclusive; // current behavior of only one manual
|
||||
bool disallow_trivial_move; // Force actual compaction to run
|
||||
const InternalKey* begin; // nullptr means beginning of key range
|
||||
const InternalKey* end; // nullptr means end of key range
|
||||
InternalKey* manual_end; // how far we are compacting
|
||||
InternalKey tmp_storage; // Used to keep track of compaction progress
|
||||
InternalKey tmp_storage1; // Used to keep track of compaction progress
|
||||
bool in_progress; // compaction request being processed?
|
||||
bool incomplete; // only part of requested range compacted
|
||||
bool exclusive; // current behavior of only one manual
|
||||
bool disallow_trivial_move; // Force actual compaction to run
|
||||
const InternalKey* begin; // nullptr means beginning of key range
|
||||
const InternalKey* end; // nullptr means end of key range
|
||||
InternalKey* manual_end; // how far we are compacting
|
||||
InternalKey tmp_storage; // Used to keep track of compaction progress
|
||||
InternalKey tmp_storage1; // Used to keep track of compaction progress
|
||||
};
|
||||
struct PrepickedCompaction {
|
||||
// background compaction takes ownership of `compaction`.
|
||||
@@ -1501,12 +1601,6 @@ class DBImpl : public DB {
|
||||
|
||||
std::string db_absolute_path_;
|
||||
|
||||
// The options to access storage files
|
||||
const EnvOptions env_options_;
|
||||
|
||||
// Additonal options for compaction and flush
|
||||
EnvOptions env_options_for_compaction_;
|
||||
|
||||
// Number of running IngestExternalFile() calls.
|
||||
// REQUIRES: mutex held
|
||||
int num_running_ingest_file_;
|
||||
@@ -1530,6 +1624,10 @@ class DBImpl : public DB {
|
||||
// Indicate DB was opened successfully
|
||||
bool opened_successfully_;
|
||||
|
||||
// The min threshold to triggere bottommost compaction for removing
|
||||
// garbages, among all column families.
|
||||
SequenceNumber bottommost_files_mark_threshold_ = kMaxSequenceNumber;
|
||||
|
||||
LogsWithPrepTracker logs_with_prep_tracker_;
|
||||
|
||||
// Callback for compaction to check if a key is visible to a snapshot.
|
||||
@@ -1540,10 +1638,14 @@ class DBImpl : public DB {
|
||||
// Only to be set during initialization
|
||||
std::unique_ptr<PreReleaseCallback> recoverable_state_pre_release_callback_;
|
||||
|
||||
// handle for scheduling jobs at fixed intervals
|
||||
// handle for scheduling stats dumping at fixed intervals
|
||||
// REQUIRES: mutex locked
|
||||
std::unique_ptr<rocksdb::RepeatableThread> thread_dump_stats_;
|
||||
|
||||
// handle for scheduling stats snapshoting at fixed intervals
|
||||
// REQUIRES: mutex locked
|
||||
std::unique_ptr<rocksdb::RepeatableThread> thread_persist_stats_;
|
||||
|
||||
// No copying allowed
|
||||
DBImpl(const DBImpl&);
|
||||
void operator=(const DBImpl&);
|
||||
@@ -1561,9 +1663,9 @@ class DBImpl : public DB {
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
using DB::GetPropertiesOfAllTables;
|
||||
virtual Status GetPropertiesOfAllTables(ColumnFamilyHandle* column_family,
|
||||
TablePropertiesCollection* props)
|
||||
override;
|
||||
virtual Status GetPropertiesOfAllTables(
|
||||
ColumnFamilyHandle* column_family,
|
||||
TablePropertiesCollection* props) override;
|
||||
virtual Status GetPropertiesOfTablesInRange(
|
||||
ColumnFamilyHandle* column_family, const Range* range, std::size_t n,
|
||||
TablePropertiesCollection* props) override;
|
||||
@@ -1588,34 +1690,29 @@ class DBImpl : public DB {
|
||||
const CompactionJobStats& compaction_job_stats,
|
||||
const int job_id, const Version* current,
|
||||
CompactionJobInfo* compaction_job_info) const;
|
||||
#endif
|
||||
// Reserve the next 'num' file numbers for to-be-ingested external SST files,
|
||||
// and return the current file_number in 'next_file_number'.
|
||||
// Write a version edit to the MANIFEST.
|
||||
Status ReserveFileNumbersBeforeIngestion(
|
||||
ColumnFamilyData* cfd, uint64_t num,
|
||||
std::list<uint64_t>::iterator* pending_output_elem,
|
||||
uint64_t* next_file_number);
|
||||
#endif //! ROCKSDB_LITE
|
||||
|
||||
bool ShouldPurge(uint64_t file_number) const;
|
||||
void MarkAsGrabbedForPurge(uint64_t file_number);
|
||||
|
||||
size_t GetWalPreallocateBlockSize(uint64_t write_buffer_size) const;
|
||||
Env::WriteLifeTimeHint CalculateWALWriteHint() {
|
||||
return Env::WLTH_SHORT;
|
||||
}
|
||||
Env::WriteLifeTimeHint CalculateWALWriteHint() { return Env::WLTH_SHORT; }
|
||||
|
||||
Status CreateWAL(uint64_t log_file_num, uint64_t recycle_log_number,
|
||||
size_t preallocate_block_size, log::Writer** new_log);
|
||||
|
||||
// When set, we use a separate queue for writes that dont write to memtable.
|
||||
// In 2PC these are the writes at Prepare phase.
|
||||
const bool two_write_queues_;
|
||||
const bool manual_wal_flush_;
|
||||
// Increase the sequence number after writing each batch, whether memtable is
|
||||
// disabled for that or not. Otherwise the sequence number is increased after
|
||||
// writing each key into memtable. This implies that when disable_memtable is
|
||||
// set, the seq is not increased at all.
|
||||
//
|
||||
// Default: false
|
||||
const bool seq_per_batch_;
|
||||
// This determines during recovery whether we expect one writebatch per
|
||||
// recovered transaction, or potentially multiple writebatches per
|
||||
// transaction. For WriteUnprepared, this is set to false, since multiple
|
||||
// batches can exist per transaction.
|
||||
//
|
||||
// Default: true
|
||||
const bool batch_per_txn_;
|
||||
|
||||
// LastSequence also indicates last published sequence visibile to the
|
||||
// readers. Otherwise LastPublishedSequence should be used.
|
||||
const bool last_seq_same_as_publish_seq_;
|
||||
@@ -1658,8 +1755,7 @@ class DBImpl : public DB {
|
||||
InstrumentedCondVar atomic_flush_install_cv_;
|
||||
};
|
||||
|
||||
extern Options SanitizeOptions(const std::string& db,
|
||||
const Options& src);
|
||||
extern Options SanitizeOptions(const std::string& db, const Options& src);
|
||||
|
||||
extern DBOptions SanitizeOptions(const std::string& db, const DBOptions& src);
|
||||
|
||||
|
||||
+172
-76
@@ -135,12 +135,12 @@ Status DBImpl::FlushMemTableToOutputFile(
|
||||
SuperVersionContext* superversion_context,
|
||||
std::vector<SequenceNumber>& snapshot_seqs,
|
||||
SequenceNumber earliest_write_conflict_snapshot,
|
||||
SnapshotChecker* snapshot_checker, LogBuffer* log_buffer) {
|
||||
SnapshotChecker* snapshot_checker, LogBuffer* log_buffer,
|
||||
Env::Priority thread_pri) {
|
||||
mutex_.AssertHeld();
|
||||
assert(cfd->imm()->NumNotFlushed() != 0);
|
||||
assert(cfd->imm()->IsFlushPending());
|
||||
|
||||
|
||||
FlushJob flush_job(
|
||||
dbname_, cfd, immutable_db_options_, mutable_cf_options,
|
||||
nullptr /* memtable_id */, env_options_for_compaction_, versions_.get(),
|
||||
@@ -149,7 +149,7 @@ Status DBImpl::FlushMemTableToOutputFile(
|
||||
GetDataDir(cfd, 0U),
|
||||
GetCompressionFlush(*cfd->ioptions(), mutable_cf_options), stats_,
|
||||
&event_logger_, mutable_cf_options.report_bg_io_stats,
|
||||
true /* sync_output_directory */, true /* write_manifest */);
|
||||
true /* sync_output_directory */, true /* write_manifest */, thread_pri);
|
||||
|
||||
FileMetaData file_meta;
|
||||
|
||||
@@ -218,7 +218,8 @@ Status DBImpl::FlushMemTableToOutputFile(
|
||||
cfd->ioptions()->cf_paths[0].path, file_meta.fd.GetNumber());
|
||||
sfm->OnAddFile(file_path);
|
||||
if (sfm->IsMaxAllowedSpaceReached()) {
|
||||
Status new_bg_error = Status::SpaceLimit("Max allowed space was reached");
|
||||
Status new_bg_error =
|
||||
Status::SpaceLimit("Max allowed space was reached");
|
||||
TEST_SYNC_POINT_CALLBACK(
|
||||
"DBImpl::FlushMemTableToOutputFile:MaxAllowedSpaceReached",
|
||||
&new_bg_error);
|
||||
@@ -232,10 +233,10 @@ Status DBImpl::FlushMemTableToOutputFile(
|
||||
|
||||
Status DBImpl::FlushMemTablesToOutputFiles(
|
||||
const autovector<BGFlushArg>& bg_flush_args, bool* made_progress,
|
||||
JobContext* job_context, LogBuffer* log_buffer) {
|
||||
JobContext* job_context, LogBuffer* log_buffer, Env::Priority thread_pri) {
|
||||
if (immutable_db_options_.atomic_flush) {
|
||||
return AtomicFlushMemTablesToOutputFiles(bg_flush_args, made_progress,
|
||||
job_context, log_buffer);
|
||||
return AtomicFlushMemTablesToOutputFiles(
|
||||
bg_flush_args, made_progress, job_context, log_buffer, thread_pri);
|
||||
}
|
||||
std::vector<SequenceNumber> snapshot_seqs;
|
||||
SequenceNumber earliest_write_conflict_snapshot;
|
||||
@@ -250,7 +251,7 @@ Status DBImpl::FlushMemTablesToOutputFiles(
|
||||
Status s = FlushMemTableToOutputFile(
|
||||
cfd, mutable_cf_options, made_progress, job_context,
|
||||
superversion_context, snapshot_seqs, earliest_write_conflict_snapshot,
|
||||
snapshot_checker, log_buffer);
|
||||
snapshot_checker, log_buffer, thread_pri);
|
||||
if (!s.ok()) {
|
||||
status = s;
|
||||
if (!s.IsShutdownInProgress()) {
|
||||
@@ -274,7 +275,7 @@ Status DBImpl::FlushMemTablesToOutputFiles(
|
||||
*/
|
||||
Status DBImpl::AtomicFlushMemTablesToOutputFiles(
|
||||
const autovector<BGFlushArg>& bg_flush_args, bool* made_progress,
|
||||
JobContext* job_context, LogBuffer* log_buffer) {
|
||||
JobContext* job_context, LogBuffer* log_buffer, Env::Priority thread_pri) {
|
||||
mutex_.AssertHeld();
|
||||
|
||||
autovector<ColumnFamilyData*> cfds;
|
||||
@@ -296,6 +297,7 @@ Status DBImpl::AtomicFlushMemTablesToOutputFiles(
|
||||
&earliest_write_conflict_snapshot, &snapshot_checker);
|
||||
|
||||
autovector<Directory*> distinct_output_dirs;
|
||||
autovector<std::string> distinct_output_dir_paths;
|
||||
std::vector<FlushJob> jobs;
|
||||
std::vector<MutableCFOptions> all_mutable_cf_options;
|
||||
int num_cfs = static_cast<int>(cfds.size());
|
||||
@@ -303,18 +305,20 @@ Status DBImpl::AtomicFlushMemTablesToOutputFiles(
|
||||
for (int i = 0; i < num_cfs; ++i) {
|
||||
auto cfd = cfds[i];
|
||||
Directory* data_dir = GetDataDir(cfd, 0U);
|
||||
const std::string& curr_path = cfd->ioptions()->cf_paths[0].path;
|
||||
|
||||
// Add to distinct output directories if eligible. Use linear search. Since
|
||||
// the number of elements in the vector is not large, performance should be
|
||||
// tolerable.
|
||||
bool found = false;
|
||||
for (const auto dir : distinct_output_dirs) {
|
||||
if (dir == data_dir) {
|
||||
for (const auto& path : distinct_output_dir_paths) {
|
||||
if (path == curr_path) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
distinct_output_dir_paths.emplace_back(curr_path);
|
||||
distinct_output_dirs.emplace_back(data_dir);
|
||||
}
|
||||
|
||||
@@ -322,31 +326,29 @@ Status DBImpl::AtomicFlushMemTablesToOutputFiles(
|
||||
const MutableCFOptions& mutable_cf_options = all_mutable_cf_options.back();
|
||||
const uint64_t* max_memtable_id = &(bg_flush_args[i].max_memtable_id_);
|
||||
jobs.emplace_back(
|
||||
dbname_, cfds[i], immutable_db_options_, mutable_cf_options,
|
||||
dbname_, cfd, immutable_db_options_, mutable_cf_options,
|
||||
max_memtable_id, env_options_for_compaction_, versions_.get(), &mutex_,
|
||||
&shutting_down_, snapshot_seqs, earliest_write_conflict_snapshot,
|
||||
snapshot_checker, job_context, log_buffer, directories_.GetDbDir(),
|
||||
data_dir, GetCompressionFlush(*cfd->ioptions(), mutable_cf_options),
|
||||
stats_, &event_logger_, mutable_cf_options.report_bg_io_stats,
|
||||
false /* sync_output_directory */, false /* write_manifest */);
|
||||
false /* sync_output_directory */, false /* write_manifest */,
|
||||
thread_pri);
|
||||
jobs.back().PickMemTable();
|
||||
}
|
||||
|
||||
autovector<FileMetaData> file_meta;
|
||||
std::vector<FileMetaData> file_meta(num_cfs);
|
||||
Status s;
|
||||
assert(num_cfs == static_cast<int>(jobs.size()));
|
||||
|
||||
for (int i = 0; i != num_cfs; ++i) {
|
||||
file_meta.emplace_back();
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
const MutableCFOptions& mutable_cf_options =
|
||||
*cfds[i]->GetLatestMutableCFOptions();
|
||||
for (int i = 0; i != num_cfs; ++i) {
|
||||
const MutableCFOptions& mutable_cf_options = all_mutable_cf_options.at(i);
|
||||
// may temporarily unlock and lock the mutex.
|
||||
NotifyOnFlushBegin(cfds[i], &file_meta[i], mutable_cf_options,
|
||||
job_context->job_id, jobs[i].GetTableProperties());
|
||||
#endif /* !ROCKSDB_LITE */
|
||||
}
|
||||
#endif /* !ROCKSDB_LITE */
|
||||
|
||||
if (logfile_number_ > 0) {
|
||||
// TODO (yanqin) investigate whether we should sync the closed logs for
|
||||
@@ -395,12 +397,21 @@ Status DBImpl::AtomicFlushMemTablesToOutputFiles(
|
||||
s = error_status.ok() ? s : error_status;
|
||||
}
|
||||
|
||||
// If db is NOT shutting down, and one or more column families have been
|
||||
// dropped.
|
||||
// TODO: use separate status code for db shutdown and column family dropped.
|
||||
if (s.IsShutdownInProgress() &&
|
||||
!shutting_down_.load(std::memory_order_acquire)) {
|
||||
s = Status::OK();
|
||||
}
|
||||
|
||||
if (s.ok() || s.IsShutdownInProgress()) {
|
||||
// Sync on all distinct output directories.
|
||||
for (auto dir : distinct_output_dirs) {
|
||||
if (dir != nullptr) {
|
||||
s = dir->Fsync();
|
||||
if (!s.ok()) {
|
||||
Status error_status = dir->Fsync();
|
||||
if (!error_status.ok()) {
|
||||
s = error_status;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -450,22 +461,24 @@ Status DBImpl::AtomicFlushMemTablesToOutputFiles(
|
||||
autovector<ColumnFamilyData*> tmp_cfds;
|
||||
autovector<const autovector<MemTable*>*> mems_list;
|
||||
autovector<const MutableCFOptions*> mutable_cf_options_list;
|
||||
autovector<FileMetaData*> tmp_file_meta;
|
||||
for (int i = 0; i != num_cfs; ++i) {
|
||||
const auto& mems = jobs[i].GetMemTables();
|
||||
if (!cfds[i]->IsDropped() && !mems.empty()) {
|
||||
tmp_cfds.emplace_back(cfds[i]);
|
||||
mems_list.emplace_back(&mems);
|
||||
mutable_cf_options_list.emplace_back(&all_mutable_cf_options[i]);
|
||||
tmp_file_meta.emplace_back(&file_meta[i]);
|
||||
}
|
||||
}
|
||||
|
||||
s = InstallMemtableAtomicFlushResults(
|
||||
nullptr /* imm_lists */, tmp_cfds, mutable_cf_options_list, mems_list,
|
||||
versions_.get(), &mutex_, file_meta, &job_context->memtables_to_free,
|
||||
directories_.GetDbDir(), log_buffer);
|
||||
versions_.get(), &mutex_, tmp_file_meta,
|
||||
&job_context->memtables_to_free, directories_.GetDbDir(), log_buffer);
|
||||
}
|
||||
|
||||
if (s.ok() || s.IsShutdownInProgress()) {
|
||||
if (s.ok()) {
|
||||
assert(num_cfs ==
|
||||
static_cast<int>(job_context->superversion_contexts.size()));
|
||||
for (int i = 0; i != num_cfs; ++i) {
|
||||
@@ -474,7 +487,7 @@ Status DBImpl::AtomicFlushMemTablesToOutputFiles(
|
||||
}
|
||||
InstallSuperVersionAndScheduleWork(cfds[i],
|
||||
&job_context->superversion_contexts[i],
|
||||
*cfds[i]->GetLatestMutableCFOptions());
|
||||
all_mutable_cf_options[i]);
|
||||
VersionStorageInfo::LevelSummaryStorage tmp;
|
||||
ROCKS_LOG_BUFFER(log_buffer, "[%s] Level summary: %s\n",
|
||||
cfds[i]->GetName().c_str(),
|
||||
@@ -490,8 +503,7 @@ Status DBImpl::AtomicFlushMemTablesToOutputFiles(
|
||||
if (cfds[i]->IsDropped()) {
|
||||
continue;
|
||||
}
|
||||
NotifyOnFlushCompleted(cfds[i], &file_meta[i],
|
||||
*cfds[i]->GetLatestMutableCFOptions(),
|
||||
NotifyOnFlushCompleted(cfds[i], &file_meta[i], all_mutable_cf_options[i],
|
||||
job_context->job_id, jobs[i].GetTableProperties());
|
||||
if (sfm) {
|
||||
std::string file_path = MakeTableFileName(
|
||||
@@ -681,6 +693,10 @@ Status DBImpl::CompactRange(const CompactRangeOptions& options,
|
||||
}
|
||||
|
||||
int max_level_with_files = 0;
|
||||
// max_file_num_to_ignore can be used to filter out newly created SST files,
|
||||
// useful for bottom level compaction in a manual compaction
|
||||
uint64_t max_file_num_to_ignore = port::kMaxUint64;
|
||||
uint64_t next_file_number = port::kMaxUint64;
|
||||
{
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
Version* base = cfd->current();
|
||||
@@ -690,9 +706,11 @@ Status DBImpl::CompactRange(const CompactRangeOptions& options,
|
||||
max_level_with_files = level;
|
||||
}
|
||||
}
|
||||
next_file_number = versions_->current_next_file_number();
|
||||
}
|
||||
|
||||
int final_output_level = 0;
|
||||
|
||||
if (cfd->ioptions()->compaction_style == kCompactionStyleUniversal &&
|
||||
cfd->NumberLevels() > 1) {
|
||||
// Always compact all files together.
|
||||
@@ -702,8 +720,8 @@ Status DBImpl::CompactRange(const CompactRangeOptions& options,
|
||||
final_output_level--;
|
||||
}
|
||||
s = RunManualCompaction(cfd, ColumnFamilyData::kCompactAllLevels,
|
||||
final_output_level, options.target_path_id,
|
||||
options.max_subcompactions, begin, end, exclusive);
|
||||
final_output_level, options, begin, end, exclusive,
|
||||
false, max_file_num_to_ignore);
|
||||
} else {
|
||||
for (int level = 0; level <= max_level_with_files; level++) {
|
||||
int output_level;
|
||||
@@ -728,6 +746,10 @@ Status DBImpl::CompactRange(const CompactRangeOptions& options,
|
||||
continue;
|
||||
}
|
||||
output_level = level;
|
||||
// update max_file_num_to_ignore only for bottom level compaction
|
||||
// because data in newly compacted files in middle levels may still need
|
||||
// to be pushed down
|
||||
max_file_num_to_ignore = next_file_number;
|
||||
} else {
|
||||
output_level = level + 1;
|
||||
if (cfd->ioptions()->compaction_style == kCompactionStyleLevel &&
|
||||
@@ -736,8 +758,8 @@ Status DBImpl::CompactRange(const CompactRangeOptions& options,
|
||||
output_level = ColumnFamilyData::kCompactToBaseLevel;
|
||||
}
|
||||
}
|
||||
s = RunManualCompaction(cfd, level, output_level, options.target_path_id,
|
||||
options.max_subcompactions, begin, end, exclusive);
|
||||
s = RunManualCompaction(cfd, level, output_level, options, begin, end,
|
||||
exclusive, false, max_file_num_to_ignore);
|
||||
if (!s.ok()) {
|
||||
break;
|
||||
}
|
||||
@@ -776,6 +798,31 @@ Status DBImpl::CompactRange(const CompactRangeOptions& options,
|
||||
return s;
|
||||
}
|
||||
|
||||
namespace {
|
||||
class SnapshotListFetchCallbackImpl : public SnapshotListFetchCallback {
|
||||
public:
|
||||
SnapshotListFetchCallbackImpl(DBImpl* db_impl, Env* env,
|
||||
uint64_t snap_refresh_nanos, Logger* info_log)
|
||||
: SnapshotListFetchCallback(env, snap_refresh_nanos),
|
||||
db_impl_(db_impl),
|
||||
info_log_(info_log) {}
|
||||
virtual void Refresh(std::vector<SequenceNumber>* snapshots,
|
||||
SequenceNumber max) override {
|
||||
size_t prev = snapshots->size();
|
||||
snapshots->clear();
|
||||
db_impl_->LoadSnapshots(snapshots, nullptr, max);
|
||||
size_t now = snapshots->size();
|
||||
ROCKS_LOG_DEBUG(info_log_,
|
||||
"Compaction snapshot count refreshed from %zu to %zu", prev,
|
||||
now);
|
||||
}
|
||||
|
||||
private:
|
||||
DBImpl* db_impl_;
|
||||
Logger* info_log_;
|
||||
};
|
||||
} // namespace
|
||||
|
||||
Status DBImpl::CompactFiles(const CompactionOptions& compact_options,
|
||||
ColumnFamilyHandle* column_family,
|
||||
const std::vector<std::string>& input_file_names,
|
||||
@@ -947,6 +994,9 @@ Status DBImpl::CompactFilesImpl(
|
||||
|
||||
assert(is_snapshot_supported_ || snapshots_.empty());
|
||||
CompactionJobStats compaction_job_stats;
|
||||
SnapshotListFetchCallbackImpl fetch_callback(
|
||||
this, env_, c->mutable_cf_options()->snap_refresh_nanos,
|
||||
immutable_db_options_.info_log.get());
|
||||
CompactionJob compaction_job(
|
||||
job_context->job_id, c.get(), immutable_db_options_,
|
||||
env_options_for_compaction_, versions_.get(), &shutting_down_,
|
||||
@@ -956,7 +1006,11 @@ Status DBImpl::CompactFilesImpl(
|
||||
snapshot_checker, table_cache_, &event_logger_,
|
||||
c->mutable_cf_options()->paranoid_file_checks,
|
||||
c->mutable_cf_options()->report_bg_io_stats, dbname_,
|
||||
&compaction_job_stats);
|
||||
&compaction_job_stats, Env::Priority::USER,
|
||||
immutable_db_options_.max_subcompactions <= 1 &&
|
||||
c->mutable_cf_options()->snap_refresh_nanos > 0
|
||||
? &fetch_callback
|
||||
: nullptr);
|
||||
|
||||
// Creating a compaction influences the compaction score because the score
|
||||
// takes running compactions into account (by skipping files that are already
|
||||
@@ -1025,6 +1079,7 @@ Status DBImpl::CompactFilesImpl(
|
||||
if (bg_compaction_scheduled_ == 0) {
|
||||
bg_cv_.SignalAll();
|
||||
}
|
||||
MaybeScheduleFlushOrCompaction();
|
||||
TEST_SYNC_POINT("CompactFilesImpl:End");
|
||||
|
||||
return status;
|
||||
@@ -1059,8 +1114,8 @@ Status DBImpl::ContinueBackgroundWork() {
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
void DBImpl::NotifyOnCompactionBegin(ColumnFamilyData* cfd,
|
||||
Compaction *c, const Status &st,
|
||||
void DBImpl::NotifyOnCompactionBegin(ColumnFamilyData* cfd, Compaction* c,
|
||||
const Status& st,
|
||||
const CompactionJobStats& job_stats,
|
||||
int job_id) {
|
||||
#ifndef ROCKSDB_LITE
|
||||
@@ -1305,8 +1360,9 @@ Status DBImpl::Flush(const FlushOptions& flush_options,
|
||||
});
|
||||
s = AtomicFlushMemTables(cfds, flush_options, FlushReason::kManualFlush);
|
||||
ROCKS_LOG_INFO(immutable_db_options_.info_log,
|
||||
"Manual atomic flush finished, status: %s\n",
|
||||
"=====Column families:=====", s.ToString().c_str());
|
||||
"Manual atomic flush finished, status: %s\n"
|
||||
"=====Column families:=====",
|
||||
s.ToString().c_str());
|
||||
for (auto cfh : column_families) {
|
||||
auto cfhi = static_cast<ColumnFamilyHandleImpl*>(cfh);
|
||||
ROCKS_LOG_INFO(immutable_db_options_.info_log, "%s",
|
||||
@@ -1318,11 +1374,11 @@ Status DBImpl::Flush(const FlushOptions& flush_options,
|
||||
return s;
|
||||
}
|
||||
|
||||
Status DBImpl::RunManualCompaction(ColumnFamilyData* cfd, int input_level,
|
||||
int output_level, uint32_t output_path_id,
|
||||
uint32_t max_subcompactions,
|
||||
const Slice* begin, const Slice* end,
|
||||
bool exclusive, bool disallow_trivial_move) {
|
||||
Status DBImpl::RunManualCompaction(
|
||||
ColumnFamilyData* cfd, int input_level, int output_level,
|
||||
const CompactRangeOptions& compact_range_options, const Slice* begin,
|
||||
const Slice* end, bool exclusive, bool disallow_trivial_move,
|
||||
uint64_t max_file_num_to_ignore) {
|
||||
assert(input_level == ColumnFamilyData::kCompactAllLevels ||
|
||||
input_level >= 0);
|
||||
|
||||
@@ -1335,7 +1391,7 @@ Status DBImpl::RunManualCompaction(ColumnFamilyData* cfd, int input_level,
|
||||
manual.cfd = cfd;
|
||||
manual.input_level = input_level;
|
||||
manual.output_level = output_level;
|
||||
manual.output_path_id = output_path_id;
|
||||
manual.output_path_id = compact_range_options.target_path_id;
|
||||
manual.done = false;
|
||||
manual.in_progress = false;
|
||||
manual.incomplete = false;
|
||||
@@ -1410,9 +1466,9 @@ Status DBImpl::RunManualCompaction(ColumnFamilyData* cfd, int input_level,
|
||||
(((manual.manual_end = &manual.tmp_storage1) != nullptr) &&
|
||||
((compaction = manual.cfd->CompactRange(
|
||||
*manual.cfd->GetLatestMutableCFOptions(), manual.input_level,
|
||||
manual.output_level, manual.output_path_id, max_subcompactions,
|
||||
manual.begin, manual.end, &manual.manual_end,
|
||||
&manual_conflict)) == nullptr &&
|
||||
manual.output_level, compact_range_options, manual.begin,
|
||||
manual.end, &manual.manual_end, &manual_conflict,
|
||||
max_file_num_to_ignore)) == nullptr &&
|
||||
manual_conflict))) {
|
||||
// exclusive manual compactions should not see a conflict during
|
||||
// CompactRange
|
||||
@@ -1443,7 +1499,7 @@ Status DBImpl::RunManualCompaction(ColumnFamilyData* cfd, int input_level,
|
||||
manual.incomplete = false;
|
||||
bg_compaction_scheduled_++;
|
||||
env_->Schedule(&DBImpl::BGWorkCompaction, ca, Env::Priority::LOW, this,
|
||||
&DBImpl::UnscheduleCallback);
|
||||
&DBImpl::UnscheduleCompactionCallback);
|
||||
scheduled = true;
|
||||
}
|
||||
}
|
||||
@@ -1620,7 +1676,7 @@ Status DBImpl::AtomicFlushMemTables(
|
||||
// it against various constrains and delays flush if it'd cause write stall.
|
||||
// Called should check status and flush_needed to see if flush already happened.
|
||||
Status DBImpl::WaitUntilFlushWouldNotStallWrites(ColumnFamilyData* cfd,
|
||||
bool* flush_needed) {
|
||||
bool* flush_needed) {
|
||||
{
|
||||
*flush_needed = true;
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
@@ -1768,7 +1824,7 @@ void DBImpl::MaybeScheduleFlushOrCompaction() {
|
||||
// we paused the background work
|
||||
return;
|
||||
} else if (error_handler_.IsBGWorkStopped() &&
|
||||
!error_handler_.IsRecoveryInProgress()) {
|
||||
!error_handler_.IsRecoveryInProgress()) {
|
||||
// There has been a hard error and this call is not part of the recovery
|
||||
// sequence. Bail out here so we don't get into an endless loop of
|
||||
// scheduling BG work which will again call this function
|
||||
@@ -1783,7 +1839,11 @@ void DBImpl::MaybeScheduleFlushOrCompaction() {
|
||||
while (!is_flush_pool_empty && unscheduled_flushes_ > 0 &&
|
||||
bg_flush_scheduled_ < bg_job_limits.max_flushes) {
|
||||
bg_flush_scheduled_++;
|
||||
env_->Schedule(&DBImpl::BGWorkFlush, this, Env::Priority::HIGH, this);
|
||||
FlushThreadArg* fta = new FlushThreadArg;
|
||||
fta->db_ = this;
|
||||
fta->thread_pri_ = Env::Priority::HIGH;
|
||||
env_->Schedule(&DBImpl::BGWorkFlush, fta, Env::Priority::HIGH, this,
|
||||
&DBImpl::UnscheduleFlushCallback);
|
||||
}
|
||||
|
||||
// special case -- if high-pri (flush) thread pool is empty, then schedule
|
||||
@@ -1793,7 +1853,11 @@ void DBImpl::MaybeScheduleFlushOrCompaction() {
|
||||
bg_flush_scheduled_ + bg_compaction_scheduled_ <
|
||||
bg_job_limits.max_flushes) {
|
||||
bg_flush_scheduled_++;
|
||||
env_->Schedule(&DBImpl::BGWorkFlush, this, Env::Priority::LOW, this);
|
||||
FlushThreadArg* fta = new FlushThreadArg;
|
||||
fta->db_ = this;
|
||||
fta->thread_pri_ = Env::Priority::LOW;
|
||||
env_->Schedule(&DBImpl::BGWorkFlush, fta, Env::Priority::LOW, this,
|
||||
&DBImpl::UnscheduleFlushCallback);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1823,7 +1887,7 @@ void DBImpl::MaybeScheduleFlushOrCompaction() {
|
||||
bg_compaction_scheduled_++;
|
||||
unscheduled_compactions_--;
|
||||
env_->Schedule(&DBImpl::BGWorkCompaction, ca, Env::Priority::LOW, this,
|
||||
&DBImpl::UnscheduleCallback);
|
||||
&DBImpl::UnscheduleCompactionCallback);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1938,10 +2002,13 @@ void DBImpl::SchedulePendingPurge(std::string fname, std::string dir_to_sync,
|
||||
purge_queue_.push_back(std::move(file_info));
|
||||
}
|
||||
|
||||
void DBImpl::BGWorkFlush(void* db) {
|
||||
IOSTATS_SET_THREAD_POOL_ID(Env::Priority::HIGH);
|
||||
void DBImpl::BGWorkFlush(void* arg) {
|
||||
FlushThreadArg fta = *(reinterpret_cast<FlushThreadArg*>(arg));
|
||||
delete reinterpret_cast<FlushThreadArg*>(arg);
|
||||
|
||||
IOSTATS_SET_THREAD_POOL_ID(fta.thread_pri_);
|
||||
TEST_SYNC_POINT("DBImpl::BGWorkFlush");
|
||||
reinterpret_cast<DBImpl*>(db)->BackgroundCallFlush();
|
||||
reinterpret_cast<DBImpl*>(fta.db_)->BackgroundCallFlush(fta.thread_pri_);
|
||||
TEST_SYNC_POINT("DBImpl::BGWorkFlush:done");
|
||||
}
|
||||
|
||||
@@ -1976,7 +2043,7 @@ void DBImpl::BGWorkPurge(void* db) {
|
||||
TEST_SYNC_POINT("DBImpl::BGWorkPurge:end");
|
||||
}
|
||||
|
||||
void DBImpl::UnscheduleCallback(void* arg) {
|
||||
void DBImpl::UnscheduleCompactionCallback(void* arg) {
|
||||
CompactionArg ca = *(reinterpret_cast<CompactionArg*>(arg));
|
||||
delete reinterpret_cast<CompactionArg*>(arg);
|
||||
if (ca.prepicked_compaction != nullptr) {
|
||||
@@ -1985,11 +2052,17 @@ void DBImpl::UnscheduleCallback(void* arg) {
|
||||
}
|
||||
delete ca.prepicked_compaction;
|
||||
}
|
||||
TEST_SYNC_POINT("DBImpl::UnscheduleCallback");
|
||||
TEST_SYNC_POINT("DBImpl::UnscheduleCompactionCallback");
|
||||
}
|
||||
|
||||
void DBImpl::UnscheduleFlushCallback(void* arg) {
|
||||
delete reinterpret_cast<FlushThreadArg*>(arg);
|
||||
TEST_SYNC_POINT("DBImpl::UnscheduleFlushCallback");
|
||||
}
|
||||
|
||||
Status DBImpl::BackgroundFlush(bool* made_progress, JobContext* job_context,
|
||||
LogBuffer* log_buffer, FlushReason* reason) {
|
||||
LogBuffer* log_buffer, FlushReason* reason,
|
||||
Env::Priority thread_pri) {
|
||||
mutex_.AssertHeld();
|
||||
|
||||
Status status;
|
||||
@@ -2011,6 +2084,7 @@ Status DBImpl::BackgroundFlush(bool* made_progress, JobContext* job_context,
|
||||
autovector<BGFlushArg> bg_flush_args;
|
||||
std::vector<SuperVersionContext>& superversion_contexts =
|
||||
job_context->superversion_contexts;
|
||||
autovector<ColumnFamilyData*> column_families_not_to_flush;
|
||||
while (!flush_queue_.empty()) {
|
||||
// This cfd is already referenced
|
||||
const FlushRequest& flush_req = PopFirstFromFlushQueue();
|
||||
@@ -2021,9 +2095,7 @@ Status DBImpl::BackgroundFlush(bool* made_progress, JobContext* job_context,
|
||||
ColumnFamilyData* cfd = iter.first;
|
||||
if (cfd->IsDropped() || !cfd->imm()->IsFlushPending()) {
|
||||
// can't flush this CF, try next one
|
||||
if (cfd->Unref()) {
|
||||
delete cfd;
|
||||
}
|
||||
column_families_not_to_flush.push_back(cfd);
|
||||
continue;
|
||||
}
|
||||
superversion_contexts.emplace_back(SuperVersionContext(true));
|
||||
@@ -2050,7 +2122,7 @@ Status DBImpl::BackgroundFlush(bool* made_progress, JobContext* job_context,
|
||||
bg_compaction_scheduled_);
|
||||
}
|
||||
status = FlushMemTablesToOutputFiles(bg_flush_args, made_progress,
|
||||
job_context, log_buffer);
|
||||
job_context, log_buffer, thread_pri);
|
||||
// All the CFDs in the FlushReq must have the same flush reason, so just
|
||||
// grab the first one
|
||||
*reason = bg_flush_args[0].cfd_->GetFlushReason();
|
||||
@@ -2062,10 +2134,15 @@ Status DBImpl::BackgroundFlush(bool* made_progress, JobContext* job_context,
|
||||
}
|
||||
}
|
||||
}
|
||||
for (auto cfd : column_families_not_to_flush) {
|
||||
if (cfd->Unref()) {
|
||||
delete cfd;
|
||||
}
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
void DBImpl::BackgroundCallFlush() {
|
||||
void DBImpl::BackgroundCallFlush(Env::Priority thread_pri) {
|
||||
bool made_progress = false;
|
||||
JobContext job_context(next_job_id_.fetch_add(1), true);
|
||||
|
||||
@@ -2082,8 +2159,8 @@ void DBImpl::BackgroundCallFlush() {
|
||||
CaptureCurrentFileNumberInPendingOutputs();
|
||||
FlushReason reason;
|
||||
|
||||
Status s =
|
||||
BackgroundFlush(&made_progress, &job_context, &log_buffer, &reason);
|
||||
Status s = BackgroundFlush(&made_progress, &job_context, &log_buffer,
|
||||
&reason, thread_pri);
|
||||
if (!s.ok() && !s.IsShutdownInProgress() &&
|
||||
reason != FlushReason::kErrorRecovery) {
|
||||
// Wait a little bit before retrying background flush in
|
||||
@@ -2166,12 +2243,12 @@ void DBImpl::BackgroundCallCompaction(PrepickedCompaction* prepicked_compaction,
|
||||
bg_bottom_compaction_scheduled_) ||
|
||||
(bg_thread_pri == Env::Priority::LOW && bg_compaction_scheduled_));
|
||||
Status s = BackgroundCompaction(&made_progress, &job_context, &log_buffer,
|
||||
prepicked_compaction);
|
||||
prepicked_compaction, bg_thread_pri);
|
||||
TEST_SYNC_POINT("BackgroundCallCompaction:1");
|
||||
if (s.IsBusy()) {
|
||||
bg_cv_.SignalAll(); // In case a waiter can proceed despite the error
|
||||
mutex_.Unlock();
|
||||
env_->SleepForMicroseconds(10000); // prevent hot loop
|
||||
env_->SleepForMicroseconds(10000); // prevent hot loop
|
||||
mutex_.Lock();
|
||||
} else if (!s.ok() && !s.IsShutdownInProgress()) {
|
||||
// Wait a little bit before retrying background compaction in
|
||||
@@ -2253,7 +2330,8 @@ void DBImpl::BackgroundCallCompaction(PrepickedCompaction* prepicked_compaction,
|
||||
Status DBImpl::BackgroundCompaction(bool* made_progress,
|
||||
JobContext* job_context,
|
||||
LogBuffer* log_buffer,
|
||||
PrepickedCompaction* prepicked_compaction) {
|
||||
PrepickedCompaction* prepicked_compaction,
|
||||
Env::Priority thread_pri) {
|
||||
ManualCompactionState* manual_compaction =
|
||||
prepicked_compaction == nullptr
|
||||
? nullptr
|
||||
@@ -2416,8 +2494,8 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
|
||||
status = Status::CompactionTooLarge();
|
||||
} else {
|
||||
// update statistics
|
||||
MeasureTime(stats_, NUM_FILES_IN_SINGLE_COMPACTION,
|
||||
c->inputs(0)->size());
|
||||
RecordInHistogram(stats_, NUM_FILES_IN_SINGLE_COMPACTION,
|
||||
c->inputs(0)->size());
|
||||
// There are three things that can change compaction score:
|
||||
// 1) When flush or compaction finish. This case is covered by
|
||||
// InstallSuperVersionAndScheduleWork
|
||||
@@ -2566,7 +2644,7 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
|
||||
ca->prepicked_compaction->task_token = std::move(task_token);
|
||||
++bg_bottom_compaction_scheduled_;
|
||||
env_->Schedule(&DBImpl::BGWorkBottomCompaction, ca, Env::Priority::BOTTOM,
|
||||
this, &DBImpl::UnscheduleCallback);
|
||||
this, &DBImpl::UnscheduleCompactionCallback);
|
||||
} else {
|
||||
TEST_SYNC_POINT_CALLBACK("DBImpl::BackgroundCompaction:BeforeCompaction",
|
||||
c->column_family_data());
|
||||
@@ -2580,16 +2658,23 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
|
||||
GetSnapshotContext(job_context, &snapshot_seqs,
|
||||
&earliest_write_conflict_snapshot, &snapshot_checker);
|
||||
assert(is_snapshot_supported_ || snapshots_.empty());
|
||||
SnapshotListFetchCallbackImpl fetch_callback(
|
||||
this, env_, c->mutable_cf_options()->snap_refresh_nanos,
|
||||
immutable_db_options_.info_log.get());
|
||||
CompactionJob compaction_job(
|
||||
job_context->job_id, c.get(), immutable_db_options_,
|
||||
env_options_for_compaction_, versions_.get(), &shutting_down_,
|
||||
preserve_deletes_seqnum_.load(), log_buffer, directories_.GetDbDir(),
|
||||
GetDataDir(c->column_family_data(), c->output_path_id()), stats_,
|
||||
&mutex_, &error_handler_, snapshot_seqs, earliest_write_conflict_snapshot,
|
||||
snapshot_checker, table_cache_, &event_logger_,
|
||||
c->mutable_cf_options()->paranoid_file_checks,
|
||||
&mutex_, &error_handler_, snapshot_seqs,
|
||||
earliest_write_conflict_snapshot, snapshot_checker, table_cache_,
|
||||
&event_logger_, c->mutable_cf_options()->paranoid_file_checks,
|
||||
c->mutable_cf_options()->report_bg_io_stats, dbname_,
|
||||
&compaction_job_stats);
|
||||
&compaction_job_stats, thread_pri,
|
||||
immutable_db_options_.max_subcompactions <= 1 &&
|
||||
c->mutable_cf_options()->snap_refresh_nanos > 0
|
||||
? &fetch_callback
|
||||
: nullptr);
|
||||
compaction_job.Prepare();
|
||||
|
||||
NotifyOnCompactionBegin(c->column_family_data(), c.get(), status,
|
||||
@@ -2812,7 +2897,7 @@ void DBImpl::BuildCompactionJobInfo(
|
||||
fmd->fd.GetNumber(), fmd->fd.GetPathId());
|
||||
compaction_job_info->input_files.push_back(fn);
|
||||
if (compaction_job_info->table_properties.count(fn) == 0) {
|
||||
shared_ptr<const TableProperties> tp;
|
||||
std::shared_ptr<const TableProperties> tp;
|
||||
auto s = current->GetTableProperties(&tp, fmd, &fn);
|
||||
if (s.ok()) {
|
||||
compaction_job_info->table_properties[fn] = tp;
|
||||
@@ -2859,6 +2944,17 @@ void DBImpl::InstallSuperVersionAndScheduleWork(
|
||||
}
|
||||
cfd->InstallSuperVersion(sv_context, &mutex_, mutable_cf_options);
|
||||
|
||||
// There may be a small data race here. The snapshot tricking bottommost
|
||||
// compaction may already be released here. But assuming there will always be
|
||||
// newer snapshot created and released frequently, the compaction will be
|
||||
// triggered soon anyway.
|
||||
bottommost_files_mark_threshold_ = kMaxSequenceNumber;
|
||||
for (auto* my_cfd : *versions_->GetColumnFamilySet()) {
|
||||
bottommost_files_mark_threshold_ = std::min(
|
||||
bottommost_files_mark_threshold_,
|
||||
my_cfd->current()->storage_info()->bottommost_files_mark_threshold());
|
||||
}
|
||||
|
||||
// Whenever we install new SuperVersion, we might need to issue new flushes or
|
||||
// compactions.
|
||||
SchedulePendingCompaction(cfd);
|
||||
|
||||
+30
-13
@@ -26,10 +26,16 @@ void DBImpl::TEST_SwitchWAL() {
|
||||
SwitchWAL(&write_context);
|
||||
}
|
||||
|
||||
bool DBImpl::TEST_WALBufferIsEmpty() {
|
||||
InstrumentedMutexLock wl(&log_write_mutex_);
|
||||
bool DBImpl::TEST_WALBufferIsEmpty(bool lock) {
|
||||
if (lock) {
|
||||
log_write_mutex_.Lock();
|
||||
}
|
||||
log::Writer* cur_log_writer = logs_.back().writer;
|
||||
return cur_log_writer->TEST_BufferIsEmpty();
|
||||
auto res = cur_log_writer->TEST_BufferIsEmpty();
|
||||
if (lock) {
|
||||
log_write_mutex_.Unlock();
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
int64_t DBImpl::TEST_MaxNextLevelOverlappingBytes(
|
||||
@@ -87,8 +93,9 @@ Status DBImpl::TEST_CompactRange(int level, const Slice* begin,
|
||||
cfd->ioptions()->compaction_style == kCompactionStyleFIFO)
|
||||
? level
|
||||
: level + 1;
|
||||
return RunManualCompaction(cfd, level, output_level, 0, 0, begin, end, true,
|
||||
disallow_trivial_move);
|
||||
return RunManualCompaction(cfd, level, output_level, CompactRangeOptions(),
|
||||
begin, end, true, disallow_trivial_move,
|
||||
port::kMaxUint64 /*max_file_num_to_ignore*/);
|
||||
}
|
||||
|
||||
Status DBImpl::TEST_SwitchMemtable(ColumnFamilyData* cfd) {
|
||||
@@ -101,7 +108,7 @@ Status DBImpl::TEST_SwitchMemtable(ColumnFamilyData* cfd) {
|
||||
}
|
||||
|
||||
Status DBImpl::TEST_FlushMemTable(bool wait, bool allow_write_stall,
|
||||
ColumnFamilyHandle* cfh) {
|
||||
ColumnFamilyHandle* cfh) {
|
||||
FlushOptions fo;
|
||||
fo.wait = wait;
|
||||
fo.allow_write_stall = allow_write_stall;
|
||||
@@ -143,13 +150,9 @@ Status DBImpl::TEST_WaitForCompact(bool wait_unscheduled) {
|
||||
return error_handler_.GetBGError();
|
||||
}
|
||||
|
||||
void DBImpl::TEST_LockMutex() {
|
||||
mutex_.Lock();
|
||||
}
|
||||
void DBImpl::TEST_LockMutex() { mutex_.Lock(); }
|
||||
|
||||
void DBImpl::TEST_UnlockMutex() {
|
||||
mutex_.Unlock();
|
||||
}
|
||||
void DBImpl::TEST_UnlockMutex() { mutex_.Unlock(); }
|
||||
|
||||
void* DBImpl::TEST_BeginWrite() {
|
||||
auto w = new WriteThread::Writer();
|
||||
@@ -243,10 +246,24 @@ size_t DBImpl::TEST_GetWalPreallocateBlockSize(
|
||||
return GetWalPreallocateBlockSize(write_buffer_size);
|
||||
}
|
||||
|
||||
void DBImpl::TEST_WaitForTimedTaskRun(std::function<void()> callback) const {
|
||||
void DBImpl::TEST_WaitForDumpStatsRun(std::function<void()> callback) const {
|
||||
if (thread_dump_stats_ != nullptr) {
|
||||
thread_dump_stats_->TEST_WaitForRun(callback);
|
||||
}
|
||||
}
|
||||
|
||||
void DBImpl::TEST_WaitForPersistStatsRun(std::function<void()> callback) const {
|
||||
if (thread_persist_stats_ != nullptr) {
|
||||
thread_persist_stats_->TEST_WaitForRun(callback);
|
||||
}
|
||||
}
|
||||
|
||||
bool DBImpl::TEST_IsPersistentStatsEnabled() const {
|
||||
return thread_persist_stats_ && thread_persist_stats_->IsRunning();
|
||||
}
|
||||
|
||||
size_t DBImpl::TEST_EstiamteStatsHistorySize() const {
|
||||
return EstiamteStatsHistorySize();
|
||||
}
|
||||
} // namespace rocksdb
|
||||
#endif // NDEBUG
|
||||
|
||||
+23
-19
@@ -108,7 +108,7 @@ void DBImpl::FindObsoleteFiles(JobContext* job_context, bool force,
|
||||
versions_->AddLiveFiles(&job_context->sst_live);
|
||||
if (doing_the_full_scan) {
|
||||
InfoLogPrefix info_log_prefix(!immutable_db_options_.db_log_dir.empty(),
|
||||
dbname_);
|
||||
dbname_);
|
||||
std::set<std::string> paths;
|
||||
for (size_t path_id = 0; path_id < immutable_db_options_.db_paths.size();
|
||||
path_id++) {
|
||||
@@ -152,8 +152,7 @@ void DBImpl::FindObsoleteFiles(JobContext* job_context, bool force,
|
||||
}
|
||||
|
||||
// TODO(icanadi) clean up this mess to avoid having one-off "/" prefixes
|
||||
job_context->full_scan_candidate_files.emplace_back(
|
||||
"/" + file, path);
|
||||
job_context->full_scan_candidate_files.emplace_back("/" + file, path);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -163,8 +162,8 @@ void DBImpl::FindObsoleteFiles(JobContext* job_context, bool force,
|
||||
env_->GetChildren(immutable_db_options_.wal_dir,
|
||||
&log_files); // Ignore errors
|
||||
for (const std::string& log_file : log_files) {
|
||||
job_context->full_scan_candidate_files.emplace_back(log_file,
|
||||
immutable_db_options_.wal_dir);
|
||||
job_context->full_scan_candidate_files.emplace_back(
|
||||
log_file, immutable_db_options_.wal_dir);
|
||||
}
|
||||
}
|
||||
// Add info log files in db_log_dir
|
||||
@@ -174,8 +173,8 @@ void DBImpl::FindObsoleteFiles(JobContext* job_context, bool force,
|
||||
// Ignore errors
|
||||
env_->GetChildren(immutable_db_options_.db_log_dir, &info_log_files);
|
||||
for (std::string& log_file : info_log_files) {
|
||||
job_context->full_scan_candidate_files.emplace_back(log_file,
|
||||
immutable_db_options_.db_log_dir);
|
||||
job_context->full_scan_candidate_files.emplace_back(
|
||||
log_file, immutable_db_options_.db_log_dir);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -260,14 +259,14 @@ void DBImpl::DeleteObsoleteFileImpl(int job_id, const std::string& fname,
|
||||
const std::string& path_to_sync,
|
||||
FileType type, uint64_t number) {
|
||||
Status file_deletion_status;
|
||||
if (type == kTableFile) {
|
||||
if (type == kTableFile || type == kLogFile) {
|
||||
file_deletion_status =
|
||||
DeleteSSTFile(&immutable_db_options_, fname, path_to_sync);
|
||||
DeleteDBFile(&immutable_db_options_, fname, path_to_sync);
|
||||
} else {
|
||||
file_deletion_status = env_->DeleteFile(fname);
|
||||
}
|
||||
TEST_SYNC_POINT_CALLBACK("DBImpl::DeleteObsoleteFileImpl:AfterDeletion",
|
||||
&file_deletion_status);
|
||||
&file_deletion_status);
|
||||
if (file_deletion_status.ok()) {
|
||||
ROCKS_LOG_DEBUG(immutable_db_options_.info_log,
|
||||
"[JOB %d] Delete %s type=%d #%" PRIu64 " -- %s\n", job_id,
|
||||
@@ -322,7 +321,8 @@ void DBImpl::PurgeObsoleteFiles(JobContext& state, bool schedule_only) {
|
||||
const char* kDumbDbName = "";
|
||||
for (auto& file : state.sst_delete_files) {
|
||||
candidate_files.emplace_back(
|
||||
MakeTableFileName(kDumbDbName, file.metadata->fd.GetNumber()), file.path);
|
||||
MakeTableFileName(kDumbDbName, file.metadata->fd.GetNumber()),
|
||||
file.path);
|
||||
if (file.metadata->table_reader_handle) {
|
||||
table_cache_->Release(file.metadata->table_reader_handle);
|
||||
}
|
||||
@@ -332,7 +332,7 @@ void DBImpl::PurgeObsoleteFiles(JobContext& state, bool schedule_only) {
|
||||
for (auto file_num : state.log_delete_files) {
|
||||
if (file_num > 0) {
|
||||
candidate_files.emplace_back(LogFileName(kDumbDbName, file_num),
|
||||
immutable_db_options_.wal_dir);
|
||||
immutable_db_options_.wal_dir);
|
||||
}
|
||||
}
|
||||
for (const auto& filename : state.manifest_delete_files) {
|
||||
@@ -465,13 +465,12 @@ void DBImpl::PurgeObsoleteFiles(JobContext& state, bool schedule_only) {
|
||||
} else {
|
||||
dir_to_sync =
|
||||
(type == kLogFile) ? immutable_db_options_.wal_dir : dbname_;
|
||||
fname = dir_to_sync
|
||||
+ (
|
||||
(!dir_to_sync.empty() && dir_to_sync.back() == '/') ||
|
||||
(!to_delete.empty() && to_delete.front() == '/')
|
||||
? "" : "/"
|
||||
)
|
||||
+ to_delete;
|
||||
fname = dir_to_sync +
|
||||
((!dir_to_sync.empty() && dir_to_sync.back() == '/') ||
|
||||
(!to_delete.empty() && to_delete.front() == '/')
|
||||
? ""
|
||||
: "/") +
|
||||
to_delete;
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
@@ -482,6 +481,11 @@ void DBImpl::PurgeObsoleteFiles(JobContext& state, bool schedule_only) {
|
||||
}
|
||||
#endif // !ROCKSDB_LITE
|
||||
|
||||
for (const auto w : state.logs_to_free) {
|
||||
// TODO: maybe check the return value of Close.
|
||||
w->Close();
|
||||
}
|
||||
|
||||
Status file_deletion_status;
|
||||
if (schedule_only) {
|
||||
InstrumentedMutexLock guard_lock(&mutex_);
|
||||
|
||||
+74
-36
@@ -176,7 +176,7 @@ static Status ValidateOptions(
|
||||
return s;
|
||||
}
|
||||
|
||||
if (cfd.options.ttl > 0 || cfd.options.compaction_options_fifo.ttl > 0) {
|
||||
if (cfd.options.ttl > 0) {
|
||||
if (db_options.max_open_files != -1) {
|
||||
return Status::NotSupported(
|
||||
"TTL is only supported when files are always "
|
||||
@@ -188,6 +188,20 @@ static Status ValidateOptions(
|
||||
"TTL is only supported in Block-Based Table format. ");
|
||||
}
|
||||
}
|
||||
|
||||
if (cfd.options.periodic_compaction_seconds > 0) {
|
||||
if (db_options.max_open_files != -1) {
|
||||
return Status::NotSupported(
|
||||
"Periodic Compaction is only supported when files are always "
|
||||
"kept open (set max_open_files = -1). ");
|
||||
}
|
||||
if (cfd.options.table_factory->Name() !=
|
||||
BlockBasedTableFactory().Name()) {
|
||||
return Status::NotSupported(
|
||||
"Periodic Compaction is only supported in "
|
||||
"Block-Based Table format. ");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (db_options.db_paths.size() > 4) {
|
||||
@@ -512,7 +526,7 @@ Status DBImpl::RecoverLogFiles(const std::vector<uint64_t>& log_numbers,
|
||||
Logger* info_log;
|
||||
const char* fname;
|
||||
Status* status; // nullptr if immutable_db_options_.paranoid_checks==false
|
||||
virtual void Corruption(size_t bytes, const Status& s) override {
|
||||
void Corruption(size_t bytes, const Status& s) override {
|
||||
ROCKS_LOG_WARN(info_log, "%s%s: dropping %d bytes; %s",
|
||||
(this->status == nullptr ? "(ignoring error) " : ""),
|
||||
fname, static_cast<int>(bytes), s.ToString().c_str());
|
||||
@@ -580,7 +594,7 @@ Status DBImpl::RecoverLogFiles(const std::vector<uint64_t>& log_numbers,
|
||||
|
||||
ROCKS_LOG_INFO(immutable_db_options_.info_log,
|
||||
"Recovering log #%" PRIu64 " mode %d", log_number,
|
||||
immutable_db_options_.wal_recovery_mode);
|
||||
static_cast<int>(immutable_db_options_.wal_recovery_mode));
|
||||
auto logFileDropped = [this, &fname]() {
|
||||
uint64_t bytes;
|
||||
if (env_->GetFileSize(fname, &bytes).ok()) {
|
||||
@@ -629,8 +643,7 @@ Status DBImpl::RecoverLogFiles(const std::vector<uint64_t>& log_numbers,
|
||||
// to be skipped instead of propagating bad information (like overly
|
||||
// large sequence numbers).
|
||||
log::Reader reader(immutable_db_options_.info_log, std::move(file_reader),
|
||||
&reporter, true /*checksum*/, log_number,
|
||||
false /* retry_after_eof */);
|
||||
&reporter, true /*checksum*/, log_number);
|
||||
|
||||
// Determine if we should tolerate incomplete records at the tail end of the
|
||||
// Read all the records and add to a memtable
|
||||
@@ -724,7 +737,8 @@ Status DBImpl::RecoverLogFiles(const std::vector<uint64_t>& log_numbers,
|
||||
" mode %d log filter %s returned "
|
||||
"more records (%d) than original (%d) which is not allowed. "
|
||||
"Aborting recovery.",
|
||||
log_number, immutable_db_options_.wal_recovery_mode,
|
||||
log_number,
|
||||
static_cast<int>(immutable_db_options_.wal_recovery_mode),
|
||||
immutable_db_options_.wal_filter->Name(), new_count,
|
||||
original_count);
|
||||
status = Status::NotSupported(
|
||||
@@ -1029,6 +1043,7 @@ Status DBImpl::WriteLevel0TableForRecovery(int job_id, ColumnFamilyData* cfd,
|
||||
cfd->int_tbl_prop_collector_factories(), cfd->GetID(), cfd->GetName(),
|
||||
snapshot_seqs, earliest_write_conflict_snapshot, snapshot_checker,
|
||||
GetCompressionFlush(*cfd->ioptions(), mutable_cf_options),
|
||||
mutable_cf_options.sample_for_compression,
|
||||
cfd->ioptions()->compression_opts, paranoid_file_checks,
|
||||
cfd->internal_stats(), TableFileCreationReason::kRecovery,
|
||||
&event_logger_, job_id, Env::IO_HIGH, nullptr /* table_properties */,
|
||||
@@ -1058,7 +1073,7 @@ Status DBImpl::WriteLevel0TableForRecovery(int job_id, ColumnFamilyData* cfd,
|
||||
stats.micros = env_->NowMicros() - start_micros;
|
||||
stats.bytes_written = meta.fd.GetFileSize();
|
||||
stats.num_output_files = 1;
|
||||
cfd->internal_stats()->AddCompactionStats(level, stats);
|
||||
cfd->internal_stats()->AddCompactionStats(level, Env::Priority::USER, stats);
|
||||
cfd->internal_stats()->AddCFStats(InternalStats::BYTES_FLUSHED,
|
||||
meta.fd.GetFileSize());
|
||||
RecordTick(stats_, COMPACT_WRITE_BYTES, meta.fd.GetFileSize());
|
||||
@@ -1091,6 +1106,45 @@ Status DB::Open(const DBOptions& db_options, const std::string& dbname,
|
||||
!kSeqPerBatch, kBatchPerTxn);
|
||||
}
|
||||
|
||||
Status DBImpl::CreateWAL(uint64_t log_file_num, uint64_t recycle_log_number,
|
||||
size_t preallocate_block_size, log::Writer** new_log) {
|
||||
Status s;
|
||||
std::unique_ptr<WritableFile> lfile;
|
||||
|
||||
DBOptions db_options =
|
||||
BuildDBOptions(immutable_db_options_, mutable_db_options_);
|
||||
EnvOptions opt_env_options =
|
||||
env_->OptimizeForLogWrite(env_options_, db_options);
|
||||
std::string log_fname =
|
||||
LogFileName(immutable_db_options_.wal_dir, log_file_num);
|
||||
|
||||
if (recycle_log_number) {
|
||||
ROCKS_LOG_INFO(immutable_db_options_.info_log,
|
||||
"reusing log %" PRIu64 " from recycle list\n",
|
||||
recycle_log_number);
|
||||
std::string old_log_fname =
|
||||
LogFileName(immutable_db_options_.wal_dir, recycle_log_number);
|
||||
s = env_->ReuseWritableFile(log_fname, old_log_fname, &lfile,
|
||||
opt_env_options);
|
||||
} else {
|
||||
s = NewWritableFile(env_, log_fname, &lfile, opt_env_options);
|
||||
}
|
||||
|
||||
if (s.ok()) {
|
||||
lfile->SetWriteLifeTimeHint(CalculateWALWriteHint());
|
||||
lfile->SetPreallocationBlockSize(preallocate_block_size);
|
||||
|
||||
const auto& listeners = immutable_db_options_.listeners;
|
||||
std::unique_ptr<WritableFileWriter> file_writer(
|
||||
new WritableFileWriter(std::move(lfile), log_fname, opt_env_options,
|
||||
env_, nullptr /* stats */, listeners));
|
||||
*new_log = new log::Writer(std::move(file_writer), log_file_num,
|
||||
immutable_db_options_.recycle_log_file_num > 0,
|
||||
immutable_db_options_.manual_wal_flush);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
Status DBImpl::Open(const DBOptions& db_options, const std::string& dbname,
|
||||
const std::vector<ColumnFamilyDescriptor>& column_families,
|
||||
std::vector<ColumnFamilyHandle*>* handles, DB** dbptr,
|
||||
@@ -1151,40 +1205,23 @@ Status DBImpl::Open(const DBOptions& db_options, const std::string& dbname,
|
||||
return s;
|
||||
}
|
||||
impl->mutex_.Lock();
|
||||
auto write_hint = impl->CalculateWALWriteHint();
|
||||
// Handles create_if_missing, error_if_exists
|
||||
s = impl->Recover(column_families);
|
||||
if (s.ok()) {
|
||||
uint64_t new_log_number = impl->versions_->NewFileNumber();
|
||||
std::unique_ptr<WritableFile> lfile;
|
||||
EnvOptions soptions(db_options);
|
||||
EnvOptions opt_env_options =
|
||||
impl->immutable_db_options_.env->OptimizeForLogWrite(
|
||||
soptions, BuildDBOptions(impl->immutable_db_options_,
|
||||
impl->mutable_db_options_));
|
||||
std::string log_fname =
|
||||
LogFileName(impl->immutable_db_options_.wal_dir, new_log_number);
|
||||
s = NewWritableFile(impl->immutable_db_options_.env, log_fname, &lfile,
|
||||
opt_env_options);
|
||||
log::Writer* new_log = nullptr;
|
||||
const size_t preallocate_block_size =
|
||||
impl->GetWalPreallocateBlockSize(max_write_buffer_size);
|
||||
s = impl->CreateWAL(new_log_number, 0 /*recycle_log_number*/,
|
||||
preallocate_block_size, &new_log);
|
||||
if (s.ok()) {
|
||||
lfile->SetWriteLifeTimeHint(write_hint);
|
||||
lfile->SetPreallocationBlockSize(
|
||||
impl->GetWalPreallocateBlockSize(max_write_buffer_size));
|
||||
{
|
||||
InstrumentedMutexLock wl(&impl->log_write_mutex_);
|
||||
impl->logfile_number_ = new_log_number;
|
||||
const auto& listeners = impl->immutable_db_options_.listeners;
|
||||
std::unique_ptr<WritableFileWriter> file_writer(
|
||||
new WritableFileWriter(std::move(lfile), log_fname, opt_env_options,
|
||||
impl->env_, nullptr /* stats */, listeners));
|
||||
impl->logs_.emplace_back(
|
||||
new_log_number,
|
||||
new log::Writer(
|
||||
std::move(file_writer), new_log_number,
|
||||
impl->immutable_db_options_.recycle_log_file_num > 0,
|
||||
impl->immutable_db_options_.manual_wal_flush));
|
||||
}
|
||||
InstrumentedMutexLock wl(&impl->log_write_mutex_);
|
||||
impl->logfile_number_ = new_log_number;
|
||||
assert(new_log != nullptr);
|
||||
impl->logs_.emplace_back(new_log_number, new_log);
|
||||
}
|
||||
|
||||
if (s.ok()) {
|
||||
// set column family handles
|
||||
for (auto cf : column_families) {
|
||||
auto cfd =
|
||||
@@ -1316,7 +1353,8 @@ Status DBImpl::Open(const DBOptions& db_options, const std::string& dbname,
|
||||
#endif // !ROCKSDB_LITE
|
||||
|
||||
if (s.ok()) {
|
||||
ROCKS_LOG_INFO(impl->immutable_db_options_.info_log, "DB pointer %p", impl);
|
||||
ROCKS_LOG_HEADER(impl->immutable_db_options_.info_log, "DB pointer %p",
|
||||
impl);
|
||||
LogFlush(impl->immutable_db_options_.info_log);
|
||||
assert(impl->TEST_WALBufferIsEmpty());
|
||||
// If the assert above fails then we need to FlushWAL before returning
|
||||
|
||||
@@ -60,7 +60,7 @@ Status DBImplReadOnly::Get(const ReadOptions& read_options,
|
||||
RecordTick(stats_, NUMBER_KEYS_READ);
|
||||
size_t size = pinnable_val->size();
|
||||
RecordTick(stats_, BYTES_READ, size);
|
||||
MeasureTime(stats_, BYTES_PER_READ, size);
|
||||
RecordInHistogram(stats_, BYTES_PER_READ, size);
|
||||
PERF_COUNTER_ADD(get_read_bytes, size);
|
||||
return s;
|
||||
}
|
||||
|
||||
@@ -7,9 +7,9 @@
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
|
||||
#include "db/db_impl.h"
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include "db/db_impl.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
@@ -122,6 +122,6 @@ class DBImplReadOnly : public DBImpl {
|
||||
DBImplReadOnly(const DBImplReadOnly&);
|
||||
void operator=(const DBImplReadOnly&);
|
||||
};
|
||||
}
|
||||
} // namespace rocksdb
|
||||
|
||||
#endif // !ROCKSDB_LITE
|
||||
|
||||
@@ -0,0 +1,524 @@
|
||||
// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under both the GPLv2 (found in the
|
||||
// COPYING file in the root directory) and Apache 2.0 License
|
||||
// (found in the LICENSE.Apache file in the root directory).
|
||||
|
||||
#include "db/db_impl_secondary.h"
|
||||
|
||||
#ifndef __STDC_FORMAT_MACROS
|
||||
#define __STDC_FORMAT_MACROS
|
||||
#endif
|
||||
#include <inttypes.h>
|
||||
|
||||
#include "db/db_iter.h"
|
||||
#include "db/merge_context.h"
|
||||
#include "monitoring/perf_context_imp.h"
|
||||
#include "util/auto_roll_logger.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
|
||||
DBImplSecondary::DBImplSecondary(const DBOptions& db_options,
|
||||
const std::string& dbname)
|
||||
: DBImpl(db_options, dbname) {
|
||||
ROCKS_LOG_INFO(immutable_db_options_.info_log,
|
||||
"Opening the db in secondary mode");
|
||||
LogFlush(immutable_db_options_.info_log);
|
||||
}
|
||||
|
||||
DBImplSecondary::~DBImplSecondary() {}
|
||||
|
||||
Status DBImplSecondary::Recover(
|
||||
const std::vector<ColumnFamilyDescriptor>& column_families,
|
||||
bool /*readonly*/, bool /*error_if_log_file_exist*/,
|
||||
bool /*error_if_data_exists_in_logs*/) {
|
||||
mutex_.AssertHeld();
|
||||
|
||||
Status s;
|
||||
s = static_cast<ReactiveVersionSet*>(versions_.get())
|
||||
->Recover(column_families, &manifest_reader_, &manifest_reporter_,
|
||||
&manifest_reader_status_);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
if (immutable_db_options_.paranoid_checks && s.ok()) {
|
||||
s = CheckConsistency();
|
||||
}
|
||||
// Initial max_total_in_memory_state_ before recovery logs.
|
||||
max_total_in_memory_state_ = 0;
|
||||
for (auto cfd : *versions_->GetColumnFamilySet()) {
|
||||
auto* mutable_cf_options = cfd->GetLatestMutableCFOptions();
|
||||
max_total_in_memory_state_ += mutable_cf_options->write_buffer_size *
|
||||
mutable_cf_options->max_write_buffer_number;
|
||||
}
|
||||
if (s.ok()) {
|
||||
default_cf_handle_ = new ColumnFamilyHandleImpl(
|
||||
versions_->GetColumnFamilySet()->GetDefault(), this, &mutex_);
|
||||
default_cf_internal_stats_ = default_cf_handle_->cfd()->internal_stats();
|
||||
single_column_family_mode_ =
|
||||
versions_->GetColumnFamilySet()->NumberOfColumnFamilies() == 1;
|
||||
|
||||
// Recover from all newer log files than the ones named in the
|
||||
// descriptor.
|
||||
std::vector<std::string> filenames;
|
||||
s = env_->GetChildren(immutable_db_options_.wal_dir, &filenames);
|
||||
if (s.IsNotFound()) {
|
||||
return Status::InvalidArgument("Failed to open wal_dir",
|
||||
immutable_db_options_.wal_dir);
|
||||
} else if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
|
||||
std::vector<uint64_t> logs;
|
||||
// if log_readers_ is non-empty, it means we have applied all logs with log
|
||||
// numbers smaller than the smallest log in log_readers_, so there is no
|
||||
// need to pass these logs to RecoverLogFiles
|
||||
uint64_t log_number_min = 0;
|
||||
if (log_readers_.size() > 0) {
|
||||
log_number_min = log_readers_.begin()->first;
|
||||
}
|
||||
for (size_t i = 0; i < filenames.size(); i++) {
|
||||
uint64_t number;
|
||||
FileType type;
|
||||
if (ParseFileName(filenames[i], &number, &type) && type == kLogFile &&
|
||||
number >= log_number_min) {
|
||||
logs.push_back(number);
|
||||
}
|
||||
}
|
||||
|
||||
if (!logs.empty()) {
|
||||
// Recover in the order in which the logs were generated
|
||||
std::sort(logs.begin(), logs.end());
|
||||
SequenceNumber next_sequence(kMaxSequenceNumber);
|
||||
s = RecoverLogFiles(logs, &next_sequence, true /*read_only*/);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: update options_file_number_ needed?
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
// try to find log reader using log_number from log_readers_ map, initialize
|
||||
// if it doesn't exist
|
||||
Status DBImplSecondary::MaybeInitLogReader(
|
||||
uint64_t log_number, log::FragmentBufferedReader** log_reader) {
|
||||
auto iter = log_readers_.find(log_number);
|
||||
// make sure the log file is still present
|
||||
if (iter == log_readers_.end() ||
|
||||
iter->second->reader_->GetLogNumber() != log_number) {
|
||||
// delete the obsolete log reader if log number mismatch
|
||||
if (iter != log_readers_.end()) {
|
||||
log_readers_.erase(iter);
|
||||
}
|
||||
// initialize log reader from log_number
|
||||
// TODO: min_log_number_to_keep_2pc check needed?
|
||||
// Open the log file
|
||||
std::string fname = LogFileName(immutable_db_options_.wal_dir, log_number);
|
||||
ROCKS_LOG_INFO(immutable_db_options_.info_log,
|
||||
"Recovering log #%" PRIu64 " mode %d", log_number,
|
||||
static_cast<int>(immutable_db_options_.wal_recovery_mode));
|
||||
|
||||
std::unique_ptr<SequentialFileReader> file_reader;
|
||||
{
|
||||
std::unique_ptr<SequentialFile> file;
|
||||
Status status = env_->NewSequentialFile(
|
||||
fname, &file, env_->OptimizeForLogRead(env_options_));
|
||||
if (!status.ok()) {
|
||||
*log_reader = nullptr;
|
||||
return status;
|
||||
}
|
||||
file_reader.reset(new SequentialFileReader(std::move(file), fname));
|
||||
}
|
||||
|
||||
// Create the log reader.
|
||||
LogReaderContainer* log_reader_container = new LogReaderContainer(
|
||||
env_, immutable_db_options_.info_log, std::move(fname),
|
||||
std::move(file_reader), log_number);
|
||||
log_readers_.insert(std::make_pair(
|
||||
log_number, std::unique_ptr<LogReaderContainer>(log_reader_container)));
|
||||
}
|
||||
iter = log_readers_.find(log_number);
|
||||
assert(iter != log_readers_.end());
|
||||
*log_reader = iter->second->reader_;
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
// After manifest recovery, replay WALs and refresh log_readers_ if necessary
|
||||
// REQUIRES: log_numbers are sorted in ascending order
|
||||
Status DBImplSecondary::RecoverLogFiles(
|
||||
const std::vector<uint64_t>& log_numbers, SequenceNumber* next_sequence,
|
||||
bool /*read_only*/) {
|
||||
mutex_.AssertHeld();
|
||||
Status status;
|
||||
for (auto log_number : log_numbers) {
|
||||
log::FragmentBufferedReader* reader = nullptr;
|
||||
status = MaybeInitLogReader(log_number, &reader);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
assert(reader != nullptr);
|
||||
}
|
||||
for (auto log_number : log_numbers) {
|
||||
auto it = log_readers_.find(log_number);
|
||||
assert(it != log_readers_.end());
|
||||
log::FragmentBufferedReader* reader = it->second->reader_;
|
||||
// Manually update the file number allocation counter in VersionSet.
|
||||
versions_->MarkFileNumberUsed(log_number);
|
||||
|
||||
// Determine if we should tolerate incomplete records at the tail end of the
|
||||
// Read all the records and add to a memtable
|
||||
std::string scratch;
|
||||
Slice record;
|
||||
WriteBatch batch;
|
||||
|
||||
while (reader->ReadRecord(&record, &scratch,
|
||||
immutable_db_options_.wal_recovery_mode) &&
|
||||
status.ok()) {
|
||||
if (record.size() < WriteBatchInternal::kHeader) {
|
||||
reader->GetReporter()->Corruption(
|
||||
record.size(), Status::Corruption("log record too small"));
|
||||
continue;
|
||||
}
|
||||
WriteBatchInternal::SetContents(&batch, record);
|
||||
// do not check sequence number because user may toggle disableWAL
|
||||
// between writes which breaks sequence number continuity guarantee
|
||||
|
||||
// If column family was not found, it might mean that the WAL write
|
||||
// batch references to the column family that was dropped after the
|
||||
// insert. We don't want to fail the whole write batch in that case --
|
||||
// we just ignore the update.
|
||||
// That's why we set ignore missing column families to true
|
||||
// passing null flush_scheduler will disable memtable flushing which is
|
||||
// needed for secondary instances
|
||||
bool has_valid_writes = false;
|
||||
status = WriteBatchInternal::InsertInto(
|
||||
&batch, column_family_memtables_.get(), nullptr /* flush_scheduler */,
|
||||
true, log_number, this, false /* concurrent_memtable_writes */,
|
||||
next_sequence, &has_valid_writes, seq_per_batch_, batch_per_txn_);
|
||||
if (!status.ok()) {
|
||||
// We are treating this as a failure while reading since we read valid
|
||||
// blocks that do not form coherent data
|
||||
reader->GetReporter()->Corruption(record.size(), status);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
|
||||
auto last_sequence = *next_sequence - 1;
|
||||
if ((*next_sequence != kMaxSequenceNumber) &&
|
||||
(versions_->LastSequence() <= last_sequence)) {
|
||||
versions_->SetLastAllocatedSequence(last_sequence);
|
||||
versions_->SetLastPublishedSequence(last_sequence);
|
||||
versions_->SetLastSequence(last_sequence);
|
||||
}
|
||||
}
|
||||
// remove logreaders from map after successfully recovering the WAL
|
||||
if (log_readers_.size() > 1) {
|
||||
auto eraseIter = log_readers_.begin();
|
||||
std::advance(eraseIter, log_readers_.size() - 1);
|
||||
log_readers_.erase(log_readers_.begin(), eraseIter);
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
// Implementation of the DB interface
|
||||
Status DBImplSecondary::Get(const ReadOptions& read_options,
|
||||
ColumnFamilyHandle* column_family, const Slice& key,
|
||||
PinnableSlice* value) {
|
||||
return GetImpl(read_options, column_family, key, value);
|
||||
}
|
||||
|
||||
Status DBImplSecondary::GetImpl(const ReadOptions& read_options,
|
||||
ColumnFamilyHandle* column_family,
|
||||
const Slice& key, PinnableSlice* pinnable_val) {
|
||||
assert(pinnable_val != nullptr);
|
||||
PERF_CPU_TIMER_GUARD(get_cpu_nanos, env_);
|
||||
StopWatch sw(env_, stats_, DB_GET);
|
||||
PERF_TIMER_GUARD(get_snapshot_time);
|
||||
|
||||
auto cfh = static_cast<ColumnFamilyHandleImpl*>(column_family);
|
||||
ColumnFamilyData* cfd = cfh->cfd();
|
||||
if (tracer_) {
|
||||
InstrumentedMutexLock lock(&trace_mutex_);
|
||||
if (tracer_) {
|
||||
tracer_->Get(column_family, key);
|
||||
}
|
||||
}
|
||||
// Acquire SuperVersion
|
||||
SuperVersion* super_version = GetAndRefSuperVersion(cfd);
|
||||
SequenceNumber snapshot = versions_->LastSequence();
|
||||
MergeContext merge_context;
|
||||
SequenceNumber max_covering_tombstone_seq = 0;
|
||||
Status s;
|
||||
LookupKey lkey(key, snapshot);
|
||||
PERF_TIMER_STOP(get_snapshot_time);
|
||||
|
||||
bool done = false;
|
||||
if (super_version->mem->Get(lkey, pinnable_val->GetSelf(), &s, &merge_context,
|
||||
&max_covering_tombstone_seq, read_options)) {
|
||||
done = true;
|
||||
pinnable_val->PinSelf();
|
||||
RecordTick(stats_, MEMTABLE_HIT);
|
||||
} else if ((s.ok() || s.IsMergeInProgress()) &&
|
||||
super_version->imm->Get(
|
||||
lkey, pinnable_val->GetSelf(), &s, &merge_context,
|
||||
&max_covering_tombstone_seq, read_options)) {
|
||||
done = true;
|
||||
pinnable_val->PinSelf();
|
||||
RecordTick(stats_, MEMTABLE_HIT);
|
||||
}
|
||||
if (!done && !s.ok() && !s.IsMergeInProgress()) {
|
||||
ReturnAndCleanupSuperVersion(cfd, super_version);
|
||||
return s;
|
||||
}
|
||||
if (!done) {
|
||||
PERF_TIMER_GUARD(get_from_output_files_time);
|
||||
super_version->current->Get(read_options, lkey, pinnable_val, &s,
|
||||
&merge_context, &max_covering_tombstone_seq);
|
||||
RecordTick(stats_, MEMTABLE_MISS);
|
||||
}
|
||||
{
|
||||
PERF_TIMER_GUARD(get_post_process_time);
|
||||
ReturnAndCleanupSuperVersion(cfd, super_version);
|
||||
RecordTick(stats_, NUMBER_KEYS_READ);
|
||||
size_t size = pinnable_val->size();
|
||||
RecordTick(stats_, BYTES_READ, size);
|
||||
RecordTimeToHistogram(stats_, BYTES_PER_READ, size);
|
||||
PERF_COUNTER_ADD(get_read_bytes, size);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
Iterator* DBImplSecondary::NewIterator(const ReadOptions& read_options,
|
||||
ColumnFamilyHandle* column_family) {
|
||||
if (read_options.managed) {
|
||||
return NewErrorIterator(
|
||||
Status::NotSupported("Managed iterator is not supported anymore."));
|
||||
}
|
||||
if (read_options.read_tier == kPersistedTier) {
|
||||
return NewErrorIterator(Status::NotSupported(
|
||||
"ReadTier::kPersistedData is not yet supported in iterators."));
|
||||
}
|
||||
Iterator* result = nullptr;
|
||||
auto cfh = reinterpret_cast<ColumnFamilyHandleImpl*>(column_family);
|
||||
auto cfd = cfh->cfd();
|
||||
ReadCallback* read_callback = nullptr; // No read callback provided.
|
||||
if (read_options.tailing) {
|
||||
return NewErrorIterator(Status::NotSupported(
|
||||
"tailing iterator not supported in secondary mode"));
|
||||
} else if (read_options.snapshot != nullptr) {
|
||||
// TODO (yanqin) support snapshot.
|
||||
return NewErrorIterator(
|
||||
Status::NotSupported("snapshot not supported in secondary mode"));
|
||||
} else {
|
||||
auto snapshot = versions_->LastSequence();
|
||||
result = NewIteratorImpl(read_options, cfd, snapshot, read_callback);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
ArenaWrappedDBIter* DBImplSecondary::NewIteratorImpl(
|
||||
const ReadOptions& read_options, ColumnFamilyData* cfd,
|
||||
SequenceNumber snapshot, ReadCallback* read_callback) {
|
||||
assert(nullptr != cfd);
|
||||
SuperVersion* super_version = cfd->GetReferencedSuperVersion(&mutex_);
|
||||
auto db_iter = NewArenaWrappedDbIterator(
|
||||
env_, read_options, *cfd->ioptions(), super_version->mutable_cf_options,
|
||||
snapshot,
|
||||
super_version->mutable_cf_options.max_sequential_skip_in_iterations,
|
||||
super_version->version_number, read_callback);
|
||||
auto internal_iter =
|
||||
NewInternalIterator(read_options, cfd, super_version, db_iter->GetArena(),
|
||||
db_iter->GetRangeDelAggregator(), snapshot);
|
||||
db_iter->SetIterUnderDBIter(internal_iter);
|
||||
return db_iter;
|
||||
}
|
||||
|
||||
Status DBImplSecondary::NewIterators(
|
||||
const ReadOptions& read_options,
|
||||
const std::vector<ColumnFamilyHandle*>& column_families,
|
||||
std::vector<Iterator*>* iterators) {
|
||||
if (read_options.managed) {
|
||||
return Status::NotSupported("Managed iterator is not supported anymore.");
|
||||
}
|
||||
if (read_options.read_tier == kPersistedTier) {
|
||||
return Status::NotSupported(
|
||||
"ReadTier::kPersistedData is not yet supported in iterators.");
|
||||
}
|
||||
ReadCallback* read_callback = nullptr; // No read callback provided.
|
||||
if (iterators == nullptr) {
|
||||
return Status::InvalidArgument("iterators not allowed to be nullptr");
|
||||
}
|
||||
iterators->clear();
|
||||
iterators->reserve(column_families.size());
|
||||
if (read_options.tailing) {
|
||||
return Status::NotSupported(
|
||||
"tailing iterator not supported in secondary mode");
|
||||
} else if (read_options.snapshot != nullptr) {
|
||||
// TODO (yanqin) support snapshot.
|
||||
return Status::NotSupported("snapshot not supported in secondary mode");
|
||||
} else {
|
||||
SequenceNumber read_seq = versions_->LastSequence();
|
||||
for (auto cfh : column_families) {
|
||||
ColumnFamilyData* cfd = static_cast<ColumnFamilyHandleImpl*>(cfh)->cfd();
|
||||
iterators->push_back(
|
||||
NewIteratorImpl(read_options, cfd, read_seq, read_callback));
|
||||
}
|
||||
}
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status DBImplSecondary::TryCatchUpWithPrimary() {
|
||||
assert(versions_.get() != nullptr);
|
||||
assert(manifest_reader_.get() != nullptr);
|
||||
Status s;
|
||||
std::unordered_set<ColumnFamilyData*> cfds_changed;
|
||||
InstrumentedMutexLock lock_guard(&mutex_);
|
||||
s = static_cast<ReactiveVersionSet*>(versions_.get())
|
||||
->ReadAndApply(&mutex_, &manifest_reader_, &cfds_changed);
|
||||
if (s.ok()) {
|
||||
SuperVersionContext sv_context(true /* create_superversion */);
|
||||
for (auto cfd : cfds_changed) {
|
||||
sv_context.NewSuperVersion();
|
||||
cfd->InstallSuperVersion(&sv_context, &mutex_);
|
||||
}
|
||||
sv_context.Clean();
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
Status DB::OpenAsSecondary(const Options& options, const std::string& dbname,
|
||||
const std::string& secondary_path, DB** dbptr) {
|
||||
*dbptr = nullptr;
|
||||
|
||||
DBOptions db_options(options);
|
||||
ColumnFamilyOptions cf_options(options);
|
||||
std::vector<ColumnFamilyDescriptor> column_families;
|
||||
column_families.emplace_back(kDefaultColumnFamilyName, cf_options);
|
||||
std::vector<ColumnFamilyHandle*> handles;
|
||||
|
||||
Status s = DB::OpenAsSecondary(db_options, dbname, secondary_path,
|
||||
column_families, &handles, dbptr);
|
||||
if (s.ok()) {
|
||||
assert(handles.size() == 1);
|
||||
delete handles[0];
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
Status DB::OpenAsSecondary(
|
||||
const DBOptions& db_options, const std::string& dbname,
|
||||
const std::string& secondary_path,
|
||||
const std::vector<ColumnFamilyDescriptor>& column_families,
|
||||
std::vector<ColumnFamilyHandle*>* handles, DB** dbptr) {
|
||||
*dbptr = nullptr;
|
||||
if (db_options.max_open_files != -1) {
|
||||
// TODO (yanqin) maybe support max_open_files != -1 by creating hard links
|
||||
// on SST files so that db secondary can still have access to old SSTs
|
||||
// while primary instance may delete original.
|
||||
return Status::InvalidArgument("require max_open_files to be -1");
|
||||
}
|
||||
|
||||
DBOptions tmp_opts(db_options);
|
||||
if (nullptr == tmp_opts.info_log) {
|
||||
Env* env = tmp_opts.env;
|
||||
assert(env != nullptr);
|
||||
std::string secondary_abs_path;
|
||||
env->GetAbsolutePath(secondary_path, &secondary_abs_path);
|
||||
std::string fname = InfoLogFileName(secondary_path, secondary_abs_path,
|
||||
tmp_opts.db_log_dir);
|
||||
|
||||
env->CreateDirIfMissing(secondary_path);
|
||||
if (tmp_opts.log_file_time_to_roll > 0 || tmp_opts.max_log_file_size > 0) {
|
||||
AutoRollLogger* result = new AutoRollLogger(
|
||||
env, secondary_path, tmp_opts.db_log_dir, tmp_opts.max_log_file_size,
|
||||
tmp_opts.log_file_time_to_roll, tmp_opts.info_log_level);
|
||||
Status s = result->GetStatus();
|
||||
if (!s.ok()) {
|
||||
delete result;
|
||||
} else {
|
||||
tmp_opts.info_log.reset(result);
|
||||
}
|
||||
}
|
||||
if (nullptr == tmp_opts.info_log) {
|
||||
env->RenameFile(
|
||||
fname, OldInfoLogFileName(secondary_path, env->NowMicros(),
|
||||
secondary_abs_path, tmp_opts.db_log_dir));
|
||||
Status s = env->NewLogger(fname, &(tmp_opts.info_log));
|
||||
if (tmp_opts.info_log != nullptr) {
|
||||
tmp_opts.info_log->SetInfoLogLevel(tmp_opts.info_log_level);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
assert(tmp_opts.info_log != nullptr);
|
||||
|
||||
handles->clear();
|
||||
DBImplSecondary* impl = new DBImplSecondary(tmp_opts, dbname);
|
||||
impl->versions_.reset(new ReactiveVersionSet(
|
||||
dbname, &impl->immutable_db_options_, impl->env_options_,
|
||||
impl->table_cache_.get(), impl->write_buffer_manager_,
|
||||
&impl->write_controller_));
|
||||
impl->column_family_memtables_.reset(
|
||||
new ColumnFamilyMemTablesImpl(impl->versions_->GetColumnFamilySet()));
|
||||
impl->mutex_.Lock();
|
||||
Status s = impl->Recover(column_families, true, false, false);
|
||||
if (s.ok()) {
|
||||
for (auto cf : column_families) {
|
||||
auto cfd =
|
||||
impl->versions_->GetColumnFamilySet()->GetColumnFamily(cf.name);
|
||||
if (nullptr == cfd) {
|
||||
s = Status::InvalidArgument("Column family not found: ", cf.name);
|
||||
break;
|
||||
}
|
||||
handles->push_back(new ColumnFamilyHandleImpl(cfd, impl, &impl->mutex_));
|
||||
}
|
||||
}
|
||||
SuperVersionContext sv_context(true /* create_superversion */);
|
||||
if (s.ok()) {
|
||||
for (auto cfd : *impl->versions_->GetColumnFamilySet()) {
|
||||
sv_context.NewSuperVersion();
|
||||
cfd->InstallSuperVersion(&sv_context, &impl->mutex_);
|
||||
}
|
||||
}
|
||||
impl->mutex_.Unlock();
|
||||
sv_context.Clean();
|
||||
if (s.ok()) {
|
||||
*dbptr = impl;
|
||||
for (auto h : *handles) {
|
||||
impl->NewThreadStatusCfInfo(
|
||||
reinterpret_cast<ColumnFamilyHandleImpl*>(h)->cfd());
|
||||
}
|
||||
} else {
|
||||
for (auto h : *handles) {
|
||||
delete h;
|
||||
}
|
||||
handles->clear();
|
||||
delete impl;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
#else // !ROCKSDB_LITE
|
||||
|
||||
Status DB::OpenAsSecondary(const Options& /*options*/,
|
||||
const std::string& /*name*/,
|
||||
const std::string& /*secondary_path*/,
|
||||
DB** /*dbptr*/) {
|
||||
return Status::NotSupported("Not supported in ROCKSDB_LITE.");
|
||||
}
|
||||
|
||||
Status DB::OpenAsSecondary(
|
||||
const DBOptions& /*db_options*/, const std::string& /*dbname*/,
|
||||
const std::string& /*secondary_path*/,
|
||||
const std::vector<ColumnFamilyDescriptor>& /*column_families*/,
|
||||
std::vector<ColumnFamilyHandle*>* /*handles*/, DB** /*dbptr*/) {
|
||||
return Status::NotSupported("Not supported in ROCKSDB_LITE.");
|
||||
}
|
||||
#endif // !ROCKSDB_LITE
|
||||
|
||||
} // namespace rocksdb
|
||||
@@ -0,0 +1,212 @@
|
||||
// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under both the GPLv2 (found in the
|
||||
// COPYING file in the root directory) and Apache 2.0 License
|
||||
// (found in the LICENSE.Apache file in the root directory).
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include "db/db_impl.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
class LogReaderContainer {
|
||||
public:
|
||||
LogReaderContainer()
|
||||
: reader_(nullptr), reporter_(nullptr), status_(nullptr) {}
|
||||
LogReaderContainer(Env* env, std::shared_ptr<Logger> info_log,
|
||||
std::string fname,
|
||||
std::unique_ptr<SequentialFileReader>&& file_reader,
|
||||
uint64_t log_number) {
|
||||
LogReporter* reporter = new LogReporter();
|
||||
status_ = new Status();
|
||||
reporter->env = env;
|
||||
reporter->info_log = info_log.get();
|
||||
reporter->fname = std::move(fname);
|
||||
reporter->status = status_;
|
||||
reporter_ = reporter;
|
||||
// We intentially make log::Reader do checksumming even if
|
||||
// paranoid_checks==false so that corruptions cause entire commits
|
||||
// to be skipped instead of propagating bad information (like overly
|
||||
// large sequence numbers).
|
||||
reader_ = new log::FragmentBufferedReader(info_log, std::move(file_reader),
|
||||
reporter, true /*checksum*/,
|
||||
log_number);
|
||||
}
|
||||
log::FragmentBufferedReader* reader_;
|
||||
log::Reader::Reporter* reporter_;
|
||||
Status* status_;
|
||||
~LogReaderContainer() {
|
||||
delete reader_;
|
||||
delete reporter_;
|
||||
delete status_;
|
||||
}
|
||||
private:
|
||||
struct LogReporter : public log::Reader::Reporter {
|
||||
Env* env;
|
||||
Logger* info_log;
|
||||
std::string fname;
|
||||
Status* status; // nullptr if immutable_db_options_.paranoid_checks==false
|
||||
void Corruption(size_t bytes, const Status& s) override {
|
||||
ROCKS_LOG_WARN(info_log, "%s%s: dropping %d bytes; %s",
|
||||
(this->status == nullptr ? "(ignoring error) " : ""),
|
||||
fname.c_str(), static_cast<int>(bytes),
|
||||
s.ToString().c_str());
|
||||
if (this->status != nullptr && this->status->ok()) {
|
||||
*this->status = s;
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
class DBImplSecondary : public DBImpl {
|
||||
public:
|
||||
DBImplSecondary(const DBOptions& options, const std::string& dbname);
|
||||
~DBImplSecondary() override;
|
||||
|
||||
Status Recover(const std::vector<ColumnFamilyDescriptor>& column_families,
|
||||
bool read_only, bool error_if_log_file_exist,
|
||||
bool error_if_data_exists_in_logs) override;
|
||||
|
||||
// Implementations of the DB interface
|
||||
using DB::Get;
|
||||
Status Get(const ReadOptions& options, ColumnFamilyHandle* column_family,
|
||||
const Slice& key, PinnableSlice* value) override;
|
||||
|
||||
Status GetImpl(const ReadOptions& options, ColumnFamilyHandle* column_family,
|
||||
const Slice& key, PinnableSlice* value);
|
||||
|
||||
using DBImpl::NewIterator;
|
||||
Iterator* NewIterator(const ReadOptions&,
|
||||
ColumnFamilyHandle* column_family) override;
|
||||
|
||||
ArenaWrappedDBIter* NewIteratorImpl(const ReadOptions& read_options,
|
||||
ColumnFamilyData* cfd,
|
||||
SequenceNumber snapshot,
|
||||
ReadCallback* read_callback);
|
||||
|
||||
Status NewIterators(const ReadOptions& options,
|
||||
const std::vector<ColumnFamilyHandle*>& column_families,
|
||||
std::vector<Iterator*>* iterators) override;
|
||||
|
||||
using DBImpl::Put;
|
||||
Status Put(const WriteOptions& /*options*/,
|
||||
ColumnFamilyHandle* /*column_family*/, const Slice& /*key*/,
|
||||
const Slice& /*value*/) override {
|
||||
return Status::NotSupported("Not supported operation in read only mode.");
|
||||
}
|
||||
|
||||
using DBImpl::Merge;
|
||||
Status Merge(const WriteOptions& /*options*/,
|
||||
ColumnFamilyHandle* /*column_family*/, const Slice& /*key*/,
|
||||
const Slice& /*value*/) override {
|
||||
return Status::NotSupported("Not supported operation in read only mode.");
|
||||
}
|
||||
|
||||
using DBImpl::Delete;
|
||||
Status Delete(const WriteOptions& /*options*/,
|
||||
ColumnFamilyHandle* /*column_family*/,
|
||||
const Slice& /*key*/) override {
|
||||
return Status::NotSupported("Not supported operation in read only mode.");
|
||||
}
|
||||
|
||||
using DBImpl::SingleDelete;
|
||||
Status SingleDelete(const WriteOptions& /*options*/,
|
||||
ColumnFamilyHandle* /*column_family*/,
|
||||
const Slice& /*key*/) override {
|
||||
return Status::NotSupported("Not supported operation in read only mode.");
|
||||
}
|
||||
|
||||
Status Write(const WriteOptions& /*options*/,
|
||||
WriteBatch* /*updates*/) override {
|
||||
return Status::NotSupported("Not supported operation in read only mode.");
|
||||
}
|
||||
|
||||
using DBImpl::CompactRange;
|
||||
Status CompactRange(const CompactRangeOptions& /*options*/,
|
||||
ColumnFamilyHandle* /*column_family*/,
|
||||
const Slice* /*begin*/, const Slice* /*end*/) override {
|
||||
return Status::NotSupported("Not supported operation in read only mode.");
|
||||
}
|
||||
|
||||
using DBImpl::CompactFiles;
|
||||
Status CompactFiles(
|
||||
const CompactionOptions& /*compact_options*/,
|
||||
ColumnFamilyHandle* /*column_family*/,
|
||||
const std::vector<std::string>& /*input_file_names*/,
|
||||
const int /*output_level*/, const int /*output_path_id*/ = -1,
|
||||
std::vector<std::string>* const /*output_file_names*/ = nullptr,
|
||||
CompactionJobInfo* /*compaction_job_info*/ = nullptr) override {
|
||||
return Status::NotSupported("Not supported operation in read only mode.");
|
||||
}
|
||||
|
||||
Status DisableFileDeletions() override {
|
||||
return Status::NotSupported("Not supported operation in read only mode.");
|
||||
}
|
||||
|
||||
Status EnableFileDeletions(bool /*force*/) override {
|
||||
return Status::NotSupported("Not supported operation in read only mode.");
|
||||
}
|
||||
|
||||
Status GetLiveFiles(std::vector<std::string>&,
|
||||
uint64_t* /*manifest_file_size*/,
|
||||
bool /*flush_memtable*/ = true) override {
|
||||
return Status::NotSupported("Not supported operation in read only mode.");
|
||||
}
|
||||
|
||||
using DBImpl::Flush;
|
||||
Status Flush(const FlushOptions& /*options*/,
|
||||
ColumnFamilyHandle* /*column_family*/) override {
|
||||
return Status::NotSupported("Not supported operation in read only mode.");
|
||||
}
|
||||
|
||||
using DBImpl::SyncWAL;
|
||||
Status SyncWAL() override {
|
||||
return Status::NotSupported("Not supported operation in read only mode.");
|
||||
}
|
||||
|
||||
using DB::IngestExternalFile;
|
||||
Status IngestExternalFile(
|
||||
ColumnFamilyHandle* /*column_family*/,
|
||||
const std::vector<std::string>& /*external_files*/,
|
||||
const IngestExternalFileOptions& /*ingestion_options*/) override {
|
||||
return Status::NotSupported("Not supported operation in read only mode.");
|
||||
}
|
||||
|
||||
// Try to catch up with the primary by reading as much as possible from the
|
||||
// log files until there is nothing more to read or encounters an error. If
|
||||
// the amount of information in the log files to process is huge, this
|
||||
// method can take long time due to all the I/O and CPU costs.
|
||||
Status TryCatchUpWithPrimary() override;
|
||||
|
||||
Status MaybeInitLogReader(uint64_t log_number,
|
||||
log::FragmentBufferedReader** log_reader);
|
||||
|
||||
private:
|
||||
friend class DB;
|
||||
|
||||
// No copying allowed
|
||||
DBImplSecondary(const DBImplSecondary&);
|
||||
void operator=(const DBImplSecondary&);
|
||||
|
||||
using DBImpl::Recover;
|
||||
|
||||
Status RecoverLogFiles(const std::vector<uint64_t>& log_numbers,
|
||||
SequenceNumber* next_sequence,
|
||||
bool read_only) override;
|
||||
|
||||
std::unique_ptr<log::FragmentBufferedReader> manifest_reader_;
|
||||
std::unique_ptr<log::Reader::Reporter> manifest_reporter_;
|
||||
std::unique_ptr<Status> manifest_reader_status_;
|
||||
|
||||
// cache log readers for each log number, used for continue WAL replay
|
||||
// after recovery
|
||||
std::map<uint64_t, std::unique_ptr<LogReaderContainer>> log_readers_;
|
||||
};
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
#endif // !ROCKSDB_LITE
|
||||
+95
-116
@@ -146,17 +146,6 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options,
|
||||
|
||||
if (write_thread_.CompleteParallelMemTableWriter(&w)) {
|
||||
// we're responsible for exit batch group
|
||||
for (auto* writer : *(w.write_group)) {
|
||||
if (!writer->CallbackFailed() && writer->pre_release_callback) {
|
||||
assert(writer->sequence != kMaxSequenceNumber);
|
||||
Status ws = writer->pre_release_callback->Callback(writer->sequence,
|
||||
disable_memtable);
|
||||
if (!ws.ok()) {
|
||||
status = ws;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// TODO(myabandeh): propagate status to write_group
|
||||
auto last_sequence = w.write_group->last_sequence;
|
||||
versions_->SetLastSequence(last_sequence);
|
||||
@@ -279,7 +268,7 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options,
|
||||
concurrent_update);
|
||||
RecordTick(stats_, WRITE_DONE_BY_OTHER, write_done_by_other);
|
||||
}
|
||||
MeasureTime(stats_, BYTES_PER_WRITE, total_byte_size);
|
||||
RecordInHistogram(stats_, BYTES_PER_WRITE, total_byte_size);
|
||||
|
||||
if (write_options.disableWAL) {
|
||||
has_unpersisted_data_.store(true, std::memory_order_relaxed);
|
||||
@@ -309,6 +298,35 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options,
|
||||
const SequenceNumber current_sequence = last_sequence + 1;
|
||||
last_sequence += seq_inc;
|
||||
|
||||
// PreReleaseCallback is called after WAL write and before memtable write
|
||||
if (status.ok()) {
|
||||
SequenceNumber next_sequence = current_sequence;
|
||||
// Note: the logic for advancing seq here must be consistent with the
|
||||
// logic in WriteBatchInternal::InsertInto(write_group...) as well as
|
||||
// with WriteBatchInternal::InsertInto(write_batch...) that is called on
|
||||
// the merged batch during recovery from the WAL.
|
||||
for (auto* writer : write_group) {
|
||||
if (writer->CallbackFailed()) {
|
||||
continue;
|
||||
}
|
||||
writer->sequence = next_sequence;
|
||||
if (writer->pre_release_callback) {
|
||||
Status ws = writer->pre_release_callback->Callback(
|
||||
writer->sequence, disable_memtable, writer->log_used);
|
||||
if (!ws.ok()) {
|
||||
status = ws;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (seq_per_batch_) {
|
||||
assert(writer->batch_cnt);
|
||||
next_sequence += writer->batch_cnt;
|
||||
} else if (writer->ShouldWriteToMemtable()) {
|
||||
next_sequence += WriteBatchInternal::Count(writer->batch);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (status.ok()) {
|
||||
PERF_TIMER_GUARD(write_memtable_time);
|
||||
|
||||
@@ -320,23 +338,6 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options,
|
||||
0 /*recovery_log_number*/, this, parallel, seq_per_batch_,
|
||||
batch_per_txn_);
|
||||
} else {
|
||||
SequenceNumber next_sequence = current_sequence;
|
||||
// Note: the logic for advancing seq here must be consistent with the
|
||||
// logic in WriteBatchInternal::InsertInto(write_group...) as well as
|
||||
// with WriteBatchInternal::InsertInto(write_batch...) that is called on
|
||||
// the merged batch during recovery from the WAL.
|
||||
for (auto* writer : write_group) {
|
||||
if (writer->CallbackFailed()) {
|
||||
continue;
|
||||
}
|
||||
writer->sequence = next_sequence;
|
||||
if (seq_per_batch_) {
|
||||
assert(writer->batch_cnt);
|
||||
next_sequence += writer->batch_cnt;
|
||||
} else if (writer->ShouldWriteToMemtable()) {
|
||||
next_sequence += WriteBatchInternal::Count(writer->batch);
|
||||
}
|
||||
}
|
||||
write_group.last_sequence = last_sequence;
|
||||
write_thread_.LaunchParallelMemTableWriters(&write_group);
|
||||
in_parallel_group = true;
|
||||
@@ -388,17 +389,8 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options,
|
||||
}
|
||||
if (should_exit_batch_group) {
|
||||
if (status.ok()) {
|
||||
for (auto* writer : write_group) {
|
||||
if (!writer->CallbackFailed() && writer->pre_release_callback) {
|
||||
assert(writer->sequence != kMaxSequenceNumber);
|
||||
Status ws = writer->pre_release_callback->Callback(writer->sequence,
|
||||
disable_memtable);
|
||||
if (!ws.ok()) {
|
||||
status = ws;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Note: if we are to resume after non-OK statuses we need to revisit how
|
||||
// we reacts to non-OK statuses here.
|
||||
versions_->SetLastSequence(last_sequence);
|
||||
}
|
||||
MemTableInsertStatusCheck(w.status);
|
||||
@@ -471,7 +463,7 @@ Status DBImpl::PipelinedWriteImpl(const WriteOptions& write_options,
|
||||
RecordTick(stats_, NUMBER_KEYS_WRITTEN, total_count);
|
||||
stats->AddDBStats(InternalStats::BYTES_WRITTEN, total_byte_size);
|
||||
RecordTick(stats_, BYTES_WRITTEN, total_byte_size);
|
||||
MeasureTime(stats_, BYTES_PER_WRITE, total_byte_size);
|
||||
RecordInHistogram(stats_, BYTES_PER_WRITE, total_byte_size);
|
||||
|
||||
PERF_TIMER_STOP(write_pre_and_post_process_time);
|
||||
|
||||
@@ -602,7 +594,7 @@ Status DBImpl::WriteImplWALOnly(const WriteOptions& write_options,
|
||||
concurrent_update);
|
||||
RecordTick(stats_, WRITE_DONE_BY_OTHER, write_done_by_other);
|
||||
}
|
||||
MeasureTime(stats_, BYTES_PER_WRITE, total_byte_size);
|
||||
RecordInHistogram(stats_, BYTES_PER_WRITE, total_byte_size);
|
||||
|
||||
PERF_TIMER_STOP(write_pre_and_post_process_time);
|
||||
|
||||
@@ -657,8 +649,8 @@ Status DBImpl::WriteImplWALOnly(const WriteOptions& write_options,
|
||||
if (!writer->CallbackFailed() && writer->pre_release_callback) {
|
||||
assert(writer->sequence != kMaxSequenceNumber);
|
||||
const bool DISABLE_MEMTABLE = true;
|
||||
Status ws = writer->pre_release_callback->Callback(writer->sequence,
|
||||
DISABLE_MEMTABLE);
|
||||
Status ws = writer->pre_release_callback->Callback(
|
||||
writer->sequence, DISABLE_MEMTABLE, writer->log_used);
|
||||
if (!ws.ok()) {
|
||||
status = ws;
|
||||
break;
|
||||
@@ -876,7 +868,7 @@ Status DBImpl::WriteToWAL(const WriteThread::WriteGroup& write_group,
|
||||
status = WriteToWAL(*merged_batch, log_writer, log_used, &log_size);
|
||||
if (to_be_cached_state) {
|
||||
cached_recoverable_state_ = *to_be_cached_state;
|
||||
cached_recoverable_state_empty_ = false;
|
||||
cached_recoverable_state_empty_ = false;
|
||||
}
|
||||
|
||||
if (status.ok() && need_log_sync) {
|
||||
@@ -952,7 +944,7 @@ Status DBImpl::ConcurrentWriteToWAL(const WriteThread::WriteGroup& write_group,
|
||||
status = WriteToWAL(*merged_batch, log_writer, log_used, &log_size);
|
||||
if (to_be_cached_state) {
|
||||
cached_recoverable_state_ = *to_be_cached_state;
|
||||
cached_recoverable_state_empty_ = false;
|
||||
cached_recoverable_state_empty_ = false;
|
||||
}
|
||||
log_write_mutex_.Unlock();
|
||||
|
||||
@@ -1001,8 +993,9 @@ Status DBImpl::WriteRecoverableState() {
|
||||
const bool DISABLE_MEMTABLE = true;
|
||||
for (uint64_t sub_batch_seq = seq + 1;
|
||||
sub_batch_seq < next_seq && status.ok(); sub_batch_seq++) {
|
||||
uint64_t const no_log_num = 0;
|
||||
status = recoverable_state_pre_release_callback_->Callback(
|
||||
sub_batch_seq, !DISABLE_MEMTABLE);
|
||||
sub_batch_seq, !DISABLE_MEMTABLE, no_log_num);
|
||||
}
|
||||
}
|
||||
if (status.ok()) {
|
||||
@@ -1072,16 +1065,17 @@ Status DBImpl::SwitchWAL(WriteContext* write_context) {
|
||||
if (!flush_wont_release_oldest_log) {
|
||||
// we only mark this log as getting flushed if we have successfully
|
||||
// flushed all data in this log. If this log contains outstanding prepared
|
||||
// transactions then we cannot flush this log until those transactions are commited.
|
||||
// transactions then we cannot flush this log until those transactions are
|
||||
// commited.
|
||||
unable_to_release_oldest_log_ = false;
|
||||
alive_log_files_.begin()->getting_flushed = true;
|
||||
}
|
||||
|
||||
ROCKS_LOG_INFO(immutable_db_options_.info_log,
|
||||
"Flushing all column families with data in WAL number %" PRIu64
|
||||
". Total log size is %" PRIu64
|
||||
" while max_total_wal_size is %" PRIu64,
|
||||
oldest_alive_log, total_log_size_.load(), GetMaxTotalWalSize());
|
||||
ROCKS_LOG_INFO(
|
||||
immutable_db_options_.info_log,
|
||||
"Flushing all column families with data in WAL number %" PRIu64
|
||||
". Total log size is %" PRIu64 " while max_total_wal_size is %" PRIu64,
|
||||
oldest_alive_log, total_log_size_.load(), GetMaxTotalWalSize());
|
||||
// no need to refcount because drop is happening in write thread, so can't
|
||||
// happen while we're in the write thread
|
||||
autovector<ColumnFamilyData*> cfds;
|
||||
@@ -1133,7 +1127,7 @@ Status DBImpl::HandleWriteBufferFull(WriteContext* write_context) {
|
||||
ROCKS_LOG_INFO(
|
||||
immutable_db_options_.info_log,
|
||||
"Flushing column family with largest mem table size. Write buffer is "
|
||||
"using %" PRIu64 " bytes out of a total of %" PRIu64 ".",
|
||||
"using %" ROCKSDB_PRIszt " bytes out of a total of %" ROCKSDB_PRIszt ".",
|
||||
write_buffer_manager_->memory_usage(),
|
||||
write_buffer_manager_->buffer_size());
|
||||
// no need to refcount because drop is happening in write thread, so can't
|
||||
@@ -1424,58 +1418,19 @@ Status DBImpl::SwitchMemtable(ColumnFamilyData* cfd, WriteContext* context) {
|
||||
// Log this later after lock release. It may be outdated, e.g., if background
|
||||
// flush happens before logging, but that should be ok.
|
||||
int num_imm_unflushed = cfd->imm()->NumNotFlushed();
|
||||
DBOptions db_options =
|
||||
BuildDBOptions(immutable_db_options_, mutable_db_options_);
|
||||
const auto preallocate_block_size =
|
||||
GetWalPreallocateBlockSize(mutable_cf_options.write_buffer_size);
|
||||
auto write_hint = CalculateWALWriteHint();
|
||||
GetWalPreallocateBlockSize(mutable_cf_options.write_buffer_size);
|
||||
mutex_.Unlock();
|
||||
{
|
||||
std::string log_fname =
|
||||
LogFileName(immutable_db_options_.wal_dir, new_log_number);
|
||||
if (creating_new_log) {
|
||||
EnvOptions opt_env_opt =
|
||||
env_->OptimizeForLogWrite(env_options_, db_options);
|
||||
if (recycle_log_number) {
|
||||
ROCKS_LOG_INFO(immutable_db_options_.info_log,
|
||||
"reusing log %" PRIu64 " from recycle list\n",
|
||||
recycle_log_number);
|
||||
std::string old_log_fname =
|
||||
LogFileName(immutable_db_options_.wal_dir, recycle_log_number);
|
||||
s = env_->ReuseWritableFile(log_fname, old_log_fname, &lfile,
|
||||
opt_env_opt);
|
||||
} else {
|
||||
s = NewWritableFile(env_, log_fname, &lfile, opt_env_opt);
|
||||
}
|
||||
if (s.ok()) {
|
||||
// Our final size should be less than write_buffer_size
|
||||
// (compression, etc) but err on the side of caution.
|
||||
|
||||
// use preallocate_block_size instead
|
||||
// of calling GetWalPreallocateBlockSize()
|
||||
lfile->SetPreallocationBlockSize(preallocate_block_size);
|
||||
lfile->SetWriteLifeTimeHint(write_hint);
|
||||
std::unique_ptr<WritableFileWriter> file_writer(new WritableFileWriter(
|
||||
std::move(lfile), log_fname, opt_env_opt, env_, nullptr /* stats */,
|
||||
immutable_db_options_.listeners));
|
||||
new_log = new log::Writer(
|
||||
std::move(file_writer), new_log_number,
|
||||
immutable_db_options_.recycle_log_file_num > 0, manual_wal_flush_);
|
||||
}
|
||||
}
|
||||
|
||||
if (s.ok()) {
|
||||
SequenceNumber seq = versions_->LastSequence();
|
||||
new_mem = cfd->ConstructNewMemtable(mutable_cf_options, seq);
|
||||
context->superversion_context.NewSuperVersion();
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
// PLEASE NOTE: We assume that there are no failable operations
|
||||
// after lock is acquired below since we are already notifying
|
||||
// client about mem table becoming immutable.
|
||||
NotifyOnMemTableSealed(cfd, memtable_info);
|
||||
#endif //ROCKSDB_LITE
|
||||
if (creating_new_log) {
|
||||
// TODO: Write buffer size passed in should be max of all CF's instead
|
||||
// of mutable_cf_options.write_buffer_size.
|
||||
s = CreateWAL(new_log_number, recycle_log_number, preallocate_block_size,
|
||||
&new_log);
|
||||
}
|
||||
if (s.ok()) {
|
||||
SequenceNumber seq = versions_->LastSequence();
|
||||
new_mem = cfd->ConstructNewMemtable(mutable_cf_options, seq);
|
||||
context->superversion_context.NewSuperVersion();
|
||||
}
|
||||
ROCKS_LOG_INFO(immutable_db_options_.info_log,
|
||||
"[%s] New memtable created with log file: #%" PRIu64
|
||||
@@ -1484,10 +1439,7 @@ Status DBImpl::SwitchMemtable(ColumnFamilyData* cfd, WriteContext* context) {
|
||||
mutex_.Lock();
|
||||
if (s.ok() && creating_new_log) {
|
||||
log_write_mutex_.Lock();
|
||||
logfile_number_ = new_log_number;
|
||||
assert(new_log != nullptr);
|
||||
log_empty_ = true;
|
||||
log_dir_synced_ = false;
|
||||
if (!logs_.empty()) {
|
||||
// Alway flush the buffer of the last log before switching to a new one
|
||||
log::Writer* cur_log_writer = logs_.back().writer;
|
||||
@@ -1495,21 +1447,41 @@ Status DBImpl::SwitchMemtable(ColumnFamilyData* cfd, WriteContext* context) {
|
||||
if (!s.ok()) {
|
||||
ROCKS_LOG_WARN(immutable_db_options_.info_log,
|
||||
"[%s] Failed to switch from #%" PRIu64 " to #%" PRIu64
|
||||
" WAL file -- %s\n",
|
||||
" WAL file\n",
|
||||
cfd->GetName().c_str(), cur_log_writer->get_log_number(),
|
||||
new_log_number);
|
||||
}
|
||||
}
|
||||
logs_.emplace_back(logfile_number_, new_log);
|
||||
alive_log_files_.push_back(LogFileNumberSize(logfile_number_));
|
||||
if (s.ok()) {
|
||||
logfile_number_ = new_log_number;
|
||||
log_empty_ = true;
|
||||
log_dir_synced_ = false;
|
||||
logs_.emplace_back(logfile_number_, new_log);
|
||||
alive_log_files_.push_back(LogFileNumberSize(logfile_number_));
|
||||
}
|
||||
log_write_mutex_.Unlock();
|
||||
}
|
||||
|
||||
if (!s.ok()) {
|
||||
// how do we fail if we're not creating new log?
|
||||
assert(creating_new_log);
|
||||
assert(!new_mem);
|
||||
assert(!new_log);
|
||||
if (new_mem) {
|
||||
delete new_mem;
|
||||
}
|
||||
if (new_log) {
|
||||
delete new_log;
|
||||
}
|
||||
SuperVersion* new_superversion =
|
||||
context->superversion_context.new_superversion.release();
|
||||
if (new_superversion != nullptr) {
|
||||
delete new_superversion;
|
||||
}
|
||||
// We may have lost data from the WritableFileBuffer in-memory buffer for
|
||||
// the current log, so treat it as a fatal error and set bg_error
|
||||
error_handler_.SetBGError(s, BackgroundErrorReason::kMemTable);
|
||||
// Read back bg_error in order to get the right severity
|
||||
s = error_handler_.GetBGError();
|
||||
|
||||
if (two_write_queues_) {
|
||||
nonmem_write_thread_.ExitUnbatched(&nonmem_w);
|
||||
}
|
||||
@@ -1536,6 +1508,13 @@ Status DBImpl::SwitchMemtable(ColumnFamilyData* cfd, WriteContext* context) {
|
||||
cfd->SetMemtable(new_mem);
|
||||
InstallSuperVersionAndScheduleWork(cfd, &context->superversion_context,
|
||||
mutable_cf_options);
|
||||
#ifndef ROCKSDB_LITE
|
||||
mutex_.Unlock();
|
||||
// Notify client that memtable is sealed, now that we have successfully
|
||||
// installed a new memtable
|
||||
NotifyOnMemTableSealed(cfd, memtable_info);
|
||||
mutex_.Lock();
|
||||
#endif // ROCKSDB_LITE
|
||||
if (two_write_queues_) {
|
||||
nonmem_write_thread_.ExitUnbatched(&nonmem_w);
|
||||
}
|
||||
@@ -1544,13 +1523,13 @@ Status DBImpl::SwitchMemtable(ColumnFamilyData* cfd, WriteContext* context) {
|
||||
|
||||
size_t DBImpl::GetWalPreallocateBlockSize(uint64_t write_buffer_size) const {
|
||||
mutex_.AssertHeld();
|
||||
size_t bsize = static_cast<size_t>(
|
||||
write_buffer_size / 10 + write_buffer_size);
|
||||
size_t bsize =
|
||||
static_cast<size_t>(write_buffer_size / 10 + write_buffer_size);
|
||||
// Some users might set very high write_buffer_size and rely on
|
||||
// max_total_wal_size or other parameters to control the WAL size.
|
||||
if (mutable_db_options_.max_total_wal_size > 0) {
|
||||
bsize = std::min<size_t>(bsize, static_cast<size_t>(
|
||||
mutable_db_options_.max_total_wal_size));
|
||||
bsize = std::min<size_t>(
|
||||
bsize, static_cast<size_t>(mutable_db_options_.max_total_wal_size));
|
||||
}
|
||||
if (immutable_db_options_.db_write_buffer_size > 0) {
|
||||
bsize = std::min<size_t>(bsize, immutable_db_options_.db_write_buffer_size);
|
||||
|
||||
+181
-175
@@ -22,12 +22,14 @@
|
||||
#include "rocksdb/merge_operator.h"
|
||||
#include "rocksdb/options.h"
|
||||
#include "table/internal_iterator.h"
|
||||
#include "table/iterator_wrapper.h"
|
||||
#include "util/arena.h"
|
||||
#include "util/filename.h"
|
||||
#include "util/logging.h"
|
||||
#include "util/mutexlock.h"
|
||||
#include "util/string_util.h"
|
||||
#include "util/trace_replay.h"
|
||||
#include "util/user_comparator_wrapper.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
@@ -117,29 +119,29 @@ class DBIter final: public Iterator {
|
||||
uint64_t max_sequential_skip_in_iterations,
|
||||
ReadCallback* read_callback, DBImpl* db_impl, ColumnFamilyData* cfd,
|
||||
bool allow_blob)
|
||||
: arena_mode_(arena_mode),
|
||||
env_(_env),
|
||||
: env_(_env),
|
||||
logger_(cf_options.info_log),
|
||||
user_comparator_(cmp),
|
||||
merge_operator_(cf_options.merge_operator),
|
||||
iter_(iter),
|
||||
read_callback_(read_callback),
|
||||
sequence_(s),
|
||||
direction_(kForward),
|
||||
valid_(false),
|
||||
current_entry_is_merged_(false),
|
||||
statistics_(cf_options.statistics),
|
||||
num_internal_keys_skipped_(0),
|
||||
iterate_lower_bound_(read_options.iterate_lower_bound),
|
||||
iterate_upper_bound_(read_options.iterate_upper_bound),
|
||||
direction_(kForward),
|
||||
valid_(false),
|
||||
current_entry_is_merged_(false),
|
||||
prefix_same_as_start_(read_options.prefix_same_as_start),
|
||||
pin_thru_lifetime_(read_options.pin_data),
|
||||
total_order_seek_(read_options.total_order_seek),
|
||||
range_del_agg_(&cf_options.internal_comparator, s),
|
||||
read_callback_(read_callback),
|
||||
db_impl_(db_impl),
|
||||
cfd_(cfd),
|
||||
allow_blob_(allow_blob),
|
||||
is_blob_(false),
|
||||
arena_mode_(arena_mode),
|
||||
range_del_agg_(&cf_options.internal_comparator, s),
|
||||
db_impl_(db_impl),
|
||||
cfd_(cfd),
|
||||
start_seqnum_(read_options.iter_start_seqnum) {
|
||||
RecordTick(statistics_, NO_ITERATOR_CREATED);
|
||||
prefix_extractor_ = mutable_cf_options.prefix_extractor.get();
|
||||
@@ -148,11 +150,11 @@ class DBIter final: public Iterator {
|
||||
if (pin_thru_lifetime_) {
|
||||
pinned_iters_mgr_.StartPinning();
|
||||
}
|
||||
if (iter_) {
|
||||
iter_->SetPinnedItersMgr(&pinned_iters_mgr_);
|
||||
if (iter_.iter()) {
|
||||
iter_.iter()->SetPinnedItersMgr(&pinned_iters_mgr_);
|
||||
}
|
||||
}
|
||||
virtual ~DBIter() {
|
||||
~DBIter() override {
|
||||
// Release pinned data if any
|
||||
if (pinned_iters_mgr_.PinningEnabled()) {
|
||||
pinned_iters_mgr_.ReleasePinnedData();
|
||||
@@ -160,32 +162,27 @@ class DBIter final: public Iterator {
|
||||
RecordTick(statistics_, NO_ITERATOR_DELETED);
|
||||
ResetInternalKeysSkippedCounter();
|
||||
local_stats_.BumpGlobalStatistics(statistics_);
|
||||
if (!arena_mode_) {
|
||||
delete iter_;
|
||||
} else {
|
||||
iter_->~InternalIterator();
|
||||
}
|
||||
iter_.DeleteIter(arena_mode_);
|
||||
}
|
||||
virtual void SetIter(InternalIterator* iter) {
|
||||
assert(iter_ == nullptr);
|
||||
iter_ = iter;
|
||||
iter_->SetPinnedItersMgr(&pinned_iters_mgr_);
|
||||
assert(iter_.iter() == nullptr);
|
||||
iter_.Set(iter);
|
||||
iter_.iter()->SetPinnedItersMgr(&pinned_iters_mgr_);
|
||||
}
|
||||
virtual ReadRangeDelAggregator* GetRangeDelAggregator() {
|
||||
return &range_del_agg_;
|
||||
}
|
||||
|
||||
virtual bool Valid() const override { return valid_; }
|
||||
virtual Slice key() const override {
|
||||
bool Valid() const override { return valid_; }
|
||||
Slice key() const override {
|
||||
assert(valid_);
|
||||
if(start_seqnum_ > 0) {
|
||||
return saved_key_.GetInternalKey();
|
||||
} else {
|
||||
return saved_key_.GetUserKey();
|
||||
}
|
||||
|
||||
}
|
||||
virtual Slice value() const override {
|
||||
Slice value() const override {
|
||||
assert(valid_);
|
||||
if (current_entry_is_merged_) {
|
||||
// If pinned_value_ is set then the result of merge operator is one of
|
||||
@@ -194,12 +191,12 @@ class DBIter final: public Iterator {
|
||||
} else if (direction_ == kReverse) {
|
||||
return pinned_value_;
|
||||
} else {
|
||||
return iter_->value();
|
||||
return iter_.value();
|
||||
}
|
||||
}
|
||||
virtual Status status() const override {
|
||||
Status status() const override {
|
||||
if (status_.ok()) {
|
||||
return iter_->status();
|
||||
return iter_.status();
|
||||
} else {
|
||||
assert(!valid_);
|
||||
return status_;
|
||||
@@ -210,14 +207,13 @@ class DBIter final: public Iterator {
|
||||
return is_blob_;
|
||||
}
|
||||
|
||||
virtual Status GetProperty(std::string prop_name,
|
||||
std::string* prop) override {
|
||||
Status GetProperty(std::string prop_name, std::string* prop) override {
|
||||
if (prop == nullptr) {
|
||||
return Status::InvalidArgument("prop is nullptr");
|
||||
}
|
||||
if (prop_name == "rocksdb.iterator.super-version-number") {
|
||||
// First try to pass the value returned from inner iterator.
|
||||
return iter_->GetProperty(prop_name, prop);
|
||||
return iter_.iter()->GetProperty(prop_name, prop);
|
||||
} else if (prop_name == "rocksdb.iterator.is-key-pinned") {
|
||||
if (valid_) {
|
||||
*prop = (pin_thru_lifetime_ && saved_key_.IsKeyPinned()) ? "1" : "0";
|
||||
@@ -232,14 +228,19 @@ class DBIter final: public Iterator {
|
||||
return Status::InvalidArgument("Unidentified property.");
|
||||
}
|
||||
|
||||
virtual void Next() override;
|
||||
virtual void Prev() override;
|
||||
virtual void Seek(const Slice& target) override;
|
||||
virtual void SeekForPrev(const Slice& target) override;
|
||||
virtual void SeekToFirst() override;
|
||||
virtual void SeekToLast() override;
|
||||
inline void Next() final override;
|
||||
inline void Prev() final override;
|
||||
inline void Seek(const Slice& target) final override;
|
||||
inline void SeekForPrev(const Slice& target) final override;
|
||||
inline void SeekToFirst() final override;
|
||||
inline void SeekToLast() final override;
|
||||
Env* env() { return env_; }
|
||||
void set_sequence(uint64_t s) { sequence_ = s; }
|
||||
void set_sequence(uint64_t s) {
|
||||
sequence_ = s;
|
||||
if (read_callback_) {
|
||||
read_callback_->Refresh(s);
|
||||
}
|
||||
}
|
||||
void set_valid(bool v) { valid_ = v; }
|
||||
|
||||
private:
|
||||
@@ -253,13 +254,13 @@ class DBIter final: public Iterator {
|
||||
bool FindValueForCurrentKeyUsingSeek();
|
||||
bool FindUserKeyBeforeSavedKey();
|
||||
inline bool FindNextUserEntry(bool skipping, bool prefix_check);
|
||||
bool FindNextUserEntryInternal(bool skipping, bool prefix_check);
|
||||
inline bool FindNextUserEntryInternal(bool skipping, bool prefix_check);
|
||||
bool ParseKey(ParsedInternalKey* key);
|
||||
bool MergeValuesNewToOld();
|
||||
|
||||
void PrevInternal();
|
||||
bool TooManyInternalKeysSkipped(bool increment = true);
|
||||
bool IsVisible(SequenceNumber sequence);
|
||||
inline bool IsVisible(SequenceNumber sequence);
|
||||
|
||||
// CanReseekToSkip() returns whether the iterator can use the optimization
|
||||
// where it reseek by sequence number to get the next key when there are too
|
||||
@@ -267,12 +268,6 @@ class DBIter final: public Iterator {
|
||||
// sequence number does not guarantee that it is visible.
|
||||
inline bool CanReseekToSkip();
|
||||
|
||||
// MaxVisibleSequenceNumber() returns the maximum visible sequence number
|
||||
// for this snapshot. This sequence number may be greater than snapshot
|
||||
// seqno because uncommitted data written to DB for write unprepared will
|
||||
// have a higher sequence number.
|
||||
inline SequenceNumber MaxVisibleSequenceNumber();
|
||||
|
||||
// Temporarily pin the blocks that we encounter until ReleaseTempPinnedData()
|
||||
// is called
|
||||
void TempPinData() {
|
||||
@@ -306,15 +301,16 @@ class DBIter final: public Iterator {
|
||||
}
|
||||
|
||||
const SliceTransform* prefix_extractor_;
|
||||
bool arena_mode_;
|
||||
Env* const env_;
|
||||
Logger* logger_;
|
||||
const Comparator* const user_comparator_;
|
||||
UserComparatorWrapper user_comparator_;
|
||||
const MergeOperator* const merge_operator_;
|
||||
InternalIterator* iter_;
|
||||
IteratorWrapper iter_;
|
||||
ReadCallback* read_callback_;
|
||||
// Max visible sequence number. It is normally the snapshot seq unless we have
|
||||
// uncommitted data in db as in WriteUnCommitted.
|
||||
SequenceNumber sequence_;
|
||||
|
||||
Status status_;
|
||||
IterKey saved_key_;
|
||||
// Reusable internal key data structure. This is only used inside one function
|
||||
// and should not be used across functions. Reusing this object can reduce
|
||||
@@ -322,9 +318,6 @@ class DBIter final: public Iterator {
|
||||
ParsedInternalKey ikey_;
|
||||
std::string saved_value_;
|
||||
Slice pinned_value_;
|
||||
Direction direction_;
|
||||
bool valid_;
|
||||
bool current_entry_is_merged_;
|
||||
// for prefix seek mode to support prev()
|
||||
Statistics* statistics_;
|
||||
uint64_t max_skip_;
|
||||
@@ -332,23 +325,29 @@ class DBIter final: public Iterator {
|
||||
uint64_t num_internal_keys_skipped_;
|
||||
const Slice* iterate_lower_bound_;
|
||||
const Slice* iterate_upper_bound_;
|
||||
|
||||
IterKey prefix_start_buf_;
|
||||
|
||||
Status status_;
|
||||
Slice prefix_start_key_;
|
||||
Direction direction_;
|
||||
bool valid_;
|
||||
bool current_entry_is_merged_;
|
||||
const bool prefix_same_as_start_;
|
||||
// Means that we will pin all data blocks we read as long the Iterator
|
||||
// is not deleted, will be true if ReadOptions::pin_data is true
|
||||
const bool pin_thru_lifetime_;
|
||||
const bool total_order_seek_;
|
||||
bool allow_blob_;
|
||||
bool is_blob_;
|
||||
bool arena_mode_;
|
||||
// List of operands for merge operator.
|
||||
MergeContext merge_context_;
|
||||
ReadRangeDelAggregator range_del_agg_;
|
||||
LocalStatistics local_stats_;
|
||||
PinnedIteratorsManager pinned_iters_mgr_;
|
||||
ReadCallback* read_callback_;
|
||||
DBImpl* db_impl_;
|
||||
ColumnFamilyData* cfd_;
|
||||
bool allow_blob_;
|
||||
bool is_blob_;
|
||||
// for diff snapshots we want the lower bound on the seqnum;
|
||||
// if this value > 0 iterator will return internal keys
|
||||
SequenceNumber start_seqnum_;
|
||||
@@ -359,11 +358,11 @@ class DBIter final: public Iterator {
|
||||
};
|
||||
|
||||
inline bool DBIter::ParseKey(ParsedInternalKey* ikey) {
|
||||
if (!ParseInternalKey(iter_->key(), ikey)) {
|
||||
if (!ParseInternalKey(iter_.key(), ikey)) {
|
||||
status_ = Status::Corruption("corrupted internal key in DBIter");
|
||||
valid_ = false;
|
||||
ROCKS_LOG_ERROR(logger_, "corrupted internal key in DBIter: %s",
|
||||
iter_->key().ToString(true).c_str());
|
||||
iter_.key().ToString(true).c_str());
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
@@ -374,28 +373,30 @@ void DBIter::Next() {
|
||||
assert(valid_);
|
||||
assert(status_.ok());
|
||||
|
||||
PERF_CPU_TIMER_GUARD(iter_next_cpu_nanos, env_);
|
||||
// Release temporarily pinned blocks from last operation
|
||||
ReleaseTempPinnedData();
|
||||
ResetInternalKeysSkippedCounter();
|
||||
local_stats_.skip_count_ += num_internal_keys_skipped_;
|
||||
local_stats_.skip_count_--;
|
||||
num_internal_keys_skipped_ = 0;
|
||||
bool ok = true;
|
||||
if (direction_ == kReverse) {
|
||||
if (!ReverseToForward()) {
|
||||
ok = false;
|
||||
}
|
||||
} else if (iter_->Valid() && !current_entry_is_merged_) {
|
||||
} else if (!current_entry_is_merged_) {
|
||||
// If the current value is not a merge, the iter position is the
|
||||
// current key, which is already returned. We can safely issue a
|
||||
// Next() without checking the current key.
|
||||
// If the current key is a merge, very likely iter already points
|
||||
// to the next internal position.
|
||||
iter_->Next();
|
||||
assert(iter_.Valid());
|
||||
iter_.Next();
|
||||
PERF_COUNTER_ADD(internal_key_skipped_count, 1);
|
||||
}
|
||||
|
||||
if (statistics_ != nullptr) {
|
||||
local_stats_.next_count_++;
|
||||
}
|
||||
if (ok && iter_->Valid()) {
|
||||
local_stats_.next_count_++;
|
||||
if (ok && iter_.Valid()) {
|
||||
FindNextUserEntry(true /* skipping the current user key */,
|
||||
prefix_same_as_start_);
|
||||
} else {
|
||||
@@ -427,9 +428,9 @@ inline bool DBIter::FindNextUserEntry(bool skipping, bool prefix_check) {
|
||||
}
|
||||
|
||||
// Actual implementation of DBIter::FindNextUserEntry()
|
||||
bool DBIter::FindNextUserEntryInternal(bool skipping, bool prefix_check) {
|
||||
inline bool DBIter::FindNextUserEntryInternal(bool skipping, bool prefix_check) {
|
||||
// Loop until we hit an acceptable entry to yield
|
||||
assert(iter_->Valid());
|
||||
assert(iter_.Valid());
|
||||
assert(status_.ok());
|
||||
assert(direction_ == kForward);
|
||||
current_entry_is_merged_ = false;
|
||||
@@ -454,7 +455,7 @@ bool DBIter::FindNextUserEntryInternal(bool skipping, bool prefix_check) {
|
||||
}
|
||||
|
||||
if (iterate_upper_bound_ != nullptr &&
|
||||
user_comparator_->Compare(ikey_.user_key, *iterate_upper_bound_) >= 0) {
|
||||
user_comparator_.Compare(ikey_.user_key, *iterate_upper_bound_) >= 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -469,8 +470,8 @@ bool DBIter::FindNextUserEntryInternal(bool skipping, bool prefix_check) {
|
||||
}
|
||||
|
||||
if (IsVisible(ikey_.sequence)) {
|
||||
if (skipping && user_comparator_->Compare(ikey_.user_key,
|
||||
saved_key_.GetUserKey()) <= 0) {
|
||||
if (skipping && user_comparator_.Compare(ikey_.user_key,
|
||||
saved_key_.GetUserKey()) <= 0) {
|
||||
num_skipped++; // skip this entry
|
||||
PERF_COUNTER_ADD(internal_key_skipped_count, 1);
|
||||
} else {
|
||||
@@ -491,8 +492,8 @@ bool DBIter::FindNextUserEntryInternal(bool skipping, bool prefix_check) {
|
||||
return true;
|
||||
} else {
|
||||
saved_key_.SetUserKey(
|
||||
ikey_.user_key,
|
||||
!pin_thru_lifetime_ || !iter_->IsKeyPinned() /* copy */);
|
||||
ikey_.user_key, !pin_thru_lifetime_ ||
|
||||
!iter_.iter()->IsKeyPinned() /* copy */);
|
||||
skipping = true;
|
||||
PERF_COUNTER_ADD(internal_delete_skipped_count, 1);
|
||||
}
|
||||
@@ -512,14 +513,16 @@ bool DBIter::FindNextUserEntryInternal(bool skipping, bool prefix_check) {
|
||||
} else {
|
||||
// this key and all previous versions shouldn't be included,
|
||||
// skipping
|
||||
saved_key_.SetUserKey(ikey_.user_key,
|
||||
!pin_thru_lifetime_ || !iter_->IsKeyPinned() /* copy */);
|
||||
saved_key_.SetUserKey(
|
||||
ikey_.user_key,
|
||||
!pin_thru_lifetime_ ||
|
||||
!iter_.iter()->IsKeyPinned() /* copy */);
|
||||
skipping = true;
|
||||
}
|
||||
} else {
|
||||
saved_key_.SetUserKey(
|
||||
ikey_.user_key,
|
||||
!pin_thru_lifetime_ || !iter_->IsKeyPinned() /* copy */);
|
||||
ikey_.user_key, !pin_thru_lifetime_ ||
|
||||
!iter_.iter()->IsKeyPinned() /* copy */);
|
||||
if (range_del_agg_.ShouldDelete(
|
||||
ikey_, RangeDelPositioningMode::kForwardTraversal)) {
|
||||
// Arrange to skip all upcoming entries for this key since
|
||||
@@ -549,7 +552,7 @@ bool DBIter::FindNextUserEntryInternal(bool skipping, bool prefix_check) {
|
||||
case kTypeMerge:
|
||||
saved_key_.SetUserKey(
|
||||
ikey_.user_key,
|
||||
!pin_thru_lifetime_ || !iter_->IsKeyPinned() /* copy */);
|
||||
!pin_thru_lifetime_ || !iter_.iter()->IsKeyPinned() /* copy */);
|
||||
if (range_del_agg_.ShouldDelete(
|
||||
ikey_, RangeDelPositioningMode::kForwardTraversal)) {
|
||||
// Arrange to skip all upcoming entries for this key since
|
||||
@@ -577,13 +580,13 @@ bool DBIter::FindNextUserEntryInternal(bool skipping, bool prefix_check) {
|
||||
// If this happens too many times in a row for the same user key, we want
|
||||
// to seek to the target sequence number.
|
||||
int cmp =
|
||||
user_comparator_->Compare(ikey_.user_key, saved_key_.GetUserKey());
|
||||
user_comparator_.Compare(ikey_.user_key, saved_key_.GetUserKey());
|
||||
if (cmp == 0 || (skipping && cmp <= 0)) {
|
||||
num_skipped++;
|
||||
} else {
|
||||
saved_key_.SetUserKey(
|
||||
ikey_.user_key,
|
||||
!iter_->IsKeyPinned() || !pin_thru_lifetime_ /* copy */);
|
||||
!iter_.iter()->IsKeyPinned() || !pin_thru_lifetime_ /* copy */);
|
||||
skipping = false;
|
||||
num_skipped = 0;
|
||||
}
|
||||
@@ -612,20 +615,20 @@ bool DBIter::FindNextUserEntryInternal(bool skipping, bool prefix_check) {
|
||||
ParsedInternalKey(saved_key_.GetUserKey(), sequence_,
|
||||
kValueTypeForSeek));
|
||||
}
|
||||
iter_->Seek(last_key);
|
||||
iter_.Seek(last_key);
|
||||
RecordTick(statistics_, NUMBER_OF_RESEEKS_IN_ITERATION);
|
||||
} else {
|
||||
iter_->Next();
|
||||
iter_.Next();
|
||||
}
|
||||
} while (iter_->Valid());
|
||||
} while (iter_.Valid());
|
||||
|
||||
valid_ = false;
|
||||
return iter_->status().ok();
|
||||
return iter_.status().ok();
|
||||
}
|
||||
|
||||
// Merge values of the same user key starting from the current iter_ position
|
||||
// Scan from the newer entries to older entries.
|
||||
// PRE: iter_->key() points to the first merge type entry
|
||||
// PRE: iter_.key() points to the first merge type entry
|
||||
// saved_key_ stores the user key
|
||||
// POST: saved_value_ has the merged value for the user key
|
||||
// iter_ points to the next entry (or invalid)
|
||||
@@ -641,19 +644,19 @@ bool DBIter::MergeValuesNewToOld() {
|
||||
TempPinData();
|
||||
merge_context_.Clear();
|
||||
// Start the merge process by pushing the first operand
|
||||
merge_context_.PushOperand(iter_->value(),
|
||||
iter_->IsValuePinned() /* operand_pinned */);
|
||||
merge_context_.PushOperand(
|
||||
iter_.value(), iter_.iter()->IsValuePinned() /* operand_pinned */);
|
||||
TEST_SYNC_POINT("DBIter::MergeValuesNewToOld:PushedFirstOperand");
|
||||
|
||||
ParsedInternalKey ikey;
|
||||
Status s;
|
||||
for (iter_->Next(); iter_->Valid(); iter_->Next()) {
|
||||
for (iter_.Next(); iter_.Valid(); iter_.Next()) {
|
||||
TEST_SYNC_POINT("DBIter::MergeValuesNewToOld:SteppedToNextOperand");
|
||||
if (!ParseKey(&ikey)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!user_comparator_->Equal(ikey.user_key, saved_key_.GetUserKey())) {
|
||||
if (!user_comparator_.Equal(ikey.user_key, saved_key_.GetUserKey())) {
|
||||
// hit the next user key, stop right here
|
||||
break;
|
||||
} else if (kTypeDeletion == ikey.type || kTypeSingleDeletion == ikey.type ||
|
||||
@@ -661,12 +664,12 @@ bool DBIter::MergeValuesNewToOld() {
|
||||
ikey, RangeDelPositioningMode::kForwardTraversal)) {
|
||||
// hit a delete with the same user key, stop right here
|
||||
// iter_ is positioned after delete
|
||||
iter_->Next();
|
||||
iter_.Next();
|
||||
break;
|
||||
} else if (kTypeValue == ikey.type) {
|
||||
// hit a put, merge the put value with operands and store the
|
||||
// final result in saved_value_. We are done!
|
||||
const Slice val = iter_->value();
|
||||
const Slice val = iter_.value();
|
||||
s = MergeHelper::TimedFullMerge(
|
||||
merge_operator_, ikey.user_key, &val, merge_context_.GetOperands(),
|
||||
&saved_value_, logger_, statistics_, env_, &pinned_value_, true);
|
||||
@@ -676,8 +679,8 @@ bool DBIter::MergeValuesNewToOld() {
|
||||
return false;
|
||||
}
|
||||
// iter_ is positioned after put
|
||||
iter_->Next();
|
||||
if (!iter_->status().ok()) {
|
||||
iter_.Next();
|
||||
if (!iter_.status().ok()) {
|
||||
valid_ = false;
|
||||
return false;
|
||||
}
|
||||
@@ -685,8 +688,8 @@ bool DBIter::MergeValuesNewToOld() {
|
||||
} else if (kTypeMerge == ikey.type) {
|
||||
// hit a merge, add the value as an operand and run associative merge.
|
||||
// when complete, add result to operands and continue.
|
||||
merge_context_.PushOperand(iter_->value(),
|
||||
iter_->IsValuePinned() /* operand_pinned */);
|
||||
merge_context_.PushOperand(
|
||||
iter_.value(), iter_.iter()->IsValuePinned() /* operand_pinned */);
|
||||
PERF_COUNTER_ADD(internal_merge_count, 1);
|
||||
} else if (kTypeBlobIndex == ikey.type) {
|
||||
if (!allow_blob_) {
|
||||
@@ -705,7 +708,7 @@ bool DBIter::MergeValuesNewToOld() {
|
||||
}
|
||||
}
|
||||
|
||||
if (!iter_->status().ok()) {
|
||||
if (!iter_.status().ok()) {
|
||||
valid_ = false;
|
||||
return false;
|
||||
}
|
||||
@@ -731,6 +734,8 @@ bool DBIter::MergeValuesNewToOld() {
|
||||
void DBIter::Prev() {
|
||||
assert(valid_);
|
||||
assert(status_.ok());
|
||||
|
||||
PERF_CPU_TIMER_GUARD(iter_prev_cpu_nanos, env_);
|
||||
ReleaseTempPinnedData();
|
||||
ResetInternalKeysSkippedCounter();
|
||||
bool ok = true;
|
||||
@@ -752,33 +757,32 @@ void DBIter::Prev() {
|
||||
}
|
||||
|
||||
bool DBIter::ReverseToForward() {
|
||||
assert(iter_->status().ok());
|
||||
assert(iter_.status().ok());
|
||||
|
||||
// When moving backwards, iter_ is positioned on _previous_ key, which may
|
||||
// not exist or may have different prefix than the current key().
|
||||
// If that's the case, seek iter_ to current key.
|
||||
if ((prefix_extractor_ != nullptr && !total_order_seek_) || !iter_->Valid()) {
|
||||
if ((prefix_extractor_ != nullptr && !total_order_seek_) || !iter_.Valid()) {
|
||||
IterKey last_key;
|
||||
last_key.SetInternalKey(ParsedInternalKey(
|
||||
saved_key_.GetUserKey(), kMaxSequenceNumber, kValueTypeForSeek));
|
||||
iter_->Seek(last_key.GetInternalKey());
|
||||
iter_.Seek(last_key.GetInternalKey());
|
||||
}
|
||||
|
||||
direction_ = kForward;
|
||||
// Skip keys less than the current key() (a.k.a. saved_key_).
|
||||
while (iter_->Valid()) {
|
||||
while (iter_.Valid()) {
|
||||
ParsedInternalKey ikey;
|
||||
if (!ParseKey(&ikey)) {
|
||||
return false;
|
||||
}
|
||||
if (user_comparator_->Compare(ikey.user_key, saved_key_.GetUserKey()) >=
|
||||
0) {
|
||||
if (user_comparator_.Compare(ikey.user_key, saved_key_.GetUserKey()) >= 0) {
|
||||
return true;
|
||||
}
|
||||
iter_->Next();
|
||||
iter_.Next();
|
||||
}
|
||||
|
||||
if (!iter_->status().ok()) {
|
||||
if (!iter_.status().ok()) {
|
||||
valid_ = false;
|
||||
return false;
|
||||
}
|
||||
@@ -788,14 +792,14 @@ bool DBIter::ReverseToForward() {
|
||||
|
||||
// Move iter_ to the key before saved_key_.
|
||||
bool DBIter::ReverseToBackward() {
|
||||
assert(iter_->status().ok());
|
||||
assert(iter_.status().ok());
|
||||
|
||||
// When current_entry_is_merged_ is true, iter_ may be positioned on the next
|
||||
// key, which may not exist or may have prefix different from current.
|
||||
// If that's the case, seek to saved_key_.
|
||||
if (current_entry_is_merged_ &&
|
||||
((prefix_extractor_ != nullptr && !total_order_seek_) ||
|
||||
!iter_->Valid())) {
|
||||
!iter_.Valid())) {
|
||||
IterKey last_key;
|
||||
// Using kMaxSequenceNumber and kValueTypeForSeek
|
||||
// (not kValueTypeForSeekForPrev) to seek to a key strictly smaller
|
||||
@@ -803,15 +807,15 @@ bool DBIter::ReverseToBackward() {
|
||||
last_key.SetInternalKey(ParsedInternalKey(
|
||||
saved_key_.GetUserKey(), kMaxSequenceNumber, kValueTypeForSeek));
|
||||
if (prefix_extractor_ != nullptr && !total_order_seek_) {
|
||||
iter_->SeekForPrev(last_key.GetInternalKey());
|
||||
iter_.SeekForPrev(last_key.GetInternalKey());
|
||||
} else {
|
||||
// Some iterators may not support SeekForPrev(), so we avoid using it
|
||||
// when prefix seek mode is disabled. This is somewhat expensive
|
||||
// (an extra Prev(), as well as an extra change of direction of iter_),
|
||||
// so we may need to reconsider it later.
|
||||
iter_->Seek(last_key.GetInternalKey());
|
||||
if (!iter_->Valid() && iter_->status().ok()) {
|
||||
iter_->SeekToLast();
|
||||
iter_.Seek(last_key.GetInternalKey());
|
||||
if (!iter_.Valid() && iter_.status().ok()) {
|
||||
iter_.SeekToLast();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -821,10 +825,10 @@ bool DBIter::ReverseToBackward() {
|
||||
}
|
||||
|
||||
void DBIter::PrevInternal() {
|
||||
while (iter_->Valid()) {
|
||||
while (iter_.Valid()) {
|
||||
saved_key_.SetUserKey(
|
||||
ExtractUserKey(iter_->key()),
|
||||
!iter_->IsKeyPinned() || !pin_thru_lifetime_ /* copy */);
|
||||
ExtractUserKey(iter_.key()),
|
||||
!iter_.iter()->IsKeyPinned() || !pin_thru_lifetime_ /* copy */);
|
||||
|
||||
if (prefix_extractor_ && prefix_same_as_start_ &&
|
||||
prefix_extractor_->Transform(saved_key_.GetUserKey())
|
||||
@@ -835,8 +839,8 @@ void DBIter::PrevInternal() {
|
||||
}
|
||||
|
||||
if (iterate_lower_bound_ != nullptr &&
|
||||
user_comparator_->Compare(saved_key_.GetUserKey(),
|
||||
*iterate_lower_bound_) < 0) {
|
||||
user_comparator_.Compare(saved_key_.GetUserKey(),
|
||||
*iterate_lower_bound_) < 0) {
|
||||
// We've iterated earlier than the user-specified lower bound.
|
||||
valid_ = false;
|
||||
return;
|
||||
@@ -878,7 +882,7 @@ void DBIter::PrevInternal() {
|
||||
// POST: iter_ is positioned on one of the entries equal to saved_key_, or on
|
||||
// the entry just before them, or on the entry just after them.
|
||||
bool DBIter::FindValueForCurrentKey() {
|
||||
assert(iter_->Valid());
|
||||
assert(iter_.Valid());
|
||||
merge_context_.Clear();
|
||||
current_entry_is_merged_ = false;
|
||||
// last entry before merge (could be kTypeDeletion, kTypeSingleDeletion or
|
||||
@@ -890,14 +894,14 @@ bool DBIter::FindValueForCurrentKey() {
|
||||
ReleaseTempPinnedData();
|
||||
TempPinData();
|
||||
size_t num_skipped = 0;
|
||||
while (iter_->Valid()) {
|
||||
while (iter_.Valid()) {
|
||||
ParsedInternalKey ikey;
|
||||
if (!ParseKey(&ikey)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!IsVisible(ikey.sequence) ||
|
||||
!user_comparator_->Equal(ikey.user_key, saved_key_.GetUserKey())) {
|
||||
!user_comparator_.Equal(ikey.user_key, saved_key_.GetUserKey())) {
|
||||
break;
|
||||
}
|
||||
if (TooManyInternalKeysSkipped()) {
|
||||
@@ -920,8 +924,8 @@ bool DBIter::FindValueForCurrentKey() {
|
||||
last_key_entry_type = kTypeRangeDeletion;
|
||||
PERF_COUNTER_ADD(internal_delete_skipped_count, 1);
|
||||
} else {
|
||||
assert(iter_->IsValuePinned());
|
||||
pinned_value_ = iter_->value();
|
||||
assert(iter_.iter()->IsValuePinned());
|
||||
pinned_value_ = iter_.value();
|
||||
}
|
||||
merge_context_.Clear();
|
||||
last_not_merge_type = last_key_entry_type;
|
||||
@@ -942,7 +946,8 @@ bool DBIter::FindValueForCurrentKey() {
|
||||
} else {
|
||||
assert(merge_operator_ != nullptr);
|
||||
merge_context_.PushOperandBack(
|
||||
iter_->value(), iter_->IsValuePinned() /* operand_pinned */);
|
||||
iter_.value(),
|
||||
iter_.iter()->IsValuePinned() /* operand_pinned */);
|
||||
PERF_COUNTER_ADD(internal_merge_count, 1);
|
||||
}
|
||||
break;
|
||||
@@ -951,11 +956,11 @@ bool DBIter::FindValueForCurrentKey() {
|
||||
}
|
||||
|
||||
PERF_COUNTER_ADD(internal_key_skipped_count, 1);
|
||||
iter_->Prev();
|
||||
iter_.Prev();
|
||||
++num_skipped;
|
||||
}
|
||||
|
||||
if (!iter_->status().ok()) {
|
||||
if (!iter_.status().ok()) {
|
||||
valid_ = false;
|
||||
return false;
|
||||
}
|
||||
@@ -1035,22 +1040,22 @@ bool DBIter::FindValueForCurrentKeyUsingSeek() {
|
||||
std::string last_key;
|
||||
AppendInternalKey(&last_key, ParsedInternalKey(saved_key_.GetUserKey(),
|
||||
sequence_, kValueTypeForSeek));
|
||||
iter_->Seek(last_key);
|
||||
iter_.Seek(last_key);
|
||||
RecordTick(statistics_, NUMBER_OF_RESEEKS_IN_ITERATION);
|
||||
|
||||
// In case read_callback presents, the value we seek to may not be visible.
|
||||
// Find the next value that's visible.
|
||||
ParsedInternalKey ikey;
|
||||
while (true) {
|
||||
if (!iter_->Valid()) {
|
||||
if (!iter_.Valid()) {
|
||||
valid_ = false;
|
||||
return iter_->status().ok();
|
||||
return iter_.status().ok();
|
||||
}
|
||||
|
||||
if (!ParseKey(&ikey)) {
|
||||
return false;
|
||||
}
|
||||
if (!user_comparator_->Equal(ikey.user_key, saved_key_.GetUserKey())) {
|
||||
if (!user_comparator_.Equal(ikey.user_key, saved_key_.GetUserKey())) {
|
||||
// No visible values for this key, even though FindValueForCurrentKey()
|
||||
// has seen some. This is possible if we're using a tailing iterator, and
|
||||
// the entries were discarded in a compaction.
|
||||
@@ -1062,7 +1067,7 @@ bool DBIter::FindValueForCurrentKeyUsingSeek() {
|
||||
break;
|
||||
}
|
||||
|
||||
iter_->Next();
|
||||
iter_.Next();
|
||||
}
|
||||
|
||||
if (ikey.type == kTypeDeletion || ikey.type == kTypeSingleDeletion ||
|
||||
@@ -1080,8 +1085,8 @@ bool DBIter::FindValueForCurrentKeyUsingSeek() {
|
||||
return false;
|
||||
}
|
||||
if (ikey.type == kTypeValue || ikey.type == kTypeBlobIndex) {
|
||||
assert(iter_->IsValuePinned());
|
||||
pinned_value_ = iter_->value();
|
||||
assert(iter_.iter()->IsValuePinned());
|
||||
pinned_value_ = iter_.value();
|
||||
valid_ = true;
|
||||
return true;
|
||||
}
|
||||
@@ -1091,13 +1096,13 @@ bool DBIter::FindValueForCurrentKeyUsingSeek() {
|
||||
assert(ikey.type == kTypeMerge);
|
||||
current_entry_is_merged_ = true;
|
||||
merge_context_.Clear();
|
||||
merge_context_.PushOperand(iter_->value(),
|
||||
iter_->IsValuePinned() /* operand_pinned */);
|
||||
merge_context_.PushOperand(
|
||||
iter_.value(), iter_.iter()->IsValuePinned() /* operand_pinned */);
|
||||
while (true) {
|
||||
iter_->Next();
|
||||
iter_.Next();
|
||||
|
||||
if (!iter_->Valid()) {
|
||||
if (!iter_->status().ok()) {
|
||||
if (!iter_.Valid()) {
|
||||
if (!iter_.status().ok()) {
|
||||
valid_ = false;
|
||||
return false;
|
||||
}
|
||||
@@ -1106,7 +1111,7 @@ bool DBIter::FindValueForCurrentKeyUsingSeek() {
|
||||
if (!ParseKey(&ikey)) {
|
||||
return false;
|
||||
}
|
||||
if (!user_comparator_->Equal(ikey.user_key, saved_key_.GetUserKey())) {
|
||||
if (!user_comparator_.Equal(ikey.user_key, saved_key_.GetUserKey())) {
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -1115,7 +1120,7 @@ bool DBIter::FindValueForCurrentKeyUsingSeek() {
|
||||
ikey, RangeDelPositioningMode::kForwardTraversal)) {
|
||||
break;
|
||||
} else if (ikey.type == kTypeValue) {
|
||||
const Slice val = iter_->value();
|
||||
const Slice val = iter_.value();
|
||||
Status s = MergeHelper::TimedFullMerge(
|
||||
merge_operator_, saved_key_.GetUserKey(), &val,
|
||||
merge_context_.GetOperands(), &saved_value_, logger_, statistics_,
|
||||
@@ -1128,8 +1133,8 @@ bool DBIter::FindValueForCurrentKeyUsingSeek() {
|
||||
valid_ = true;
|
||||
return true;
|
||||
} else if (ikey.type == kTypeMerge) {
|
||||
merge_context_.PushOperand(iter_->value(),
|
||||
iter_->IsValuePinned() /* operand_pinned */);
|
||||
merge_context_.PushOperand(
|
||||
iter_.value(), iter_.iter()->IsValuePinned() /* operand_pinned */);
|
||||
PERF_COUNTER_ADD(internal_merge_count, 1);
|
||||
} else if (ikey.type == kTypeBlobIndex) {
|
||||
if (!allow_blob_) {
|
||||
@@ -1161,13 +1166,13 @@ bool DBIter::FindValueForCurrentKeyUsingSeek() {
|
||||
// Make sure we leave iter_ in a good state. If it's valid and we don't care
|
||||
// about prefixes, that's already good enough. Otherwise it needs to be
|
||||
// seeked to the current key.
|
||||
if ((prefix_extractor_ != nullptr && !total_order_seek_) || !iter_->Valid()) {
|
||||
if ((prefix_extractor_ != nullptr && !total_order_seek_) || !iter_.Valid()) {
|
||||
if (prefix_extractor_ != nullptr && !total_order_seek_) {
|
||||
iter_->SeekForPrev(last_key);
|
||||
iter_.SeekForPrev(last_key);
|
||||
} else {
|
||||
iter_->Seek(last_key);
|
||||
if (!iter_->Valid() && iter_->status().ok()) {
|
||||
iter_->SeekToLast();
|
||||
iter_.Seek(last_key);
|
||||
if (!iter_.Valid() && iter_.status().ok()) {
|
||||
iter_.SeekToLast();
|
||||
}
|
||||
}
|
||||
RecordTick(statistics_, NUMBER_OF_RESEEKS_IN_ITERATION);
|
||||
@@ -1182,13 +1187,13 @@ bool DBIter::FindValueForCurrentKeyUsingSeek() {
|
||||
bool DBIter::FindUserKeyBeforeSavedKey() {
|
||||
assert(status_.ok());
|
||||
size_t num_skipped = 0;
|
||||
while (iter_->Valid()) {
|
||||
while (iter_.Valid()) {
|
||||
ParsedInternalKey ikey;
|
||||
if (!ParseKey(&ikey)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (user_comparator_->Compare(ikey.user_key, saved_key_.GetUserKey()) < 0) {
|
||||
if (user_comparator_.Compare(ikey.user_key, saved_key_.GetUserKey()) < 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1210,19 +1215,19 @@ bool DBIter::FindUserKeyBeforeSavedKey() {
|
||||
saved_key_.GetUserKey(), kMaxSequenceNumber, kValueTypeForSeek));
|
||||
// It would be more efficient to use SeekForPrev() here, but some
|
||||
// iterators may not support it.
|
||||
iter_->Seek(last_key.GetInternalKey());
|
||||
iter_.Seek(last_key.GetInternalKey());
|
||||
RecordTick(statistics_, NUMBER_OF_RESEEKS_IN_ITERATION);
|
||||
if (!iter_->Valid()) {
|
||||
if (!iter_.Valid()) {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
++num_skipped;
|
||||
}
|
||||
|
||||
iter_->Prev();
|
||||
iter_.Prev();
|
||||
}
|
||||
|
||||
if (!iter_->status().ok()) {
|
||||
if (!iter_.status().ok()) {
|
||||
valid_ = false;
|
||||
return false;
|
||||
}
|
||||
@@ -1243,30 +1248,25 @@ bool DBIter::TooManyInternalKeysSkipped(bool increment) {
|
||||
}
|
||||
|
||||
bool DBIter::IsVisible(SequenceNumber sequence) {
|
||||
return sequence <= MaxVisibleSequenceNumber() &&
|
||||
(read_callback_ == nullptr || read_callback_->IsVisible(sequence));
|
||||
if (read_callback_ == nullptr) {
|
||||
return sequence <= sequence_;
|
||||
} else {
|
||||
return read_callback_->IsVisible(sequence);
|
||||
}
|
||||
}
|
||||
|
||||
bool DBIter::CanReseekToSkip() {
|
||||
return read_callback_ == nullptr ||
|
||||
read_callback_->MaxUnpreparedSequenceNumber() == 0;
|
||||
}
|
||||
|
||||
SequenceNumber DBIter::MaxVisibleSequenceNumber() {
|
||||
if (read_callback_ == nullptr) {
|
||||
return sequence_;
|
||||
}
|
||||
|
||||
return std::max(sequence_, read_callback_->MaxUnpreparedSequenceNumber());
|
||||
return read_callback_ == nullptr || read_callback_->CanReseekToSkip();
|
||||
}
|
||||
|
||||
void DBIter::Seek(const Slice& target) {
|
||||
PERF_CPU_TIMER_GUARD(iter_seek_cpu_nanos, env_);
|
||||
StopWatch sw(env_, statistics_, DB_SEEK);
|
||||
status_ = Status::OK();
|
||||
ReleaseTempPinnedData();
|
||||
ResetInternalKeysSkippedCounter();
|
||||
|
||||
SequenceNumber seq = MaxVisibleSequenceNumber();
|
||||
SequenceNumber seq = sequence_;
|
||||
saved_key_.Clear();
|
||||
saved_key_.SetInternalKey(target, seq);
|
||||
|
||||
@@ -1277,19 +1277,19 @@ void DBIter::Seek(const Slice& target) {
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
if (iterate_lower_bound_ != nullptr &&
|
||||
user_comparator_->Compare(saved_key_.GetUserKey(),
|
||||
*iterate_lower_bound_) < 0) {
|
||||
user_comparator_.Compare(saved_key_.GetUserKey(), *iterate_lower_bound_) <
|
||||
0) {
|
||||
saved_key_.Clear();
|
||||
saved_key_.SetInternalKey(*iterate_lower_bound_, seq);
|
||||
}
|
||||
|
||||
{
|
||||
PERF_TIMER_GUARD(seek_internal_seek_time);
|
||||
iter_->Seek(saved_key_.GetInternalKey());
|
||||
iter_.Seek(saved_key_.GetInternalKey());
|
||||
range_del_agg_.InvalidateRangeDelMapPositions();
|
||||
}
|
||||
RecordTick(statistics_, NUMBER_DB_SEEK);
|
||||
if (iter_->Valid()) {
|
||||
if (iter_.Valid()) {
|
||||
if (prefix_extractor_ && prefix_same_as_start_) {
|
||||
prefix_start_key_ = prefix_extractor_->Transform(target);
|
||||
}
|
||||
@@ -1318,6 +1318,7 @@ void DBIter::Seek(const Slice& target) {
|
||||
}
|
||||
|
||||
void DBIter::SeekForPrev(const Slice& target) {
|
||||
PERF_CPU_TIMER_GUARD(iter_seek_cpu_nanos, env_);
|
||||
StopWatch sw(env_, statistics_, DB_SEEK);
|
||||
status_ = Status::OK();
|
||||
ReleaseTempPinnedData();
|
||||
@@ -1328,15 +1329,15 @@ void DBIter::SeekForPrev(const Slice& target) {
|
||||
kValueTypeForSeekForPrev);
|
||||
|
||||
if (iterate_upper_bound_ != nullptr &&
|
||||
user_comparator_->Compare(saved_key_.GetUserKey(),
|
||||
*iterate_upper_bound_) >= 0) {
|
||||
user_comparator_.Compare(saved_key_.GetUserKey(),
|
||||
*iterate_upper_bound_) >= 0) {
|
||||
saved_key_.Clear();
|
||||
saved_key_.SetInternalKey(*iterate_upper_bound_, kMaxSequenceNumber);
|
||||
}
|
||||
|
||||
{
|
||||
PERF_TIMER_GUARD(seek_internal_seek_time);
|
||||
iter_->SeekForPrev(saved_key_.GetInternalKey());
|
||||
iter_.SeekForPrev(saved_key_.GetInternalKey());
|
||||
range_del_agg_.InvalidateRangeDelMapPositions();
|
||||
}
|
||||
|
||||
@@ -1347,7 +1348,7 @@ void DBIter::SeekForPrev(const Slice& target) {
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
RecordTick(statistics_, NUMBER_DB_SEEK);
|
||||
if (iter_->Valid()) {
|
||||
if (iter_.Valid()) {
|
||||
if (prefix_extractor_ && prefix_same_as_start_) {
|
||||
prefix_start_key_ = prefix_extractor_->Transform(target);
|
||||
}
|
||||
@@ -1378,6 +1379,7 @@ void DBIter::SeekToFirst() {
|
||||
Seek(*iterate_lower_bound_);
|
||||
return;
|
||||
}
|
||||
PERF_CPU_TIMER_GUARD(iter_seek_cpu_nanos, env_);
|
||||
// Don't use iter_::Seek() if we set a prefix extractor
|
||||
// because prefix seek will be used.
|
||||
if (prefix_extractor_ != nullptr && !total_order_seek_) {
|
||||
@@ -1391,15 +1393,15 @@ void DBIter::SeekToFirst() {
|
||||
|
||||
{
|
||||
PERF_TIMER_GUARD(seek_internal_seek_time);
|
||||
iter_->SeekToFirst();
|
||||
iter_.SeekToFirst();
|
||||
range_del_agg_.InvalidateRangeDelMapPositions();
|
||||
}
|
||||
|
||||
RecordTick(statistics_, NUMBER_DB_SEEK);
|
||||
if (iter_->Valid()) {
|
||||
if (iter_.Valid()) {
|
||||
saved_key_.SetUserKey(
|
||||
ExtractUserKey(iter_->key()),
|
||||
!iter_->IsKeyPinned() || !pin_thru_lifetime_ /* copy */);
|
||||
ExtractUserKey(iter_.key()),
|
||||
!iter_.iter()->IsKeyPinned() || !pin_thru_lifetime_ /* copy */);
|
||||
FindNextUserEntry(false /* not skipping */, false /* no prefix check */);
|
||||
if (statistics_ != nullptr) {
|
||||
if (valid_) {
|
||||
@@ -1422,13 +1424,14 @@ void DBIter::SeekToLast() {
|
||||
if (iterate_upper_bound_ != nullptr) {
|
||||
// Seek to last key strictly less than ReadOptions.iterate_upper_bound.
|
||||
SeekForPrev(*iterate_upper_bound_);
|
||||
if (Valid() && user_comparator_->Equal(*iterate_upper_bound_, key())) {
|
||||
if (Valid() && user_comparator_.Equal(*iterate_upper_bound_, key())) {
|
||||
ReleaseTempPinnedData();
|
||||
PrevInternal();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
PERF_CPU_TIMER_GUARD(iter_seek_cpu_nanos, env_);
|
||||
// Don't use iter_::Seek() if we set a prefix extractor
|
||||
// because prefix seek will be used.
|
||||
if (prefix_extractor_ != nullptr && !total_order_seek_) {
|
||||
@@ -1442,7 +1445,7 @@ void DBIter::SeekToLast() {
|
||||
|
||||
{
|
||||
PERF_TIMER_GUARD(seek_internal_seek_time);
|
||||
iter_->SeekToLast();
|
||||
iter_.SeekToLast();
|
||||
range_del_agg_.InvalidateRangeDelMapPositions();
|
||||
}
|
||||
PrevInternal();
|
||||
@@ -1549,6 +1552,9 @@ Status ArenaWrappedDBIter::Refresh() {
|
||||
new (&arena_) Arena();
|
||||
|
||||
SuperVersion* sv = cfd_->GetReferencedSuperVersion(db_impl_->mutex());
|
||||
if (read_callback_) {
|
||||
read_callback_->Refresh(latest_seq);
|
||||
}
|
||||
Init(env, read_options_, *(cfd_->ioptions()), sv->mutable_cf_options,
|
||||
latest_seq, sv->mutable_cf_options.max_sequential_skip_in_iterations,
|
||||
cur_sv_number, read_callback_, db_impl_, cfd_, allow_blob_,
|
||||
|
||||
+12
-12
@@ -116,12 +116,12 @@ class TestIterator : public InternalIterator {
|
||||
// Number of operations done on this iterator since construction.
|
||||
size_t steps() const { return steps_; }
|
||||
|
||||
virtual bool Valid() const override {
|
||||
bool Valid() const override {
|
||||
assert(initialized_);
|
||||
return valid_;
|
||||
}
|
||||
|
||||
virtual void SeekToFirst() override {
|
||||
void SeekToFirst() override {
|
||||
assert(initialized_);
|
||||
++steps_;
|
||||
DeleteCurrentIfNeeded();
|
||||
@@ -129,7 +129,7 @@ class TestIterator : public InternalIterator {
|
||||
iter_ = 0;
|
||||
}
|
||||
|
||||
virtual void SeekToLast() override {
|
||||
void SeekToLast() override {
|
||||
assert(initialized_);
|
||||
++steps_;
|
||||
DeleteCurrentIfNeeded();
|
||||
@@ -137,7 +137,7 @@ class TestIterator : public InternalIterator {
|
||||
iter_ = data_.size() - 1;
|
||||
}
|
||||
|
||||
virtual void Seek(const Slice& target) override {
|
||||
void Seek(const Slice& target) override {
|
||||
assert(initialized_);
|
||||
SeekToFirst();
|
||||
++steps_;
|
||||
@@ -154,13 +154,13 @@ class TestIterator : public InternalIterator {
|
||||
}
|
||||
}
|
||||
|
||||
virtual void SeekForPrev(const Slice& target) override {
|
||||
void SeekForPrev(const Slice& target) override {
|
||||
assert(initialized_);
|
||||
DeleteCurrentIfNeeded();
|
||||
SeekForPrevImpl(target, &cmp);
|
||||
}
|
||||
|
||||
virtual void Next() override {
|
||||
void Next() override {
|
||||
assert(initialized_);
|
||||
assert(valid_);
|
||||
assert(iter_ < data_.size());
|
||||
@@ -174,7 +174,7 @@ class TestIterator : public InternalIterator {
|
||||
valid_ = iter_ < data_.size();
|
||||
}
|
||||
|
||||
virtual void Prev() override {
|
||||
void Prev() override {
|
||||
assert(initialized_);
|
||||
assert(valid_);
|
||||
assert(iter_ < data_.size());
|
||||
@@ -188,23 +188,23 @@ class TestIterator : public InternalIterator {
|
||||
}
|
||||
}
|
||||
|
||||
virtual Slice key() const override {
|
||||
Slice key() const override {
|
||||
assert(initialized_);
|
||||
return data_[iter_].first;
|
||||
}
|
||||
|
||||
virtual Slice value() const override {
|
||||
Slice value() const override {
|
||||
assert(initialized_);
|
||||
return data_[iter_].second;
|
||||
}
|
||||
|
||||
virtual Status status() const override {
|
||||
Status status() const override {
|
||||
assert(initialized_);
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
virtual bool IsKeyPinned() const override { return true; }
|
||||
virtual bool IsValuePinned() const override { return true; }
|
||||
bool IsKeyPinned() const override { return true; }
|
||||
bool IsValuePinned() const override { return true; }
|
||||
|
||||
private:
|
||||
bool initialized_;
|
||||
|
||||
+59
-81
@@ -15,12 +15,16 @@
|
||||
#include "port/stack_trace.h"
|
||||
#include "rocksdb/iostats_context.h"
|
||||
#include "rocksdb/perf_context.h"
|
||||
#include "table/flush_block_policy.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
// A dumb ReadCallback which saying every key is committed.
|
||||
class DummyReadCallback : public ReadCallback {
|
||||
bool IsVisible(SequenceNumber /*seq*/) override { return true; }
|
||||
public:
|
||||
DummyReadCallback() : ReadCallback(kMaxSequenceNumber) {}
|
||||
bool IsVisibleFullCheck(SequenceNumber /*seq*/) override { return true; }
|
||||
void SetSnapshot(SequenceNumber seq) { max_visible_seq_ = seq; }
|
||||
};
|
||||
|
||||
// Test param:
|
||||
@@ -40,40 +44,20 @@ class DBIteratorTest : public DBTestBase,
|
||||
? read_options.snapshot->GetSequenceNumber()
|
||||
: db_->GetLatestSequenceNumber();
|
||||
bool use_read_callback = GetParam();
|
||||
ReadCallback* read_callback = use_read_callback ? &read_callback_ : nullptr;
|
||||
DummyReadCallback* read_callback = nullptr;
|
||||
if (use_read_callback) {
|
||||
read_callback = new DummyReadCallback();
|
||||
read_callback->SetSnapshot(seq);
|
||||
InstrumentedMutexLock lock(&mutex_);
|
||||
read_callbacks_.push_back(
|
||||
std::unique_ptr<DummyReadCallback>(read_callback));
|
||||
}
|
||||
return dbfull()->NewIteratorImpl(read_options, cfd, seq, read_callback);
|
||||
}
|
||||
|
||||
private:
|
||||
DummyReadCallback read_callback_;
|
||||
};
|
||||
|
||||
class FlushBlockEveryKeyPolicy : public FlushBlockPolicy {
|
||||
public:
|
||||
virtual bool Update(const Slice& /*key*/, const Slice& /*value*/) override {
|
||||
if (!start_) {
|
||||
start_ = true;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
private:
|
||||
bool start_ = false;
|
||||
};
|
||||
|
||||
class FlushBlockEveryKeyPolicyFactory : public FlushBlockPolicyFactory {
|
||||
public:
|
||||
explicit FlushBlockEveryKeyPolicyFactory() {}
|
||||
|
||||
const char* Name() const override {
|
||||
return "FlushBlockEveryKeyPolicyFactory";
|
||||
}
|
||||
|
||||
FlushBlockPolicy* NewFlushBlockPolicy(
|
||||
const BlockBasedTableOptions& /*table_options*/,
|
||||
const BlockBuilder& /*data_block_builder*/) const override {
|
||||
return new FlushBlockEveryKeyPolicy;
|
||||
}
|
||||
InstrumentedMutex mutex_;
|
||||
std::vector<std::unique_ptr<DummyReadCallback>> read_callbacks_;
|
||||
};
|
||||
|
||||
TEST_P(DBIteratorTest, IteratorProperty) {
|
||||
@@ -179,9 +163,7 @@ TEST_P(DBIteratorTest, NonBlockingIteration) {
|
||||
|
||||
// This test verifies block cache behaviors, which is not used by plain
|
||||
// table format.
|
||||
// Exclude kHashCuckoo as it does not support iteration currently
|
||||
} while (ChangeOptions(kSkipPlainTable | kSkipNoSeekToLast | kSkipHashCuckoo |
|
||||
kSkipMmapReads));
|
||||
} while (ChangeOptions(kSkipPlainTable | kSkipNoSeekToLast | kSkipMmapReads));
|
||||
}
|
||||
|
||||
TEST_P(DBIteratorTest, IterSeekBeforePrev) {
|
||||
@@ -765,8 +747,7 @@ TEST_P(DBIteratorTest, IterWithSnapshot) {
|
||||
}
|
||||
db_->ReleaseSnapshot(snapshot);
|
||||
delete iter;
|
||||
// skip as HashCuckooRep does not support snapshot
|
||||
} while (ChangeOptions(kSkipHashCuckoo));
|
||||
} while (ChangeOptions());
|
||||
}
|
||||
|
||||
TEST_P(DBIteratorTest, IteratorPinsRef) {
|
||||
@@ -1025,48 +1006,48 @@ TEST_P(DBIteratorTest, DBIteratorBoundMultiSeek) {
|
||||
#endif
|
||||
|
||||
TEST_P(DBIteratorTest, DBIteratorBoundOptimizationTest) {
|
||||
int upper_bound_hits = 0;
|
||||
Options options = CurrentOptions();
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"BlockBasedTable::BlockEntryIteratorState::KeyReachedUpperBound",
|
||||
[&upper_bound_hits](void* arg) {
|
||||
assert(arg != nullptr);
|
||||
upper_bound_hits += (*static_cast<bool*>(arg) ? 1 : 0);
|
||||
});
|
||||
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
||||
options.env = env_;
|
||||
options.create_if_missing = true;
|
||||
options.prefix_extractor = nullptr;
|
||||
BlockBasedTableOptions table_options;
|
||||
table_options.flush_block_policy_factory =
|
||||
std::make_shared<FlushBlockEveryKeyPolicyFactory>();
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
for (auto format_version : {2, 3, 4}) {
|
||||
int upper_bound_hits = 0;
|
||||
Options options = CurrentOptions();
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"BlockBasedTableIterator:out_of_bound",
|
||||
[&upper_bound_hits](void*) { upper_bound_hits++; });
|
||||
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
||||
options.env = env_;
|
||||
options.create_if_missing = true;
|
||||
options.prefix_extractor = nullptr;
|
||||
BlockBasedTableOptions table_options;
|
||||
table_options.format_version = format_version;
|
||||
table_options.flush_block_policy_factory =
|
||||
std::make_shared<FlushBlockEveryKeyPolicyFactory>();
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
|
||||
DestroyAndReopen(options);
|
||||
ASSERT_OK(Put("foo1", "bar1"));
|
||||
ASSERT_OK(Put("foo2", "bar2"));
|
||||
ASSERT_OK(Put("foo4", "bar4"));
|
||||
ASSERT_OK(Flush());
|
||||
DestroyAndReopen(options);
|
||||
ASSERT_OK(Put("foo1", "bar1"));
|
||||
ASSERT_OK(Put("foo2", "bar2"));
|
||||
ASSERT_OK(Put("foo4", "bar4"));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
Slice ub("foo3");
|
||||
ReadOptions ro;
|
||||
ro.iterate_upper_bound = &ub;
|
||||
Slice ub("foo3");
|
||||
ReadOptions ro;
|
||||
ro.iterate_upper_bound = &ub;
|
||||
|
||||
std::unique_ptr<Iterator> iter(NewIterator(ro));
|
||||
std::unique_ptr<Iterator> iter(NewIterator(ro));
|
||||
|
||||
iter->Seek("foo");
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ(iter->key().compare(Slice("foo1")), 0);
|
||||
ASSERT_EQ(upper_bound_hits, 0);
|
||||
iter->Seek("foo");
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ(iter->key().compare(Slice("foo1")), 0);
|
||||
ASSERT_EQ(upper_bound_hits, 0);
|
||||
|
||||
iter->Next();
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ(iter->key().compare(Slice("foo2")), 0);
|
||||
ASSERT_EQ(upper_bound_hits, 0);
|
||||
iter->Next();
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ(iter->key().compare(Slice("foo2")), 0);
|
||||
ASSERT_EQ(upper_bound_hits, 0);
|
||||
|
||||
iter->Next();
|
||||
ASSERT_FALSE(iter->Valid());
|
||||
ASSERT_EQ(upper_bound_hits, 1);
|
||||
iter->Next();
|
||||
ASSERT_FALSE(iter->Valid());
|
||||
ASSERT_EQ(upper_bound_hits, 1);
|
||||
}
|
||||
}
|
||||
// TODO(3.13): fix the issue of Seek() + Prev() which might not necessary
|
||||
// return the biggest key which is smaller than the seek key.
|
||||
@@ -1962,8 +1943,8 @@ TEST_P(DBIteratorTest, ReadAhead) {
|
||||
delete iter;
|
||||
int64_t num_file_closes_readahead =
|
||||
TestGetTickerCount(options, NO_FILE_CLOSES);
|
||||
ASSERT_EQ(num_file_opens + 3, num_file_opens_readahead);
|
||||
ASSERT_EQ(num_file_closes + 3, num_file_closes_readahead);
|
||||
ASSERT_EQ(num_file_opens, num_file_opens_readahead);
|
||||
ASSERT_EQ(num_file_closes, num_file_closes_readahead);
|
||||
ASSERT_GT(bytes_read_readahead, bytes_read);
|
||||
ASSERT_GT(bytes_read_readahead, read_options.readahead_size * 3);
|
||||
|
||||
@@ -2478,15 +2459,12 @@ class DBIteratorWithReadCallbackTest : public DBIteratorTest {};
|
||||
TEST_F(DBIteratorWithReadCallbackTest, ReadCallback) {
|
||||
class TestReadCallback : public ReadCallback {
|
||||
public:
|
||||
explicit TestReadCallback(SequenceNumber last_visible_seq)
|
||||
: last_visible_seq_(last_visible_seq) {}
|
||||
explicit TestReadCallback(SequenceNumber _max_visible_seq)
|
||||
: ReadCallback(_max_visible_seq) {}
|
||||
|
||||
bool IsVisible(SequenceNumber seq) override {
|
||||
return seq <= last_visible_seq_;
|
||||
bool IsVisibleFullCheck(SequenceNumber seq) override {
|
||||
return seq <= max_visible_seq_;
|
||||
}
|
||||
|
||||
private:
|
||||
SequenceNumber last_visible_seq_;
|
||||
};
|
||||
|
||||
ASSERT_OK(Put("foo", "v1"));
|
||||
|
||||
@@ -249,22 +249,20 @@ TEST_F(DBTestXactLogIterator, TransactionLogIteratorBlobs) {
|
||||
auto res = OpenTransactionLogIter(0)->GetBatch();
|
||||
struct Handler : public WriteBatch::Handler {
|
||||
std::string seen;
|
||||
virtual Status PutCF(uint32_t cf, const Slice& key,
|
||||
const Slice& value) override {
|
||||
Status PutCF(uint32_t cf, const Slice& key, const Slice& value) override {
|
||||
seen += "Put(" + ToString(cf) + ", " + key.ToString() + ", " +
|
||||
ToString(value.size()) + ")";
|
||||
return Status::OK();
|
||||
}
|
||||
virtual Status MergeCF(uint32_t cf, const Slice& key,
|
||||
const Slice& value) override {
|
||||
Status MergeCF(uint32_t cf, const Slice& key, const Slice& value) override {
|
||||
seen += "Merge(" + ToString(cf) + ", " + key.ToString() + ", " +
|
||||
ToString(value.size()) + ")";
|
||||
return Status::OK();
|
||||
}
|
||||
virtual void LogData(const Slice& blob) override {
|
||||
void LogData(const Slice& blob) override {
|
||||
seen += "LogData(" + blob.ToString() + ")";
|
||||
}
|
||||
virtual Status DeleteCF(uint32_t cf, const Slice& key) override {
|
||||
Status DeleteCF(uint32_t cf, const Slice& key) override {
|
||||
seen += "Delete(" + ToString(cf) + ", " + key.ToString() + ")";
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
+22
-25
@@ -25,13 +25,13 @@ class MockMemTableRep : public MemTableRep {
|
||||
explicit MockMemTableRep(Allocator* allocator, MemTableRep* rep)
|
||||
: MemTableRep(allocator), rep_(rep), num_insert_with_hint_(0) {}
|
||||
|
||||
virtual KeyHandle Allocate(const size_t len, char** buf) override {
|
||||
KeyHandle Allocate(const size_t len, char** buf) override {
|
||||
return rep_->Allocate(len, buf);
|
||||
}
|
||||
|
||||
virtual void Insert(KeyHandle handle) override { rep_->Insert(handle); }
|
||||
void Insert(KeyHandle handle) override { rep_->Insert(handle); }
|
||||
|
||||
virtual void InsertWithHint(KeyHandle handle, void** hint) override {
|
||||
void InsertWithHint(KeyHandle handle, void** hint) override {
|
||||
num_insert_with_hint_++;
|
||||
EXPECT_NE(nullptr, hint);
|
||||
last_hint_in_ = *hint;
|
||||
@@ -39,21 +39,18 @@ class MockMemTableRep : public MemTableRep {
|
||||
last_hint_out_ = *hint;
|
||||
}
|
||||
|
||||
virtual bool Contains(const char* key) const override {
|
||||
return rep_->Contains(key);
|
||||
}
|
||||
bool Contains(const char* key) const override { return rep_->Contains(key); }
|
||||
|
||||
virtual void Get(const LookupKey& k, void* callback_args,
|
||||
bool (*callback_func)(void* arg,
|
||||
const char* entry)) override {
|
||||
void Get(const LookupKey& k, void* callback_args,
|
||||
bool (*callback_func)(void* arg, const char* entry)) override {
|
||||
rep_->Get(k, callback_args, callback_func);
|
||||
}
|
||||
|
||||
virtual size_t ApproximateMemoryUsage() override {
|
||||
size_t ApproximateMemoryUsage() override {
|
||||
return rep_->ApproximateMemoryUsage();
|
||||
}
|
||||
|
||||
virtual Iterator* GetIterator(Arena* arena) override {
|
||||
Iterator* GetIterator(Arena* arena) override {
|
||||
return rep_->GetIterator(arena);
|
||||
}
|
||||
|
||||
@@ -70,10 +67,10 @@ class MockMemTableRep : public MemTableRep {
|
||||
|
||||
class MockMemTableRepFactory : public MemTableRepFactory {
|
||||
public:
|
||||
virtual MemTableRep* CreateMemTableRep(const MemTableRep::KeyComparator& cmp,
|
||||
Allocator* allocator,
|
||||
const SliceTransform* transform,
|
||||
Logger* logger) override {
|
||||
MemTableRep* CreateMemTableRep(const MemTableRep::KeyComparator& cmp,
|
||||
Allocator* allocator,
|
||||
const SliceTransform* transform,
|
||||
Logger* logger) override {
|
||||
SkipListFactory factory;
|
||||
MemTableRep* skiplist_rep =
|
||||
factory.CreateMemTableRep(cmp, allocator, transform, logger);
|
||||
@@ -81,16 +78,16 @@ class MockMemTableRepFactory : public MemTableRepFactory {
|
||||
return mock_rep_;
|
||||
}
|
||||
|
||||
virtual MemTableRep* CreateMemTableRep(const MemTableRep::KeyComparator& cmp,
|
||||
Allocator* allocator,
|
||||
const SliceTransform* transform,
|
||||
Logger* logger,
|
||||
uint32_t column_family_id) override {
|
||||
MemTableRep* CreateMemTableRep(const MemTableRep::KeyComparator& cmp,
|
||||
Allocator* allocator,
|
||||
const SliceTransform* transform,
|
||||
Logger* logger,
|
||||
uint32_t column_family_id) override {
|
||||
last_column_family_id_ = column_family_id;
|
||||
return CreateMemTableRep(cmp, allocator, transform, logger);
|
||||
}
|
||||
|
||||
virtual const char* Name() const override { return "MockMemTableRepFactory"; }
|
||||
const char* Name() const override { return "MockMemTableRepFactory"; }
|
||||
|
||||
MockMemTableRep* rep() { return mock_rep_; }
|
||||
|
||||
@@ -106,9 +103,9 @@ class MockMemTableRepFactory : public MemTableRepFactory {
|
||||
|
||||
class TestPrefixExtractor : public SliceTransform {
|
||||
public:
|
||||
virtual const char* Name() const override { return "TestPrefixExtractor"; }
|
||||
const char* Name() const override { return "TestPrefixExtractor"; }
|
||||
|
||||
virtual Slice Transform(const Slice& key) const override {
|
||||
Slice Transform(const Slice& key) const override {
|
||||
const char* p = separator(key);
|
||||
if (p == nullptr) {
|
||||
return Slice();
|
||||
@@ -116,11 +113,11 @@ class TestPrefixExtractor : public SliceTransform {
|
||||
return Slice(key.data(), p - key.data() + 1);
|
||||
}
|
||||
|
||||
virtual bool InDomain(const Slice& key) const override {
|
||||
bool InDomain(const Slice& key) const override {
|
||||
return separator(key) != nullptr;
|
||||
}
|
||||
|
||||
virtual bool InRange(const Slice& /*key*/) const override { return false; }
|
||||
bool InRange(const Slice& /*key*/) const override { return false; }
|
||||
|
||||
private:
|
||||
const char* separator(const Slice& key) const {
|
||||
|
||||
@@ -18,9 +18,11 @@ class TestReadCallback : public ReadCallback {
|
||||
public:
|
||||
TestReadCallback(SnapshotChecker* snapshot_checker,
|
||||
SequenceNumber snapshot_seq)
|
||||
: snapshot_checker_(snapshot_checker), snapshot_seq_(snapshot_seq) {}
|
||||
: ReadCallback(snapshot_seq),
|
||||
snapshot_checker_(snapshot_checker),
|
||||
snapshot_seq_(snapshot_seq) {}
|
||||
|
||||
bool IsVisible(SequenceNumber seq) override {
|
||||
bool IsVisibleFullCheck(SequenceNumber seq) override {
|
||||
return snapshot_checker_->CheckInSnapshot(seq, snapshot_seq_) ==
|
||||
SnapshotCheckerResult::kInSnapshot;
|
||||
}
|
||||
@@ -332,8 +334,7 @@ TEST_P(MergeOperatorPinningTest, Randomized) {
|
||||
|
||||
VerifyDBFromMap(true_data);
|
||||
|
||||
// Skip HashCuckoo since it does not support merge operators
|
||||
} while (ChangeOptions(kSkipMergePut | kSkipHashCuckoo));
|
||||
} while (ChangeOptions(kSkipMergePut));
|
||||
}
|
||||
|
||||
class MergeOperatorHook : public MergeOperator {
|
||||
@@ -341,15 +342,15 @@ class MergeOperatorHook : public MergeOperator {
|
||||
explicit MergeOperatorHook(std::shared_ptr<MergeOperator> _merge_op)
|
||||
: merge_op_(_merge_op) {}
|
||||
|
||||
virtual bool FullMergeV2(const MergeOperationInput& merge_in,
|
||||
MergeOperationOutput* merge_out) const override {
|
||||
bool FullMergeV2(const MergeOperationInput& merge_in,
|
||||
MergeOperationOutput* merge_out) const override {
|
||||
before_merge_();
|
||||
bool res = merge_op_->FullMergeV2(merge_in, merge_out);
|
||||
after_merge_();
|
||||
return res;
|
||||
}
|
||||
|
||||
virtual const char* Name() const override { return merge_op_->Name(); }
|
||||
const char* Name() const override { return merge_op_->Name(); }
|
||||
|
||||
std::shared_ptr<MergeOperator> merge_op_;
|
||||
std::function<void()> before_merge_ = []() {};
|
||||
|
||||
+267
-11
@@ -18,12 +18,15 @@
|
||||
#include "rocksdb/cache.h"
|
||||
#include "rocksdb/convenience.h"
|
||||
#include "rocksdb/rate_limiter.h"
|
||||
#include "rocksdb/stats_history.h"
|
||||
#include "util/random.h"
|
||||
#include "util/sync_point.h"
|
||||
#include "util/testutil.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
const int kMicrosInSec = 1000000;
|
||||
|
||||
class DBOptionsTest : public DBTestBase {
|
||||
public:
|
||||
DBOptionsTest() : DBTestBase("/db_options_test") {}
|
||||
@@ -508,10 +511,11 @@ TEST_F(DBOptionsTest, SetStatsDumpPeriodSec) {
|
||||
|
||||
for (int i = 0; i < 20; i++) {
|
||||
int num = rand() % 5000 + 1;
|
||||
ASSERT_OK(dbfull()->SetDBOptions(
|
||||
{{"stats_dump_period_sec", std::to_string(num)}}));
|
||||
ASSERT_OK(
|
||||
dbfull()->SetDBOptions({{"stats_dump_period_sec", ToString(num)}}));
|
||||
ASSERT_EQ(num, dbfull()->GetDBOptions().stats_dump_period_sec);
|
||||
}
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(DBOptionsTest, RunStatsDumpPeriodSec) {
|
||||
@@ -523,6 +527,17 @@ TEST_F(DBOptionsTest, RunStatsDumpPeriodSec) {
|
||||
mock_env->set_current_time(0); // in seconds
|
||||
options.env = mock_env.get();
|
||||
int counter = 0;
|
||||
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
||||
rocksdb::SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
#if defined(OS_MACOSX) && !defined(NDEBUG)
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"InstrumentedCondVar::TimedWaitInternal", [&](void* arg) {
|
||||
uint64_t time_us = *reinterpret_cast<uint64_t*>(arg);
|
||||
if (time_us < mock_env->RealNowMicros()) {
|
||||
*reinterpret_cast<uint64_t*>(arg) = mock_env->RealNowMicros() + 1000;
|
||||
}
|
||||
});
|
||||
#endif // OS_MACOSX && !NDEBUG
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::DumpStats:1", [&](void* /*arg*/) {
|
||||
counter++;
|
||||
@@ -530,17 +545,258 @@ TEST_F(DBOptionsTest, RunStatsDumpPeriodSec) {
|
||||
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
||||
Reopen(options);
|
||||
ASSERT_EQ(5, dbfull()->GetDBOptions().stats_dump_period_sec);
|
||||
dbfull()->TEST_WaitForTimedTaskRun([&] { mock_env->set_current_time(5); });
|
||||
dbfull()->TEST_WaitForDumpStatsRun([&] { mock_env->set_current_time(5); });
|
||||
ASSERT_GE(counter, 1);
|
||||
|
||||
// Test cacel job through SetOptions
|
||||
ASSERT_OK(dbfull()->SetDBOptions({{"stats_dump_period_sec", "0"}}));
|
||||
int old_val = counter;
|
||||
env_->SleepForMicroseconds(10000000);
|
||||
for (int i = 6; i < 20; ++i) {
|
||||
dbfull()->TEST_WaitForDumpStatsRun([&] { mock_env->set_current_time(i); });
|
||||
}
|
||||
ASSERT_EQ(counter, old_val);
|
||||
Close();
|
||||
}
|
||||
|
||||
// Test persistent stats background thread scheduling and cancelling
|
||||
TEST_F(DBOptionsTest, StatsPersistScheduling) {
|
||||
Options options;
|
||||
options.create_if_missing = true;
|
||||
options.stats_persist_period_sec = 5;
|
||||
std::unique_ptr<rocksdb::MockTimeEnv> mock_env;
|
||||
mock_env.reset(new rocksdb::MockTimeEnv(env_));
|
||||
mock_env->set_current_time(0); // in seconds
|
||||
options.env = mock_env.get();
|
||||
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
||||
rocksdb::SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
#if defined(OS_MACOSX) && !defined(NDEBUG)
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"InstrumentedCondVar::TimedWaitInternal", [&](void* arg) {
|
||||
uint64_t time_us = *reinterpret_cast<uint64_t*>(arg);
|
||||
if (time_us < mock_env->RealNowMicros()) {
|
||||
*reinterpret_cast<uint64_t*>(arg) = mock_env->RealNowMicros() + 1000;
|
||||
}
|
||||
});
|
||||
#endif // OS_MACOSX && !NDEBUG
|
||||
int counter = 0;
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::PersistStats:Entry", [&](void* /*arg*/) { counter++; });
|
||||
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
||||
Reopen(options);
|
||||
ASSERT_EQ(5, dbfull()->GetDBOptions().stats_persist_period_sec);
|
||||
dbfull()->TEST_WaitForPersistStatsRun([&] { mock_env->set_current_time(5); });
|
||||
ASSERT_GE(counter, 1);
|
||||
|
||||
// Test cacel job through SetOptions
|
||||
ASSERT_TRUE(dbfull()->TEST_IsPersistentStatsEnabled());
|
||||
ASSERT_OK(dbfull()->SetDBOptions({{"stats_persist_period_sec", "0"}}));
|
||||
ASSERT_FALSE(dbfull()->TEST_IsPersistentStatsEnabled());
|
||||
Close();
|
||||
}
|
||||
|
||||
// Test enabling persistent stats for the first time
|
||||
TEST_F(DBOptionsTest, PersistentStatsFreshInstall) {
|
||||
Options options;
|
||||
options.create_if_missing = true;
|
||||
options.stats_persist_period_sec = 0;
|
||||
std::unique_ptr<rocksdb::MockTimeEnv> mock_env;
|
||||
mock_env.reset(new rocksdb::MockTimeEnv(env_));
|
||||
mock_env->set_current_time(0); // in seconds
|
||||
options.env = mock_env.get();
|
||||
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
||||
rocksdb::SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
#if defined(OS_MACOSX) && !defined(NDEBUG)
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"InstrumentedCondVar::TimedWaitInternal", [&](void* arg) {
|
||||
uint64_t time_us = *reinterpret_cast<uint64_t*>(arg);
|
||||
if (time_us < mock_env->RealNowMicros()) {
|
||||
*reinterpret_cast<uint64_t*>(arg) = mock_env->RealNowMicros() + 1000;
|
||||
}
|
||||
});
|
||||
#endif // OS_MACOSX && !NDEBUG
|
||||
int counter = 0;
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::PersistStats:Entry", [&](void* /*arg*/) { counter++; });
|
||||
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
||||
Reopen(options);
|
||||
ASSERT_OK(dbfull()->SetDBOptions({{"stats_persist_period_sec", "5"}}));
|
||||
ASSERT_EQ(5, dbfull()->GetDBOptions().stats_persist_period_sec);
|
||||
dbfull()->TEST_WaitForPersistStatsRun([&] { mock_env->set_current_time(5); });
|
||||
ASSERT_GE(counter, 1);
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(DBOptionsTest, SetOptionsStatsPersistPeriodSec) {
|
||||
Options options;
|
||||
options.create_if_missing = true;
|
||||
options.stats_persist_period_sec = 5;
|
||||
options.env = env_;
|
||||
Reopen(options);
|
||||
ASSERT_EQ(5, dbfull()->GetDBOptions().stats_persist_period_sec);
|
||||
|
||||
ASSERT_OK(dbfull()->SetDBOptions({{"stats_persist_period_sec", "12345"}}));
|
||||
ASSERT_EQ(12345, dbfull()->GetDBOptions().stats_persist_period_sec);
|
||||
ASSERT_NOK(dbfull()->SetDBOptions({{"stats_persist_period_sec", "abcde"}}));
|
||||
ASSERT_EQ(12345, dbfull()->GetDBOptions().stats_persist_period_sec);
|
||||
}
|
||||
|
||||
TEST_F(DBOptionsTest, GetStatsHistory) {
|
||||
Options options;
|
||||
options.create_if_missing = true;
|
||||
options.stats_persist_period_sec = 5;
|
||||
options.statistics = rocksdb::CreateDBStatistics();
|
||||
std::unique_ptr<rocksdb::MockTimeEnv> mock_env;
|
||||
mock_env.reset(new rocksdb::MockTimeEnv(env_));
|
||||
mock_env->set_current_time(0); // in seconds
|
||||
options.env = mock_env.get();
|
||||
#if defined(OS_MACOSX) && !defined(NDEBUG)
|
||||
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
||||
rocksdb::SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"InstrumentedCondVar::TimedWaitInternal", [&](void* arg) {
|
||||
uint64_t time_us = *reinterpret_cast<uint64_t*>(arg);
|
||||
if (time_us < mock_env->RealNowMicros()) {
|
||||
*reinterpret_cast<uint64_t*>(arg) = mock_env->RealNowMicros() + 1000;
|
||||
}
|
||||
});
|
||||
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
||||
#endif // OS_MACOSX && !NDEBUG
|
||||
|
||||
CreateColumnFamilies({"pikachu"}, options);
|
||||
ASSERT_OK(Put("foo", "bar"));
|
||||
ReopenWithColumnFamilies({"default", "pikachu"}, options);
|
||||
|
||||
int mock_time = 1;
|
||||
// Wait for stats persist to finish
|
||||
dbfull()->TEST_WaitForPersistStatsRun([&] { mock_env->set_current_time(5); });
|
||||
std::unique_ptr<StatsHistoryIterator> stats_iter;
|
||||
db_->GetStatsHistory(0, 6 * kMicrosInSec, &stats_iter);
|
||||
ASSERT_TRUE(stats_iter != nullptr);
|
||||
// disabled stats snapshots
|
||||
ASSERT_OK(dbfull()->SetDBOptions({{"stats_persist_period_sec", "0"}}));
|
||||
size_t stats_count = 0;
|
||||
for (; stats_iter->Valid(); stats_iter->Next()) {
|
||||
auto stats_map = stats_iter->GetStatsMap();
|
||||
stats_count += stats_map.size();
|
||||
}
|
||||
ASSERT_GT(stats_count, 0);
|
||||
// Wait a bit and verify no more stats are found
|
||||
for (mock_time = 6; mock_time < 20; ++mock_time) {
|
||||
dbfull()->TEST_WaitForPersistStatsRun(
|
||||
[&] { mock_env->set_current_time(mock_time); });
|
||||
}
|
||||
db_->GetStatsHistory(0, 20 * kMicrosInSec, &stats_iter);
|
||||
ASSERT_TRUE(stats_iter != nullptr);
|
||||
size_t stats_count_new = 0;
|
||||
for (; stats_iter->Valid(); stats_iter->Next()) {
|
||||
stats_count_new += stats_iter->GetStatsMap().size();
|
||||
}
|
||||
ASSERT_EQ(stats_count_new, stats_count);
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(DBOptionsTest, InMemoryStatsHistoryPurging) {
|
||||
Options options;
|
||||
options.create_if_missing = true;
|
||||
options.statistics = rocksdb::CreateDBStatistics();
|
||||
options.stats_persist_period_sec = 1;
|
||||
std::unique_ptr<rocksdb::MockTimeEnv> mock_env;
|
||||
mock_env.reset(new rocksdb::MockTimeEnv(env_));
|
||||
mock_env->set_current_time(0); // in seconds
|
||||
options.env = mock_env.get();
|
||||
#if defined(OS_MACOSX) && !defined(NDEBUG)
|
||||
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
||||
rocksdb::SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"InstrumentedCondVar::TimedWaitInternal", [&](void* arg) {
|
||||
uint64_t time_us = *reinterpret_cast<uint64_t*>(arg);
|
||||
if (time_us < mock_env->RealNowMicros()) {
|
||||
*reinterpret_cast<uint64_t*>(arg) = mock_env->RealNowMicros() + 1000;
|
||||
}
|
||||
});
|
||||
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
||||
#endif // OS_MACOSX && !NDEBUG
|
||||
|
||||
CreateColumnFamilies({"pikachu"}, options);
|
||||
ASSERT_OK(Put("foo", "bar"));
|
||||
ReopenWithColumnFamilies({"default", "pikachu"}, options);
|
||||
// some random operation to populate statistics
|
||||
ASSERT_OK(Delete("foo"));
|
||||
ASSERT_OK(Put("sol", "sol"));
|
||||
ASSERT_OK(Put("epic", "epic"));
|
||||
ASSERT_OK(Put("ltd", "ltd"));
|
||||
ASSERT_EQ("sol", Get("sol"));
|
||||
ASSERT_EQ("epic", Get("epic"));
|
||||
ASSERT_EQ("ltd", Get("ltd"));
|
||||
Iterator* iterator = db_->NewIterator(ReadOptions());
|
||||
for (iterator->SeekToFirst(); iterator->Valid(); iterator->Next()) {
|
||||
ASSERT_TRUE(iterator->key() == iterator->value());
|
||||
}
|
||||
delete iterator;
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(Delete("sol"));
|
||||
db_->CompactRange(CompactRangeOptions(), nullptr, nullptr);
|
||||
int mock_time = 1;
|
||||
// Wait for stats persist to finish
|
||||
for (; mock_time < 5; ++mock_time) {
|
||||
dbfull()->TEST_WaitForPersistStatsRun(
|
||||
[&] { mock_env->set_current_time(mock_time); });
|
||||
}
|
||||
|
||||
// second round of ops
|
||||
ASSERT_OK(Put("saigon", "saigon"));
|
||||
ASSERT_OK(Put("noodle talk", "noodle talk"));
|
||||
ASSERT_OK(Put("ping bistro", "ping bistro"));
|
||||
iterator = db_->NewIterator(ReadOptions());
|
||||
for (iterator->SeekToFirst(); iterator->Valid(); iterator->Next()) {
|
||||
ASSERT_TRUE(iterator->key() == iterator->value());
|
||||
}
|
||||
delete iterator;
|
||||
ASSERT_OK(Flush());
|
||||
db_->CompactRange(CompactRangeOptions(), nullptr, nullptr);
|
||||
for (; mock_time < 10; ++mock_time) {
|
||||
dbfull()->TEST_WaitForPersistStatsRun(
|
||||
[&] { mock_env->set_current_time(mock_time); });
|
||||
}
|
||||
std::unique_ptr<StatsHistoryIterator> stats_iter;
|
||||
db_->GetStatsHistory(0, 10 * kMicrosInSec, &stats_iter);
|
||||
ASSERT_TRUE(stats_iter != nullptr);
|
||||
size_t stats_count = 0;
|
||||
int slice_count = 0;
|
||||
for (; stats_iter->Valid(); stats_iter->Next()) {
|
||||
slice_count++;
|
||||
auto stats_map = stats_iter->GetStatsMap();
|
||||
stats_count += stats_map.size();
|
||||
}
|
||||
size_t stats_history_size = dbfull()->TEST_EstiamteStatsHistorySize();
|
||||
ASSERT_GE(slice_count, 9);
|
||||
ASSERT_GE(stats_history_size, 12000);
|
||||
// capping memory cost at 12000 bytes since one slice is around 10000~12000
|
||||
ASSERT_OK(dbfull()->SetDBOptions({{"stats_history_buffer_size", "12000"}}));
|
||||
ASSERT_EQ(12000, dbfull()->GetDBOptions().stats_history_buffer_size);
|
||||
// Wait for stats persist to finish
|
||||
for (; mock_time < 20; ++mock_time) {
|
||||
dbfull()->TEST_WaitForPersistStatsRun(
|
||||
[&] { mock_env->set_current_time(mock_time); });
|
||||
}
|
||||
db_->GetStatsHistory(0, 20 * kMicrosInSec, &stats_iter);
|
||||
ASSERT_TRUE(stats_iter != nullptr);
|
||||
size_t stats_count_reopen = 0;
|
||||
slice_count = 0;
|
||||
for (; stats_iter->Valid(); stats_iter->Next()) {
|
||||
slice_count++;
|
||||
auto stats_map = stats_iter->GetStatsMap();
|
||||
stats_count_reopen += stats_map.size();
|
||||
}
|
||||
size_t stats_history_size_reopen = dbfull()->TEST_EstiamteStatsHistorySize();
|
||||
// only one slice can fit under the new stats_history_buffer_size
|
||||
ASSERT_LT(slice_count, 2);
|
||||
ASSERT_TRUE(stats_history_size_reopen < 12000 &&
|
||||
stats_history_size_reopen > 0);
|
||||
ASSERT_TRUE(stats_count_reopen < stats_count && stats_count_reopen > 0);
|
||||
Close();
|
||||
}
|
||||
|
||||
static void assert_candidate_files_empty(DBImpl* dbfull, const bool empty) {
|
||||
dbfull->TEST_LockMutex();
|
||||
JobContext job_context(0);
|
||||
@@ -628,9 +884,9 @@ TEST_F(DBOptionsTest, SetFIFOCompactionOptions) {
|
||||
env_->time_elapse_only_sleep_ = false;
|
||||
options.env = env_;
|
||||
|
||||
// Test dynamically changing compaction_options_fifo.ttl
|
||||
// Test dynamically changing ttl.
|
||||
env_->addon_time_.store(0);
|
||||
options.compaction_options_fifo.ttl = 1 * 60 * 60; // 1 hour
|
||||
options.ttl = 1 * 60 * 60; // 1 hour
|
||||
ASSERT_OK(TryReopen(options));
|
||||
|
||||
Random rnd(301);
|
||||
@@ -648,13 +904,13 @@ TEST_F(DBOptionsTest, SetFIFOCompactionOptions) {
|
||||
env_->addon_time_.fetch_add(61);
|
||||
|
||||
// No files should be compacted as ttl is set to 1 hour.
|
||||
ASSERT_EQ(dbfull()->GetOptions().compaction_options_fifo.ttl, 3600);
|
||||
ASSERT_EQ(dbfull()->GetOptions().ttl, 3600);
|
||||
dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr);
|
||||
ASSERT_EQ(NumTableFilesAtLevel(0), 10);
|
||||
|
||||
// Set ttl to 1 minute. So all files should get deleted.
|
||||
ASSERT_OK(dbfull()->SetOptions({{"compaction_options_fifo", "{ttl=60;}"}}));
|
||||
ASSERT_EQ(dbfull()->GetOptions().compaction_options_fifo.ttl, 60);
|
||||
ASSERT_OK(dbfull()->SetOptions({{"ttl", "60"}}));
|
||||
ASSERT_EQ(dbfull()->GetOptions().ttl, 60);
|
||||
dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr);
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
ASSERT_EQ(NumTableFilesAtLevel(0), 0);
|
||||
@@ -662,7 +918,7 @@ TEST_F(DBOptionsTest, SetFIFOCompactionOptions) {
|
||||
// Test dynamically changing compaction_options_fifo.max_table_files_size
|
||||
env_->addon_time_.store(0);
|
||||
options.compaction_options_fifo.max_table_files_size = 500 << 10; // 00KB
|
||||
options.compaction_options_fifo.ttl = 0;
|
||||
options.ttl = 0;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
for (int i = 0; i < 10; i++) {
|
||||
@@ -692,7 +948,7 @@ TEST_F(DBOptionsTest, SetFIFOCompactionOptions) {
|
||||
|
||||
// Test dynamically changing compaction_options_fifo.allow_compaction
|
||||
options.compaction_options_fifo.max_table_files_size = 500 << 10; // 500KB
|
||||
options.compaction_options_fifo.ttl = 0;
|
||||
options.ttl = 0;
|
||||
options.compaction_options_fifo.allow_compaction = false;
|
||||
options.level0_file_num_compaction_trigger = 6;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
@@ -1094,7 +1094,7 @@ class CountingUserTblPropCollector : public TablePropertiesCollector {
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
virtual UserCollectedProperties GetReadableProperties() const override {
|
||||
UserCollectedProperties GetReadableProperties() const override {
|
||||
return UserCollectedProperties{};
|
||||
}
|
||||
|
||||
@@ -1110,7 +1110,7 @@ class CountingUserTblPropCollectorFactory
|
||||
uint32_t expected_column_family_id)
|
||||
: expected_column_family_id_(expected_column_family_id),
|
||||
num_created_(0) {}
|
||||
virtual TablePropertiesCollector* CreateTablePropertiesCollector(
|
||||
TablePropertiesCollector* CreateTablePropertiesCollector(
|
||||
TablePropertiesCollectorFactory::Context context) override {
|
||||
EXPECT_EQ(expected_column_family_id_, context.column_family_id);
|
||||
num_created_++;
|
||||
@@ -1158,7 +1158,7 @@ class CountingDeleteTabPropCollector : public TablePropertiesCollector {
|
||||
class CountingDeleteTabPropCollectorFactory
|
||||
: public TablePropertiesCollectorFactory {
|
||||
public:
|
||||
virtual TablePropertiesCollector* CreateTablePropertiesCollector(
|
||||
TablePropertiesCollector* CreateTablePropertiesCollector(
|
||||
TablePropertiesCollectorFactory::Context /*context*/) override {
|
||||
return new CountingDeleteTabPropCollector();
|
||||
}
|
||||
@@ -1432,7 +1432,7 @@ TEST_F(DBPropertiesTest, EstimateOldestKeyTime) {
|
||||
}
|
||||
|
||||
options.compaction_style = kCompactionStyleFIFO;
|
||||
options.compaction_options_fifo.ttl = 300;
|
||||
options.ttl = 300;
|
||||
options.compaction_options_fifo.allow_compaction = false;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
|
||||
+34
-10
@@ -72,8 +72,8 @@ TEST_F(DBRangeDelTest, CompactionOutputHasOnlyRangeTombstone) {
|
||||
// Skip cuckoo memtables, which do not support snapshots. Skip non-leveled
|
||||
// compactions as the above assertions about the number of files in a level
|
||||
// do not hold true.
|
||||
} while (ChangeOptions(kRangeDelSkipConfigs | kSkipHashCuckoo |
|
||||
kSkipUniversalCompaction | kSkipFIFOCompaction));
|
||||
} while (ChangeOptions(kRangeDelSkipConfigs | kSkipUniversalCompaction |
|
||||
kSkipFIFOCompaction));
|
||||
}
|
||||
|
||||
TEST_F(DBRangeDelTest, CompactionOutputFilesExactlyFilled) {
|
||||
@@ -438,9 +438,10 @@ TEST_F(DBRangeDelTest, ValidUniversalSubcompactionBoundaries) {
|
||||
ASSERT_OK(dbfull()->RunManualCompaction(
|
||||
reinterpret_cast<ColumnFamilyHandleImpl*>(db_->DefaultColumnFamily())
|
||||
->cfd(),
|
||||
1 /* input_level */, 2 /* output_level */, 0 /* output_path_id */,
|
||||
0 /* max_subcompactions */, nullptr /* begin */, nullptr /* end */,
|
||||
true /* exclusive */, true /* disallow_trivial_move */));
|
||||
1 /* input_level */, 2 /* output_level */, CompactRangeOptions(),
|
||||
nullptr /* begin */, nullptr /* end */, true /* exclusive */,
|
||||
true /* disallow_trivial_move */,
|
||||
port::kMaxUint64 /* max_file_num_to_ignore */));
|
||||
}
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
@@ -490,6 +491,30 @@ TEST_F(DBRangeDelTest, CompactionRemovesCoveredMergeOperands) {
|
||||
ASSERT_EQ(expected, actual);
|
||||
}
|
||||
|
||||
TEST_F(DBRangeDelTest, PutDeleteRangeMergeFlush) {
|
||||
// Test the sequence of operations: (1) Put, (2) DeleteRange, (3) Merge, (4)
|
||||
// Flush. The `CompactionIterator` previously had a bug where we forgot to
|
||||
// check for covering range tombstones when processing the (1) Put, causing
|
||||
// it to reappear after the flush.
|
||||
Options opts = CurrentOptions();
|
||||
opts.merge_operator = MergeOperators::CreateUInt64AddOperator();
|
||||
Reopen(opts);
|
||||
|
||||
std::string val;
|
||||
PutFixed64(&val, 1);
|
||||
ASSERT_OK(db_->Put(WriteOptions(), "key", val));
|
||||
ASSERT_OK(db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(),
|
||||
"key", "key_"));
|
||||
ASSERT_OK(db_->Merge(WriteOptions(), "key", val));
|
||||
ASSERT_OK(db_->Flush(FlushOptions()));
|
||||
|
||||
ReadOptions read_opts;
|
||||
std::string expected, actual;
|
||||
ASSERT_OK(db_->Get(read_opts, "key", &actual));
|
||||
PutFixed64(&expected, 1);
|
||||
ASSERT_EQ(expected, actual);
|
||||
}
|
||||
|
||||
// NumTableFilesAtLevel() is not supported in ROCKSDB_LITE
|
||||
#ifndef ROCKSDB_LITE
|
||||
TEST_F(DBRangeDelTest, ObsoleteTombstoneCleanup) {
|
||||
@@ -645,8 +670,7 @@ TEST_F(DBRangeDelTest, GetCoveredKeyFromSst) {
|
||||
std::string value;
|
||||
ASSERT_TRUE(db_->Get(read_opts, "key", &value).IsNotFound());
|
||||
db_->ReleaseSnapshot(snapshot);
|
||||
// Cuckoo memtables do not support snapshots.
|
||||
} while (ChangeOptions(kRangeDelSkipConfigs | kSkipHashCuckoo));
|
||||
} while (ChangeOptions(kRangeDelSkipConfigs));
|
||||
}
|
||||
|
||||
TEST_F(DBRangeDelTest, GetCoveredMergeOperandFromMemtable) {
|
||||
@@ -1112,14 +1136,14 @@ class MockMergeOperator : public MergeOperator {
|
||||
// Mock non-associative operator. Non-associativity is expressed by lack of
|
||||
// implementation for any `PartialMerge*` functions.
|
||||
public:
|
||||
virtual bool FullMergeV2(const MergeOperationInput& merge_in,
|
||||
MergeOperationOutput* merge_out) const override {
|
||||
bool FullMergeV2(const MergeOperationInput& merge_in,
|
||||
MergeOperationOutput* merge_out) const override {
|
||||
assert(merge_out != nullptr);
|
||||
merge_out->new_value = merge_in.operand_list.back().ToString();
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual const char* Name() const override { return "MockMergeOperator"; }
|
||||
const char* Name() const override { return "MockMergeOperator"; }
|
||||
};
|
||||
|
||||
TEST_F(DBRangeDelTest, KeyAtOverlappingEndpointReappears) {
|
||||
|
||||
@@ -0,0 +1,524 @@
|
||||
// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under both the GPLv2 (found in the
|
||||
// COPYING file in the root directory) and Apache 2.0 License
|
||||
// (found in the LICENSE.Apache file in the root directory).
|
||||
//
|
||||
// Copyright (c) 2011 The LevelDB Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
|
||||
#include "db/db_impl_secondary.h"
|
||||
#include "db/db_test_util.h"
|
||||
#include "port/stack_trace.h"
|
||||
#include "util/fault_injection_test_env.h"
|
||||
#include "util/sync_point.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
class DBSecondaryTest : public DBTestBase {
|
||||
public:
|
||||
DBSecondaryTest()
|
||||
: DBTestBase("/db_secondary_test"),
|
||||
secondary_path_(),
|
||||
handles_secondary_(),
|
||||
db_secondary_(nullptr) {
|
||||
secondary_path_ =
|
||||
test::PerThreadDBPath(env_, "/db_secondary_test_secondary");
|
||||
}
|
||||
|
||||
~DBSecondaryTest() override {
|
||||
CloseSecondary();
|
||||
if (getenv("KEEP_DB") != nullptr) {
|
||||
fprintf(stdout, "Secondary DB is still at %s\n", secondary_path_.c_str());
|
||||
} else {
|
||||
Options options;
|
||||
options.env = env_;
|
||||
EXPECT_OK(DestroyDB(secondary_path_, options));
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
Status ReopenAsSecondary(const Options& options) {
|
||||
return DB::OpenAsSecondary(options, dbname_, secondary_path_, &db_);
|
||||
}
|
||||
|
||||
void OpenSecondary(const Options& options);
|
||||
|
||||
void OpenSecondaryWithColumnFamilies(
|
||||
const std::vector<std::string>& column_families, const Options& options);
|
||||
|
||||
void CloseSecondary() {
|
||||
for (auto h : handles_secondary_) {
|
||||
db_secondary_->DestroyColumnFamilyHandle(h);
|
||||
}
|
||||
handles_secondary_.clear();
|
||||
delete db_secondary_;
|
||||
db_secondary_ = nullptr;
|
||||
}
|
||||
|
||||
DBImplSecondary* db_secondary_full() {
|
||||
return static_cast<DBImplSecondary*>(db_secondary_);
|
||||
}
|
||||
|
||||
void CheckFileTypeCounts(const std::string& dir, int expected_log,
|
||||
int expected_sst, int expected_manifest) const;
|
||||
|
||||
std::string secondary_path_;
|
||||
std::vector<ColumnFamilyHandle*> handles_secondary_;
|
||||
DB* db_secondary_;
|
||||
};
|
||||
|
||||
void DBSecondaryTest::OpenSecondary(const Options& options) {
|
||||
Status s =
|
||||
DB::OpenAsSecondary(options, dbname_, secondary_path_, &db_secondary_);
|
||||
ASSERT_OK(s);
|
||||
}
|
||||
|
||||
void DBSecondaryTest::OpenSecondaryWithColumnFamilies(
|
||||
const std::vector<std::string>& column_families, const Options& options) {
|
||||
std::vector<ColumnFamilyDescriptor> cf_descs;
|
||||
cf_descs.emplace_back(kDefaultColumnFamilyName, options);
|
||||
for (const auto& cf_name : column_families) {
|
||||
cf_descs.emplace_back(cf_name, options);
|
||||
}
|
||||
Status s = DB::OpenAsSecondary(options, dbname_, secondary_path_, cf_descs,
|
||||
&handles_secondary_, &db_secondary_);
|
||||
ASSERT_OK(s);
|
||||
}
|
||||
|
||||
void DBSecondaryTest::CheckFileTypeCounts(const std::string& dir,
|
||||
int expected_log, int expected_sst,
|
||||
int expected_manifest) const {
|
||||
std::vector<std::string> filenames;
|
||||
env_->GetChildren(dir, &filenames);
|
||||
|
||||
int log_cnt = 0, sst_cnt = 0, manifest_cnt = 0;
|
||||
for (auto file : filenames) {
|
||||
uint64_t number;
|
||||
FileType type;
|
||||
if (ParseFileName(file, &number, &type)) {
|
||||
log_cnt += (type == kLogFile);
|
||||
sst_cnt += (type == kTableFile);
|
||||
manifest_cnt += (type == kDescriptorFile);
|
||||
}
|
||||
}
|
||||
ASSERT_EQ(expected_log, log_cnt);
|
||||
ASSERT_EQ(expected_sst, sst_cnt);
|
||||
ASSERT_EQ(expected_manifest, manifest_cnt);
|
||||
}
|
||||
|
||||
TEST_F(DBSecondaryTest, ReopenAsSecondary) {
|
||||
Options options;
|
||||
options.env = env_;
|
||||
Reopen(options);
|
||||
ASSERT_OK(Put("foo", "foo_value"));
|
||||
ASSERT_OK(Put("bar", "bar_value"));
|
||||
ASSERT_OK(dbfull()->Flush(FlushOptions()));
|
||||
Close();
|
||||
|
||||
ASSERT_OK(ReopenAsSecondary(options));
|
||||
ASSERT_EQ("foo_value", Get("foo"));
|
||||
ASSERT_EQ("bar_value", Get("bar"));
|
||||
ReadOptions ropts;
|
||||
ropts.verify_checksums = true;
|
||||
auto db1 = static_cast<DBImplSecondary*>(db_);
|
||||
ASSERT_NE(nullptr, db1);
|
||||
Iterator* iter = db1->NewIterator(ropts);
|
||||
ASSERT_NE(nullptr, iter);
|
||||
size_t count = 0;
|
||||
for (iter->SeekToFirst(); iter->Valid(); iter->Next()) {
|
||||
if (0 == count) {
|
||||
ASSERT_EQ("bar", iter->key().ToString());
|
||||
ASSERT_EQ("bar_value", iter->value().ToString());
|
||||
} else if (1 == count) {
|
||||
ASSERT_EQ("foo", iter->key().ToString());
|
||||
ASSERT_EQ("foo_value", iter->value().ToString());
|
||||
}
|
||||
++count;
|
||||
}
|
||||
delete iter;
|
||||
ASSERT_EQ(2, count);
|
||||
}
|
||||
|
||||
TEST_F(DBSecondaryTest, OpenAsSecondary) {
|
||||
Options options;
|
||||
options.env = env_;
|
||||
options.level0_file_num_compaction_trigger = 4;
|
||||
Reopen(options);
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
ASSERT_OK(Put("foo", "foo_value" + std::to_string(i)));
|
||||
ASSERT_OK(Put("bar", "bar_value" + std::to_string(i)));
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
Options options1;
|
||||
options1.env = env_;
|
||||
options1.max_open_files = -1;
|
||||
OpenSecondary(options1);
|
||||
ASSERT_OK(dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
|
||||
ReadOptions ropts;
|
||||
ropts.verify_checksums = true;
|
||||
const auto verify_db_func = [&](const std::string& foo_val,
|
||||
const std::string& bar_val) {
|
||||
std::string value;
|
||||
ASSERT_OK(db_secondary_->Get(ropts, "foo", &value));
|
||||
ASSERT_EQ(foo_val, value);
|
||||
ASSERT_OK(db_secondary_->Get(ropts, "bar", &value));
|
||||
ASSERT_EQ(bar_val, value);
|
||||
Iterator* iter = db_secondary_->NewIterator(ropts);
|
||||
ASSERT_NE(nullptr, iter);
|
||||
iter->Seek("foo");
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ("foo", iter->key().ToString());
|
||||
ASSERT_EQ(foo_val, iter->value().ToString());
|
||||
iter->Seek("bar");
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ("bar", iter->key().ToString());
|
||||
ASSERT_EQ(bar_val, iter->value().ToString());
|
||||
size_t count = 0;
|
||||
for (iter->SeekToFirst(); iter->Valid(); iter->Next()) {
|
||||
++count;
|
||||
}
|
||||
ASSERT_EQ(2, count);
|
||||
delete iter;
|
||||
};
|
||||
|
||||
verify_db_func("foo_value2", "bar_value2");
|
||||
|
||||
ASSERT_OK(Put("foo", "new_foo_value"));
|
||||
ASSERT_OK(Put("bar", "new_bar_value"));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
ASSERT_OK(db_secondary_->TryCatchUpWithPrimary());
|
||||
verify_db_func("new_foo_value", "new_bar_value");
|
||||
}
|
||||
|
||||
TEST_F(DBSecondaryTest, OpenAsSecondaryWALTailing) {
|
||||
Options options;
|
||||
options.env = env_;
|
||||
options.level0_file_num_compaction_trigger = 4;
|
||||
Reopen(options);
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
ASSERT_OK(Put("foo", "foo_value" + std::to_string(i)));
|
||||
ASSERT_OK(Put("bar", "bar_value" + std::to_string(i)));
|
||||
}
|
||||
Options options1;
|
||||
options1.env = env_;
|
||||
options1.max_open_files = -1;
|
||||
OpenSecondary(options1);
|
||||
|
||||
ReadOptions ropts;
|
||||
ropts.verify_checksums = true;
|
||||
const auto verify_db_func = [&](const std::string& foo_val,
|
||||
const std::string& bar_val) {
|
||||
std::string value;
|
||||
ASSERT_OK(db_secondary_->Get(ropts, "foo", &value));
|
||||
ASSERT_EQ(foo_val, value);
|
||||
ASSERT_OK(db_secondary_->Get(ropts, "bar", &value));
|
||||
ASSERT_EQ(bar_val, value);
|
||||
Iterator* iter = db_secondary_->NewIterator(ropts);
|
||||
ASSERT_NE(nullptr, iter);
|
||||
iter->Seek("foo");
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ("foo", iter->key().ToString());
|
||||
ASSERT_EQ(foo_val, iter->value().ToString());
|
||||
iter->Seek("bar");
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ("bar", iter->key().ToString());
|
||||
ASSERT_EQ(bar_val, iter->value().ToString());
|
||||
size_t count = 0;
|
||||
for (iter->SeekToFirst(); iter->Valid(); iter->Next()) {
|
||||
++count;
|
||||
}
|
||||
ASSERT_EQ(2, count);
|
||||
delete iter;
|
||||
};
|
||||
|
||||
verify_db_func("foo_value2", "bar_value2");
|
||||
}
|
||||
|
||||
TEST_F(DBSecondaryTest, OpenWithNonExistColumnFamily) {
|
||||
Options options;
|
||||
options.env = env_;
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
|
||||
Options options1;
|
||||
options1.env = env_;
|
||||
options1.max_open_files = -1;
|
||||
std::vector<ColumnFamilyDescriptor> cf_descs;
|
||||
cf_descs.emplace_back(kDefaultColumnFamilyName, options1);
|
||||
cf_descs.emplace_back("pikachu", options1);
|
||||
cf_descs.emplace_back("eevee", options1);
|
||||
Status s = DB::OpenAsSecondary(options1, dbname_, secondary_path_, cf_descs,
|
||||
&handles_secondary_, &db_secondary_);
|
||||
ASSERT_NOK(s);
|
||||
}
|
||||
|
||||
TEST_F(DBSecondaryTest, OpenWithSubsetOfColumnFamilies) {
|
||||
Options options;
|
||||
options.env = env_;
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
Options options1;
|
||||
options1.env = env_;
|
||||
options1.max_open_files = -1;
|
||||
OpenSecondary(options1);
|
||||
ASSERT_EQ(0, handles_secondary_.size());
|
||||
ASSERT_NE(nullptr, db_secondary_);
|
||||
|
||||
ASSERT_OK(Put(0 /*cf*/, "foo", "foo_value"));
|
||||
ASSERT_OK(Put(1 /*cf*/, "foo", "foo_value"));
|
||||
ASSERT_OK(Flush(0 /*cf*/));
|
||||
ASSERT_OK(Flush(1 /*cf*/));
|
||||
ASSERT_OK(db_secondary_->TryCatchUpWithPrimary());
|
||||
ReadOptions ropts;
|
||||
ropts.verify_checksums = true;
|
||||
std::string value;
|
||||
ASSERT_OK(db_secondary_->Get(ropts, "foo", &value));
|
||||
ASSERT_EQ("foo_value", value);
|
||||
}
|
||||
|
||||
TEST_F(DBSecondaryTest, SwitchToNewManifestDuringOpen) {
|
||||
Options options;
|
||||
options.env = env_;
|
||||
Reopen(options);
|
||||
Close();
|
||||
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
SyncPoint::GetInstance()->LoadDependency(
|
||||
{{"ReactiveVersionSet::MaybeSwitchManifest:AfterGetCurrentManifestPath:0",
|
||||
"VersionSet::ProcessManifestWrites:BeforeNewManifest"},
|
||||
{"VersionSet::ProcessManifestWrites:AfterNewManifest",
|
||||
"ReactiveVersionSet::MaybeSwitchManifest:AfterGetCurrentManifestPath:"
|
||||
"1"}});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
// Make sure db calls RecoverLogFiles so as to trigger a manifest write,
|
||||
// which causes the db to switch to a new MANIFEST upon start.
|
||||
port::Thread ro_db_thread([&]() {
|
||||
Options options1;
|
||||
options1.env = env_;
|
||||
options1.max_open_files = -1;
|
||||
OpenSecondary(options1);
|
||||
CloseSecondary();
|
||||
});
|
||||
Reopen(options);
|
||||
ro_db_thread.join();
|
||||
}
|
||||
|
||||
TEST_F(DBSecondaryTest, MissingTableFileDuringOpen) {
|
||||
Options options;
|
||||
options.env = env_;
|
||||
options.level0_file_num_compaction_trigger = 4;
|
||||
Reopen(options);
|
||||
for (int i = 0; i != options.level0_file_num_compaction_trigger; ++i) {
|
||||
ASSERT_OK(Put("foo", "foo_value" + std::to_string(i)));
|
||||
ASSERT_OK(Put("bar", "bar_value" + std::to_string(i)));
|
||||
ASSERT_OK(dbfull()->Flush(FlushOptions()));
|
||||
}
|
||||
ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable());
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
Options options1;
|
||||
options1.env = env_;
|
||||
options1.max_open_files = -1;
|
||||
OpenSecondary(options1);
|
||||
ReadOptions ropts;
|
||||
ropts.verify_checksums = true;
|
||||
std::string value;
|
||||
ASSERT_OK(db_secondary_->Get(ropts, "foo", &value));
|
||||
ASSERT_EQ("foo_value" +
|
||||
std::to_string(options.level0_file_num_compaction_trigger - 1),
|
||||
value);
|
||||
ASSERT_OK(db_secondary_->Get(ropts, "bar", &value));
|
||||
ASSERT_EQ("bar_value" +
|
||||
std::to_string(options.level0_file_num_compaction_trigger - 1),
|
||||
value);
|
||||
Iterator* iter = db_secondary_->NewIterator(ropts);
|
||||
ASSERT_NE(nullptr, iter);
|
||||
iter->Seek("bar");
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ("bar", iter->key().ToString());
|
||||
ASSERT_EQ("bar_value" +
|
||||
std::to_string(options.level0_file_num_compaction_trigger - 1),
|
||||
iter->value().ToString());
|
||||
iter->Seek("foo");
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ("foo", iter->key().ToString());
|
||||
ASSERT_EQ("foo_value" +
|
||||
std::to_string(options.level0_file_num_compaction_trigger - 1),
|
||||
iter->value().ToString());
|
||||
size_t count = 0;
|
||||
for (iter->SeekToFirst(); iter->Valid(); iter->Next()) {
|
||||
++count;
|
||||
}
|
||||
ASSERT_EQ(2, count);
|
||||
delete iter;
|
||||
}
|
||||
|
||||
TEST_F(DBSecondaryTest, MissingTableFile) {
|
||||
int table_files_not_exist = 0;
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"ReactiveVersionSet::ReadAndApply:AfterLoadTableHandlers",
|
||||
[&](void* arg) {
|
||||
Status s = *reinterpret_cast<Status*>(arg);
|
||||
if (s.IsPathNotFound()) {
|
||||
++table_files_not_exist;
|
||||
} else if (!s.ok()) {
|
||||
assert(false); // Should not reach here
|
||||
}
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
Options options;
|
||||
options.env = env_;
|
||||
options.level0_file_num_compaction_trigger = 4;
|
||||
Reopen(options);
|
||||
|
||||
Options options1;
|
||||
options1.env = env_;
|
||||
options1.max_open_files = -1;
|
||||
OpenSecondary(options1);
|
||||
|
||||
for (int i = 0; i != options.level0_file_num_compaction_trigger; ++i) {
|
||||
ASSERT_OK(Put("foo", "foo_value" + std::to_string(i)));
|
||||
ASSERT_OK(Put("bar", "bar_value" + std::to_string(i)));
|
||||
ASSERT_OK(dbfull()->Flush(FlushOptions()));
|
||||
}
|
||||
ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable());
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
|
||||
ASSERT_NE(nullptr, db_secondary_full());
|
||||
ReadOptions ropts;
|
||||
ropts.verify_checksums = true;
|
||||
std::string value;
|
||||
ASSERT_NOK(db_secondary_->Get(ropts, "foo", &value));
|
||||
ASSERT_NOK(db_secondary_->Get(ropts, "bar", &value));
|
||||
|
||||
ASSERT_OK(db_secondary_->TryCatchUpWithPrimary());
|
||||
ASSERT_EQ(options.level0_file_num_compaction_trigger, table_files_not_exist);
|
||||
ASSERT_OK(db_secondary_->Get(ropts, "foo", &value));
|
||||
ASSERT_EQ("foo_value" +
|
||||
std::to_string(options.level0_file_num_compaction_trigger - 1),
|
||||
value);
|
||||
ASSERT_OK(db_secondary_->Get(ropts, "bar", &value));
|
||||
ASSERT_EQ("bar_value" +
|
||||
std::to_string(options.level0_file_num_compaction_trigger - 1),
|
||||
value);
|
||||
Iterator* iter = db_secondary_->NewIterator(ropts);
|
||||
ASSERT_NE(nullptr, iter);
|
||||
iter->Seek("bar");
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ("bar", iter->key().ToString());
|
||||
ASSERT_EQ("bar_value" +
|
||||
std::to_string(options.level0_file_num_compaction_trigger - 1),
|
||||
iter->value().ToString());
|
||||
iter->Seek("foo");
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ("foo", iter->key().ToString());
|
||||
ASSERT_EQ("foo_value" +
|
||||
std::to_string(options.level0_file_num_compaction_trigger - 1),
|
||||
iter->value().ToString());
|
||||
size_t count = 0;
|
||||
for (iter->SeekToFirst(); iter->Valid(); iter->Next()) {
|
||||
++count;
|
||||
}
|
||||
ASSERT_EQ(2, count);
|
||||
delete iter;
|
||||
}
|
||||
|
||||
TEST_F(DBSecondaryTest, PrimaryDropColumnFamily) {
|
||||
Options options;
|
||||
options.env = env_;
|
||||
const std::string kCfName1 = "pikachu";
|
||||
CreateAndReopenWithCF({kCfName1}, options);
|
||||
|
||||
Options options1;
|
||||
options1.env = env_;
|
||||
options1.max_open_files = -1;
|
||||
OpenSecondaryWithColumnFamilies({kCfName1}, options1);
|
||||
ASSERT_EQ(2, handles_secondary_.size());
|
||||
|
||||
ASSERT_OK(Put(1 /*cf*/, "foo", "foo_val_1"));
|
||||
ASSERT_OK(Flush(1 /*cf*/));
|
||||
|
||||
ASSERT_OK(db_secondary_->TryCatchUpWithPrimary());
|
||||
ReadOptions ropts;
|
||||
ropts.verify_checksums = true;
|
||||
std::string value;
|
||||
ASSERT_OK(db_secondary_->Get(ropts, handles_secondary_[1], "foo", &value));
|
||||
ASSERT_EQ("foo_val_1", value);
|
||||
|
||||
ASSERT_OK(dbfull()->DropColumnFamily(handles_[1]));
|
||||
Close();
|
||||
CheckFileTypeCounts(dbname_, 1, 0, 1);
|
||||
ASSERT_OK(db_secondary_->TryCatchUpWithPrimary());
|
||||
value.clear();
|
||||
ASSERT_OK(db_secondary_->Get(ropts, handles_secondary_[1], "foo", &value));
|
||||
ASSERT_EQ("foo_val_1", value);
|
||||
}
|
||||
|
||||
TEST_F(DBSecondaryTest, SwitchManifest) {
|
||||
Options options;
|
||||
options.env = env_;
|
||||
options.level0_file_num_compaction_trigger = 4;
|
||||
Reopen(options);
|
||||
|
||||
Options options1;
|
||||
options1.env = env_;
|
||||
options1.max_open_files = -1;
|
||||
OpenSecondary(options1);
|
||||
|
||||
const int kNumFiles = options.level0_file_num_compaction_trigger - 1;
|
||||
// Keep it smaller than 10 so that key0, key1, ..., key9 are sorted as 0, 1,
|
||||
// ..., 9.
|
||||
const int kNumKeys = 10;
|
||||
// Create two sst
|
||||
for (int i = 0; i != kNumFiles; ++i) {
|
||||
for (int j = 0; j != kNumKeys; ++j) {
|
||||
ASSERT_OK(Put("key" + std::to_string(j), "value_" + std::to_string(i)));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
|
||||
ASSERT_OK(db_secondary_->TryCatchUpWithPrimary());
|
||||
const auto& range_scan_db = [&]() {
|
||||
ReadOptions tmp_ropts;
|
||||
tmp_ropts.total_order_seek = true;
|
||||
tmp_ropts.verify_checksums = true;
|
||||
std::unique_ptr<Iterator> iter(db_secondary_->NewIterator(tmp_ropts));
|
||||
int cnt = 0;
|
||||
for (iter->SeekToFirst(); iter->Valid(); iter->Next(), ++cnt) {
|
||||
ASSERT_EQ("key" + std::to_string(cnt), iter->key().ToString());
|
||||
ASSERT_EQ("value_" + std::to_string(kNumFiles - 1),
|
||||
iter->value().ToString());
|
||||
}
|
||||
};
|
||||
|
||||
range_scan_db();
|
||||
|
||||
// While secondary instance still keeps old MANIFEST open, we close primary,
|
||||
// restart primary, performs full compaction, close again, restart again so
|
||||
// that next time secondary tries to catch up with primary, the secondary
|
||||
// will skip the MANIFEST in middle.
|
||||
Reopen(options);
|
||||
ASSERT_OK(dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
|
||||
Reopen(options);
|
||||
ASSERT_OK(dbfull()->SetOptions({{"disable_auto_compactions", "false"}}));
|
||||
|
||||
ASSERT_OK(db_secondary_->TryCatchUpWithPrimary());
|
||||
range_scan_db();
|
||||
}
|
||||
#endif //! ROCKSDB_LITE
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
rocksdb::port::InstallStackTraceHandler();
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
+90
-13
@@ -25,9 +25,9 @@ class DBSSTTest : public DBTestBase {
|
||||
class FlushedFileCollector : public EventListener {
|
||||
public:
|
||||
FlushedFileCollector() {}
|
||||
~FlushedFileCollector() {}
|
||||
~FlushedFileCollector() override {}
|
||||
|
||||
virtual void OnFlushCompleted(DB* /*db*/, const FlushJobInfo& info) override {
|
||||
void OnFlushCompleted(DB* /*db*/, const FlushJobInfo& info) override {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
flushed_files_.push_back(info.file_path);
|
||||
}
|
||||
@@ -356,6 +356,14 @@ TEST_F(DBSSTTest, RateLimitedDelete) {
|
||||
env_->time_elapse_only_sleep_ = true;
|
||||
Options options = CurrentOptions();
|
||||
options.disable_auto_compactions = true;
|
||||
// Need to disable stats dumping and persisting which also use
|
||||
// RepeatableThread, one of whose member variables is of type
|
||||
// InstrumentedCondVar. The callback for
|
||||
// InstrumentedCondVar::TimedWaitInternal can be triggered by stats dumping
|
||||
// and persisting threads and cause time_spent_deleting measurement to become
|
||||
// incorrect.
|
||||
options.stats_dump_period_sec = 0;
|
||||
options.stats_persist_period_sec = 0;
|
||||
options.env = env_;
|
||||
|
||||
int64_t rate_bytes_per_sec = 1024 * 10; // 10 Kbs / Sec
|
||||
@@ -367,14 +375,16 @@ TEST_F(DBSSTTest, RateLimitedDelete) {
|
||||
auto sfm = static_cast<SstFileManagerImpl*>(options.sst_file_manager.get());
|
||||
sfm->delete_scheduler()->SetMaxTrashDBRatio(1.1);
|
||||
|
||||
WriteOptions wo;
|
||||
wo.disableWAL = true;
|
||||
ASSERT_OK(TryReopen(options));
|
||||
// Create 4 files in L0
|
||||
for (char v = 'a'; v <= 'd'; v++) {
|
||||
ASSERT_OK(Put("Key2", DummyString(1024, v)));
|
||||
ASSERT_OK(Put("Key3", DummyString(1024, v)));
|
||||
ASSERT_OK(Put("Key4", DummyString(1024, v)));
|
||||
ASSERT_OK(Put("Key1", DummyString(1024, v)));
|
||||
ASSERT_OK(Put("Key4", DummyString(1024, v)));
|
||||
ASSERT_OK(Put("Key2", DummyString(1024, v), wo));
|
||||
ASSERT_OK(Put("Key3", DummyString(1024, v), wo));
|
||||
ASSERT_OK(Put("Key4", DummyString(1024, v), wo));
|
||||
ASSERT_OK(Put("Key1", DummyString(1024, v), wo));
|
||||
ASSERT_OK(Put("Key4", DummyString(1024, v), wo));
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
// We created 4 sst files in L0
|
||||
@@ -408,6 +418,57 @@ TEST_F(DBSSTTest, RateLimitedDelete) {
|
||||
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
|
||||
TEST_F(DBSSTTest, RateLimitedWALDelete) {
|
||||
Destroy(last_options_);
|
||||
|
||||
std::vector<uint64_t> penalties;
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"DeleteScheduler::BackgroundEmptyTrash:Wait",
|
||||
[&](void* arg) { penalties.push_back(*(static_cast<uint64_t*>(arg))); });
|
||||
|
||||
env_->no_slowdown_ = true;
|
||||
env_->time_elapse_only_sleep_ = true;
|
||||
Options options = CurrentOptions();
|
||||
options.disable_auto_compactions = true;
|
||||
options.env = env_;
|
||||
|
||||
int64_t rate_bytes_per_sec = 1024 * 10; // 10 Kbs / Sec
|
||||
Status s;
|
||||
options.sst_file_manager.reset(
|
||||
NewSstFileManager(env_, nullptr, "", 0, false, &s, 0));
|
||||
ASSERT_OK(s);
|
||||
options.sst_file_manager->SetDeleteRateBytesPerSecond(rate_bytes_per_sec);
|
||||
auto sfm = static_cast<SstFileManagerImpl*>(options.sst_file_manager.get());
|
||||
sfm->delete_scheduler()->SetMaxTrashDBRatio(2.1);
|
||||
|
||||
ASSERT_OK(TryReopen(options));
|
||||
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
// Create 4 files in L0
|
||||
for (char v = 'a'; v <= 'd'; v++) {
|
||||
ASSERT_OK(Put("Key2", DummyString(1024, v)));
|
||||
ASSERT_OK(Put("Key3", DummyString(1024, v)));
|
||||
ASSERT_OK(Put("Key4", DummyString(1024, v)));
|
||||
ASSERT_OK(Put("Key1", DummyString(1024, v)));
|
||||
ASSERT_OK(Put("Key4", DummyString(1024, v)));
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
// We created 4 sst files in L0
|
||||
ASSERT_EQ("4", FilesPerLevel(0));
|
||||
|
||||
// Compaction will move the 4 files in L0 to trash and create 1 L1 file
|
||||
CompactRangeOptions cro;
|
||||
cro.bottommost_level_compaction = BottommostLevelCompaction::kForce;
|
||||
ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr));
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact(true));
|
||||
ASSERT_EQ("0,1", FilesPerLevel(0));
|
||||
|
||||
sfm->WaitForEmptyTrash();
|
||||
ASSERT_EQ(penalties.size(), 8);
|
||||
|
||||
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
|
||||
TEST_F(DBSSTTest, OpenDBWithExistingTrash) {
|
||||
Options options = CurrentOptions();
|
||||
|
||||
@@ -446,7 +507,6 @@ TEST_F(DBSSTTest, DeleteSchedulerMultipleDBPaths) {
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"DeleteScheduler::DeleteFile",
|
||||
[&](void* /*arg*/) { bg_delete_file++; });
|
||||
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
Options options = CurrentOptions();
|
||||
options.disable_auto_compactions = true;
|
||||
@@ -464,10 +524,14 @@ TEST_F(DBSSTTest, DeleteSchedulerMultipleDBPaths) {
|
||||
auto sfm = static_cast<SstFileManagerImpl*>(options.sst_file_manager.get());
|
||||
|
||||
DestroyAndReopen(options);
|
||||
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
WriteOptions wo;
|
||||
wo.disableWAL = true;
|
||||
|
||||
// Create 4 files in L0
|
||||
for (int i = 0; i < 4; i++) {
|
||||
ASSERT_OK(Put("Key" + ToString(i), DummyString(1024, 'A')));
|
||||
ASSERT_OK(Put("Key" + ToString(i), DummyString(1024, 'A'), wo));
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
// We created 4 sst files in L0
|
||||
@@ -483,7 +547,7 @@ TEST_F(DBSSTTest, DeleteSchedulerMultipleDBPaths) {
|
||||
|
||||
// Create 4 files in L0
|
||||
for (int i = 4; i < 8; i++) {
|
||||
ASSERT_OK(Put("Key" + ToString(i), DummyString(1024, 'B')));
|
||||
ASSERT_OK(Put("Key" + ToString(i), DummyString(1024, 'B'), wo));
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
ASSERT_EQ("4,1", FilesPerLevel(0));
|
||||
@@ -501,7 +565,7 @@ TEST_F(DBSSTTest, DeleteSchedulerMultipleDBPaths) {
|
||||
// Compaction will delete both files and regenerate a file in L1 in second
|
||||
// db path. The deleted files should still be cleaned up via delete scheduler.
|
||||
compact_options.bottommost_level_compaction =
|
||||
BottommostLevelCompaction::kForce;
|
||||
BottommostLevelCompaction::kForceOptimized;
|
||||
ASSERT_OK(db_->CompactRange(compact_options, nullptr, nullptr));
|
||||
ASSERT_EQ("0,1", FilesPerLevel(0));
|
||||
|
||||
@@ -538,14 +602,27 @@ TEST_F(DBSSTTest, DestroyDBWithRateLimitedDelete) {
|
||||
// Close DB and destroy it using DeleteScheduler
|
||||
Close();
|
||||
|
||||
int num_sst_files = 0;
|
||||
int num_wal_files = 0;
|
||||
std::vector<std::string> db_files;
|
||||
env_->GetChildren(dbname_, &db_files);
|
||||
for (std::string f : db_files) {
|
||||
if (f.substr(f.find_last_of(".") + 1) == "sst") {
|
||||
num_sst_files++;
|
||||
} else if (f.substr(f.find_last_of(".") + 1) == "log") {
|
||||
num_wal_files++;
|
||||
}
|
||||
}
|
||||
ASSERT_GT(num_sst_files, 0);
|
||||
ASSERT_GT(num_wal_files, 0);
|
||||
|
||||
auto sfm = static_cast<SstFileManagerImpl*>(options.sst_file_manager.get());
|
||||
|
||||
sfm->SetDeleteRateBytesPerSecond(1024 * 1024);
|
||||
sfm->delete_scheduler()->SetMaxTrashDBRatio(1.1);
|
||||
ASSERT_OK(DestroyDB(dbname_, options));
|
||||
sfm->WaitForEmptyTrash();
|
||||
// We have deleted the 4 sst files in the delete_scheduler
|
||||
ASSERT_EQ(bg_delete_file, 4);
|
||||
ASSERT_EQ(bg_delete_file, num_sst_files + num_wal_files);
|
||||
}
|
||||
|
||||
TEST_F(DBSSTTest, DBWithMaxSpaceAllowed) {
|
||||
|
||||
@@ -46,7 +46,7 @@ TEST_F(DBStatisticsTest, CompressionStatsTest) {
|
||||
Options options = CurrentOptions();
|
||||
options.compression = type;
|
||||
options.statistics = rocksdb::CreateDBStatistics();
|
||||
options.statistics->stats_level_ = StatsLevel::kExceptTimeForMutex;
|
||||
options.statistics->set_stats_level(StatsLevel::kExceptTimeForMutex);
|
||||
DestroyAndReopen(options);
|
||||
|
||||
int kNumKeysWritten = 100000;
|
||||
@@ -105,7 +105,7 @@ TEST_F(DBStatisticsTest, MutexWaitStats) {
|
||||
Options options = CurrentOptions();
|
||||
options.create_if_missing = true;
|
||||
options.statistics = rocksdb::CreateDBStatistics();
|
||||
options.statistics->stats_level_ = StatsLevel::kAll;
|
||||
options.statistics->set_stats_level(StatsLevel::kAll);
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
const uint64_t kMutexWaitDelay = 100;
|
||||
ThreadStatusUtil::TEST_SetStateDelay(ThreadStatus::STATE_MUTEX_WAIT,
|
||||
|
||||
+265
-204
@@ -487,11 +487,11 @@ TEST_F(DBTest, PutSingleDeleteGet) {
|
||||
ASSERT_EQ("v2", Get(1, "foo2"));
|
||||
ASSERT_OK(SingleDelete(1, "foo"));
|
||||
ASSERT_EQ("NOT_FOUND", Get(1, "foo"));
|
||||
// Skip HashCuckooRep as it does not support single delete. FIFO and
|
||||
// universal compaction do not apply to the test case. Skip MergePut
|
||||
// because single delete does not get removed when it encounters a merge.
|
||||
} while (ChangeOptions(kSkipHashCuckoo | kSkipFIFOCompaction |
|
||||
kSkipUniversalCompaction | kSkipMergePut));
|
||||
// Skip FIFO and universal compaction beccause they do not apply to the test
|
||||
// case. Skip MergePut because single delete does not get removed when it
|
||||
// encounters a merge.
|
||||
} while (ChangeOptions(kSkipFIFOCompaction | kSkipUniversalCompaction |
|
||||
kSkipMergePut));
|
||||
}
|
||||
|
||||
TEST_F(DBTest, ReadFromPersistedTier) {
|
||||
@@ -604,7 +604,7 @@ TEST_F(DBTest, ReadFromPersistedTier) {
|
||||
DestroyAndReopen(options);
|
||||
}
|
||||
}
|
||||
} while (ChangeOptions(kSkipHashCuckoo));
|
||||
} while (ChangeOptions());
|
||||
}
|
||||
|
||||
TEST_F(DBTest, SingleDeleteFlush) {
|
||||
@@ -640,11 +640,11 @@ TEST_F(DBTest, SingleDeleteFlush) {
|
||||
|
||||
ASSERT_EQ("NOT_FOUND", Get(1, "bar"));
|
||||
ASSERT_EQ("NOT_FOUND", Get(1, "foo"));
|
||||
// Skip HashCuckooRep as it does not support single delete. FIFO and
|
||||
// universal compaction do not apply to the test case. Skip MergePut
|
||||
// because merges cannot be combined with single deletions.
|
||||
} while (ChangeOptions(kSkipHashCuckoo | kSkipFIFOCompaction |
|
||||
kSkipUniversalCompaction | kSkipMergePut));
|
||||
// Skip FIFO and universal compaction beccause they do not apply to the test
|
||||
// case. Skip MergePut because single delete does not get removed when it
|
||||
// encounters a merge.
|
||||
} while (ChangeOptions(kSkipFIFOCompaction | kSkipUniversalCompaction |
|
||||
kSkipMergePut));
|
||||
}
|
||||
|
||||
TEST_F(DBTest, SingleDeletePutFlush) {
|
||||
@@ -663,11 +663,11 @@ TEST_F(DBTest, SingleDeletePutFlush) {
|
||||
ASSERT_OK(Flush(1));
|
||||
|
||||
ASSERT_EQ("[ ]", AllEntriesFor("a", 1));
|
||||
// Skip HashCuckooRep as it does not support single delete. FIFO and
|
||||
// universal compaction do not apply to the test case. Skip MergePut
|
||||
// because merges cannot be combined with single deletions.
|
||||
} while (ChangeOptions(kSkipHashCuckoo | kSkipFIFOCompaction |
|
||||
kSkipUniversalCompaction | kSkipMergePut));
|
||||
// Skip FIFO and universal compaction beccause they do not apply to the test
|
||||
// case. Skip MergePut because single delete does not get removed when it
|
||||
// encounters a merge.
|
||||
} while (ChangeOptions(kSkipFIFOCompaction | kSkipUniversalCompaction |
|
||||
kSkipMergePut));
|
||||
}
|
||||
|
||||
// Disable because not all platform can run it.
|
||||
@@ -940,13 +940,13 @@ TEST_F(DBTest, FlushSchedule) {
|
||||
namespace {
|
||||
class KeepFilter : public CompactionFilter {
|
||||
public:
|
||||
virtual bool Filter(int /*level*/, const Slice& /*key*/,
|
||||
const Slice& /*value*/, std::string* /*new_value*/,
|
||||
bool* /*value_changed*/) const override {
|
||||
bool Filter(int /*level*/, const Slice& /*key*/, const Slice& /*value*/,
|
||||
std::string* /*new_value*/,
|
||||
bool* /*value_changed*/) const override {
|
||||
return false;
|
||||
}
|
||||
|
||||
virtual const char* Name() const override { return "KeepFilter"; }
|
||||
const char* Name() const override { return "KeepFilter"; }
|
||||
};
|
||||
|
||||
class KeepFilterFactory : public CompactionFilterFactory {
|
||||
@@ -954,7 +954,7 @@ class KeepFilterFactory : public CompactionFilterFactory {
|
||||
explicit KeepFilterFactory(bool check_context = false)
|
||||
: check_context_(check_context) {}
|
||||
|
||||
virtual std::unique_ptr<CompactionFilter> CreateCompactionFilter(
|
||||
std::unique_ptr<CompactionFilter> CreateCompactionFilter(
|
||||
const CompactionFilter::Context& context) override {
|
||||
if (check_context_) {
|
||||
EXPECT_EQ(expect_full_compaction_.load(), context.is_full_compaction);
|
||||
@@ -963,7 +963,7 @@ class KeepFilterFactory : public CompactionFilterFactory {
|
||||
return std::unique_ptr<CompactionFilter>(new KeepFilter());
|
||||
}
|
||||
|
||||
virtual const char* Name() const override { return "KeepFilterFactory"; }
|
||||
const char* Name() const override { return "KeepFilterFactory"; }
|
||||
bool check_context_;
|
||||
std::atomic_bool expect_full_compaction_;
|
||||
std::atomic_bool expect_manual_compaction_;
|
||||
@@ -972,14 +972,14 @@ class KeepFilterFactory : public CompactionFilterFactory {
|
||||
class DelayFilter : public CompactionFilter {
|
||||
public:
|
||||
explicit DelayFilter(DBTestBase* d) : db_test(d) {}
|
||||
virtual bool Filter(int /*level*/, const Slice& /*key*/,
|
||||
const Slice& /*value*/, std::string* /*new_value*/,
|
||||
bool* /*value_changed*/) const override {
|
||||
bool Filter(int /*level*/, const Slice& /*key*/, const Slice& /*value*/,
|
||||
std::string* /*new_value*/,
|
||||
bool* /*value_changed*/) const override {
|
||||
db_test->env_->addon_time_.fetch_add(1000);
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual const char* Name() const override { return "DelayFilter"; }
|
||||
const char* Name() const override { return "DelayFilter"; }
|
||||
|
||||
private:
|
||||
DBTestBase* db_test;
|
||||
@@ -988,12 +988,12 @@ class DelayFilter : public CompactionFilter {
|
||||
class DelayFilterFactory : public CompactionFilterFactory {
|
||||
public:
|
||||
explicit DelayFilterFactory(DBTestBase* d) : db_test(d) {}
|
||||
virtual std::unique_ptr<CompactionFilter> CreateCompactionFilter(
|
||||
std::unique_ptr<CompactionFilter> CreateCompactionFilter(
|
||||
const CompactionFilter::Context& /*context*/) override {
|
||||
return std::unique_ptr<CompactionFilter>(new DelayFilter(db_test));
|
||||
}
|
||||
|
||||
virtual const char* Name() const override { return "DelayFilterFactory"; }
|
||||
const char* Name() const override { return "DelayFilterFactory"; }
|
||||
|
||||
private:
|
||||
DBTestBase* db_test;
|
||||
@@ -1569,7 +1569,7 @@ TEST_F(DBTest, Snapshot) {
|
||||
ASSERT_EQ(0U, GetNumSnapshots());
|
||||
ASSERT_EQ("0v4", Get(0, "foo"));
|
||||
ASSERT_EQ("1v4", Get(1, "foo"));
|
||||
} while (ChangeOptions(kSkipHashCuckoo));
|
||||
} while (ChangeOptions());
|
||||
}
|
||||
|
||||
TEST_F(DBTest, HiddenValuesAreRemoved) {
|
||||
@@ -1606,9 +1606,8 @@ TEST_F(DBTest, HiddenValuesAreRemoved) {
|
||||
ASSERT_TRUE(Between(Size("", "pastfoo", 1), 0, 1000));
|
||||
// ApproximateOffsetOf() is not yet implemented in plain table format,
|
||||
// which is used by Size().
|
||||
// skip HashCuckooRep as it does not support snapshot
|
||||
} while (ChangeOptions(kSkipUniversalCompaction | kSkipFIFOCompaction |
|
||||
kSkipPlainTable | kSkipHashCuckoo));
|
||||
kSkipPlainTable));
|
||||
}
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
@@ -1654,11 +1653,11 @@ TEST_F(DBTest, UnremovableSingleDelete) {
|
||||
ASSERT_EQ("first", Get(1, "foo", snapshot));
|
||||
ASSERT_EQ("NOT_FOUND", Get(1, "foo"));
|
||||
db_->ReleaseSnapshot(snapshot);
|
||||
// Skip HashCuckooRep as it does not support single delete. FIFO and
|
||||
// universal compaction do not apply to the test case. Skip MergePut
|
||||
// because single delete does not get removed when it encounters a merge.
|
||||
} while (ChangeOptions(kSkipHashCuckoo | kSkipFIFOCompaction |
|
||||
kSkipUniversalCompaction | kSkipMergePut));
|
||||
// Skip FIFO and universal compaction beccause they do not apply to the test
|
||||
// case. Skip MergePut because single delete does not get removed when it
|
||||
// encounters a merge.
|
||||
} while (ChangeOptions(kSkipFIFOCompaction | kSkipUniversalCompaction |
|
||||
kSkipMergePut));
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
@@ -1776,17 +1775,14 @@ TEST_F(DBTest, OverlapInLevel0) {
|
||||
TEST_F(DBTest, ComparatorCheck) {
|
||||
class NewComparator : public Comparator {
|
||||
public:
|
||||
virtual const char* Name() const override {
|
||||
return "rocksdb.NewComparator";
|
||||
}
|
||||
virtual int Compare(const Slice& a, const Slice& b) const override {
|
||||
const char* Name() const override { return "rocksdb.NewComparator"; }
|
||||
int Compare(const Slice& a, const Slice& b) const override {
|
||||
return BytewiseComparator()->Compare(a, b);
|
||||
}
|
||||
virtual void FindShortestSeparator(std::string* s,
|
||||
const Slice& l) const override {
|
||||
void FindShortestSeparator(std::string* s, const Slice& l) const override {
|
||||
BytewiseComparator()->FindShortestSeparator(s, l);
|
||||
}
|
||||
virtual void FindShortSuccessor(std::string* key) const override {
|
||||
void FindShortSuccessor(std::string* key) const override {
|
||||
BytewiseComparator()->FindShortSuccessor(key);
|
||||
}
|
||||
};
|
||||
@@ -1809,18 +1805,15 @@ TEST_F(DBTest, ComparatorCheck) {
|
||||
TEST_F(DBTest, CustomComparator) {
|
||||
class NumberComparator : public Comparator {
|
||||
public:
|
||||
virtual const char* Name() const override {
|
||||
return "test.NumberComparator";
|
||||
}
|
||||
virtual int Compare(const Slice& a, const Slice& b) const override {
|
||||
const char* Name() const override { return "test.NumberComparator"; }
|
||||
int Compare(const Slice& a, const Slice& b) const override {
|
||||
return ToNumber(a) - ToNumber(b);
|
||||
}
|
||||
virtual void FindShortestSeparator(std::string* s,
|
||||
const Slice& l) const override {
|
||||
void FindShortestSeparator(std::string* s, const Slice& l) const override {
|
||||
ToNumber(*s); // Check format
|
||||
ToNumber(l); // Check format
|
||||
}
|
||||
virtual void FindShortSuccessor(std::string* key) const override {
|
||||
void FindShortSuccessor(std::string* key) const override {
|
||||
ToNumber(*key); // Check format
|
||||
}
|
||||
|
||||
@@ -2160,6 +2153,7 @@ struct MTState {
|
||||
struct MTThread {
|
||||
MTState* state;
|
||||
int id;
|
||||
bool multiget_batched;
|
||||
};
|
||||
|
||||
static void MTThreadBody(void* arg) {
|
||||
@@ -2208,8 +2202,28 @@ static void MTThreadBody(void* arg) {
|
||||
// same)
|
||||
std::vector<Slice> keys(kColumnFamilies, Slice(keybuf));
|
||||
std::vector<std::string> values;
|
||||
std::vector<Status> statuses =
|
||||
db->MultiGet(ReadOptions(), t->state->test->handles_, keys, &values);
|
||||
std::vector<Status> statuses;
|
||||
if (!t->multiget_batched) {
|
||||
statuses = db->MultiGet(ReadOptions(), t->state->test->handles_, keys,
|
||||
&values);
|
||||
} else {
|
||||
std::vector<PinnableSlice> pin_values(keys.size());
|
||||
statuses.resize(keys.size());
|
||||
const Snapshot* snapshot = db->GetSnapshot();
|
||||
ReadOptions ro;
|
||||
ro.snapshot = snapshot;
|
||||
for (int cf = 0; cf < kColumnFamilies; ++cf) {
|
||||
db->MultiGet(ro, t->state->test->handles_[cf], 1, &keys[cf],
|
||||
&pin_values[cf], &statuses[cf]);
|
||||
}
|
||||
db->ReleaseSnapshot(snapshot);
|
||||
values.resize(keys.size());
|
||||
for (int cf = 0; cf < kColumnFamilies; ++cf) {
|
||||
if (statuses[cf].ok()) {
|
||||
values[cf].assign(pin_values[cf].data(), pin_values[cf].size());
|
||||
}
|
||||
}
|
||||
}
|
||||
Status s = statuses[0];
|
||||
// all statuses have to be the same
|
||||
for (size_t i = 1; i < statuses.size(); ++i) {
|
||||
@@ -2251,21 +2265,23 @@ static void MTThreadBody(void* arg) {
|
||||
|
||||
} // namespace
|
||||
|
||||
class MultiThreadedDBTest : public DBTest,
|
||||
public ::testing::WithParamInterface<int> {
|
||||
class MultiThreadedDBTest
|
||||
: public DBTest,
|
||||
public ::testing::WithParamInterface<std::tuple<int, bool>> {
|
||||
public:
|
||||
virtual void SetUp() override { option_config_ = GetParam(); }
|
||||
void SetUp() override {
|
||||
std::tie(option_config_, multiget_batched_) = GetParam();
|
||||
}
|
||||
|
||||
static std::vector<int> GenerateOptionConfigs() {
|
||||
std::vector<int> optionConfigs;
|
||||
for (int optionConfig = kDefault; optionConfig < kEnd; ++optionConfig) {
|
||||
// skip as HashCuckooRep does not support snapshot
|
||||
if (optionConfig != kHashCuckoo) {
|
||||
optionConfigs.push_back(optionConfig);
|
||||
}
|
||||
optionConfigs.push_back(optionConfig);
|
||||
}
|
||||
return optionConfigs;
|
||||
}
|
||||
|
||||
bool multiget_batched_;
|
||||
};
|
||||
|
||||
TEST_P(MultiThreadedDBTest, MultiThreaded) {
|
||||
@@ -2292,6 +2308,7 @@ TEST_P(MultiThreadedDBTest, MultiThreaded) {
|
||||
for (int id = 0; id < kNumThreads; id++) {
|
||||
thread[id].state = &mt;
|
||||
thread[id].id = id;
|
||||
thread[id].multiget_batched = multiget_batched_;
|
||||
env_->StartThread(MTThreadBody, &thread[id]);
|
||||
}
|
||||
|
||||
@@ -2309,7 +2326,9 @@ TEST_P(MultiThreadedDBTest, MultiThreaded) {
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(
|
||||
MultiThreaded, MultiThreadedDBTest,
|
||||
::testing::ValuesIn(MultiThreadedDBTest::GenerateOptionConfigs()));
|
||||
::testing::Combine(
|
||||
::testing::ValuesIn(MultiThreadedDBTest::GenerateOptionConfigs()),
|
||||
::testing::Bool()));
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
// Group commit test:
|
||||
@@ -2402,7 +2421,7 @@ class ModelDB : public DB {
|
||||
public:
|
||||
KVMap map_;
|
||||
|
||||
virtual SequenceNumber GetSequenceNumber() const override {
|
||||
SequenceNumber GetSequenceNumber() const override {
|
||||
// no need to call this
|
||||
assert(false);
|
||||
return 0;
|
||||
@@ -2411,43 +2430,43 @@ class ModelDB : public DB {
|
||||
|
||||
explicit ModelDB(const Options& options) : options_(options) {}
|
||||
using DB::Put;
|
||||
virtual Status Put(const WriteOptions& o, ColumnFamilyHandle* cf,
|
||||
const Slice& k, const Slice& v) override {
|
||||
Status Put(const WriteOptions& o, ColumnFamilyHandle* cf, const Slice& k,
|
||||
const Slice& v) override {
|
||||
WriteBatch batch;
|
||||
batch.Put(cf, k, v);
|
||||
return Write(o, &batch);
|
||||
}
|
||||
using DB::Close;
|
||||
virtual Status Close() override { return Status::OK(); }
|
||||
Status Close() override { return Status::OK(); }
|
||||
using DB::Delete;
|
||||
virtual Status Delete(const WriteOptions& o, ColumnFamilyHandle* cf,
|
||||
const Slice& key) override {
|
||||
Status Delete(const WriteOptions& o, ColumnFamilyHandle* cf,
|
||||
const Slice& key) override {
|
||||
WriteBatch batch;
|
||||
batch.Delete(cf, key);
|
||||
return Write(o, &batch);
|
||||
}
|
||||
using DB::SingleDelete;
|
||||
virtual Status SingleDelete(const WriteOptions& o, ColumnFamilyHandle* cf,
|
||||
const Slice& key) override {
|
||||
Status SingleDelete(const WriteOptions& o, ColumnFamilyHandle* cf,
|
||||
const Slice& key) override {
|
||||
WriteBatch batch;
|
||||
batch.SingleDelete(cf, key);
|
||||
return Write(o, &batch);
|
||||
}
|
||||
using DB::Merge;
|
||||
virtual Status Merge(const WriteOptions& o, ColumnFamilyHandle* cf,
|
||||
const Slice& k, const Slice& v) override {
|
||||
Status Merge(const WriteOptions& o, ColumnFamilyHandle* cf, const Slice& k,
|
||||
const Slice& v) override {
|
||||
WriteBatch batch;
|
||||
batch.Merge(cf, k, v);
|
||||
return Write(o, &batch);
|
||||
}
|
||||
using DB::Get;
|
||||
virtual Status Get(const ReadOptions& /*options*/, ColumnFamilyHandle* /*cf*/,
|
||||
const Slice& key, PinnableSlice* /*value*/) override {
|
||||
Status Get(const ReadOptions& /*options*/, ColumnFamilyHandle* /*cf*/,
|
||||
const Slice& key, PinnableSlice* /*value*/) override {
|
||||
return Status::NotSupported(key);
|
||||
}
|
||||
|
||||
using DB::MultiGet;
|
||||
virtual std::vector<Status> MultiGet(
|
||||
std::vector<Status> MultiGet(
|
||||
const ReadOptions& /*options*/,
|
||||
const std::vector<ColumnFamilyHandle*>& /*column_family*/,
|
||||
const std::vector<Slice>& keys,
|
||||
@@ -2459,25 +2478,31 @@ class ModelDB : public DB {
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
using DB::IngestExternalFile;
|
||||
virtual Status IngestExternalFile(
|
||||
Status IngestExternalFile(
|
||||
ColumnFamilyHandle* /*column_family*/,
|
||||
const std::vector<std::string>& /*external_files*/,
|
||||
const IngestExternalFileOptions& /*options*/) override {
|
||||
return Status::NotSupported("Not implemented.");
|
||||
}
|
||||
|
||||
virtual Status VerifyChecksum() override {
|
||||
using DB::IngestExternalFiles;
|
||||
Status IngestExternalFiles(
|
||||
const std::vector<IngestExternalFileArg>& /*args*/) override {
|
||||
return Status::NotSupported("Not implemented");
|
||||
}
|
||||
|
||||
Status VerifyChecksum() override {
|
||||
return Status::NotSupported("Not implemented.");
|
||||
}
|
||||
|
||||
using DB::GetPropertiesOfAllTables;
|
||||
virtual Status GetPropertiesOfAllTables(
|
||||
Status GetPropertiesOfAllTables(
|
||||
ColumnFamilyHandle* /*column_family*/,
|
||||
TablePropertiesCollection* /*props*/) override {
|
||||
return Status();
|
||||
}
|
||||
|
||||
virtual Status GetPropertiesOfTablesInRange(
|
||||
Status GetPropertiesOfTablesInRange(
|
||||
ColumnFamilyHandle* /*column_family*/, const Range* /*range*/,
|
||||
std::size_t /*n*/, TablePropertiesCollection* /*props*/) override {
|
||||
return Status();
|
||||
@@ -2485,19 +2510,18 @@ class ModelDB : public DB {
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
using DB::KeyMayExist;
|
||||
virtual bool KeyMayExist(const ReadOptions& /*options*/,
|
||||
ColumnFamilyHandle* /*column_family*/,
|
||||
const Slice& /*key*/, std::string* /*value*/,
|
||||
bool* value_found = nullptr) override {
|
||||
bool KeyMayExist(const ReadOptions& /*options*/,
|
||||
ColumnFamilyHandle* /*column_family*/, const Slice& /*key*/,
|
||||
std::string* /*value*/,
|
||||
bool* value_found = nullptr) override {
|
||||
if (value_found != nullptr) {
|
||||
*value_found = false;
|
||||
}
|
||||
return true; // Not Supported directly
|
||||
}
|
||||
using DB::NewIterator;
|
||||
virtual Iterator* NewIterator(
|
||||
const ReadOptions& options,
|
||||
ColumnFamilyHandle* /*column_family*/) override {
|
||||
Iterator* NewIterator(const ReadOptions& options,
|
||||
ColumnFamilyHandle* /*column_family*/) override {
|
||||
if (options.snapshot == nullptr) {
|
||||
KVMap* saved = new KVMap;
|
||||
*saved = map_;
|
||||
@@ -2508,38 +2532,33 @@ class ModelDB : public DB {
|
||||
return new ModelIter(snapshot_state, false);
|
||||
}
|
||||
}
|
||||
virtual Status NewIterators(
|
||||
const ReadOptions& /*options*/,
|
||||
const std::vector<ColumnFamilyHandle*>& /*column_family*/,
|
||||
std::vector<Iterator*>* /*iterators*/) override {
|
||||
Status NewIterators(const ReadOptions& /*options*/,
|
||||
const std::vector<ColumnFamilyHandle*>& /*column_family*/,
|
||||
std::vector<Iterator*>* /*iterators*/) override {
|
||||
return Status::NotSupported("Not supported yet");
|
||||
}
|
||||
virtual const Snapshot* GetSnapshot() override {
|
||||
const Snapshot* GetSnapshot() override {
|
||||
ModelSnapshot* snapshot = new ModelSnapshot;
|
||||
snapshot->map_ = map_;
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
virtual void ReleaseSnapshot(const Snapshot* snapshot) override {
|
||||
void ReleaseSnapshot(const Snapshot* snapshot) override {
|
||||
delete reinterpret_cast<const ModelSnapshot*>(snapshot);
|
||||
}
|
||||
|
||||
virtual Status Write(const WriteOptions& /*options*/,
|
||||
WriteBatch* batch) override {
|
||||
Status Write(const WriteOptions& /*options*/, WriteBatch* batch) override {
|
||||
class Handler : public WriteBatch::Handler {
|
||||
public:
|
||||
KVMap* map_;
|
||||
virtual void Put(const Slice& key, const Slice& value) override {
|
||||
void Put(const Slice& key, const Slice& value) override {
|
||||
(*map_)[key.ToString()] = value.ToString();
|
||||
}
|
||||
virtual void Merge(const Slice& /*key*/,
|
||||
const Slice& /*value*/) override {
|
||||
void Merge(const Slice& /*key*/, const Slice& /*value*/) override {
|
||||
// ignore merge for now
|
||||
// (*map_)[key.ToString()] = value.ToString();
|
||||
}
|
||||
virtual void Delete(const Slice& key) override {
|
||||
map_->erase(key.ToString());
|
||||
}
|
||||
void Delete(const Slice& key) override { map_->erase(key.ToString()); }
|
||||
};
|
||||
Handler handler;
|
||||
handler.map_ = &map_;
|
||||
@@ -2547,61 +2566,58 @@ class ModelDB : public DB {
|
||||
}
|
||||
|
||||
using DB::GetProperty;
|
||||
virtual bool GetProperty(ColumnFamilyHandle* /*column_family*/,
|
||||
const Slice& /*property*/,
|
||||
std::string* /*value*/) override {
|
||||
bool GetProperty(ColumnFamilyHandle* /*column_family*/,
|
||||
const Slice& /*property*/, std::string* /*value*/) override {
|
||||
return false;
|
||||
}
|
||||
using DB::GetIntProperty;
|
||||
virtual bool GetIntProperty(ColumnFamilyHandle* /*column_family*/,
|
||||
const Slice& /*property*/,
|
||||
uint64_t* /*value*/) override {
|
||||
bool GetIntProperty(ColumnFamilyHandle* /*column_family*/,
|
||||
const Slice& /*property*/, uint64_t* /*value*/) override {
|
||||
return false;
|
||||
}
|
||||
using DB::GetMapProperty;
|
||||
virtual bool GetMapProperty(
|
||||
ColumnFamilyHandle* /*column_family*/, const Slice& /*property*/,
|
||||
std::map<std::string, std::string>* /*value*/) override {
|
||||
bool GetMapProperty(ColumnFamilyHandle* /*column_family*/,
|
||||
const Slice& /*property*/,
|
||||
std::map<std::string, std::string>* /*value*/) override {
|
||||
return false;
|
||||
}
|
||||
using DB::GetAggregatedIntProperty;
|
||||
virtual bool GetAggregatedIntProperty(const Slice& /*property*/,
|
||||
uint64_t* /*value*/) override {
|
||||
bool GetAggregatedIntProperty(const Slice& /*property*/,
|
||||
uint64_t* /*value*/) override {
|
||||
return false;
|
||||
}
|
||||
using DB::GetApproximateSizes;
|
||||
virtual void GetApproximateSizes(ColumnFamilyHandle* /*column_family*/,
|
||||
const Range* /*range*/, int n,
|
||||
uint64_t* sizes,
|
||||
uint8_t /*include_flags*/
|
||||
= INCLUDE_FILES) override {
|
||||
void GetApproximateSizes(ColumnFamilyHandle* /*column_family*/,
|
||||
const Range* /*range*/, int n, uint64_t* sizes,
|
||||
uint8_t /*include_flags*/
|
||||
= INCLUDE_FILES) override {
|
||||
for (int i = 0; i < n; i++) {
|
||||
sizes[i] = 0;
|
||||
}
|
||||
}
|
||||
using DB::GetApproximateMemTableStats;
|
||||
virtual void GetApproximateMemTableStats(
|
||||
ColumnFamilyHandle* /*column_family*/, const Range& /*range*/,
|
||||
uint64_t* const count, uint64_t* const size) override {
|
||||
void GetApproximateMemTableStats(ColumnFamilyHandle* /*column_family*/,
|
||||
const Range& /*range*/,
|
||||
uint64_t* const count,
|
||||
uint64_t* const size) override {
|
||||
*count = 0;
|
||||
*size = 0;
|
||||
}
|
||||
using DB::CompactRange;
|
||||
virtual Status CompactRange(const CompactRangeOptions& /*options*/,
|
||||
ColumnFamilyHandle* /*column_family*/,
|
||||
const Slice* /*start*/,
|
||||
const Slice* /*end*/) override {
|
||||
Status CompactRange(const CompactRangeOptions& /*options*/,
|
||||
ColumnFamilyHandle* /*column_family*/,
|
||||
const Slice* /*start*/, const Slice* /*end*/) override {
|
||||
return Status::NotSupported("Not supported operation.");
|
||||
}
|
||||
|
||||
virtual Status SetDBOptions(
|
||||
Status SetDBOptions(
|
||||
const std::unordered_map<std::string, std::string>& /*new_options*/)
|
||||
override {
|
||||
return Status::NotSupported("Not supported operation.");
|
||||
}
|
||||
|
||||
using DB::CompactFiles;
|
||||
virtual Status CompactFiles(
|
||||
Status CompactFiles(
|
||||
const CompactionOptions& /*compact_options*/,
|
||||
ColumnFamilyHandle* /*column_family*/,
|
||||
const std::vector<std::string>& /*input_file_names*/,
|
||||
@@ -2626,69 +2642,60 @@ class ModelDB : public DB {
|
||||
}
|
||||
|
||||
using DB::NumberLevels;
|
||||
virtual int NumberLevels(ColumnFamilyHandle* /*column_family*/) override {
|
||||
return 1;
|
||||
}
|
||||
int NumberLevels(ColumnFamilyHandle* /*column_family*/) override { return 1; }
|
||||
|
||||
using DB::MaxMemCompactionLevel;
|
||||
virtual int MaxMemCompactionLevel(
|
||||
ColumnFamilyHandle* /*column_family*/) override {
|
||||
int MaxMemCompactionLevel(ColumnFamilyHandle* /*column_family*/) override {
|
||||
return 1;
|
||||
}
|
||||
|
||||
using DB::Level0StopWriteTrigger;
|
||||
virtual int Level0StopWriteTrigger(
|
||||
ColumnFamilyHandle* /*column_family*/) override {
|
||||
int Level0StopWriteTrigger(ColumnFamilyHandle* /*column_family*/) override {
|
||||
return -1;
|
||||
}
|
||||
|
||||
virtual const std::string& GetName() const override { return name_; }
|
||||
const std::string& GetName() const override { return name_; }
|
||||
|
||||
virtual Env* GetEnv() const override { return nullptr; }
|
||||
Env* GetEnv() const override { return nullptr; }
|
||||
|
||||
using DB::GetOptions;
|
||||
virtual Options GetOptions(
|
||||
ColumnFamilyHandle* /*column_family*/) const override {
|
||||
Options GetOptions(ColumnFamilyHandle* /*column_family*/) const override {
|
||||
return options_;
|
||||
}
|
||||
|
||||
using DB::GetDBOptions;
|
||||
virtual DBOptions GetDBOptions() const override { return options_; }
|
||||
DBOptions GetDBOptions() const override { return options_; }
|
||||
|
||||
using DB::Flush;
|
||||
virtual Status Flush(const rocksdb::FlushOptions& /*options*/,
|
||||
ColumnFamilyHandle* /*column_family*/) override {
|
||||
Status Flush(const rocksdb::FlushOptions& /*options*/,
|
||||
ColumnFamilyHandle* /*column_family*/) override {
|
||||
Status ret;
|
||||
return ret;
|
||||
}
|
||||
virtual Status Flush(
|
||||
Status Flush(
|
||||
const rocksdb::FlushOptions& /*options*/,
|
||||
const std::vector<ColumnFamilyHandle*>& /*column_families*/) override {
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
virtual Status SyncWAL() override { return Status::OK(); }
|
||||
Status SyncWAL() override { return Status::OK(); }
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
virtual Status DisableFileDeletions() override { return Status::OK(); }
|
||||
Status DisableFileDeletions() override { return Status::OK(); }
|
||||
|
||||
virtual Status EnableFileDeletions(bool /*force*/) override {
|
||||
return Status::OK();
|
||||
}
|
||||
virtual Status GetLiveFiles(std::vector<std::string>&, uint64_t* /*size*/,
|
||||
bool /*flush_memtable*/ = true) override {
|
||||
Status EnableFileDeletions(bool /*force*/) override { return Status::OK(); }
|
||||
Status GetLiveFiles(std::vector<std::string>&, uint64_t* /*size*/,
|
||||
bool /*flush_memtable*/ = true) override {
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
virtual Status GetSortedWalFiles(VectorLogPtr& /*files*/) override {
|
||||
Status GetSortedWalFiles(VectorLogPtr& /*files*/) override {
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
virtual Status DeleteFile(std::string /*name*/) override {
|
||||
return Status::OK();
|
||||
}
|
||||
Status DeleteFile(std::string /*name*/) override { return Status::OK(); }
|
||||
|
||||
virtual Status GetUpdatesSince(
|
||||
Status GetUpdatesSince(
|
||||
rocksdb::SequenceNumber,
|
||||
std::unique_ptr<rocksdb::TransactionLogIterator>*,
|
||||
const TransactionLogIterator::ReadOptions& /*read_options*/ =
|
||||
@@ -2696,52 +2703,48 @@ class ModelDB : public DB {
|
||||
return Status::NotSupported("Not supported in Model DB");
|
||||
}
|
||||
|
||||
virtual void GetColumnFamilyMetaData(
|
||||
ColumnFamilyHandle* /*column_family*/,
|
||||
ColumnFamilyMetaData* /*metadata*/) override {}
|
||||
void GetColumnFamilyMetaData(ColumnFamilyHandle* /*column_family*/,
|
||||
ColumnFamilyMetaData* /*metadata*/) override {}
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
virtual Status GetDbIdentity(std::string& /*identity*/) const override {
|
||||
Status GetDbIdentity(std::string& /*identity*/) const override {
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
virtual SequenceNumber GetLatestSequenceNumber() const override { return 0; }
|
||||
SequenceNumber GetLatestSequenceNumber() const override { return 0; }
|
||||
|
||||
virtual bool SetPreserveDeletesSequenceNumber(
|
||||
SequenceNumber /*seqnum*/) override {
|
||||
bool SetPreserveDeletesSequenceNumber(SequenceNumber /*seqnum*/) override {
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual ColumnFamilyHandle* DefaultColumnFamily() const override {
|
||||
return nullptr;
|
||||
}
|
||||
ColumnFamilyHandle* DefaultColumnFamily() const override { return nullptr; }
|
||||
|
||||
private:
|
||||
class ModelIter : public Iterator {
|
||||
public:
|
||||
ModelIter(const KVMap* map, bool owned)
|
||||
: map_(map), owned_(owned), iter_(map_->end()) {}
|
||||
~ModelIter() {
|
||||
~ModelIter() override {
|
||||
if (owned_) delete map_;
|
||||
}
|
||||
virtual bool Valid() const override { return iter_ != map_->end(); }
|
||||
virtual void SeekToFirst() override { iter_ = map_->begin(); }
|
||||
virtual void SeekToLast() override {
|
||||
bool Valid() const override { return iter_ != map_->end(); }
|
||||
void SeekToFirst() override { iter_ = map_->begin(); }
|
||||
void SeekToLast() override {
|
||||
if (map_->empty()) {
|
||||
iter_ = map_->end();
|
||||
} else {
|
||||
iter_ = map_->find(map_->rbegin()->first);
|
||||
}
|
||||
}
|
||||
virtual void Seek(const Slice& k) override {
|
||||
void Seek(const Slice& k) override {
|
||||
iter_ = map_->lower_bound(k.ToString());
|
||||
}
|
||||
virtual void SeekForPrev(const Slice& k) override {
|
||||
void SeekForPrev(const Slice& k) override {
|
||||
iter_ = map_->upper_bound(k.ToString());
|
||||
Prev();
|
||||
}
|
||||
virtual void Next() override { ++iter_; }
|
||||
virtual void Prev() override {
|
||||
void Next() override { ++iter_; }
|
||||
void Prev() override {
|
||||
if (iter_ == map_->begin()) {
|
||||
iter_ = map_->end();
|
||||
return;
|
||||
@@ -2749,9 +2752,9 @@ class ModelDB : public DB {
|
||||
--iter_;
|
||||
}
|
||||
|
||||
virtual Slice key() const override { return iter_->first; }
|
||||
virtual Slice value() const override { return iter_->second; }
|
||||
virtual Status status() const override { return Status::OK(); }
|
||||
Slice key() const override { return iter_->first; }
|
||||
Slice value() const override { return iter_->second; }
|
||||
Status status() const override { return Status::OK(); }
|
||||
|
||||
private:
|
||||
const KVMap* const map_;
|
||||
@@ -2819,15 +2822,14 @@ static bool CompareIterators(int step, DB* model, DB* db,
|
||||
class DBTestRandomized : public DBTest,
|
||||
public ::testing::WithParamInterface<int> {
|
||||
public:
|
||||
virtual void SetUp() override { option_config_ = GetParam(); }
|
||||
void SetUp() override { option_config_ = GetParam(); }
|
||||
|
||||
static std::vector<int> GenerateOptionConfigs() {
|
||||
std::vector<int> option_configs;
|
||||
// skip cuckoo hash as it does not support snapshot.
|
||||
for (int option_config = kDefault; option_config < kEnd; ++option_config) {
|
||||
if (!ShouldSkipOptions(option_config, kSkipDeletesFilterFirst |
|
||||
kSkipNoSeekToLast |
|
||||
kSkipHashCuckoo)) {
|
||||
if (!ShouldSkipOptions(option_config,
|
||||
kSkipDeletesFilterFirst | kSkipNoSeekToLast)) {
|
||||
option_configs.push_back(option_config);
|
||||
}
|
||||
}
|
||||
@@ -2857,7 +2859,6 @@ TEST_P(DBTestRandomized, Randomized) {
|
||||
int p = rnd.Uniform(100);
|
||||
int minimum = 0;
|
||||
if (option_config_ == kHashSkipList || option_config_ == kHashLinkList ||
|
||||
option_config_ == kHashCuckoo ||
|
||||
option_config_ == kPlainTableFirstBytePrefix ||
|
||||
option_config_ == kBlockBasedTableWithWholeKeyHashIndex ||
|
||||
option_config_ == kBlockBasedTableWithPrefixHashIndex) {
|
||||
@@ -3111,7 +3112,7 @@ TEST_F(DBTest, FIFOCompactionWithTTLAndMaxOpenFilesTest) {
|
||||
Options options;
|
||||
options.compaction_style = kCompactionStyleFIFO;
|
||||
options.create_if_missing = true;
|
||||
options.compaction_options_fifo.ttl = 600; // seconds
|
||||
options.ttl = 600; // seconds
|
||||
|
||||
// Check that it is not supported with max_open_files != -1.
|
||||
options.max_open_files = 100;
|
||||
@@ -3127,7 +3128,7 @@ TEST_F(DBTest, FIFOCompactionWithTTLAndVariousTableFormatsTest) {
|
||||
Options options;
|
||||
options.compaction_style = kCompactionStyleFIFO;
|
||||
options.create_if_missing = true;
|
||||
options.compaction_options_fifo.ttl = 600; // seconds
|
||||
options.ttl = 600; // seconds
|
||||
|
||||
options = CurrentOptions(options);
|
||||
options.table_factory.reset(NewBlockBasedTableFactory());
|
||||
@@ -3137,10 +3138,6 @@ TEST_F(DBTest, FIFOCompactionWithTTLAndVariousTableFormatsTest) {
|
||||
options.table_factory.reset(NewPlainTableFactory());
|
||||
ASSERT_TRUE(TryReopen(options).IsNotSupported());
|
||||
|
||||
Destroy(options);
|
||||
options.table_factory.reset(NewCuckooTableFactory());
|
||||
ASSERT_TRUE(TryReopen(options).IsNotSupported());
|
||||
|
||||
Destroy(options);
|
||||
options.table_factory.reset(NewAdaptiveTableFactory());
|
||||
ASSERT_TRUE(TryReopen(options).IsNotSupported());
|
||||
@@ -3162,7 +3159,7 @@ TEST_F(DBTest, FIFOCompactionWithTTLTest) {
|
||||
env_->addon_time_.store(0);
|
||||
options.compaction_options_fifo.max_table_files_size = 150 << 10; // 150KB
|
||||
options.compaction_options_fifo.allow_compaction = false;
|
||||
options.compaction_options_fifo.ttl = 1 * 60 * 60 ; // 1 hour
|
||||
options.ttl = 1 * 60 * 60 ; // 1 hour
|
||||
options = CurrentOptions(options);
|
||||
DestroyAndReopen(options);
|
||||
|
||||
@@ -3197,7 +3194,7 @@ TEST_F(DBTest, FIFOCompactionWithTTLTest) {
|
||||
{
|
||||
options.compaction_options_fifo.max_table_files_size = 150 << 10; // 150KB
|
||||
options.compaction_options_fifo.allow_compaction = false;
|
||||
options.compaction_options_fifo.ttl = 1 * 60 * 60; // 1 hour
|
||||
options.ttl = 1 * 60 * 60; // 1 hour
|
||||
options = CurrentOptions(options);
|
||||
DestroyAndReopen(options);
|
||||
|
||||
@@ -3239,7 +3236,7 @@ TEST_F(DBTest, FIFOCompactionWithTTLTest) {
|
||||
options.write_buffer_size = 10 << 10; // 10KB
|
||||
options.compaction_options_fifo.max_table_files_size = 150 << 10; // 150KB
|
||||
options.compaction_options_fifo.allow_compaction = false;
|
||||
options.compaction_options_fifo.ttl = 1 * 60 * 60; // 1 hour
|
||||
options.ttl = 1 * 60 * 60; // 1 hour
|
||||
options = CurrentOptions(options);
|
||||
DestroyAndReopen(options);
|
||||
|
||||
@@ -3276,7 +3273,7 @@ TEST_F(DBTest, FIFOCompactionWithTTLTest) {
|
||||
{
|
||||
options.compaction_options_fifo.max_table_files_size = 150 << 10; // 150KB
|
||||
options.compaction_options_fifo.allow_compaction = true;
|
||||
options.compaction_options_fifo.ttl = 1 * 60 * 60; // 1 hour
|
||||
options.ttl = 1 * 60 * 60; // 1 hour
|
||||
options.level0_file_num_compaction_trigger = 6;
|
||||
options = CurrentOptions(options);
|
||||
DestroyAndReopen(options);
|
||||
@@ -3320,7 +3317,7 @@ TEST_F(DBTest, FIFOCompactionWithTTLTest) {
|
||||
options.write_buffer_size = 20 << 10; // 20K
|
||||
options.compaction_options_fifo.max_table_files_size = 1500 << 10; // 1.5MB
|
||||
options.compaction_options_fifo.allow_compaction = true;
|
||||
options.compaction_options_fifo.ttl = 1 * 60 * 60; // 1 hour
|
||||
options.ttl = 1 * 60 * 60; // 1 hour
|
||||
options.level0_file_num_compaction_trigger = 6;
|
||||
options = CurrentOptions(options);
|
||||
DestroyAndReopen(options);
|
||||
@@ -4619,7 +4616,7 @@ TEST_F(DBTest, DynamicCompactionOptions) {
|
||||
ASSERT_LT(NumTableFilesAtLevel(0), 4);
|
||||
}
|
||||
|
||||
// Test dynamic FIFO copmaction options.
|
||||
// Test dynamic FIFO compaction options.
|
||||
// This test covers just option parsing and makes sure that the options are
|
||||
// correctly assigned. Also look at DBOptionsTest.SetFIFOCompactionOptions
|
||||
// test which makes sure that the FIFO compaction funcionality is working
|
||||
@@ -4633,7 +4630,7 @@ TEST_F(DBTest, DynamicFIFOCompactionOptions) {
|
||||
// Initial defaults
|
||||
ASSERT_EQ(dbfull()->GetOptions().compaction_options_fifo.max_table_files_size,
|
||||
1024 * 1024 * 1024);
|
||||
ASSERT_EQ(dbfull()->GetOptions().compaction_options_fifo.ttl, 0);
|
||||
ASSERT_EQ(dbfull()->GetOptions().ttl, 0);
|
||||
ASSERT_EQ(dbfull()->GetOptions().compaction_options_fifo.allow_compaction,
|
||||
false);
|
||||
|
||||
@@ -4641,21 +4638,21 @@ TEST_F(DBTest, DynamicFIFOCompactionOptions) {
|
||||
{{"compaction_options_fifo", "{max_table_files_size=23;}"}}));
|
||||
ASSERT_EQ(dbfull()->GetOptions().compaction_options_fifo.max_table_files_size,
|
||||
23);
|
||||
ASSERT_EQ(dbfull()->GetOptions().compaction_options_fifo.ttl, 0);
|
||||
ASSERT_EQ(dbfull()->GetOptions().ttl, 0);
|
||||
ASSERT_EQ(dbfull()->GetOptions().compaction_options_fifo.allow_compaction,
|
||||
false);
|
||||
|
||||
ASSERT_OK(dbfull()->SetOptions({{"compaction_options_fifo", "{ttl=97}"}}));
|
||||
ASSERT_OK(dbfull()->SetOptions({{"ttl", "97"}}));
|
||||
ASSERT_EQ(dbfull()->GetOptions().compaction_options_fifo.max_table_files_size,
|
||||
23);
|
||||
ASSERT_EQ(dbfull()->GetOptions().compaction_options_fifo.ttl, 97);
|
||||
ASSERT_EQ(dbfull()->GetOptions().ttl, 97);
|
||||
ASSERT_EQ(dbfull()->GetOptions().compaction_options_fifo.allow_compaction,
|
||||
false);
|
||||
|
||||
ASSERT_OK(dbfull()->SetOptions({{"compaction_options_fifo", "{ttl=203;}"}}));
|
||||
ASSERT_OK(dbfull()->SetOptions({{"ttl", "203"}}));
|
||||
ASSERT_EQ(dbfull()->GetOptions().compaction_options_fifo.max_table_files_size,
|
||||
23);
|
||||
ASSERT_EQ(dbfull()->GetOptions().compaction_options_fifo.ttl, 203);
|
||||
ASSERT_EQ(dbfull()->GetOptions().ttl, 203);
|
||||
ASSERT_EQ(dbfull()->GetOptions().compaction_options_fifo.allow_compaction,
|
||||
false);
|
||||
|
||||
@@ -4663,24 +4660,25 @@ TEST_F(DBTest, DynamicFIFOCompactionOptions) {
|
||||
{{"compaction_options_fifo", "{allow_compaction=true;}"}}));
|
||||
ASSERT_EQ(dbfull()->GetOptions().compaction_options_fifo.max_table_files_size,
|
||||
23);
|
||||
ASSERT_EQ(dbfull()->GetOptions().compaction_options_fifo.ttl, 203);
|
||||
ASSERT_EQ(dbfull()->GetOptions().ttl, 203);
|
||||
ASSERT_EQ(dbfull()->GetOptions().compaction_options_fifo.allow_compaction,
|
||||
true);
|
||||
|
||||
ASSERT_OK(dbfull()->SetOptions(
|
||||
{{"compaction_options_fifo", "{max_table_files_size=31;ttl=19;}"}}));
|
||||
{{"compaction_options_fifo", "{max_table_files_size=31;}"}}));
|
||||
ASSERT_EQ(dbfull()->GetOptions().compaction_options_fifo.max_table_files_size,
|
||||
31);
|
||||
ASSERT_EQ(dbfull()->GetOptions().compaction_options_fifo.ttl, 19);
|
||||
ASSERT_EQ(dbfull()->GetOptions().ttl, 203);
|
||||
ASSERT_EQ(dbfull()->GetOptions().compaction_options_fifo.allow_compaction,
|
||||
true);
|
||||
|
||||
ASSERT_OK(dbfull()->SetOptions(
|
||||
{{"compaction_options_fifo",
|
||||
"{max_table_files_size=51;ttl=49;allow_compaction=true;}"}}));
|
||||
"{max_table_files_size=51;allow_compaction=true;}"}}));
|
||||
ASSERT_OK(dbfull()->SetOptions({{"ttl", "49"}}));
|
||||
ASSERT_EQ(dbfull()->GetOptions().compaction_options_fifo.max_table_files_size,
|
||||
51);
|
||||
ASSERT_EQ(dbfull()->GetOptions().compaction_options_fifo.ttl, 49);
|
||||
ASSERT_EQ(dbfull()->GetOptions().ttl, 49);
|
||||
ASSERT_EQ(dbfull()->GetOptions().compaction_options_fifo.allow_compaction,
|
||||
true);
|
||||
}
|
||||
@@ -5047,14 +5045,14 @@ class DelayedMergeOperator : public MergeOperator {
|
||||
public:
|
||||
explicit DelayedMergeOperator(DBTest* d) : db_test_(d) {}
|
||||
|
||||
virtual bool FullMergeV2(const MergeOperationInput& /*merge_in*/,
|
||||
MergeOperationOutput* merge_out) const override {
|
||||
bool FullMergeV2(const MergeOperationInput& /*merge_in*/,
|
||||
MergeOperationOutput* merge_out) const override {
|
||||
db_test_->env_->addon_time_.fetch_add(1000);
|
||||
merge_out->new_value = "";
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual const char* Name() const override { return "DelayedMergeOperator"; }
|
||||
const char* Name() const override { return "DelayedMergeOperator"; }
|
||||
};
|
||||
|
||||
TEST_F(DBTest, MergeTestTime) {
|
||||
@@ -5133,7 +5131,7 @@ TEST_P(DBTestWithParam, FilterCompactionTimeTest) {
|
||||
options.disable_auto_compactions = true;
|
||||
options.create_if_missing = true;
|
||||
options.statistics = rocksdb::CreateDBStatistics();
|
||||
options.statistics->stats_level_ = kExceptTimeForMutex;
|
||||
options.statistics->set_stats_level(kExceptTimeForMutex);
|
||||
options.max_subcompactions = max_subcompactions_;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
@@ -5190,7 +5188,7 @@ TEST_F(DBTest, EmptyCompactedDB) {
|
||||
TEST_F(DBTest, SuggestCompactRangeTest) {
|
||||
class CompactionFilterFactoryGetContext : public CompactionFilterFactory {
|
||||
public:
|
||||
virtual std::unique_ptr<CompactionFilter> CreateCompactionFilter(
|
||||
std::unique_ptr<CompactionFilter> CreateCompactionFilter(
|
||||
const CompactionFilter::Context& context) override {
|
||||
saved_context = context;
|
||||
std::unique_ptr<CompactionFilter> empty_filter;
|
||||
@@ -5440,6 +5438,69 @@ TEST_F(DBTest, AutomaticConflictsWithManualCompaction) {
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
TEST_F(DBTest, CompactFilesShouldTriggerAutoCompaction) {
|
||||
Options options = CurrentOptions();
|
||||
options.max_background_compactions = 1;
|
||||
options.level0_file_num_compaction_trigger = 4;
|
||||
options.level0_slowdown_writes_trigger = 36;
|
||||
options.level0_stop_writes_trigger = 36;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
// generate files for manual compaction
|
||||
Random rnd(301);
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
// put two keys to ensure no trivial move
|
||||
for (int j = 0; j < 2; ++j) {
|
||||
ASSERT_OK(Put(Key(j), RandomString(&rnd, 1024)));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
|
||||
rocksdb::ColumnFamilyMetaData cf_meta_data;
|
||||
db_->GetColumnFamilyMetaData(db_->DefaultColumnFamily(), &cf_meta_data);
|
||||
|
||||
std::vector<std::string> input_files;
|
||||
input_files.push_back(cf_meta_data.levels[0].files[0].name);
|
||||
|
||||
SyncPoint::GetInstance()->LoadDependency({
|
||||
{"CompactFilesImpl:0",
|
||||
"DBTest::CompactFilesShouldTriggerAutoCompaction:Begin"},
|
||||
{"DBTest::CompactFilesShouldTriggerAutoCompaction:End",
|
||||
"CompactFilesImpl:1"},
|
||||
});
|
||||
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
port::Thread manual_compaction_thread([&]() {
|
||||
auto s = db_->CompactFiles(CompactionOptions(),
|
||||
db_->DefaultColumnFamily(), input_files, 0);
|
||||
});
|
||||
|
||||
TEST_SYNC_POINT(
|
||||
"DBTest::CompactFilesShouldTriggerAutoCompaction:Begin");
|
||||
// generate enough files to trigger compaction
|
||||
for (int i = 0; i < 20; ++i) {
|
||||
for (int j = 0; j < 2; ++j) {
|
||||
ASSERT_OK(Put(Key(j), RandomString(&rnd, 1024)));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
db_->GetColumnFamilyMetaData(db_->DefaultColumnFamily(), &cf_meta_data);
|
||||
ASSERT_GT(cf_meta_data.levels[0].files.size(),
|
||||
options.level0_file_num_compaction_trigger);
|
||||
TEST_SYNC_POINT(
|
||||
"DBTest::CompactFilesShouldTriggerAutoCompaction:End");
|
||||
|
||||
manual_compaction_thread.join();
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
|
||||
db_->GetColumnFamilyMetaData(db_->DefaultColumnFamily(), &cf_meta_data);
|
||||
ASSERT_LE(cf_meta_data.levels[0].files.size(),
|
||||
options.level0_file_num_compaction_trigger);
|
||||
}
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
// Github issue #595
|
||||
// Large write batch with column families
|
||||
TEST_F(DBTest, LargeBatchWithColumnFamilies) {
|
||||
|
||||
+544
-71
@@ -30,7 +30,7 @@ class PrefixFullBloomWithReverseComparator
|
||||
public:
|
||||
PrefixFullBloomWithReverseComparator()
|
||||
: DBTestBase("/prefix_bloom_reverse") {}
|
||||
virtual void SetUp() override { if_cache_filter_ = GetParam(); }
|
||||
void SetUp() override { if_cache_filter_ = GetParam(); }
|
||||
bool if_cache_filter_;
|
||||
};
|
||||
|
||||
@@ -200,7 +200,7 @@ TEST_P(DBTestSharedWriteBufferAcrossCFs, SharedWriteBufferAcrossCFs) {
|
||||
|
||||
// The total soft write buffer size is about 105000
|
||||
std::shared_ptr<Cache> cache = NewLRUCache(4 * 1024 * 1024, 2);
|
||||
ASSERT_LT(cache->GetUsage(), 1024 * 1024);
|
||||
ASSERT_LT(cache->GetUsage(), 256 * 1024);
|
||||
|
||||
if (use_old_interface_) {
|
||||
options.db_write_buffer_size = 120000; // this is the real limit
|
||||
@@ -236,14 +236,14 @@ TEST_P(DBTestSharedWriteBufferAcrossCFs, SharedWriteBufferAcrossCFs) {
|
||||
|
||||
ASSERT_OK(Put(3, Key(1), DummyString(30000), wo));
|
||||
if (cost_cache_) {
|
||||
ASSERT_GE(cache->GetUsage(), 1024 * 1024);
|
||||
ASSERT_LE(cache->GetUsage(), 2 * 1024 * 1024);
|
||||
ASSERT_GE(cache->GetUsage(), 256 * 1024);
|
||||
ASSERT_LE(cache->GetUsage(), 2 * 256 * 1024);
|
||||
}
|
||||
wait_flush();
|
||||
ASSERT_OK(Put(0, Key(1), DummyString(60000), wo));
|
||||
if (cost_cache_) {
|
||||
ASSERT_GE(cache->GetUsage(), 1024 * 1024);
|
||||
ASSERT_LE(cache->GetUsage(), 2 * 1024 * 1024);
|
||||
ASSERT_GE(cache->GetUsage(), 256 * 1024);
|
||||
ASSERT_LE(cache->GetUsage(), 2 * 256 * 1024);
|
||||
}
|
||||
wait_flush();
|
||||
ASSERT_OK(Put(2, Key(1), DummyString(1), wo));
|
||||
@@ -339,11 +339,11 @@ TEST_P(DBTestSharedWriteBufferAcrossCFs, SharedWriteBufferAcrossCFs) {
|
||||
static_cast<uint64_t>(2));
|
||||
}
|
||||
if (cost_cache_) {
|
||||
ASSERT_GE(cache->GetUsage(), 1024 * 1024);
|
||||
ASSERT_GE(cache->GetUsage(), 256 * 1024);
|
||||
Close();
|
||||
options.write_buffer_manager.reset();
|
||||
last_options_.write_buffer_manager.reset();
|
||||
ASSERT_LT(cache->GetUsage(), 1024 * 1024);
|
||||
ASSERT_LT(cache->GetUsage(), 256 * 1024);
|
||||
}
|
||||
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
@@ -467,8 +467,8 @@ TEST_F(DBTest2, TestWriteBufferNoLimitWithCache) {
|
||||
Reopen(options);
|
||||
|
||||
ASSERT_OK(Put("foo", "bar"));
|
||||
// One dummy entry is 1MB.
|
||||
ASSERT_GT(cache->GetUsage(), 500000);
|
||||
// One dummy entry is 256KB.
|
||||
ASSERT_GT(cache->GetUsage(), 128000);
|
||||
}
|
||||
|
||||
namespace {
|
||||
@@ -515,9 +515,9 @@ TEST_F(DBTest2, WalFilterTest) {
|
||||
apply_option_at_record_index_(apply_option_for_record_index),
|
||||
current_record_index_(0) {}
|
||||
|
||||
virtual WalProcessingOption LogRecord(
|
||||
const WriteBatch& /*batch*/, WriteBatch* /*new_batch*/,
|
||||
bool* /*batch_changed*/) const override {
|
||||
WalProcessingOption LogRecord(const WriteBatch& /*batch*/,
|
||||
WriteBatch* /*new_batch*/,
|
||||
bool* /*batch_changed*/) const override {
|
||||
WalFilter::WalProcessingOption option_to_return;
|
||||
|
||||
if (current_record_index_ == apply_option_at_record_index_) {
|
||||
@@ -535,7 +535,7 @@ TEST_F(DBTest2, WalFilterTest) {
|
||||
return option_to_return;
|
||||
}
|
||||
|
||||
virtual const char* Name() const override { return "TestWalFilter"; }
|
||||
const char* Name() const override { return "TestWalFilter"; }
|
||||
};
|
||||
|
||||
// Create 3 batches with two keys each
|
||||
@@ -687,7 +687,7 @@ TEST_F(DBTest2, WalFilterTestWithChangeBatch) {
|
||||
: new_write_batch_(new_write_batch),
|
||||
num_keys_to_add_in_new_batch_(num_keys_to_add_in_new_batch),
|
||||
num_keys_added_(0) {}
|
||||
virtual void Put(const Slice& key, const Slice& value) override {
|
||||
void Put(const Slice& key, const Slice& value) override {
|
||||
if (num_keys_added_ < num_keys_to_add_in_new_batch_) {
|
||||
new_write_batch_->Put(key, value);
|
||||
++num_keys_added_;
|
||||
@@ -711,9 +711,9 @@ TEST_F(DBTest2, WalFilterTestWithChangeBatch) {
|
||||
num_keys_to_add_in_new_batch_(num_keys_to_add_in_new_batch),
|
||||
current_record_index_(0) {}
|
||||
|
||||
virtual WalProcessingOption LogRecord(const WriteBatch& batch,
|
||||
WriteBatch* new_batch,
|
||||
bool* batch_changed) const override {
|
||||
WalProcessingOption LogRecord(const WriteBatch& batch,
|
||||
WriteBatch* new_batch,
|
||||
bool* batch_changed) const override {
|
||||
if (current_record_index_ >= change_records_from_index_) {
|
||||
ChangeBatchHandler handler(new_batch, num_keys_to_add_in_new_batch_);
|
||||
batch.Iterate(&handler);
|
||||
@@ -729,9 +729,7 @@ TEST_F(DBTest2, WalFilterTestWithChangeBatch) {
|
||||
return WalProcessingOption::kContinueProcessing;
|
||||
}
|
||||
|
||||
virtual const char* Name() const override {
|
||||
return "TestWalFilterWithChangeBatch";
|
||||
}
|
||||
const char* Name() const override { return "TestWalFilterWithChangeBatch"; }
|
||||
};
|
||||
|
||||
std::vector<std::vector<std::string>> batch_keys(3);
|
||||
@@ -809,18 +807,17 @@ TEST_F(DBTest2, WalFilterTestWithChangeBatch) {
|
||||
TEST_F(DBTest2, WalFilterTestWithChangeBatchExtraKeys) {
|
||||
class TestWalFilterWithChangeBatchAddExtraKeys : public WalFilter {
|
||||
public:
|
||||
virtual WalProcessingOption LogRecord(const WriteBatch& batch,
|
||||
WriteBatch* new_batch,
|
||||
bool* batch_changed) const override {
|
||||
*new_batch = batch;
|
||||
new_batch->Put("key_extra", "value_extra");
|
||||
*batch_changed = true;
|
||||
return WalProcessingOption::kContinueProcessing;
|
||||
}
|
||||
WalProcessingOption LogRecord(const WriteBatch& batch, WriteBatch* new_batch,
|
||||
bool* batch_changed) const override {
|
||||
*new_batch = batch;
|
||||
new_batch->Put("key_extra", "value_extra");
|
||||
*batch_changed = true;
|
||||
return WalProcessingOption::kContinueProcessing;
|
||||
}
|
||||
|
||||
virtual const char* Name() const override {
|
||||
return "WalFilterTestWithChangeBatchExtraKeys";
|
||||
}
|
||||
const char* Name() const override {
|
||||
return "WalFilterTestWithChangeBatchExtraKeys";
|
||||
}
|
||||
};
|
||||
|
||||
std::vector<std::vector<std::string>> batch_keys(3);
|
||||
@@ -884,18 +881,19 @@ TEST_F(DBTest2, WalFilterTestWithColumnFamilies) {
|
||||
// for verification against the keys we expect.
|
||||
std::map<uint32_t, std::vector<std::string>> cf_wal_keys_;
|
||||
public:
|
||||
virtual void ColumnFamilyLogNumberMap(
|
||||
const std::map<uint32_t, uint64_t>& cf_lognumber_map,
|
||||
const std::map<std::string, uint32_t>& cf_name_id_map) override {
|
||||
cf_log_number_map_ = cf_lognumber_map;
|
||||
cf_name_id_map_ = cf_name_id_map;
|
||||
}
|
||||
void ColumnFamilyLogNumberMap(
|
||||
const std::map<uint32_t, uint64_t>& cf_lognumber_map,
|
||||
const std::map<std::string, uint32_t>& cf_name_id_map) override {
|
||||
cf_log_number_map_ = cf_lognumber_map;
|
||||
cf_name_id_map_ = cf_name_id_map;
|
||||
}
|
||||
|
||||
virtual WalProcessingOption LogRecordFound(
|
||||
unsigned long long log_number, const std::string& /*log_file_name*/,
|
||||
const WriteBatch& batch, WriteBatch* /*new_batch*/,
|
||||
bool* /*batch_changed*/) override {
|
||||
class LogRecordBatchHandler : public WriteBatch::Handler {
|
||||
WalProcessingOption LogRecordFound(unsigned long long log_number,
|
||||
const std::string& /*log_file_name*/,
|
||||
const WriteBatch& batch,
|
||||
WriteBatch* /*new_batch*/,
|
||||
bool* /*batch_changed*/) override {
|
||||
class LogRecordBatchHandler : public WriteBatch::Handler {
|
||||
private:
|
||||
const std::map<uint32_t, uint64_t> & cf_log_number_map_;
|
||||
std::map<uint32_t, std::vector<std::string>> & cf_wal_keys_;
|
||||
@@ -908,8 +906,8 @@ TEST_F(DBTest2, WalFilterTestWithColumnFamilies) {
|
||||
cf_wal_keys_(cf_wal_keys),
|
||||
log_number_(current_log_number){}
|
||||
|
||||
virtual Status PutCF(uint32_t column_family_id, const Slice& key,
|
||||
const Slice& /*value*/) override {
|
||||
Status PutCF(uint32_t column_family_id, const Slice& key,
|
||||
const Slice& /*value*/) override {
|
||||
auto it = cf_log_number_map_.find(column_family_id);
|
||||
assert(it != cf_log_number_map_.end());
|
||||
unsigned long long log_number_for_cf = it->second;
|
||||
@@ -927,11 +925,11 @@ TEST_F(DBTest2, WalFilterTestWithColumnFamilies) {
|
||||
batch.Iterate(&handler);
|
||||
|
||||
return WalProcessingOption::kContinueProcessing;
|
||||
}
|
||||
}
|
||||
|
||||
virtual const char* Name() const override {
|
||||
return "WalFilterTestWithColumnFamilies";
|
||||
}
|
||||
const char* Name() const override {
|
||||
return "WalFilterTestWithColumnFamilies";
|
||||
}
|
||||
|
||||
const std::map<uint32_t, std::vector<std::string>>& GetColumnFamilyKeys() {
|
||||
return cf_wal_keys_;
|
||||
@@ -1038,18 +1036,19 @@ TEST_F(DBTest2, WalFilterTestWithColumnFamilies) {
|
||||
ASSERT_TRUE(index == keys_cf.size());
|
||||
}
|
||||
|
||||
TEST_F(DBTest2, PresetCompressionDict) {
|
||||
// Temporarily disable it because the test is flaky.
|
||||
TEST_F(DBTest2, DISABLED_PresetCompressionDict) {
|
||||
// Verifies that compression ratio improves when dictionary is enabled, and
|
||||
// improves even further when the dictionary is trained by ZSTD.
|
||||
const size_t kBlockSizeBytes = 4 << 10;
|
||||
const size_t kL0FileBytes = 128 << 10;
|
||||
const size_t kApproxPerBlockOverheadBytes = 50;
|
||||
const int kNumL0Files = 5;
|
||||
const int kZstdTrainFactor = 16;
|
||||
|
||||
Options options;
|
||||
options.env = CurrentOptions().env; // Make sure to use any custom env that the test is configured with.
|
||||
options.allow_concurrent_memtable_write = false;
|
||||
options.arena_block_size = kBlockSizeBytes;
|
||||
options.compaction_style = kCompactionStyleUniversal;
|
||||
options.create_if_missing = true;
|
||||
options.disable_auto_compactions = true;
|
||||
options.level0_file_num_compaction_trigger = kNumL0Files;
|
||||
@@ -1091,16 +1090,15 @@ TEST_F(DBTest2, PresetCompressionDict) {
|
||||
options.compression_opts.zstd_max_train_bytes = 0;
|
||||
break;
|
||||
case 1:
|
||||
options.compression_opts.max_dict_bytes = kBlockSizeBytes;
|
||||
options.compression_opts.max_dict_bytes = 4 * kBlockSizeBytes;
|
||||
options.compression_opts.zstd_max_train_bytes = 0;
|
||||
break;
|
||||
case 2:
|
||||
if (compression_type != kZSTD) {
|
||||
continue;
|
||||
}
|
||||
options.compression_opts.max_dict_bytes = kBlockSizeBytes;
|
||||
options.compression_opts.zstd_max_train_bytes =
|
||||
kZstdTrainFactor * kBlockSizeBytes;
|
||||
options.compression_opts.max_dict_bytes = 4 * kBlockSizeBytes;
|
||||
options.compression_opts.zstd_max_train_bytes = kL0FileBytes;
|
||||
break;
|
||||
default:
|
||||
assert(false);
|
||||
@@ -1110,20 +1108,24 @@ TEST_F(DBTest2, PresetCompressionDict) {
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
Random rnd(301);
|
||||
std::string seq_data =
|
||||
RandomString(&rnd, kBlockSizeBytes - kApproxPerBlockOverheadBytes);
|
||||
std::string seq_datas[10];
|
||||
for (int j = 0; j < 10; ++j) {
|
||||
seq_datas[j] =
|
||||
RandomString(&rnd, kBlockSizeBytes - kApproxPerBlockOverheadBytes);
|
||||
}
|
||||
|
||||
ASSERT_EQ(0, NumTableFilesAtLevel(0, 1));
|
||||
for (int j = 0; j < kNumL0Files; ++j) {
|
||||
for (size_t k = 0; k < kL0FileBytes / kBlockSizeBytes + 1; ++k) {
|
||||
ASSERT_OK(Put(1, Key(static_cast<int>(
|
||||
j * (kL0FileBytes / kBlockSizeBytes) + k)),
|
||||
seq_data));
|
||||
auto key_num = j * (kL0FileBytes / kBlockSizeBytes) + k;
|
||||
ASSERT_OK(Put(1, Key(static_cast<int>(key_num)),
|
||||
seq_datas[(key_num / 10) % 10]));
|
||||
}
|
||||
dbfull()->TEST_WaitForFlushMemTable(handles_[1]);
|
||||
ASSERT_EQ(j + 1, NumTableFilesAtLevel(0, 1));
|
||||
}
|
||||
db_->CompactRange(CompactRangeOptions(), handles_[1], nullptr, nullptr);
|
||||
dbfull()->TEST_CompactRange(0, nullptr, nullptr, handles_[1],
|
||||
true /* disallow_trivial_move */);
|
||||
ASSERT_EQ(0, NumTableFilesAtLevel(0, 1));
|
||||
ASSERT_GT(NumTableFilesAtLevel(1, 1), 0);
|
||||
|
||||
@@ -1138,7 +1140,7 @@ TEST_F(DBTest2, PresetCompressionDict) {
|
||||
|
||||
for (size_t j = 0; j < kNumL0Files * (kL0FileBytes / kBlockSizeBytes);
|
||||
j++) {
|
||||
ASSERT_EQ(seq_data, Get(1, Key(static_cast<int>(j))));
|
||||
ASSERT_EQ(seq_datas[(j / 10) % 10], Get(1, Key(static_cast<int>(j))));
|
||||
}
|
||||
if (i) {
|
||||
ASSERT_GT(prev_out_bytes, out_bytes);
|
||||
@@ -1149,6 +1151,70 @@ TEST_F(DBTest2, PresetCompressionDict) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBTest2, PresetCompressionDictLocality) {
|
||||
if (!ZSTD_Supported()) {
|
||||
return;
|
||||
}
|
||||
// Verifies that compression dictionary is generated from local data. The
|
||||
// verification simply checks all output SSTs have different compression
|
||||
// dictionaries. We do not verify effectiveness as that'd likely be flaky in
|
||||
// the future.
|
||||
const int kNumEntriesPerFile = 1 << 10; // 1KB
|
||||
const int kNumBytesPerEntry = 1 << 10; // 1KB
|
||||
const int kNumFiles = 4;
|
||||
Options options = CurrentOptions();
|
||||
options.compression = kZSTD;
|
||||
options.compression_opts.max_dict_bytes = 1 << 14; // 16KB
|
||||
options.compression_opts.zstd_max_train_bytes = 1 << 18; // 256KB
|
||||
options.statistics = rocksdb::CreateDBStatistics();
|
||||
options.target_file_size_base = kNumEntriesPerFile * kNumBytesPerEntry;
|
||||
BlockBasedTableOptions table_options;
|
||||
table_options.cache_index_and_filter_blocks = true;
|
||||
options.table_factory.reset(new BlockBasedTableFactory(table_options));
|
||||
Reopen(options);
|
||||
|
||||
Random rnd(301);
|
||||
for (int i = 0; i < kNumFiles; ++i) {
|
||||
for (int j = 0; j < kNumEntriesPerFile; ++j) {
|
||||
ASSERT_OK(Put(Key(i * kNumEntriesPerFile + j),
|
||||
RandomString(&rnd, kNumBytesPerEntry)));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
MoveFilesToLevel(1);
|
||||
ASSERT_EQ(NumTableFilesAtLevel(1), i + 1);
|
||||
}
|
||||
|
||||
// Store all the dictionaries generated during a full compaction.
|
||||
std::vector<std::string> compression_dicts;
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"BlockBasedTableBuilder::WriteCompressionDictBlock:RawDict",
|
||||
[&](void* arg) {
|
||||
compression_dicts.emplace_back(static_cast<Slice*>(arg)->ToString());
|
||||
});
|
||||
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
||||
CompactRangeOptions compact_range_opts;
|
||||
compact_range_opts.bottommost_level_compaction =
|
||||
BottommostLevelCompaction::kForceOptimized;
|
||||
ASSERT_OK(db_->CompactRange(compact_range_opts, nullptr, nullptr));
|
||||
|
||||
// Dictionary compression should not be so good as to compress four totally
|
||||
// random files into one. If it does then there's probably something wrong
|
||||
// with the test.
|
||||
ASSERT_GT(NumTableFilesAtLevel(1), 1);
|
||||
|
||||
// Furthermore, there should be one compression dictionary generated per file.
|
||||
// And they should all be different from each other.
|
||||
ASSERT_EQ(NumTableFilesAtLevel(1),
|
||||
static_cast<int>(compression_dicts.size()));
|
||||
for (size_t i = 1; i < compression_dicts.size(); ++i) {
|
||||
std::string& a = compression_dicts[i - 1];
|
||||
std::string& b = compression_dicts[i];
|
||||
size_t alen = a.size();
|
||||
size_t blen = b.size();
|
||||
ASSERT_TRUE(alen != blen || memcmp(a.data(), b.data(), alen) != 0);
|
||||
}
|
||||
}
|
||||
|
||||
class CompactionCompressionListener : public EventListener {
|
||||
public:
|
||||
explicit CompactionCompressionListener(Options* db_options)
|
||||
@@ -1385,7 +1451,7 @@ class PinL0IndexAndFilterBlocksTest
|
||||
public testing::WithParamInterface<std::tuple<bool, bool>> {
|
||||
public:
|
||||
PinL0IndexAndFilterBlocksTest() : DBTestBase("/db_pin_l0_index_bloom_test") {}
|
||||
virtual void SetUp() override {
|
||||
void SetUp() override {
|
||||
infinite_max_files_ = std::get<0>(GetParam());
|
||||
disallow_preload_ = std::get<1>(GetParam());
|
||||
}
|
||||
@@ -1646,7 +1712,7 @@ TEST_F(DBTest2, MaxCompactionBytesTest) {
|
||||
GenerateNewRandomFile(&rnd);
|
||||
}
|
||||
CompactRangeOptions cro;
|
||||
cro.bottommost_level_compaction = BottommostLevelCompaction::kForce;
|
||||
cro.bottommost_level_compaction = BottommostLevelCompaction::kForceOptimized;
|
||||
ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr));
|
||||
ASSERT_EQ("0,0,8", FilesPerLevel(0));
|
||||
|
||||
@@ -1688,7 +1754,7 @@ class MockPersistentCache : public PersistentCache {
|
||||
"GetUniqueIdFromFile:FS_IOC_GETVERSION", UniqueIdCallback);
|
||||
}
|
||||
|
||||
virtual ~MockPersistentCache() {}
|
||||
~MockPersistentCache() override {}
|
||||
|
||||
PersistentCache::StatsType Stats() override {
|
||||
return PersistentCache::StatsType();
|
||||
@@ -1739,7 +1805,7 @@ class MockPersistentCache : public PersistentCache {
|
||||
#ifdef OS_LINUX
|
||||
// Make sure that in CPU time perf context counters, Env::NowCPUNanos()
|
||||
// is used, rather than Env::CPUNanos();
|
||||
TEST_F(DBTest2, TestPerfContextCpuTime) {
|
||||
TEST_F(DBTest2, TestPerfContextGetCpuTime) {
|
||||
// force resizing table cache so table handle is not preloaded so that
|
||||
// we can measure find_table_nanos during Get().
|
||||
dbfull()->TEST_table_cache()->SetCapacity(0);
|
||||
@@ -1770,6 +1836,91 @@ TEST_F(DBTest2, TestPerfContextCpuTime) {
|
||||
SetPerfLevel(PerfLevel::kDisable);
|
||||
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
|
||||
TEST_F(DBTest2, TestPerfContextIterCpuTime) {
|
||||
DestroyAndReopen(CurrentOptions());
|
||||
// force resizing table cache so table handle is not preloaded so that
|
||||
// we can measure find_table_nanos during iteration
|
||||
dbfull()->TEST_table_cache()->SetCapacity(0);
|
||||
|
||||
const size_t kNumEntries = 10;
|
||||
for (size_t i = 0; i < kNumEntries; ++i) {
|
||||
ASSERT_OK(Put("k" + ToString(i), "v" + ToString(i)));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
for (size_t i = 0; i < kNumEntries; ++i) {
|
||||
ASSERT_EQ("v" + ToString(i), Get("k" + ToString(i)));
|
||||
}
|
||||
std::string last_key = "k" + ToString(kNumEntries - 1);
|
||||
std::string last_value = "v" + ToString(kNumEntries - 1);
|
||||
env_->now_cpu_count_.store(0);
|
||||
|
||||
// CPU timing is not enabled with kEnableTimeExceptForMutex
|
||||
SetPerfLevel(PerfLevel::kEnableTimeExceptForMutex);
|
||||
Iterator* iter = db_->NewIterator(ReadOptions());
|
||||
iter->Seek("k0");
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ("v0", iter->value().ToString());
|
||||
iter->SeekForPrev(last_key);
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
iter->SeekToLast();
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ(last_value, iter->value().ToString());
|
||||
iter->SeekToFirst();
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ("v0", iter->value().ToString());
|
||||
ASSERT_EQ(0, get_perf_context()->iter_seek_cpu_nanos);
|
||||
iter->Next();
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ("v1", iter->value().ToString());
|
||||
ASSERT_EQ(0, get_perf_context()->iter_next_cpu_nanos);
|
||||
iter->Prev();
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ("v0", iter->value().ToString());
|
||||
ASSERT_EQ(0, get_perf_context()->iter_prev_cpu_nanos);
|
||||
ASSERT_EQ(0, env_->now_cpu_count_.load());
|
||||
delete iter;
|
||||
|
||||
uint64_t kDummyAddonTime = uint64_t{1000000000000};
|
||||
|
||||
// Add time to NowNanos() reading.
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"TableCache::FindTable:0",
|
||||
[&](void* /*arg*/) { env_->addon_time_.fetch_add(kDummyAddonTime); });
|
||||
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
SetPerfLevel(PerfLevel::kEnableTimeAndCPUTimeExceptForMutex);
|
||||
iter = db_->NewIterator(ReadOptions());
|
||||
iter->Seek("k0");
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ("v0", iter->value().ToString());
|
||||
iter->SeekForPrev(last_key);
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
iter->SeekToLast();
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ(last_value, iter->value().ToString());
|
||||
iter->SeekToFirst();
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ("v0", iter->value().ToString());
|
||||
ASSERT_GT(get_perf_context()->iter_seek_cpu_nanos, 0);
|
||||
ASSERT_LT(get_perf_context()->iter_seek_cpu_nanos, kDummyAddonTime);
|
||||
iter->Next();
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ("v1", iter->value().ToString());
|
||||
ASSERT_GT(get_perf_context()->iter_next_cpu_nanos, 0);
|
||||
ASSERT_LT(get_perf_context()->iter_next_cpu_nanos, kDummyAddonTime);
|
||||
iter->Prev();
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ("v0", iter->value().ToString());
|
||||
ASSERT_GT(get_perf_context()->iter_prev_cpu_nanos, 0);
|
||||
ASSERT_LT(get_perf_context()->iter_prev_cpu_nanos, kDummyAddonTime);
|
||||
ASSERT_GE(env_->now_cpu_count_.load(), 12);
|
||||
ASSERT_GT(get_perf_context()->find_table_nanos, kDummyAddonTime);
|
||||
|
||||
SetPerfLevel(PerfLevel::kDisable);
|
||||
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
||||
delete iter;
|
||||
}
|
||||
#endif // OS_LINUX
|
||||
|
||||
#ifndef OS_SOLARIS // GetUniqueIdFromFile is not implemented
|
||||
@@ -2152,7 +2303,7 @@ TEST_F(DBTest2, AutomaticCompactionOverlapManualCompaction) {
|
||||
// Run a manual compaction that will compact the 2 files in L2
|
||||
// into 1 file in L2
|
||||
cro.exclusive_manual_compaction = false;
|
||||
cro.bottommost_level_compaction = BottommostLevelCompaction::kForce;
|
||||
cro.bottommost_level_compaction = BottommostLevelCompaction::kForceOptimized;
|
||||
ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr));
|
||||
|
||||
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
||||
@@ -2226,7 +2377,7 @@ TEST_F(DBTest2, ManualCompactionOverlapManualCompaction) {
|
||||
// into 1 file in L1
|
||||
CompactRangeOptions cro;
|
||||
cro.exclusive_manual_compaction = false;
|
||||
cro.bottommost_level_compaction = BottommostLevelCompaction::kForce;
|
||||
cro.bottommost_level_compaction = BottommostLevelCompaction::kForceOptimized;
|
||||
ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr));
|
||||
bg_thread.join();
|
||||
|
||||
@@ -2245,6 +2396,41 @@ TEST_F(DBTest2, OptimizeForPointLookup) {
|
||||
ASSERT_EQ("v1", Get("foo"));
|
||||
}
|
||||
|
||||
TEST_F(DBTest2, OptimizeForSmallDB) {
|
||||
Options options = CurrentOptions();
|
||||
Close();
|
||||
options.OptimizeForSmallDb();
|
||||
|
||||
// Find the cache object
|
||||
ASSERT_EQ(std::string(BlockBasedTableFactory::kName),
|
||||
std::string(options.table_factory->Name()));
|
||||
BlockBasedTableOptions* table_options =
|
||||
reinterpret_cast<BlockBasedTableOptions*>(
|
||||
options.table_factory->GetOptions());
|
||||
ASSERT_TRUE(table_options != nullptr);
|
||||
std::shared_ptr<Cache> cache = table_options->block_cache;
|
||||
|
||||
ASSERT_EQ(0, cache->GetUsage());
|
||||
ASSERT_OK(DB::Open(options, dbname_, &db_));
|
||||
ASSERT_OK(Put("foo", "v1"));
|
||||
|
||||
// memtable size is costed to the block cache
|
||||
ASSERT_NE(0, cache->GetUsage());
|
||||
|
||||
ASSERT_EQ("v1", Get("foo"));
|
||||
Flush();
|
||||
|
||||
size_t prev_size = cache->GetUsage();
|
||||
// Remember block cache size, so that we can find that
|
||||
// it is filled after Get().
|
||||
// Use pinnable slice so that it can ping the block so that
|
||||
// when we check the size it is not evicted.
|
||||
PinnableSlice value;
|
||||
ASSERT_OK(db_->Get(ReadOptions(), db_->DefaultColumnFamily(), "foo", &value));
|
||||
ASSERT_GT(cache->GetUsage(), prev_size);
|
||||
value.Reset();
|
||||
}
|
||||
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
TEST_F(DBTest2, GetRaceFlush1) {
|
||||
@@ -2578,8 +2764,9 @@ TEST_F(DBTest2, ReadCallbackTest) {
|
||||
|
||||
class TestReadCallback : public ReadCallback {
|
||||
public:
|
||||
explicit TestReadCallback(SequenceNumber snapshot) : snapshot_(snapshot) {}
|
||||
virtual bool IsVisible(SequenceNumber seq) override {
|
||||
explicit TestReadCallback(SequenceNumber snapshot)
|
||||
: ReadCallback(snapshot), snapshot_(snapshot) {}
|
||||
bool IsVisibleFullCheck(SequenceNumber seq) override {
|
||||
return seq <= snapshot_;
|
||||
}
|
||||
|
||||
@@ -2723,6 +2910,8 @@ TEST_F(DBTest2, TraceAndReplay) {
|
||||
Random rnd(301);
|
||||
Iterator* single_iter = nullptr;
|
||||
|
||||
ASSERT_TRUE(db_->EndTrace().IsIOError());
|
||||
|
||||
std::string trace_filename = dbname_ + "/rocksdb.trace";
|
||||
std::unique_ptr<TraceWriter> trace_writer;
|
||||
ASSERT_OK(NewFileTraceWriter(env_, env_opts, trace_filename, &trace_writer));
|
||||
@@ -2882,6 +3071,248 @@ TEST_F(DBTest2, TraceWithLimit) {
|
||||
ASSERT_OK(DestroyDB(dbname2, options));
|
||||
}
|
||||
|
||||
TEST_F(DBTest2, TraceWithSampling) {
|
||||
Options options = CurrentOptions();
|
||||
ReadOptions ro;
|
||||
WriteOptions wo;
|
||||
TraceOptions trace_opts;
|
||||
EnvOptions env_opts;
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
Random rnd(301);
|
||||
|
||||
// test the trace file sampling options
|
||||
trace_opts.sampling_frequency = 2;
|
||||
std::string trace_filename = dbname_ + "/rocksdb.trace_sampling";
|
||||
std::unique_ptr<TraceWriter> trace_writer;
|
||||
ASSERT_OK(NewFileTraceWriter(env_, env_opts, trace_filename, &trace_writer));
|
||||
ASSERT_OK(db_->StartTrace(trace_opts, std::move(trace_writer)));
|
||||
ASSERT_OK(Put(0, "a", "1"));
|
||||
ASSERT_OK(Put(0, "b", "2"));
|
||||
ASSERT_OK(Put(0, "c", "3"));
|
||||
ASSERT_OK(Put(0, "d", "4"));
|
||||
ASSERT_OK(Put(0, "e", "5"));
|
||||
ASSERT_OK(db_->EndTrace());
|
||||
|
||||
std::string dbname2 = test::TmpDir(env_) + "/db_replay_sampling";
|
||||
std::string value;
|
||||
ASSERT_OK(DestroyDB(dbname2, options));
|
||||
|
||||
// Using a different name than db2, to pacify infer's use-after-lifetime
|
||||
// warnings (http://fbinfer.com).
|
||||
DB* db2_init = nullptr;
|
||||
options.create_if_missing = true;
|
||||
ASSERT_OK(DB::Open(options, dbname2, &db2_init));
|
||||
ColumnFamilyHandle* cf;
|
||||
ASSERT_OK(
|
||||
db2_init->CreateColumnFamily(ColumnFamilyOptions(), "pikachu", &cf));
|
||||
delete cf;
|
||||
delete db2_init;
|
||||
|
||||
DB* db2 = nullptr;
|
||||
std::vector<ColumnFamilyDescriptor> column_families;
|
||||
ColumnFamilyOptions cf_options;
|
||||
column_families.push_back(ColumnFamilyDescriptor("default", cf_options));
|
||||
column_families.push_back(
|
||||
ColumnFamilyDescriptor("pikachu", ColumnFamilyOptions()));
|
||||
std::vector<ColumnFamilyHandle*> handles;
|
||||
ASSERT_OK(DB::Open(DBOptions(), dbname2, column_families, &handles, &db2));
|
||||
|
||||
env_->SleepForMicroseconds(100);
|
||||
ASSERT_TRUE(db2->Get(ro, handles[0], "a", &value).IsNotFound());
|
||||
ASSERT_TRUE(db2->Get(ro, handles[0], "b", &value).IsNotFound());
|
||||
ASSERT_TRUE(db2->Get(ro, handles[0], "c", &value).IsNotFound());
|
||||
ASSERT_TRUE(db2->Get(ro, handles[0], "d", &value).IsNotFound());
|
||||
ASSERT_TRUE(db2->Get(ro, handles[0], "e", &value).IsNotFound());
|
||||
|
||||
std::unique_ptr<TraceReader> trace_reader;
|
||||
ASSERT_OK(NewFileTraceReader(env_, env_opts, trace_filename, &trace_reader));
|
||||
Replayer replayer(db2, handles_, std::move(trace_reader));
|
||||
ASSERT_OK(replayer.Replay());
|
||||
|
||||
ASSERT_TRUE(db2->Get(ro, handles[0], "a", &value).IsNotFound());
|
||||
ASSERT_FALSE(db2->Get(ro, handles[0], "b", &value).IsNotFound());
|
||||
ASSERT_TRUE(db2->Get(ro, handles[0], "c", &value).IsNotFound());
|
||||
ASSERT_FALSE(db2->Get(ro, handles[0], "d", &value).IsNotFound());
|
||||
ASSERT_TRUE(db2->Get(ro, handles[0], "e", &value).IsNotFound());
|
||||
|
||||
for (auto handle : handles) {
|
||||
delete handle;
|
||||
}
|
||||
delete db2;
|
||||
ASSERT_OK(DestroyDB(dbname2, options));
|
||||
}
|
||||
|
||||
TEST_F(DBTest2, TraceWithFilter) {
|
||||
Options options = CurrentOptions();
|
||||
options.merge_operator = MergeOperators::CreatePutOperator();
|
||||
ReadOptions ro;
|
||||
WriteOptions wo;
|
||||
TraceOptions trace_opts;
|
||||
EnvOptions env_opts;
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
Random rnd(301);
|
||||
Iterator* single_iter = nullptr;
|
||||
|
||||
trace_opts.filter = TraceFilterType::kTraceFilterWrite;
|
||||
|
||||
std::string trace_filename = dbname_ + "/rocksdb.trace";
|
||||
std::unique_ptr<TraceWriter> trace_writer;
|
||||
ASSERT_OK(NewFileTraceWriter(env_, env_opts, trace_filename, &trace_writer));
|
||||
ASSERT_OK(db_->StartTrace(trace_opts, std::move(trace_writer)));
|
||||
|
||||
ASSERT_OK(Put(0, "a", "1"));
|
||||
ASSERT_OK(Merge(0, "b", "2"));
|
||||
ASSERT_OK(Delete(0, "c"));
|
||||
ASSERT_OK(SingleDelete(0, "d"));
|
||||
ASSERT_OK(db_->DeleteRange(wo, dbfull()->DefaultColumnFamily(), "e", "f"));
|
||||
|
||||
WriteBatch batch;
|
||||
ASSERT_OK(batch.Put("f", "11"));
|
||||
ASSERT_OK(batch.Merge("g", "12"));
|
||||
ASSERT_OK(batch.Delete("h"));
|
||||
ASSERT_OK(batch.SingleDelete("i"));
|
||||
ASSERT_OK(batch.DeleteRange("j", "k"));
|
||||
ASSERT_OK(db_->Write(wo, &batch));
|
||||
|
||||
single_iter = db_->NewIterator(ro);
|
||||
single_iter->Seek("f");
|
||||
single_iter->SeekForPrev("g");
|
||||
delete single_iter;
|
||||
|
||||
ASSERT_EQ("1", Get(0, "a"));
|
||||
ASSERT_EQ("12", Get(0, "g"));
|
||||
|
||||
ASSERT_OK(Put(1, "foo", "bar"));
|
||||
ASSERT_OK(Put(1, "rocksdb", "rocks"));
|
||||
ASSERT_EQ("NOT_FOUND", Get(1, "leveldb"));
|
||||
|
||||
ASSERT_OK(db_->EndTrace());
|
||||
// These should not get into the trace file as it is after EndTrace.
|
||||
Put("hello", "world");
|
||||
Merge("foo", "bar");
|
||||
|
||||
// Open another db, replay, and verify the data
|
||||
std::string value;
|
||||
std::string dbname2 = test::TmpDir(env_) + "/db_replay";
|
||||
ASSERT_OK(DestroyDB(dbname2, options));
|
||||
|
||||
// Using a different name than db2, to pacify infer's use-after-lifetime
|
||||
// warnings (http://fbinfer.com).
|
||||
DB* db2_init = nullptr;
|
||||
options.create_if_missing = true;
|
||||
ASSERT_OK(DB::Open(options, dbname2, &db2_init));
|
||||
ColumnFamilyHandle* cf;
|
||||
ASSERT_OK(
|
||||
db2_init->CreateColumnFamily(ColumnFamilyOptions(), "pikachu", &cf));
|
||||
delete cf;
|
||||
delete db2_init;
|
||||
|
||||
DB* db2 = nullptr;
|
||||
std::vector<ColumnFamilyDescriptor> column_families;
|
||||
ColumnFamilyOptions cf_options;
|
||||
cf_options.merge_operator = MergeOperators::CreatePutOperator();
|
||||
column_families.push_back(ColumnFamilyDescriptor("default", cf_options));
|
||||
column_families.push_back(
|
||||
ColumnFamilyDescriptor("pikachu", ColumnFamilyOptions()));
|
||||
std::vector<ColumnFamilyHandle*> handles;
|
||||
ASSERT_OK(DB::Open(DBOptions(), dbname2, column_families, &handles, &db2));
|
||||
|
||||
env_->SleepForMicroseconds(100);
|
||||
// Verify that the keys don't already exist
|
||||
ASSERT_TRUE(db2->Get(ro, handles[0], "a", &value).IsNotFound());
|
||||
ASSERT_TRUE(db2->Get(ro, handles[0], "g", &value).IsNotFound());
|
||||
|
||||
std::unique_ptr<TraceReader> trace_reader;
|
||||
ASSERT_OK(NewFileTraceReader(env_, env_opts, trace_filename, &trace_reader));
|
||||
Replayer replayer(db2, handles_, std::move(trace_reader));
|
||||
ASSERT_OK(replayer.Replay());
|
||||
|
||||
// All the key-values should not present since we filter out the WRITE ops.
|
||||
ASSERT_TRUE(db2->Get(ro, handles[0], "a", &value).IsNotFound());
|
||||
ASSERT_TRUE(db2->Get(ro, handles[0], "g", &value).IsNotFound());
|
||||
ASSERT_TRUE(db2->Get(ro, handles[0], "hello", &value).IsNotFound());
|
||||
ASSERT_TRUE(db2->Get(ro, handles[0], "world", &value).IsNotFound());
|
||||
ASSERT_TRUE(db2->Get(ro, handles[0], "foo", &value).IsNotFound());
|
||||
ASSERT_TRUE(db2->Get(ro, handles[0], "rocksdb", &value).IsNotFound());
|
||||
|
||||
for (auto handle : handles) {
|
||||
delete handle;
|
||||
}
|
||||
delete db2;
|
||||
ASSERT_OK(DestroyDB(dbname2, options));
|
||||
|
||||
// Set up a new db.
|
||||
std::string dbname3 = test::TmpDir(env_) + "/db_not_trace_read";
|
||||
ASSERT_OK(DestroyDB(dbname3, options));
|
||||
|
||||
DB* db3_init = nullptr;
|
||||
options.create_if_missing = true;
|
||||
ColumnFamilyHandle* cf3;
|
||||
ASSERT_OK(DB::Open(options, dbname3, &db3_init));
|
||||
ASSERT_OK(
|
||||
db3_init->CreateColumnFamily(ColumnFamilyOptions(), "pikachu", &cf3));
|
||||
delete cf3;
|
||||
delete db3_init;
|
||||
|
||||
column_families.clear();
|
||||
column_families.push_back(ColumnFamilyDescriptor("default", cf_options));
|
||||
column_families.push_back(
|
||||
ColumnFamilyDescriptor("pikachu", ColumnFamilyOptions()));
|
||||
handles.clear();
|
||||
|
||||
DB* db3 = nullptr;
|
||||
ASSERT_OK(DB::Open(DBOptions(), dbname3, column_families, &handles, &db3));
|
||||
|
||||
env_->SleepForMicroseconds(100);
|
||||
// Verify that the keys don't already exist
|
||||
ASSERT_TRUE(db3->Get(ro, handles[0], "a", &value).IsNotFound());
|
||||
ASSERT_TRUE(db3->Get(ro, handles[0], "g", &value).IsNotFound());
|
||||
|
||||
//The tracer will not record the READ ops.
|
||||
trace_opts.filter = TraceFilterType::kTraceFilterGet;
|
||||
std::string trace_filename3 = dbname_ + "/rocksdb.trace_3";
|
||||
std::unique_ptr<TraceWriter> trace_writer3;
|
||||
ASSERT_OK(
|
||||
NewFileTraceWriter(env_, env_opts, trace_filename3, &trace_writer3));
|
||||
ASSERT_OK(db3->StartTrace(trace_opts, std::move(trace_writer3)));
|
||||
|
||||
ASSERT_OK(db3->Put(wo, handles[0], "a", "1"));
|
||||
ASSERT_OK(db3->Merge(wo, handles[0], "b", "2"));
|
||||
ASSERT_OK(db3->Delete(wo, handles[0], "c"));
|
||||
ASSERT_OK(db3->SingleDelete(wo, handles[0], "d"));
|
||||
|
||||
ASSERT_OK(db3->Get(ro, handles[0], "a", &value));
|
||||
ASSERT_EQ(value, "1");
|
||||
ASSERT_TRUE(db3->Get(ro, handles[0], "c", &value).IsNotFound());
|
||||
|
||||
ASSERT_OK(db3->EndTrace());
|
||||
|
||||
for (auto handle : handles) {
|
||||
delete handle;
|
||||
}
|
||||
delete db3;
|
||||
ASSERT_OK(DestroyDB(dbname3, options));
|
||||
|
||||
std::unique_ptr<TraceReader> trace_reader3;
|
||||
ASSERT_OK(
|
||||
NewFileTraceReader(env_, env_opts, trace_filename3, &trace_reader3));
|
||||
|
||||
// Count the number of records in the trace file;
|
||||
int count = 0;
|
||||
std::string data;
|
||||
Status s;
|
||||
while (true) {
|
||||
s = trace_reader3->Read(&data);
|
||||
if (!s.ok()) {
|
||||
break;
|
||||
}
|
||||
count += 1;
|
||||
}
|
||||
// We also need to count the header and footer
|
||||
// 4 WRITE + HEADER + FOOTER = 6
|
||||
ASSERT_EQ(count, 6);
|
||||
}
|
||||
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
TEST_F(DBTest2, PinnableSliceAndMmapReads) {
|
||||
@@ -3265,6 +3696,48 @@ TEST_F(DBTest2, MultiDBParallelOpenTest) {
|
||||
}
|
||||
#endif // OS_WIN
|
||||
|
||||
namespace {
|
||||
class DummyOldStats : public Statistics {
|
||||
public:
|
||||
uint64_t getTickerCount(uint32_t /*ticker_type*/) const override { return 0; }
|
||||
void recordTick(uint32_t /* ticker_type */, uint64_t /* count */) override {
|
||||
num_rt++;
|
||||
}
|
||||
void setTickerCount(uint32_t /*ticker_type*/, uint64_t /*count*/) override {}
|
||||
uint64_t getAndResetTickerCount(uint32_t /*ticker_type*/) override {
|
||||
return 0;
|
||||
}
|
||||
void measureTime(uint32_t /*histogram_type*/, uint64_t /*count*/) override {
|
||||
num_mt++;
|
||||
}
|
||||
void histogramData(uint32_t /*histogram_type*/,
|
||||
rocksdb::HistogramData* const /*data*/) const override {}
|
||||
std::string getHistogramString(uint32_t /*type*/) const override {
|
||||
return "";
|
||||
}
|
||||
bool HistEnabledForType(uint32_t /*type*/) const override { return false; }
|
||||
std::string ToString() const override { return ""; }
|
||||
int num_rt = 0;
|
||||
int num_mt = 0;
|
||||
};
|
||||
} // namespace
|
||||
|
||||
TEST_F(DBTest2, OldStatsInterface) {
|
||||
DummyOldStats* dos = new DummyOldStats();
|
||||
std::shared_ptr<Statistics> stats(dos);
|
||||
Options options = CurrentOptions();
|
||||
options.create_if_missing = true;
|
||||
options.statistics = stats;
|
||||
Reopen(options);
|
||||
|
||||
Put("foo", "bar");
|
||||
ASSERT_EQ("bar", Get("foo"));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_EQ("bar", Get("foo"));
|
||||
|
||||
ASSERT_GT(dos->num_rt, 0);
|
||||
ASSERT_GT(dos->num_mt, 0);
|
||||
}
|
||||
} // namespace rocksdb
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
+40
-20
@@ -101,18 +101,18 @@ DBTestBase::~DBTestBase() {
|
||||
bool DBTestBase::ShouldSkipOptions(int option_config, int skip_mask) {
|
||||
#ifdef ROCKSDB_LITE
|
||||
// These options are not supported in ROCKSDB_LITE
|
||||
if (option_config == kHashSkipList ||
|
||||
option_config == kPlainTableFirstBytePrefix ||
|
||||
option_config == kPlainTableCappedPrefix ||
|
||||
option_config == kPlainTableCappedPrefixNonMmap ||
|
||||
option_config == kPlainTableAllBytesPrefix ||
|
||||
option_config == kVectorRep || option_config == kHashLinkList ||
|
||||
option_config == kHashCuckoo || option_config == kUniversalCompaction ||
|
||||
option_config == kUniversalCompactionMultiLevel ||
|
||||
option_config == kUniversalSubcompactions ||
|
||||
option_config == kFIFOCompaction ||
|
||||
option_config == kConcurrentSkipList) {
|
||||
return true;
|
||||
if (option_config == kHashSkipList ||
|
||||
option_config == kPlainTableFirstBytePrefix ||
|
||||
option_config == kPlainTableCappedPrefix ||
|
||||
option_config == kPlainTableCappedPrefixNonMmap ||
|
||||
option_config == kPlainTableAllBytesPrefix ||
|
||||
option_config == kVectorRep || option_config == kHashLinkList ||
|
||||
option_config == kUniversalCompaction ||
|
||||
option_config == kUniversalCompactionMultiLevel ||
|
||||
option_config == kUniversalSubcompactions ||
|
||||
option_config == kFIFOCompaction ||
|
||||
option_config == kConcurrentSkipList) {
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -141,9 +141,6 @@ bool DBTestBase::ShouldSkipOptions(int option_config, int skip_mask) {
|
||||
option_config == kBlockBasedTableWithWholeKeyHashIndex)) {
|
||||
return true;
|
||||
}
|
||||
if ((skip_mask & kSkipHashCuckoo) && (option_config == kHashCuckoo)) {
|
||||
return true;
|
||||
}
|
||||
if ((skip_mask & kSkipFIFOCompaction) && option_config == kFIFOCompaction) {
|
||||
return true;
|
||||
}
|
||||
@@ -383,11 +380,6 @@ Options DBTestBase::GetOptions(
|
||||
NewHashLinkListRepFactory(4, 0, 3, true, 4));
|
||||
options.allow_concurrent_memtable_write = false;
|
||||
break;
|
||||
case kHashCuckoo:
|
||||
options.memtable_factory.reset(
|
||||
NewHashCuckooRepFactory(options.write_buffer_size));
|
||||
options.allow_concurrent_memtable_write = false;
|
||||
break;
|
||||
case kDirectIO: {
|
||||
options.use_direct_reads = true;
|
||||
options.use_direct_io_for_flush_and_compaction = true;
|
||||
@@ -788,6 +780,34 @@ std::vector<std::string> DBTestBase::MultiGet(std::vector<int> cfs,
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<std::string> DBTestBase::MultiGet(const std::vector<std::string>& k,
|
||||
const Snapshot* snapshot) {
|
||||
ReadOptions options;
|
||||
options.verify_checksums = true;
|
||||
options.snapshot = snapshot;
|
||||
std::vector<Slice> keys;
|
||||
std::vector<std::string> result;
|
||||
std::vector<Status> statuses(k.size());
|
||||
std::vector<PinnableSlice> pin_values(k.size());
|
||||
|
||||
for (unsigned int i = 0; i < k.size(); ++i) {
|
||||
keys.push_back(k[i]);
|
||||
}
|
||||
db_->MultiGet(options, dbfull()->DefaultColumnFamily(), keys.size(),
|
||||
keys.data(), pin_values.data(), statuses.data());
|
||||
result.resize(k.size());
|
||||
for (auto iter = result.begin(); iter != result.end(); ++iter) {
|
||||
iter->assign(pin_values[iter - result.begin()].data(),
|
||||
pin_values[iter - result.begin()].size());
|
||||
}
|
||||
for (unsigned int i = 0; i < statuses.size(); ++i) {
|
||||
if (statuses[i].IsNotFound()) {
|
||||
result[i] = "NOT_FOUND";
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
Status DBTestBase::Get(const std::string& k, PinnableSlice* v) {
|
||||
ReadOptions options;
|
||||
options.verify_checksums = true;
|
||||
|
||||
+31
-24
@@ -438,6 +438,12 @@ class SpecialEnv : public EnvWrapper {
|
||||
return s;
|
||||
}
|
||||
|
||||
virtual Status Prefetch(uint64_t offset, size_t n) override {
|
||||
Status s = target_->Prefetch(offset, n);
|
||||
*bytes_read_ += n;
|
||||
return s;
|
||||
}
|
||||
|
||||
private:
|
||||
std::unique_ptr<RandomAccessFile> target_;
|
||||
anon::AtomicCounter* counter_;
|
||||
@@ -652,29 +658,28 @@ class DBTestBase : public testing::Test {
|
||||
kPlainTableAllBytesPrefix = 6,
|
||||
kVectorRep = 7,
|
||||
kHashLinkList = 8,
|
||||
kHashCuckoo = 9,
|
||||
kMergePut = 10,
|
||||
kFilter = 11,
|
||||
kFullFilterWithNewTableReaderForCompactions = 12,
|
||||
kUncompressed = 13,
|
||||
kNumLevel_3 = 14,
|
||||
kDBLogDir = 15,
|
||||
kWalDirAndMmapReads = 16,
|
||||
kManifestFileSize = 17,
|
||||
kPerfOptions = 18,
|
||||
kHashSkipList = 19,
|
||||
kUniversalCompaction = 20,
|
||||
kUniversalCompactionMultiLevel = 21,
|
||||
kCompressedBlockCache = 22,
|
||||
kInfiniteMaxOpenFiles = 23,
|
||||
kxxHashChecksum = 24,
|
||||
kFIFOCompaction = 25,
|
||||
kOptimizeFiltersForHits = 26,
|
||||
kRowCache = 27,
|
||||
kRecycleLogFiles = 28,
|
||||
kConcurrentSkipList = 29,
|
||||
kPipelinedWrite = 30,
|
||||
kConcurrentWALWrites = 31,
|
||||
kMergePut = 9,
|
||||
kFilter = 10,
|
||||
kFullFilterWithNewTableReaderForCompactions = 11,
|
||||
kUncompressed = 12,
|
||||
kNumLevel_3 = 13,
|
||||
kDBLogDir = 14,
|
||||
kWalDirAndMmapReads = 15,
|
||||
kManifestFileSize = 16,
|
||||
kPerfOptions = 17,
|
||||
kHashSkipList = 18,
|
||||
kUniversalCompaction = 19,
|
||||
kUniversalCompactionMultiLevel = 20,
|
||||
kCompressedBlockCache = 21,
|
||||
kInfiniteMaxOpenFiles = 22,
|
||||
kxxHashChecksum = 23,
|
||||
kFIFOCompaction = 24,
|
||||
kOptimizeFiltersForHits = 25,
|
||||
kRowCache = 26,
|
||||
kRecycleLogFiles = 27,
|
||||
kConcurrentSkipList = 28,
|
||||
kPipelinedWrite = 29,
|
||||
kConcurrentWALWrites = 30,
|
||||
kDirectIO,
|
||||
kLevelSubcompactions,
|
||||
kBlockBasedTableWithIndexRestartInterval,
|
||||
@@ -710,7 +715,6 @@ class DBTestBase : public testing::Test {
|
||||
kSkipPlainTable = 8,
|
||||
kSkipHashIndex = 16,
|
||||
kSkipNoSeekToLast = 32,
|
||||
kSkipHashCuckoo = 64,
|
||||
kSkipFIFOCompaction = 128,
|
||||
kSkipMmapReads = 256,
|
||||
};
|
||||
@@ -844,6 +848,9 @@ class DBTestBase : public testing::Test {
|
||||
const std::vector<std::string>& k,
|
||||
const Snapshot* snapshot = nullptr);
|
||||
|
||||
std::vector<std::string> MultiGet(const std::vector<std::string>& k,
|
||||
const Snapshot* snapshot = nullptr);
|
||||
|
||||
uint64_t GetNumSnapshots();
|
||||
|
||||
uint64_t GetTimeOldestSnapshots();
|
||||
|
||||
@@ -27,7 +27,7 @@ class DBTestUniversalCompactionBase
|
||||
public:
|
||||
explicit DBTestUniversalCompactionBase(
|
||||
const std::string& path) : DBTestBase(path) {}
|
||||
virtual void SetUp() override {
|
||||
void SetUp() override {
|
||||
num_levels_ = std::get<0>(GetParam());
|
||||
exclusive_manual_compaction_ = std::get<1>(GetParam());
|
||||
}
|
||||
@@ -63,13 +63,13 @@ void VerifyCompactionResult(
|
||||
|
||||
class KeepFilter : public CompactionFilter {
|
||||
public:
|
||||
virtual bool Filter(int /*level*/, const Slice& /*key*/,
|
||||
const Slice& /*value*/, std::string* /*new_value*/,
|
||||
bool* /*value_changed*/) const override {
|
||||
bool Filter(int /*level*/, const Slice& /*key*/, const Slice& /*value*/,
|
||||
std::string* /*new_value*/,
|
||||
bool* /*value_changed*/) const override {
|
||||
return false;
|
||||
}
|
||||
|
||||
virtual const char* Name() const override { return "KeepFilter"; }
|
||||
const char* Name() const override { return "KeepFilter"; }
|
||||
};
|
||||
|
||||
class KeepFilterFactory : public CompactionFilterFactory {
|
||||
@@ -77,7 +77,7 @@ class KeepFilterFactory : public CompactionFilterFactory {
|
||||
explicit KeepFilterFactory(bool check_context = false)
|
||||
: check_context_(check_context) {}
|
||||
|
||||
virtual std::unique_ptr<CompactionFilter> CreateCompactionFilter(
|
||||
std::unique_ptr<CompactionFilter> CreateCompactionFilter(
|
||||
const CompactionFilter::Context& context) override {
|
||||
if (check_context_) {
|
||||
EXPECT_EQ(expect_full_compaction_.load(), context.is_full_compaction);
|
||||
@@ -86,7 +86,7 @@ class KeepFilterFactory : public CompactionFilterFactory {
|
||||
return std::unique_ptr<CompactionFilter>(new KeepFilter());
|
||||
}
|
||||
|
||||
virtual const char* Name() const override { return "KeepFilterFactory"; }
|
||||
const char* Name() const override { return "KeepFilterFactory"; }
|
||||
bool check_context_;
|
||||
std::atomic_bool expect_full_compaction_;
|
||||
std::atomic_bool expect_manual_compaction_;
|
||||
@@ -95,14 +95,14 @@ class KeepFilterFactory : public CompactionFilterFactory {
|
||||
class DelayFilter : public CompactionFilter {
|
||||
public:
|
||||
explicit DelayFilter(DBTestBase* d) : db_test(d) {}
|
||||
virtual bool Filter(int /*level*/, const Slice& /*key*/,
|
||||
const Slice& /*value*/, std::string* /*new_value*/,
|
||||
bool* /*value_changed*/) const override {
|
||||
bool Filter(int /*level*/, const Slice& /*key*/, const Slice& /*value*/,
|
||||
std::string* /*new_value*/,
|
||||
bool* /*value_changed*/) const override {
|
||||
db_test->env_->addon_time_.fetch_add(1000);
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual const char* Name() const override { return "DelayFilter"; }
|
||||
const char* Name() const override { return "DelayFilter"; }
|
||||
|
||||
private:
|
||||
DBTestBase* db_test;
|
||||
@@ -111,12 +111,12 @@ class DelayFilter : public CompactionFilter {
|
||||
class DelayFilterFactory : public CompactionFilterFactory {
|
||||
public:
|
||||
explicit DelayFilterFactory(DBTestBase* d) : db_test(d) {}
|
||||
virtual std::unique_ptr<CompactionFilter> CreateCompactionFilter(
|
||||
std::unique_ptr<CompactionFilter> CreateCompactionFilter(
|
||||
const CompactionFilter::Context& /*context*/) override {
|
||||
return std::unique_ptr<CompactionFilter>(new DelayFilter(db_test));
|
||||
}
|
||||
|
||||
virtual const char* Name() const override { return "DelayFilterFactory"; }
|
||||
const char* Name() const override { return "DelayFilterFactory"; }
|
||||
|
||||
private:
|
||||
DBTestBase* db_test;
|
||||
|
||||
@@ -140,6 +140,51 @@ TEST_P(DBWriteTest, IOErrorOnWALWriteTriggersReadOnlyMode) {
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_P(DBWriteTest, IOErrorOnSwitchMemtable) {
|
||||
Random rnd(301);
|
||||
std::unique_ptr<FaultInjectionTestEnv> mock_env(
|
||||
new FaultInjectionTestEnv(Env::Default()));
|
||||
Options options = GetOptions();
|
||||
options.env = mock_env.get();
|
||||
options.writable_file_max_buffer_size = 4 * 1024 * 1024;
|
||||
options.write_buffer_size = 3 * 512 * 1024;
|
||||
options.wal_bytes_per_sync = 256 * 1024;
|
||||
options.manual_wal_flush = true;
|
||||
Reopen(options);
|
||||
mock_env->SetFilesystemActive(false, Status::IOError("Not active"));
|
||||
Status s;
|
||||
for (int i = 0; i < 4 * 512; ++i) {
|
||||
s = Put(Key(i), RandomString(&rnd, 1024));
|
||||
if (!s.ok()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
ASSERT_EQ(s.severity(), Status::Severity::kFatalError);
|
||||
|
||||
mock_env->SetFilesystemActive(true);
|
||||
// Close before mock_env destruct.
|
||||
Close();
|
||||
}
|
||||
|
||||
// Test that db->LockWAL() flushes the WAL after locking.
|
||||
TEST_P(DBWriteTest, LockWalInEffect) {
|
||||
Options options = GetOptions();
|
||||
Reopen(options);
|
||||
// try the 1st WAL created during open
|
||||
ASSERT_OK(Put("key" + ToString(0), "value"));
|
||||
ASSERT_TRUE(options.manual_wal_flush != dbfull()->TEST_WALBufferIsEmpty());
|
||||
ASSERT_OK(dbfull()->LockWAL());
|
||||
ASSERT_TRUE(dbfull()->TEST_WALBufferIsEmpty(false));
|
||||
ASSERT_OK(dbfull()->UnlockWAL());
|
||||
// try the 2nd wal created during SwitchWAL
|
||||
dbfull()->TEST_SwitchWAL();
|
||||
ASSERT_OK(Put("key" + ToString(0), "value"));
|
||||
ASSERT_TRUE(options.manual_wal_flush != dbfull()->TEST_WALBufferIsEmpty());
|
||||
ASSERT_OK(dbfull()->LockWAL());
|
||||
ASSERT_TRUE(dbfull()->TEST_WALBufferIsEmpty(false));
|
||||
ASSERT_OK(dbfull()->UnlockWAL());
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(DBWriteTestInstance, DBWriteTest,
|
||||
testing::Values(DBTestBase::kDefault,
|
||||
DBTestBase::kConcurrentWALWrites,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user