mirror of
https://github.com/facebook/rocksdb.git
synced 2026-11-06 11:02:36 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4d57a393a8 | ||
|
|
224da7f494 | ||
|
|
9674b843c2 | ||
|
|
7448618ac8 | ||
|
|
52626f10b1 | ||
|
|
7c25388623 | ||
|
|
f72fd58565 | ||
|
|
633f069c29 | ||
|
|
0a73ada7b5 | ||
|
|
0c53b41856 | ||
|
|
4217d1bce7 | ||
|
|
c66b4429ff | ||
|
|
6d93b87588 | ||
|
|
f20b07cebb | ||
|
|
f053851af6 | ||
|
|
b234a3f569 | ||
|
|
0534393fc8 | ||
|
|
8fb3fe8d39 | ||
|
|
8d615a2b1d | ||
|
|
ad5325a736 | ||
|
|
aa21896880 | ||
|
|
f0841d4faf | ||
|
|
314de7e7de | ||
|
|
86cf7266c3 | ||
|
|
53a0ab2bea | ||
|
|
b4326b5273 | ||
|
|
908a999a72 | ||
|
|
9d66d6d13e | ||
|
|
3ffb3baa0b | ||
|
|
678ba5e41c | ||
|
|
ab6755711b | ||
|
|
fbb09cf7aa | ||
|
|
97b30dee5b | ||
|
|
c246c9c6e2 | ||
|
|
140db3c44e | ||
|
|
e5aa7deae1 | ||
|
|
4c277ab201 | ||
|
|
f5526af8ed | ||
|
|
ffc48b6cad | ||
|
|
37418105d0 | ||
|
|
4bfd415e34 | ||
|
|
b4e59a48fd | ||
|
|
7cc52cd8f5 | ||
|
|
22d4dc5066 | ||
|
|
e1139167ae | ||
|
|
1a79839c59 | ||
|
|
3e1bf771a3 | ||
|
|
a282eff3d1 | ||
|
|
ee239df351 | ||
|
|
6c3bf83d6f | ||
|
|
418831cf45 | ||
|
|
bcd049cd2d | ||
|
|
2f1296ef48 | ||
|
|
c0ec58ecb9 | ||
|
|
fcaa7ff638 | ||
|
|
8717c26823 | ||
|
|
b632ed0c67 | ||
|
|
699f45049d | ||
|
|
fe994bbd0b | ||
|
|
53e595d1f3 | ||
|
|
d16ceba687 | ||
|
|
a776406de3 | ||
|
|
787229837e | ||
|
|
2cdaf5ca5b | ||
|
|
84d71f30c4 | ||
|
|
e36b9da57c | ||
|
|
7f08a8503f | ||
|
|
e5bfb91d09 | ||
|
|
1953b63cdd | ||
|
|
532ff334d9 | ||
|
|
2acffecca1 | ||
|
|
61a63ae2f9 | ||
|
|
13ae16c315 | ||
|
|
559943cdc0 | ||
|
|
6b34eb0ebc | ||
|
|
d6bd1a0291 | ||
|
|
7fd68b7c39 | ||
|
|
b88109db19 | ||
|
|
345f4c9462 | ||
|
|
a2f29ce70a | ||
|
|
78afb4d81e | ||
|
|
0774d640c0 | ||
|
|
b59b7570cf | ||
|
|
e0f697d2bd | ||
|
|
b92cef2d1d | ||
|
|
6d424be910 | ||
|
|
791bff5b4e | ||
|
|
0595101c4f | ||
|
|
7c6a7e8fa8 | ||
|
|
b25f2afeff | ||
|
|
be206db351 | ||
|
|
c988e4720b | ||
|
|
58444eadda | ||
|
|
9320067703 | ||
|
|
fcce1f2c7a | ||
|
|
1de588668c | ||
|
|
5268cdc997 | ||
|
|
6924869867 | ||
|
|
d497cdfbb2 | ||
|
|
99fe4c5005 | ||
|
|
1c290c785d | ||
|
|
b512f4bc76 | ||
|
|
ba48ff8303 | ||
|
|
4149d044cd | ||
|
|
65411b8d4e | ||
|
|
272cc77751 | ||
|
|
d6aa8c49f8 | ||
|
|
4f1dd05cec | ||
|
|
b97c53b629 | ||
|
|
64ca0d9b46 | ||
|
|
f4a1d10668 | ||
|
|
2819c7840e | ||
|
|
82e7631de6 | ||
|
|
5c92aa38ea | ||
|
|
7743f033b1 | ||
|
|
8df334342e | ||
|
|
12d798ac06 | ||
|
|
d648cb47b9 | ||
|
|
e10e4162c8 | ||
|
|
34afdc2d3d | ||
|
|
0b6be7eb68 | ||
|
|
a5566d508b | ||
|
|
7bef598440 | ||
|
|
306b779957 | ||
|
|
9bb6cc7e78 | ||
|
|
dafa584fd1 | ||
|
|
2a2b3e03a5 | ||
|
|
5f40b05c98 | ||
|
|
bda8d93ba9 | ||
|
|
dc0dc90cf5 | ||
|
|
12542488ef | ||
|
|
0fb938c448 | ||
|
|
7e78d7c540 | ||
|
|
0aad4ca0ff | ||
|
|
6785135bc2 | ||
|
|
eea566864e | ||
|
|
45175ca2e1 | ||
|
|
dd092c2d11 | ||
|
|
91b95cadee | ||
|
|
0cb0fc6fd3 | ||
|
|
7aa8447728 | ||
|
|
beed86473a | ||
|
|
e40b04e9fa | ||
|
|
cb5b851ff8 | ||
|
|
9308ff366c | ||
|
|
6cca9fab7c | ||
|
|
941543721d | ||
|
|
0ef88538c6 | ||
|
|
4750421ece | ||
|
|
55ef8972fc | ||
|
|
e8eb02145a | ||
|
|
972e3400e1 | ||
|
|
0c942a9c0d | ||
|
|
361895ad79 | ||
|
|
e8a7001159 | ||
|
|
b9e9872819 | ||
|
|
71bbe79abd | ||
|
|
2e03abf9df | ||
|
|
af292cef27 | ||
|
|
ac48e0d589 | ||
|
|
c9cd5d25a8 | ||
|
|
32752551b9 | ||
|
|
f9ffeaed3f | ||
|
|
59eb4b35a6 | ||
|
|
2e09a54c4f | ||
|
|
f756448f28 | ||
|
|
35dd251187 | ||
|
|
240c4126fd | ||
|
|
7b55554605 | ||
|
|
ec9f52ece6 | ||
|
|
13ded69484 | ||
|
|
6c2bd28a61 | ||
|
|
2bc914094d | ||
|
|
7ddc096d7d | ||
|
|
22ecd7edc1 | ||
|
|
ebaa3c8a59 | ||
|
|
4afa24f8ae | ||
|
|
6e63e77af1 | ||
|
|
f484a60d1f | ||
|
|
d8eb824325 | ||
|
|
f235f4b0a3 | ||
|
|
785faf2d07 | ||
|
|
318fe6941a | ||
|
|
229350ef48 | ||
|
|
74cfe7db60 | ||
|
|
c521f22a1e | ||
|
|
f6437ea4d7 | ||
|
|
249b1078c9 | ||
|
|
1a5eb33d91 | ||
|
|
0637c8d36c | ||
|
|
2e062b2227 |
No files matched your search
+31
-11
@@ -5,9 +5,9 @@ orbs:
|
||||
slack: circleci/slack@3.4.2
|
||||
|
||||
aliases:
|
||||
- ¬ify-on-master-failure
|
||||
- ¬ify-on-main-failure
|
||||
fail_only: true
|
||||
only_for_branches: master
|
||||
only_for_branches: main
|
||||
|
||||
commands:
|
||||
install-pyenv-on-macos:
|
||||
@@ -60,7 +60,7 @@ commands:
|
||||
|
||||
post-steps:
|
||||
steps:
|
||||
- slack/status: *notify-on-master-failure
|
||||
- slack/status: *notify-on-main-failure
|
||||
- store_test_results: # store test result if there's any
|
||||
path: /tmp/test-results
|
||||
- store_artifacts: # store LOG for debugging if there's any
|
||||
@@ -98,6 +98,13 @@ commands:
|
||||
command: |
|
||||
sudo apt-get update -y && sudo apt-get install -y libbenchmark-dev
|
||||
|
||||
install-librados:
|
||||
steps:
|
||||
- run:
|
||||
name: Install librados
|
||||
command: |
|
||||
sudo apt-get update -y && sudo apt-get install -y librados-dev
|
||||
|
||||
upgrade-cmake:
|
||||
steps:
|
||||
- run:
|
||||
@@ -171,14 +178,15 @@ jobs:
|
||||
- run: make V=1 J=32 -j32 check | .circleci/cat_ignore_eagain
|
||||
- post-steps
|
||||
|
||||
build-linux-mem-env:
|
||||
build-linux-mem-env-librados:
|
||||
machine:
|
||||
image: ubuntu-1604:202104-01
|
||||
resource_class: 2xlarge
|
||||
steps:
|
||||
- pre-steps
|
||||
- install-gflags
|
||||
- run: MEM_ENV=1 make V=1 J=32 -j32 check | .circleci/cat_ignore_eagain
|
||||
- install-librados
|
||||
- run: MEM_ENV=1 ROCKSDB_USE_LIBRADOS=1 make V=1 J=32 -j32 check | .circleci/cat_ignore_eagain
|
||||
- post-steps
|
||||
|
||||
build-linux-encrypted-env:
|
||||
@@ -328,7 +336,7 @@ jobs:
|
||||
- run: (mkdir build && cd build && cmake -DWITH_GFLAGS=1 -DWITH_BENCHMARK=1 .. && make V=1 -j20 && ctest -j20 && make microbench) | .circleci/cat_ignore_eagain
|
||||
- post-steps
|
||||
|
||||
build-linux-unity:
|
||||
build-linux-unity-and-headers:
|
||||
docker: # executor type
|
||||
- image: gcc:latest
|
||||
resource_class: large
|
||||
@@ -336,6 +344,7 @@ jobs:
|
||||
- checkout # check out the code in the project directory
|
||||
- run: apt-get update -y && apt-get install -y libgflags-dev
|
||||
- run: TEST_TMPDIR=/dev/shm && make V=1 -j8 unity_test | .circleci/cat_ignore_eagain
|
||||
- run: make V=1 -j8 -k check-headers | .circleci/cat_ignore_eagain # could be moved to a different build
|
||||
- post-steps
|
||||
|
||||
build-linux-gcc-4_8-no_test_run:
|
||||
@@ -378,6 +387,16 @@ jobs:
|
||||
- run: CC=gcc-10 CXX=g++-10 V=1 SKIP_LINK=1 ROCKSDB_CXX_STANDARD=c++20 make -j16 all | .circleci/cat_ignore_eagain # Linking broken because libgflags compiled with newer ABI
|
||||
- post-steps
|
||||
|
||||
build-linux-gcc-11-no_test_run:
|
||||
machine:
|
||||
image: ubuntu-2004:202010-01
|
||||
resource_class: xlarge
|
||||
steps:
|
||||
- pre-steps
|
||||
- run: sudo apt-get update -y && sudo add-apt-repository -y ppa:ubuntu-toolchain-r/test && sudo apt-get install gcc-11 g++-11 libgflags-dev
|
||||
- run: CC=gcc-11 CXX=g++-11 V=1 SKIP_LINK=1 make -j16 all | .circleci/cat_ignore_eagain # Linking broken because libgflags compiled with newer ABI
|
||||
- post-steps
|
||||
|
||||
# This job is only to make sure the microbench tests are able to run, the benchmark result is not meaningful as the CI host is changing.
|
||||
build-linux-microbench:
|
||||
machine:
|
||||
@@ -697,9 +716,9 @@ workflows:
|
||||
jobs:
|
||||
- build-linux-cmake
|
||||
- build-linux-cmake-ubuntu-20
|
||||
build-linux-mem-env:
|
||||
build-linux-mem-env-librados:
|
||||
jobs:
|
||||
- build-linux-mem-env
|
||||
- build-linux-mem-env-librados
|
||||
build-linux-encrypted-env:
|
||||
jobs:
|
||||
- build-linux-encrypted-env
|
||||
@@ -730,9 +749,9 @@ workflows:
|
||||
build-linux-clang10-clang-analyze:
|
||||
jobs:
|
||||
- build-linux-clang10-clang-analyze
|
||||
build-linux-unity:
|
||||
build-linux-unity-and-headers:
|
||||
jobs:
|
||||
- build-linux-unity
|
||||
- build-linux-unity-and-headers
|
||||
build-windows-vs2019:
|
||||
jobs:
|
||||
- build-windows:
|
||||
@@ -778,6 +797,7 @@ workflows:
|
||||
- build-linux-gcc-8-no_test_run
|
||||
- build-linux-gcc-9-no_test_run
|
||||
- build-linux-gcc-10-cxx20-no_test_run
|
||||
- build-linux-gcc-11-no_test_run
|
||||
- build-linux-arm-cmake-no_test_run
|
||||
build-macos:
|
||||
jobs:
|
||||
@@ -801,7 +821,7 @@ workflows:
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
- main
|
||||
jobs:
|
||||
- build-format-compatible
|
||||
- build-linux-arm-test-full
|
||||
@@ -39,3 +39,6 @@ jobs:
|
||||
|
||||
- name: Compare buckify output
|
||||
run: make check-buck-targets
|
||||
|
||||
- name: Simple source code checks
|
||||
run: make check-sources
|
||||
@@ -624,6 +624,7 @@ find_package(Threads REQUIRED)
|
||||
set(SOURCES
|
||||
cache/cache.cc
|
||||
cache/cache_entry_roles.cc
|
||||
cache/cache_reservation_manager.cc
|
||||
cache/clock_cache.cc
|
||||
cache/lru_cache.cc
|
||||
cache/sharded_cache.cc
|
||||
@@ -712,6 +713,7 @@ set(SOURCES
|
||||
env/file_system_tracer.cc
|
||||
env/fs_remap.cc
|
||||
env/mock_env.cc
|
||||
env/unique_id_gen.cc
|
||||
file/delete_scheduler.cc
|
||||
file/file_prefetch_buffer.cc
|
||||
file/file_util.cc
|
||||
@@ -805,6 +807,7 @@ set(SOURCES
|
||||
table/table_factory.cc
|
||||
table/table_properties.cc
|
||||
table/two_level_iterator.cc
|
||||
table/unique_id.cc
|
||||
test_util/sync_point.cc
|
||||
test_util/sync_point_impl.cc
|
||||
test_util/testutil.cc
|
||||
@@ -834,6 +837,7 @@ set(SOURCES
|
||||
util/random.cc
|
||||
util/rate_limiter.cc
|
||||
util/ribbon_config.cc
|
||||
util/regex.cc
|
||||
util/slice.cc
|
||||
util/file_checksum_helper.cc
|
||||
util/status.cc
|
||||
@@ -848,6 +852,8 @@ set(SOURCES
|
||||
utilities/blob_db/blob_db_impl_filesnapshot.cc
|
||||
utilities/blob_db/blob_dump_tool.cc
|
||||
utilities/blob_db/blob_file.cc
|
||||
utilities/cache_dump_load.cc
|
||||
utilities/cache_dump_load_impl.cc
|
||||
utilities/cassandra/cassandra_compaction_filter.cc
|
||||
utilities/cassandra/format.cc
|
||||
utilities/cassandra/merge_operator.cc
|
||||
@@ -900,6 +906,7 @@ set(SOURCES
|
||||
utilities/transactions/write_unprepared_txn.cc
|
||||
utilities/transactions/write_unprepared_txn_db.cc
|
||||
utilities/ttl/db_ttl_impl.cc
|
||||
utilities/wal_filter.cc
|
||||
utilities/write_batch_with_index/write_batch_with_index.cc
|
||||
utilities/write_batch_with_index/write_batch_with_index_internal.cc)
|
||||
|
||||
@@ -1123,6 +1130,7 @@ if(WITH_TESTS)
|
||||
)
|
||||
if(WITH_ALL_TESTS)
|
||||
list(APPEND TESTS
|
||||
cache/cache_reservation_manager_test.cc
|
||||
cache/cache_test.cc
|
||||
cache/lru_cache_test.cc
|
||||
db/blob/blob_counting_iterator_test.cc
|
||||
|
||||
+93
-6
@@ -1,4 +1,91 @@
|
||||
# Rocksdb Change Log
|
||||
# Rocksdb Change Lo
|
||||
## 6.26.1 (2021-11-18)
|
||||
### Bug Fixes
|
||||
* Fix builds for some platforms.
|
||||
|
||||
## 6.26.0 (2021-10-20)
|
||||
### Bug Fixes
|
||||
* Fixes a bug in directed IO mode when calling MultiGet() for blobs in the same blob file. The bug is caused by not sorting the blob read requests by file offsets.
|
||||
* Fix the incorrect disabling of SST rate limited deletion when the WAL and DB are in different directories. Only WAL rate limited deletion should be disabled if its in a different directory.
|
||||
* Fix `DisableManualCompaction()` to cancel compactions even when they are waiting on automatic compactions to drain due to `CompactRangeOptions::exclusive_manual_compactions == true`.
|
||||
* Fix contract of `Env::ReopenWritableFile()` and `FileSystem::ReopenWritableFile()` to specify any existing file must not be deleted or truncated.
|
||||
* Fixed bug in calls to `IngestExternalFiles()` with files for multiple column families. The bug could have introduced a delay in ingested file keys becoming visible after `IngestExternalFiles()` returned. Furthermore, mutations to ingested file keys while they were invisible could have been dropped (not necessarily immediately).
|
||||
* Fixed a possible race condition impacting users of `WriteBufferManager` who constructed it with `allow_stall == true`. The race condition led to undefined behavior (in our experience, typically a process crash).
|
||||
* Fixed a bug where stalled writes would remain stalled forever after the user calls `WriteBufferManager::SetBufferSize()` with `new_size == 0` to dynamically disable memory limiting.
|
||||
* Make `DB::close()` thread-safe.
|
||||
* Fix a bug in atomic flush where one bg flush thread will wait forever for a preceding bg flush thread to commit its result to MANIFEST but encounters an error which is mapped to a soft error (DB not stopped).
|
||||
|
||||
### New Features
|
||||
* Print information about blob files when using "ldb list_live_files_metadata"
|
||||
* Provided support for SingleDelete with user defined timestamp.
|
||||
* Experimental new function DB::GetLiveFilesStorageInfo offers essentially a unified version of other functions like GetLiveFiles, GetLiveFilesChecksumInfo, and GetSortedWalFiles. Checkpoints and backups could show small behavioral changes and/or improved performance as they now use this new API.
|
||||
* Add remote compaction read/write bytes statistics: `REMOTE_COMPACT_READ_BYTES`, `REMOTE_COMPACT_WRITE_BYTES`.
|
||||
* Introduce an experimental feature to dump out the blocks from block cache and insert them to the secondary cache to reduce the cache warmup time (e.g., used while migrating DB instance). More information are in `class CacheDumper` and `CacheDumpedLoader` at `rocksdb/utilities/cache_dump_load.h` Note that, this feature is subject to the potential change in the future, it is still experimental.
|
||||
* Introduced a new BlobDB configuration option `blob_garbage_collection_force_threshold`, which can be used to trigger compactions targeting the SST files which reference the oldest blob files when the ratio of garbage in those blob files meets or exceeds the specified threshold. This can reduce space amplification with skewed workloads where the affected SST files might not otherwise get picked up for compaction.
|
||||
* Added EXPERIMENTAL support for table file (SST) unique identifiers that are stable and universally unique, available with new function `GetUniqueIdFromTableProperties`. Only SST files from RocksDB >= 6.24 support unique IDs.
|
||||
* Added `GetMapProperty()` support for "rocksdb.dbstats" (`DB::Properties::kDBStats`). As a map property, it includes DB-level internal stats accumulated over the DB's lifetime, such as user write related stats and uptime.
|
||||
|
||||
### Public API change
|
||||
* Made SystemClock extend the Customizable class and added a CreateFromString method. Implementations need to be registered with the ObjectRegistry and to implement a Name() method in order to be created via this method.
|
||||
* Made SliceTransform extend the Customizable class and added a CreateFromString method. Implementations need to be registered with the ObjectRegistry and to implement a Name() method in order to be created via this method. The Capped and Prefixed transform classes return a short name (no length); use GetId for the fully qualified name.
|
||||
* Made FileChecksumGenFactory, SstPartitionerFactory, TablePropertiesCollectorFactory, and WalFilter extend the Customizable class and added a CreateFromString method.
|
||||
* Some fields of SstFileMetaData are deprecated for compatibility with new base class FileStorageInfo.
|
||||
* Add `file_temperature` to `IngestExternalFileArg` such that when ingesting SST files, we are able to indicate the temperature of the this batch of files.
|
||||
* If `DB::Close()` failed with a non aborted status, calling `DB::Close()` again will return the original status instead of Status::OK.
|
||||
* Add CacheTier to advanced_options.h to describe the cache tier we used. Add a `lowest_used_cache_tier` option to `DBOptions` (immutable) and pass it to BlockBasedTableReader. By default it is `CacheTier::kNonVolatileBlockTier`, which means, we always use both block cache (kVolatileTier) and secondary cache (kNonVolatileBlockTier). By set it to `CacheTier::kVolatileTier`, the DB will not use the secondary cache.
|
||||
* Even when options.max_compaction_bytes is hit, compaction output files are only cut when it aligns with grandparent files' boundaries. options.max_compaction_bytes could be slightly violated with the change, but the violation is no more than one target SST file size, which is usually much smaller.
|
||||
|
||||
### Performance Improvements
|
||||
* Improved CPU efficiency of building block-based table (SST) files (#9039 and #9040).
|
||||
|
||||
### Java API Changes
|
||||
* Add Java API bindings for new integrated BlobDB options
|
||||
* `keyMayExist()` supports ByteBuffer.
|
||||
* Fix multiget throwing Null Pointer Exception for num of keys > 70k (https://github.com/facebook/rocksdb/issues/8039).
|
||||
|
||||
## 6.25.0 (2021-09-20)
|
||||
### Bug Fixes
|
||||
* Allow secondary instance to refresh iterator. Assign read seq after referencing SuperVersion.
|
||||
* Fixed a bug of secondary instance's last_sequence going backward, and reads on the secondary fail to see recent updates from the primary.
|
||||
* Fixed a bug that could lead to duplicate DB ID or DB session ID in POSIX environments without /proc/sys/kernel/random/uuid.
|
||||
* Fix a race in DumpStats() with column family destruction due to not taking a Ref on each entry while iterating the ColumnFamilySet.
|
||||
* Fix a race in item ref counting in LRUCache when promoting an item from the SecondaryCache.
|
||||
* Fix a race in BackupEngine if RateLimiter is reconfigured during concurrent Restore operations.
|
||||
* Fix a bug on POSIX in which failure to create a lock file (e.g. out of space) can prevent future LockFile attempts in the same process on the same file from succeeding.
|
||||
* Fix a bug that backup_rate_limiter and restore_rate_limiter in BackupEngine could not limit read rates.
|
||||
* Fix the implementation of `prepopulate_block_cache = kFlushOnly` to only apply to flushes rather than to all generated files.
|
||||
* Fix WAL log data corruption when using DBOptions.manual_wal_flush(true) and WriteOptions.sync(true) together. The sync WAL should work with locked log_write_mutex_.
|
||||
* Add checks for validity of the IO uring completion queue entries, and fail the BlockBasedTableReader MultiGet sub-batch if there's an invalid completion
|
||||
* Add an interface RocksDbIOUringEnable() that, if defined by the user, will allow them to enable/disable the use of IO uring by RocksDB
|
||||
* Fix the bug that when direct I/O is used and MultiRead() returns a short result, RandomAccessFileReader::MultiRead() still returns full size buffer, with returned short value together with some data in original buffer. This bug is unlikely cause incorrect results, because (1) since FileSystem layer is expected to retry on short result, returning short results is only possible when asking more bytes in the end of the file, which RocksDB doesn't do when using MultiRead(); (2) checksum is unlikely to match.
|
||||
|
||||
### New Features
|
||||
* RemoteCompaction's interface now includes `db_name`, `db_id`, `session_id`, which could help the user uniquely identify compaction job between db instances and sessions.
|
||||
* Added a ticker statistic, "rocksdb.verify_checksum.read.bytes", reporting how many bytes were read from file to serve `VerifyChecksum()` and `VerifyFileChecksums()` queries.
|
||||
* Added ticker statistics, "rocksdb.backup.read.bytes" and "rocksdb.backup.write.bytes", reporting how many bytes were read and written during backup.
|
||||
* Added properties for BlobDB: `rocksdb.num-blob-files`, `rocksdb.blob-stats`, `rocksdb.total-blob-file-size`, and `rocksdb.live-blob-file-size`. The existing property `rocksdb.estimate_live-data-size` was also extended to include live bytes residing in blob files.
|
||||
* Added two new RateLimiter IOPriorities: `Env::IO_USER`,`Env::IO_MID`. `Env::IO_USER` will have superior priority over all other RateLimiter IOPriorities without being subject to fair scheduling constraint.
|
||||
* `SstFileWriter` now supports `Put`s and `Delete`s with user-defined timestamps. Note that the ingestion logic itself is not timestamp-aware yet.
|
||||
* Allow a single write batch to include keys from multiple column families whose timestamps' formats can differ. For example, some column families may disable timestamp, while others enable timestamp.
|
||||
* Add compaction priority information in RemoteCompaction, which can be used to schedule high priority job first.
|
||||
* Added new callback APIs `OnBlobFileCreationStarted`,`OnBlobFileCreated`and `OnBlobFileDeleted` in `EventListener` class of listener.h. It notifies listeners during creation/deletion of individual blob files in Integrated BlobDB. It also log blob file creation finished event and deletion event in LOG file.
|
||||
* Batch blob read requests for `DB::MultiGet` using `MultiRead`.
|
||||
* Add support for fallback to local compaction, the user can return `CompactionServiceJobStatus::kUseLocal` to instruct RocksDB to run the compaction locally instead of waiting for the remote compaction result.
|
||||
* Add built-in rate limiter's implementation of `RateLimiter::GetTotalPendingRequest(int64_t* total_pending_requests, const Env::IOPriority pri)` for the total number of requests that are pending for bytes in the rate limiter.
|
||||
* Charge memory usage during data buffering, from which training samples are gathered for dictionary compression, to block cache. Unbuffering data can now be triggered if the block cache becomes full and `strict_capacity_limit=true` for the block cache, in addition to existing conditions that can trigger unbuffering.
|
||||
|
||||
### Public API change
|
||||
* Remove obsolete implementation details FullKey and ParseFullKey from public API
|
||||
* Change `SstFileMetaData::size` from `size_t` to `uint64_t`.
|
||||
* Made Statistics extend the Customizable class and added a CreateFromString method. Implementations of Statistics need to be registered with the ObjectRegistry and to implement a Name() method in order to be created via this method.
|
||||
* Extended `FlushJobInfo` and `CompactionJobInfo` in listener.h to provide information about the blob files generated by a flush/compaction and garbage collected during compaction in Integrated BlobDB. Added struct members `blob_file_addition_infos` and `blob_file_garbage_infos` that contain this information.
|
||||
* Extended parameter `output_file_names` of `CompactFiles` API to also include paths of the blob files generated by the compaction in Integrated BlobDB.
|
||||
* Most `BackupEngine` functions now return `IOStatus` instead of `Status`. Most existing code should be compatible with this change but some calls might need to be updated.
|
||||
* Add a new field `level_at_creation` in `TablePropertiesCollectorFactory::Context` to capture the level at creating the SST file (i.e, table), of which the properties are being collected.
|
||||
|
||||
### Miscellaneous
|
||||
* Add a paranoid check where in case FileSystem layer doesn't fill the buffer but returns succeed, checksum is unlikely to match even if buffer contains a previous block. The byte modified is not useful anyway, so it isn't expected to change any behavior when FileSystem is satisfying its contract.
|
||||
|
||||
## 6.24.0 (2021-08-20)
|
||||
### Bug Fixes
|
||||
* If the primary's CURRENT file is missing or inaccessible, the secondary instance should not hang repeatedly trying to switch to a new MANIFEST. It should instead return the error code encountered while accessing the file.
|
||||
@@ -9,7 +96,7 @@
|
||||
* Fixed a bug affecting the batched `MultiGet` API when used with keys spanning multiple column families and `sorted_input == false`.
|
||||
* Fixed a potential incorrect result in opt mode and assertion failures caused by releasing snapshot(s) during compaction.
|
||||
* Fixed passing of BlobFileCompletionCallback to Compaction job and Atomic flush job which was default paramter (nullptr). BlobFileCompletitionCallback is internal callback that manages addition of blob files to SSTFileManager.
|
||||
* Fixed MultiGet not updating the block_read_count and block_read_byte PerfContext counters
|
||||
* Fixed MultiGet not updating the block_read_count and block_read_byte PerfContext counters.
|
||||
|
||||
### New Features
|
||||
* Made the EventListener extend the Customizable class.
|
||||
@@ -20,15 +107,15 @@
|
||||
* Add a comment to suggest btrfs user to disable file preallocation by setting `options.allow_fallocate=false`.
|
||||
* Fast forward option in Trace replay changed to double type to allow replaying at a lower speed, by settings the value between 0 and 1. This option can be set via `ReplayOptions` in `Replayer::Replay()`, or via `--trace_replay_fast_forward` in db_bench.
|
||||
* Add property `LiveSstFilesSizeAtTemperature` to retrieve sst file size at different temperature.
|
||||
* Added a stat rocksdb.secondary.cache.hits
|
||||
* Added a PerfContext counter secondary_cache_hit_count
|
||||
* Added a stat rocksdb.secondary.cache.hits.
|
||||
* Added a PerfContext counter secondary_cache_hit_count.
|
||||
* The integrated BlobDB implementation now supports the tickers `BLOB_DB_BLOB_FILE_BYTES_READ`, `BLOB_DB_GC_NUM_KEYS_RELOCATED`, and `BLOB_DB_GC_BYTES_RELOCATED`, as well as the histograms `BLOB_DB_COMPRESSION_MICROS` and `BLOB_DB_DECOMPRESSION_MICROS`.
|
||||
* Added hybrid configuration of Ribbon filter and Bloom filter where some LSM levels use Ribbon for memory space efficiency and some use Bloom for speed. See NewRibbonFilterPolicy. This also changes the default behavior of NewRibbonFilterPolicy to use Bloom for flushes under Leveled and Universal compaction and Ribbon otherwise. The C API function `rocksdb_filterpolicy_create_ribbon` is unchanged but adds new `rocksdb_filterpolicy_create_ribbon_hybrid`.
|
||||
|
||||
## Public API change
|
||||
### Public API change
|
||||
* Added APIs to decode and replay trace file via Replayer class. Added `DB::NewDefaultReplayer()` to create a default Replayer instance. Added `TraceReader::Reset()` to restart reading a trace file. Created trace_record.h, trace_record_result.h and utilities/replayer.h files to access the decoded Trace records, replay them, and query the actual operation results.
|
||||
* Added Configurable::GetOptionsMap to the public API for use in creating new Customizable classes.
|
||||
* Generalized bits_per_key parameters in C API from int to double for greater configurability.
|
||||
* Generalized bits_per_key parameters in C API from int to double for greater configurability. Although this is a compatible change for existing C source code, anything depending on C API signatures, such as foreign function interfaces, will need to be updated.
|
||||
|
||||
### Performance Improvements
|
||||
* Try to avoid updating DBOptions if `SetDBOptions()` does not change any option value.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
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
|
||||
* Java - https://github.com/facebook/rocksdb/tree/main/java
|
||||
* Python
|
||||
* http://python-rocksdb.readthedocs.io/en/latest/
|
||||
* http://pyrocksdb.readthedocs.org/en/latest/ (unmaintained)
|
||||
|
||||
@@ -222,6 +222,7 @@ am__v_AR_1 =
|
||||
|
||||
ifdef ROCKSDB_USE_LIBRADOS
|
||||
LIB_SOURCES += utilities/env_librados.cc
|
||||
TEST_MAIN_SOURCES += utilities/env_librados_test.cc
|
||||
LDFLAGS += -lrados
|
||||
endif
|
||||
|
||||
@@ -516,6 +517,29 @@ ifeq ($(USE_FOLLY_DISTRIBUTED_MUTEX),1)
|
||||
ALL_SOURCES += third-party/folly/folly/synchronization/test/DistributedMutexTest.cc
|
||||
endif
|
||||
|
||||
# `make check-headers` to very that each header file includes its own
|
||||
# dependencies
|
||||
ifneq ($(filter check-headers, $(MAKECMDGOALS)),)
|
||||
# TODO: add/support JNI headers
|
||||
DEV_HEADER_DIRS := $(sort include/ hdfs/ $(dir $(ALL_SOURCES)))
|
||||
# Some headers like in port/ are platform-specific
|
||||
DEV_HEADERS := $(shell $(FIND) $(DEV_HEADER_DIRS) -type f -name '*.h' | egrep -v 'port/|plugin/|lua/|range_tree/|tools/rdb/db_wrapper.h|include/rocksdb/utilities/env_librados.h')
|
||||
else
|
||||
DEV_HEADERS :=
|
||||
endif
|
||||
HEADER_OK_FILES = $(patsubst %.h, %.h.ok, $(DEV_HEADERS))
|
||||
|
||||
AM_V_CCH = $(am__v_CCH_$(V))
|
||||
am__v_CCH_ = $(am__v_CCH_$(AM_DEFAULT_VERBOSITY))
|
||||
am__v_CCH_0 = @echo " CC.h " $<;
|
||||
am__v_CCH_1 =
|
||||
|
||||
%.h.ok: %.h # .h.ok not actually created, so re-checked on each invocation
|
||||
# -DROCKSDB_NAMESPACE=42 ensures the namespace header is included
|
||||
$(AM_V_CCH) echo '#include "$<"' | $(CXX) $(CXXFLAGS) -DROCKSDB_NAMESPACE=42 -x c++ -c - -o /dev/null
|
||||
|
||||
check-headers: $(HEADER_OK_FILES)
|
||||
|
||||
# options_settable_test doesn't pass with UBSAN as we use hack in the test
|
||||
ifdef COMPILE_WITH_UBSAN
|
||||
TESTS := $(shell echo $(TESTS) | sed 's/\boptions_settable_test\b//g')
|
||||
@@ -716,7 +740,7 @@ 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 gen-pc install install-static install-shared uninstall \
|
||||
analyze tools tools_lib \
|
||||
analyze tools tools_lib check-headers \
|
||||
blackbox_crash_test_with_atomic_flush whitebox_crash_test_with_atomic_flush \
|
||||
blackbox_crash_test_with_txn whitebox_crash_test_with_txn \
|
||||
blackbox_crash_test_with_best_efforts_recovery \
|
||||
@@ -943,6 +967,7 @@ endif
|
||||
ifndef SKIP_FORMAT_BUCK_CHECKS
|
||||
$(MAKE) check-format
|
||||
$(MAKE) check-buck-targets
|
||||
$(MAKE) check-sources
|
||||
endif
|
||||
|
||||
# TODO add ldb_tests
|
||||
@@ -1220,6 +1245,9 @@ check-format:
|
||||
check-buck-targets:
|
||||
buckifier/check_buck_targets.sh
|
||||
|
||||
check-sources:
|
||||
build_tools/check-sources.sh
|
||||
|
||||
package:
|
||||
bash build_tools/make_package.sh $(SHARED_MAJOR).$(SHARED_MINOR)
|
||||
|
||||
@@ -1898,6 +1926,8 @@ clipping_iterator_test: $(OBJ_DIR)/db/compaction/clipping_iterator_test.o $(TEST
|
||||
ribbon_bench: $(OBJ_DIR)/microbench/ribbon_bench.o $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
cache_reservation_manager_test: $(OBJ_DIR)/cache/cache_reservation_manager_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
#-------------------------------------------------
|
||||
# make install related stuff
|
||||
PREFIX ?= /usr/local
|
||||
@@ -2005,7 +2035,7 @@ ZLIB_SHA256 ?= c3e5e9fdd5004dcb542feda5ee4f0ff0744628baf8ed2dd5d66f8ca1197cb1a1
|
||||
ZLIB_DOWNLOAD_BASE ?= http://zlib.net
|
||||
BZIP2_VER ?= 1.0.8
|
||||
BZIP2_SHA256 ?= ab5a03176ee106d3f0fa90e381da478ddae405918153cca248e682cd0c4a2269
|
||||
BZIP2_DOWNLOAD_BASE ?= https://sourceware.org/pub/bzip2
|
||||
BZIP2_DOWNLOAD_BASE ?= http://sourceware.org/pub/bzip2
|
||||
SNAPPY_VER ?= 1.1.8
|
||||
SNAPPY_SHA256 ?= 16b677f07832a612b0836178db7f374e414f94657c138e6993cbfc5dcc58651f
|
||||
SNAPPY_DOWNLOAD_BASE ?= https://github.com/google/snappy/archive
|
||||
@@ -2362,7 +2392,7 @@ build_subset_tests: $(ROCKSDBTESTS_SUBSET)
|
||||
|
||||
# Remove the rules for which dependencies should not be generated and see if any are left.
|
||||
#If so, include the dependencies; if not, do not include the dependency files
|
||||
ROCKS_DEP_RULES=$(filter-out clean format check-format check-buck-targets jclean jtest package analyze tags rocksdbjavastatic% unity.% unity_test, $(MAKECMDGOALS))
|
||||
ROCKS_DEP_RULES=$(filter-out clean format check-format check-buck-targets check-headers check-sources jclean jtest package analyze tags rocksdbjavastatic% unity.% unity_test, $(MAKECMDGOALS))
|
||||
ifneq ("$(ROCKS_DEP_RULES)", "")
|
||||
-include $(DEPFILES)
|
||||
endif
|
||||
@@ -1,8 +1,8 @@
|
||||
## RocksDB: A Persistent Key-Value Store for Flash and RAM Storage
|
||||
|
||||
[](https://circleci.com/gh/facebook/rocksdb)
|
||||
[](https://travis-ci.org/facebook/rocksdb)
|
||||
[](https://ci.appveyor.com/project/Facebook/rocksdb/branch/master)
|
||||
[](https://travis-ci.com/github/facebook/rocksdb)
|
||||
[](https://ci.appveyor.com/project/Facebook/rocksdb/branch/main)
|
||||
[](http://140-211-168-68-openstack.osuosl.org:8080/job/rocksdb)
|
||||
|
||||
RocksDB is developed and maintained by Facebook Database Engineering Team.
|
||||
@@ -17,7 +17,7 @@ and Space-Amplification-Factor (SAF). It has multi-threaded compactions,
|
||||
making it especially suitable for storing multiple terabytes of data in a
|
||||
single database.
|
||||
|
||||
Start with example usage here: https://github.com/facebook/rocksdb/tree/master/examples
|
||||
Start with example usage here: https://github.com/facebook/rocksdb/tree/main/examples
|
||||
|
||||
See the [github wiki](https://github.com/facebook/rocksdb/wiki) for more explanation.
|
||||
|
||||
|
||||
@@ -133,6 +133,7 @@ cpp_library(
|
||||
srcs = [
|
||||
"cache/cache.cc",
|
||||
"cache/cache_entry_roles.cc",
|
||||
"cache/cache_reservation_manager.cc",
|
||||
"cache/clock_cache.cc",
|
||||
"cache/lru_cache.cc",
|
||||
"cache/sharded_cache.cc",
|
||||
@@ -224,6 +225,7 @@ cpp_library(
|
||||
"env/fs_remap.cc",
|
||||
"env/io_posix.cc",
|
||||
"env/mock_env.cc",
|
||||
"env/unique_id_gen.cc",
|
||||
"file/delete_scheduler.cc",
|
||||
"file/file_prefetch_buffer.cc",
|
||||
"file/file_util.cc",
|
||||
@@ -325,6 +327,7 @@ cpp_library(
|
||||
"table/table_factory.cc",
|
||||
"table/table_properties.cc",
|
||||
"table/two_level_iterator.cc",
|
||||
"table/unique_id.cc",
|
||||
"test_util/sync_point.cc",
|
||||
"test_util/sync_point_impl.cc",
|
||||
"test_util/transaction_test_util.cc",
|
||||
@@ -353,6 +356,7 @@ cpp_library(
|
||||
"util/murmurhash.cc",
|
||||
"util/random.cc",
|
||||
"util/rate_limiter.cc",
|
||||
"util/regex.cc",
|
||||
"util/ribbon_config.cc",
|
||||
"util/slice.cc",
|
||||
"util/status.cc",
|
||||
@@ -367,6 +371,8 @@ cpp_library(
|
||||
"utilities/blob_db/blob_db_impl_filesnapshot.cc",
|
||||
"utilities/blob_db/blob_dump_tool.cc",
|
||||
"utilities/blob_db/blob_file.cc",
|
||||
"utilities/cache_dump_load.cc",
|
||||
"utilities/cache_dump_load_impl.cc",
|
||||
"utilities/cassandra/cassandra_compaction_filter.cc",
|
||||
"utilities/cassandra/format.cc",
|
||||
"utilities/cassandra/merge_operator.cc",
|
||||
@@ -432,6 +438,7 @@ cpp_library(
|
||||
"utilities/transactions/write_unprepared_txn.cc",
|
||||
"utilities/transactions/write_unprepared_txn_db.cc",
|
||||
"utilities/ttl/db_ttl_impl.cc",
|
||||
"utilities/wal_filter.cc",
|
||||
"utilities/write_batch_with_index/write_batch_with_index.cc",
|
||||
"utilities/write_batch_with_index/write_batch_with_index_internal.cc",
|
||||
],
|
||||
@@ -452,6 +459,7 @@ cpp_library(
|
||||
srcs = [
|
||||
"cache/cache.cc",
|
||||
"cache/cache_entry_roles.cc",
|
||||
"cache/cache_reservation_manager.cc",
|
||||
"cache/clock_cache.cc",
|
||||
"cache/lru_cache.cc",
|
||||
"cache/sharded_cache.cc",
|
||||
@@ -543,6 +551,7 @@ cpp_library(
|
||||
"env/fs_remap.cc",
|
||||
"env/io_posix.cc",
|
||||
"env/mock_env.cc",
|
||||
"env/unique_id_gen.cc",
|
||||
"file/delete_scheduler.cc",
|
||||
"file/file_prefetch_buffer.cc",
|
||||
"file/file_util.cc",
|
||||
@@ -644,6 +653,7 @@ cpp_library(
|
||||
"table/table_factory.cc",
|
||||
"table/table_properties.cc",
|
||||
"table/two_level_iterator.cc",
|
||||
"table/unique_id.cc",
|
||||
"test_util/sync_point.cc",
|
||||
"test_util/sync_point_impl.cc",
|
||||
"test_util/transaction_test_util.cc",
|
||||
@@ -672,6 +682,7 @@ cpp_library(
|
||||
"util/murmurhash.cc",
|
||||
"util/random.cc",
|
||||
"util/rate_limiter.cc",
|
||||
"util/regex.cc",
|
||||
"util/ribbon_config.cc",
|
||||
"util/slice.cc",
|
||||
"util/status.cc",
|
||||
@@ -686,6 +697,8 @@ cpp_library(
|
||||
"utilities/blob_db/blob_db_impl_filesnapshot.cc",
|
||||
"utilities/blob_db/blob_dump_tool.cc",
|
||||
"utilities/blob_db/blob_file.cc",
|
||||
"utilities/cache_dump_load.cc",
|
||||
"utilities/cache_dump_load_impl.cc",
|
||||
"utilities/cassandra/cassandra_compaction_filter.cc",
|
||||
"utilities/cassandra/format.cc",
|
||||
"utilities/cassandra/merge_operator.cc",
|
||||
@@ -751,6 +764,7 @@ cpp_library(
|
||||
"utilities/transactions/write_unprepared_txn.cc",
|
||||
"utilities/transactions/write_unprepared_txn_db.cc",
|
||||
"utilities/ttl/db_ttl_impl.cc",
|
||||
"utilities/wal_filter.cc",
|
||||
"utilities/write_batch_with_index/write_batch_with_index.cc",
|
||||
"utilities/write_batch_with_index/write_batch_with_index_internal.cc",
|
||||
],
|
||||
@@ -836,9 +850,11 @@ cpp_library(
|
||||
"db_stress_tool/db_stress_common.cc",
|
||||
"db_stress_tool/db_stress_driver.cc",
|
||||
"db_stress_tool/db_stress_gflags.cc",
|
||||
"db_stress_tool/db_stress_listener.cc",
|
||||
"db_stress_tool/db_stress_shared_state.cc",
|
||||
"db_stress_tool/db_stress_test_base.cc",
|
||||
"db_stress_tool/db_stress_tool.cc",
|
||||
"db_stress_tool/expected_state.cc",
|
||||
"db_stress_tool/no_batched_ops_stress.cc",
|
||||
"test_util/testutil.cc",
|
||||
"tools/block_cache_analyzer/block_cache_trace_analyzer.cc",
|
||||
@@ -1025,6 +1041,13 @@ ROCKS_TESTS = [
|
||||
[],
|
||||
[],
|
||||
],
|
||||
[
|
||||
"cache_reservation_manager_test",
|
||||
"cache/cache_reservation_manager_test.cc",
|
||||
"parallel",
|
||||
[],
|
||||
[],
|
||||
],
|
||||
[
|
||||
"cache_simulator_test",
|
||||
"utilities/simulator_cache/cache_simulator_test.cc",
|
||||
|
||||
@@ -113,5 +113,14 @@ LzLabs is using RocksDB as a storage engine in their multi-database distributed
|
||||
[YugabyteDB](https://www.yugabyte.com/) is an open source, high performance, distributed SQL database that uses RocksDB as its storage layer. For more information, please see https://github.com/yugabyte/yugabyte-db/.
|
||||
|
||||
## ArangoDB
|
||||
[ArangoDB](https://www.arangodb.com/) is a native multi-model database with flexible data models for documents, graphs, and key-values, for building high performance applications using a convenient SQL-like query language or JavaScript extensions. It uses RocksDB as its sotrage engine.
|
||||
[ArangoDB](https://www.arangodb.com/) is a native multi-model database with flexible data models for documents, graphs, and key-values, for building high performance applications using a convenient SQL-like query language or JavaScript extensions. It uses RocksDB as its storage engine.
|
||||
|
||||
## Milvus
|
||||
[Milvus](https://milvus.io/) is an open source vector database for unstructured data. It uses RocksDB not only as one of the supported kv storage engines, but also as a message queue.
|
||||
|
||||
## Kafka
|
||||
[Kafka](https://kafka.apache.org/) is an open-source distributed event streaming platform, it uses RocksDB to store state in Kafka Streams: https://www.confluent.io/blog/how-to-tune-rocksdb-kafka-streams-state-stores-performance/.
|
||||
|
||||
## Others
|
||||
More databases using RocksDB can be found at [dbdb.io](https://dbdb.io/browse?embeds=rocksdb).
|
||||
|
||||
+2
-2
@@ -24,7 +24,7 @@ We strive to achieve the following goals:
|
||||
* make all unit test pass both in debug and release builds.
|
||||
* Note: latest introduction of SyncPoint seems to disable running db_test in Release.
|
||||
* make performance on par with published benchmarks accounting for HW differences
|
||||
* we would like to keep the port code inline with the master branch with no forking
|
||||
* we would like to keep the port code inline with the main branch with no forking
|
||||
|
||||
## Build system
|
||||
We have chosen CMake as a widely accepted build system to build the Windows port. It is very fast and convenient.
|
||||
@@ -66,7 +66,7 @@ We endeavored to make it functionally on par with posix_env. This means we repli
|
||||
Even though Windows provides its own efficient thread-pool implementation we chose to replicate posix logic using `std::thread` primitives. This allows anyone to quickly detect any changes within the posix source code and replicate them within windows env. This has proven to work very well. At the same time for anyone who wishes to replace the built-in thread-pool can do so using RocksDB stackable environments.
|
||||
|
||||
For disk access we implemented all of the functionality present within the posix_env which includes memory mapped files, random access, rate-limiter support etc.
|
||||
The `use_os_buffer` flag on Posix platforms currently denotes disabling read-ahead log via `fadvise` mechanism. Windows does not have `fadvise` system call. What is more, it implements disk cache in a way that differs from Linux greatly. It’s not an uncommon practice on Windows to perform un-buffered disk access to gain control of the memory consumption. We think that in our use case this may also be a good configuration option at the expense of disk throughput. To compensate one may increase the configured in-memory cache size instead. Thus we have chosen `use_os_buffer=false` to disable OS disk buffering for `WinWritableFile` and `WinRandomAccessFile`. The OS imposes restrictions on the alignment of the disk offsets, buffers used and the amount of data that is read/written when accessing files in un-buffered mode. When the option is true, the classes behave in a standard way. This allows to perform writes and reads in cases when un-buffered access does not make sense such as WAL and MANIFEST.
|
||||
The `use_os_buffer` flag on Posix platforms currently denotes disabling read-ahead log via `fadvise` mechanism. Windows does not have `fadvise` system call. What is more, it implements disk cache in a way that differs from Linux greatly. It's not an uncommon practice on Windows to perform un-buffered disk access to gain control of the memory consumption. We think that in our use case this may also be a good configuration option at the expense of disk throughput. To compensate one may increase the configured in-memory cache size instead. Thus we have chosen `use_os_buffer=false` to disable OS disk buffering for `WinWritableFile` and `WinRandomAccessFile`. The OS imposes restrictions on the alignment of the disk offsets, buffers used and the amount of data that is read/written when accessing files in un-buffered mode. When the option is true, the classes behave in a standard way. This allows to perform writes and reads in cases when un-buffered access does not make sense such as WAL and MANIFEST.
|
||||
|
||||
We have replaced `pread/pwrite` with `WriteFile/ReadFile` with `OVERLAPPED` structure so we can atomically seek to the position of the disk operation but still perform the operation synchronously. Thus we able to emulate that functionality of `pread/pwrite` reasonably well. The only difference is that the file pointer is not returned to its original position but that hardly matters given the random nature of access.
|
||||
|
||||
|
||||
@@ -606,8 +606,8 @@ EOF
|
||||
fi
|
||||
|
||||
# TODO(tec): Fix -Wshorten-64-to-32 errors on FreeBSD and enable the warning.
|
||||
# -Wshorten-64-to-32 breaks compilation on FreeBSD i386
|
||||
if ! [ "$TARGET_OS" = FreeBSD -a "$TARGET_ARCHITECTURE" = i386 ]; then
|
||||
# -Wshorten-64-to-32 breaks compilation on FreeBSD aarch64 and i386
|
||||
if ! { [ "$TARGET_OS" = FreeBSD ] && [ "$TARGET_ARCHITECTURE" = arm64 -o "$TARGET_ARCHITECTURE" = i386 ]; }; then
|
||||
# Test whether -Wshorten-64-to-32 is available
|
||||
$CXX $PLATFORM_CXXFLAGS -x c++ - -o /dev/null -Wshorten-64-to-32 2>/dev/null <<EOF
|
||||
int main() {}
|
||||
|
||||
Executable
+28
@@ -0,0 +1,28 @@
|
||||
#!/usr/bin/env bash
|
||||
# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
|
||||
#
|
||||
# Check for some simple mistakes that should prevent commit or push
|
||||
|
||||
BAD=""
|
||||
|
||||
git grep 'namespace rocksdb' -- '*.[ch]*'
|
||||
if [ "$?" != "1" ]; then
|
||||
echo "^^^^^ Do not hardcode namespace rocksdb. Use ROCKSDB_NAMESPACE"
|
||||
BAD=1
|
||||
fi
|
||||
|
||||
git grep -i 'nocommit' -- ':!build_tools/check-sources.sh'
|
||||
if [ "$?" != "1" ]; then
|
||||
echo "^^^^^ Code was not intended to be committed"
|
||||
BAD=1
|
||||
fi
|
||||
|
||||
git grep '<rocksdb/' -- ':!build_tools/check-sources.sh'
|
||||
if [ "$?" != "1" ]; then
|
||||
echo '^^^^^ Use double-quotes as in #include "rocksdb/something.h"'
|
||||
BAD=1
|
||||
fi
|
||||
|
||||
if [ "$BAD" ]; then
|
||||
exit 1
|
||||
fi
|
||||
@@ -18,3 +18,4 @@ KERNEL_HEADERS_BASE=/mnt/gvfs/third-party2/kernel-headers/32b8a2407b634df3f8f948
|
||||
BINUTILS_BASE=/mnt/gvfs/third-party2/binutils/08634589372fa5f237bfd374e8c644a8364e78c1/2.32/platform009/ba86d1f/
|
||||
VALGRIND_BASE=/mnt/gvfs/third-party2/valgrind/6ae525939ad02e5e676855082fbbc7828dbafeac/3.15.0/platform009/7f3b187
|
||||
LUA_BASE=/mnt/gvfs/third-party2/lua/162efd9561a3d21f6869f4814011e9cf1b3ff4dc/5.3.4/platform009/a6271c4
|
||||
BENCHMARK_BASE=/mnt/gvfs/third-party2/benchmark/bce8d9564eaf161700aa3a20b1051564acf555fb/1.5.5/platform009/7f3b187
|
||||
@@ -63,6 +63,13 @@ else
|
||||
fi
|
||||
CFLAGS+=" -DGFLAGS=gflags"
|
||||
|
||||
BENCHMARK_INCLUDE=" -I $BENCHMARK_BASE/include/"
|
||||
if test -z $PIC_BUILD; then
|
||||
BENCHMARK_LIBS=" $BENCHMARK_BASE/lib/libbenchmark.a"
|
||||
else
|
||||
BENCHMARK_LIBS=" $BENCHMARK_BASE/lib/libbenchmark_pic.a"
|
||||
fi
|
||||
|
||||
# location of jemalloc
|
||||
JEMALLOC_INCLUDE=" -I $JEMALLOC_BASE/include/"
|
||||
JEMALLOC_LIB=" $JEMALLOC_BASE/lib/libjemalloc.a"
|
||||
@@ -102,8 +109,9 @@ export PORTABLE
|
||||
|
||||
BINUTILS="$BINUTILS_BASE/bin"
|
||||
AR="$BINUTILS/ar"
|
||||
AS="$BINUTILS/as"
|
||||
|
||||
DEPS_INCLUDE="$SNAPPY_INCLUDE $ZLIB_INCLUDE $BZIP_INCLUDE $LZ4_INCLUDE $ZSTD_INCLUDE $GFLAGS_INCLUDE $NUMA_INCLUDE $TBB_INCLUDE $LIBURING_INCLUDE"
|
||||
DEPS_INCLUDE="$SNAPPY_INCLUDE $ZLIB_INCLUDE $BZIP_INCLUDE $LZ4_INCLUDE $ZSTD_INCLUDE $GFLAGS_INCLUDE $NUMA_INCLUDE $TBB_INCLUDE $LIBURING_INCLUDE $BENCHMARK_INCLUDE"
|
||||
|
||||
STDLIBS="-L $GCC_BASE/lib64"
|
||||
|
||||
@@ -149,8 +157,7 @@ CFLAGS+=" $DEPS_INCLUDE"
|
||||
CFLAGS+=" -DROCKSDB_PLATFORM_POSIX -DROCKSDB_LIB_IO_POSIX -DROCKSDB_FALLOCATE_PRESENT -DROCKSDB_MALLOC_USABLE_SIZE -DROCKSDB_RANGESYNC_PRESENT -DROCKSDB_SCHED_GETCPU_PRESENT -DROCKSDB_SUPPORT_THREAD_LOCAL -DHAVE_SSE42 -DROCKSDB_IOURING_PRESENT"
|
||||
CXXFLAGS+=" $CFLAGS"
|
||||
|
||||
EXEC_LDFLAGS=" $SNAPPY_LIBS $ZLIB_LIBS $BZIP_LIBS $LZ4_LIBS $ZSTD_LIBS $GFLAGS_LIBS $NUMA_LIB $TBB_LIBS $LIBURING_LIBS"
|
||||
EXEC_LDFLAGS+=" -B$BINUTILS"
|
||||
EXEC_LDFLAGS=" $SNAPPY_LIBS $ZLIB_LIBS $BZIP_LIBS $LZ4_LIBS $ZSTD_LIBS $GFLAGS_LIBS $NUMA_LIB $TBB_LIBS $LIBURING_LIBS $BENCHMARK_LIBS"
|
||||
EXEC_LDFLAGS+=" -Wl,--dynamic-linker,/usr/local/fbcode/platform009/lib/ld.so"
|
||||
EXEC_LDFLAGS+=" $LIBUNWIND"
|
||||
EXEC_LDFLAGS+=" -Wl,-rpath=/usr/local/fbcode/platform009/lib"
|
||||
@@ -159,8 +166,9 @@ EXEC_LDFLAGS+=" -Wl,-rpath=$GCC_BASE/lib64"
|
||||
EXEC_LDFLAGS+=" -ldl"
|
||||
|
||||
PLATFORM_LDFLAGS="$LIBGCC_LIBS $GLIBC_LIBS $STDLIBS -lgcc -lstdc++"
|
||||
PLATFORM_LDFLAGS+=" -B$BINUTILS"
|
||||
|
||||
EXEC_LDFLAGS_SHARED="$SNAPPY_LIBS $ZLIB_LIBS $BZIP_LIBS $LZ4_LIBS $ZSTD_LIBS $GFLAGS_LIBS $TBB_LIBS $LIBURING_LIBS"
|
||||
EXEC_LDFLAGS_SHARED="$SNAPPY_LIBS $ZLIB_LIBS $BZIP_LIBS $LZ4_LIBS $ZSTD_LIBS $GFLAGS_LIBS $TBB_LIBS $LIBURING_LIBS $BENCHMARK_LIBS"
|
||||
|
||||
VALGRIND_VER="$VALGRIND_BASE/bin/"
|
||||
|
||||
@@ -168,4 +176,4 @@ VALGRIND_VER="$VALGRIND_BASE/bin/"
|
||||
LUA_PATH=
|
||||
LUA_LIB=
|
||||
|
||||
export CC CXX AR CFLAGS CXXFLAGS EXEC_LDFLAGS EXEC_LDFLAGS_SHARED VALGRIND_VER JEMALLOC_LIB JEMALLOC_INCLUDE CLANG_ANALYZER CLANG_SCAN_BUILD LUA_PATH LUA_LIB
|
||||
export CC CXX AR AS CFLAGS CXXFLAGS EXEC_LDFLAGS EXEC_LDFLAGS_SHARED VALGRIND_VER JEMALLOC_LIB JEMALLOC_INCLUDE CLANG_ANALYZER CLANG_SCAN_BUILD LUA_PATH LUA_LIB
|
||||
@@ -38,7 +38,9 @@ if [ "$CLANG_FORMAT_DIFF" ]; then
|
||||
fi
|
||||
else
|
||||
# First try directly executing the possibilities
|
||||
if clang-format-diff.py --help &> /dev/null < /dev/null; then
|
||||
if clang-format-diff --help &> /dev/null < /dev/null; then
|
||||
CLANG_FORMAT_DIFF=clang-format-diff
|
||||
elif clang-format-diff.py --help &> /dev/null < /dev/null; then
|
||||
CLANG_FORMAT_DIFF=clang-format-diff.py
|
||||
elif $REPO_ROOT/clang-format-diff.py --help &> /dev/null < /dev/null; then
|
||||
CLANG_FORMAT_DIFF=$REPO_ROOT/clang-format-diff.py
|
||||
@@ -120,7 +122,7 @@ uncommitted_code=`git diff HEAD`
|
||||
|
||||
# If there's no uncommitted changes, we assume user are doing post-commit
|
||||
# format check, in which case we'll try to check the modified lines vs. the
|
||||
# facebook/rocksdb.git master branch. Otherwise, we'll check format of the
|
||||
# facebook/rocksdb.git main branch. Otherwise, we'll check format of the
|
||||
# uncommitted code only.
|
||||
if [ -z "$uncommitted_code" ]
|
||||
then
|
||||
@@ -128,8 +130,8 @@ then
|
||||
[ "$FORMAT_REMOTE" ] || FORMAT_REMOTE="$(git remote -v | grep 'facebook/rocksdb.git' | head -n 1 | cut -f 1)"
|
||||
# Fall back on 'origin' if that fails
|
||||
[ "$FORMAT_REMOTE" ] || FORMAT_REMOTE=origin
|
||||
# Use master branch from that remote
|
||||
[ "$FORMAT_UPSTREAM" ] || FORMAT_UPSTREAM="$FORMAT_REMOTE/master"
|
||||
# Use main branch from that remote
|
||||
[ "$FORMAT_UPSTREAM" ] || FORMAT_UPSTREAM="$FORMAT_REMOTE/$(git remote show $FORMAT_REMOTE | sed -n '/HEAD branch/s/.*: //p')"
|
||||
# Get the common ancestor with that remote branch. Everything after that
|
||||
# common ancestor would be considered the contents of a pull request, so
|
||||
# should be relevant for formatting fixes.
|
||||
|
||||
@@ -20,26 +20,11 @@ STAT_FILE=${STAT_FILE:-$(mktemp -t -u rocksdb_test_stats_XXXX)}
|
||||
|
||||
function cleanup {
|
||||
rm -rf $DATA_DIR
|
||||
rm -f $STAT_FILE.fillseq
|
||||
rm -f $STAT_FILE.readrandom
|
||||
rm -f $STAT_FILE.overwrite
|
||||
rm -f $STAT_FILE.memtablefillreadrandom
|
||||
rm -f $STAT_FILE.*
|
||||
}
|
||||
|
||||
trap cleanup EXIT
|
||||
|
||||
if [ -z $GIT_BRANCH ]; then
|
||||
git_br=`git rev-parse --abbrev-ref HEAD`
|
||||
else
|
||||
git_br=$(basename $GIT_BRANCH)
|
||||
fi
|
||||
|
||||
if [ $git_br == "master" ]; then
|
||||
git_br=""
|
||||
else
|
||||
git_br="."$git_br
|
||||
fi
|
||||
|
||||
make release
|
||||
|
||||
# measure fillseq + fill up the DB for overwrite benchmark
|
||||
@@ -286,12 +271,10 @@ common_in_mem_args="--db=/dev/shm/rocksdb \
|
||||
--sync=0 \
|
||||
--verify_checksum=1 \
|
||||
--delete_obsolete_files_period_micros=314572800 \
|
||||
--max_grandparent_overlap_factor=10 \
|
||||
--use_plain_table=1 \
|
||||
--open_files=-1 \
|
||||
--mmap_read=1 \
|
||||
--mmap_write=0 \
|
||||
--memtablerep=prefix_hash \
|
||||
--bloom_bits=10 \
|
||||
--bloom_locality=1 \
|
||||
--perf_level=0"
|
||||
@@ -378,7 +361,7 @@ function send_to_ods {
|
||||
echo >&2 "ERROR: Key $key doesn't have a value."
|
||||
return
|
||||
fi
|
||||
curl --silent "https://www.intern.facebook.com/intern/agent/ods_set.php?entity=rocksdb_build$git_br&key=$key&value=$value" \
|
||||
curl --silent "https://www.intern.facebook.com/intern/agent/ods_set.php?entity=rocksdb_build&key=$key&value=$value" \
|
||||
--connect-timeout 60
|
||||
}
|
||||
|
||||
|
||||
@@ -113,7 +113,7 @@ NON_TSAN_CRASH="CRASH_TEST_EXT_ARGS=--compression_type=zstd"
|
||||
DISABLE_JEMALLOC="DISABLE_JEMALLOC=1"
|
||||
HTTP_PROXY="https_proxy=http://fwdproxy.29.prn1:8080 http_proxy=http://fwdproxy.29.prn1:8080 ftp_proxy=http://fwdproxy.29.prn1:8080"
|
||||
SETUP_JAVA_ENV="export $HTTP_PROXY; export JAVA_HOME=/usr/local/jdk-8u60-64/; export PATH=\$JAVA_HOME/bin:\$PATH"
|
||||
PARSER="'parser':'python build_tools/error_filter.py $1'"
|
||||
PARSER="'parser':'/usr/bin/env python3 build_tools/error_filter.py $1'"
|
||||
|
||||
CONTRUN_NAME="ROCKSDB_CONTRUN_NAME"
|
||||
SKIP_FORMAT_CHECKS="SKIP_FORMAT_BUCK_CHECKS=1"
|
||||
@@ -379,23 +379,6 @@ LITE_BUILD_COMMANDS="[
|
||||
}
|
||||
]"
|
||||
|
||||
#
|
||||
# Report RocksDB lite binary size to scuba
|
||||
REPORT_LITE_BINARY_SIZE_COMMANDS="[
|
||||
{
|
||||
'name':'Rocksdb Lite Binary Size',
|
||||
'oncall':'$ONCALL',
|
||||
'executeLocal': 'true',
|
||||
'steps': [
|
||||
$CLEANUP_ENV,
|
||||
{
|
||||
'name':'Report RocksDB Lite binary size to scuba',
|
||||
'shell':'cd $WORKING_DIR; tools/report_lite_binary_size.sh',
|
||||
'user':'root',
|
||||
},
|
||||
],
|
||||
]"
|
||||
|
||||
#
|
||||
# RocksDB stress/crash test
|
||||
#
|
||||
@@ -588,7 +571,7 @@ WRITE_STRESS_COMMANDS="[
|
||||
$CLEANUP_ENV,
|
||||
{
|
||||
'name':'Build and run RocksDB write stress tests',
|
||||
'shell':'cd $WORKING_DIR; make write_stress && python tools/write_stress_runner.py --runtime_sec=3600 --db=/tmp/rocksdb_write_stress || $CONTRUN_NAME=write_stress $TASK_CREATION_TOOL',
|
||||
'shell':'cd $WORKING_DIR; make write_stress && /usr/bin/env python3 tools/write_stress_runner.py --runtime_sec=3600 --db=/tmp/rocksdb_write_stress || $CONTRUN_NAME=write_stress $TASK_CREATION_TOOL',
|
||||
'user':'root',
|
||||
$PARSER
|
||||
}
|
||||
@@ -611,7 +594,7 @@ ASAN_TEST_COMMANDS="[
|
||||
$CLEANUP_ENV,
|
||||
{
|
||||
'name':'Test RocksDB debug under ASAN',
|
||||
'shell':'cd $WORKING_DIR; set -o pipefail && ($SHM $ASAN $DEBUG $SKIP_FORMAT_CHECKS make $PARALLELISM asan_check || $CONTRUN_NAME=asan_check $TASK_CREATION_TOOL) |& /usr/facebook/ops/scripts/asan_symbolize.py -d',
|
||||
'shell':'cd $WORKING_DIR; set -o pipefail && $SHM $ASAN $DEBUG $SKIP_FORMAT_CHECKS make $PARALLELISM asan_check || $CONTRUN_NAME=asan_check $TASK_CREATION_TOOL',
|
||||
'user':'root',
|
||||
$PARSER
|
||||
}
|
||||
@@ -1120,7 +1103,7 @@ NO_COMPRESSION_COMMANDS="[
|
||||
#
|
||||
run_regression()
|
||||
{
|
||||
time -v bash -vx ./build_tools/regression_build_test.sh $(mktemp -d $WORKSPACE/leveldb.XXXX) $(mktemp leveldb_test_stats.XXXX)
|
||||
time bash -vx ./build_tools/regression_build_test.sh $(mktemp -d $WORKING_DIR/rocksdb.XXXX) $(mktemp rocksdb_test_stats.XXXX)
|
||||
|
||||
# ======= report size to ODS ========
|
||||
|
||||
@@ -1161,6 +1144,7 @@ REGRESSION_COMMANDS="[
|
||||
{
|
||||
'name':'Rocksdb regression commands',
|
||||
'oncall':'$ONCALL',
|
||||
'executeLocal': 'true',
|
||||
'steps': [
|
||||
$CLEANUP_ENV,
|
||||
{
|
||||
@@ -1207,7 +1191,7 @@ FBCODE_STRESS_CRASH_TEST_COMMANDS="[
|
||||
'steps': [
|
||||
{
|
||||
'name':'Copy RocksDB code to fbcode repo',
|
||||
'shell':'cd internal_repo_rocksdb/repo && git init && git add * && git commit -a -m \"Make internal_repo_rocksdb/repo a git repo\" && cd ../.. && echo Y | python3 rocks/release_script/release_to_fbcode.py -u internal_repo_rocksdb/repo master || $CONTRUN_NAME=db_stress_fbcode $TASK_CREATION_TOOL',
|
||||
'shell':'cd internal_repo_rocksdb/repo && git init -b main && git add * && git commit -a -m \"Make internal_repo_rocksdb/repo a git repo\" && cd ../.. && echo Y | python3 rocks/release_script/release_to_fbcode.py -u internal_repo_rocksdb/repo main || $CONTRUN_NAME=db_stress_fbcode $TASK_CREATION_TOOL',
|
||||
'user':'root',
|
||||
$PARSER
|
||||
},
|
||||
@@ -1220,7 +1204,7 @@ FBCODE_STRESS_CRASH_TEST_COMMANDS="[
|
||||
{
|
||||
'name':'Run RocksDB whitebox crash tests',
|
||||
'timeout': 86400,
|
||||
'shell':'cd $WORKING_DIR; mkdir /dev/shm/rocksdb_whitebox_crash_test && TEST_TMPDIR=\$(mktemp -d --tmpdir=/dev/shm/rocksdb_whitebox_crash_test) python3 rocksdb/src/tools/db_crashtest.py --stress_cmd=buck-out/dbg/gen/rocks/tools/rocks_db_stress -secondary_cache_uri=\"$SECONDARY_CACHE_URI\" --env_uri=$ENV_URI -checkpoint_one_in=0 -backup_one_in=0 -cache_size=134217728 -cache_numshardbits=4 $EXTRA_DB_STRESS_ARGS -logtostderr=false whitebox || $CONTRUN_NAME=whitebox_crash_test $TASK_CREATION_TOOL',
|
||||
'shell':'cd $WORKING_DIR; mkdir /dev/shm/rocksdb_fbcode_crash_test && TEST_TMPDIR=\$(mktemp -d --tmpdir=/dev/shm/rocksdb_fbcode_crash_test) python3 rocksdb/src/tools/db_crashtest.py --stress_cmd=buck-out/dbg/gen/rocks/tools/rocks_db_stress -secondary_cache_uri=\"$SECONDARY_CACHE_URI\" --env_uri=$ENV_URI $EXTRA_DB_STRESS_ARGS -logtostderr=false $TEST_TYPE || $CONTRUN_NAME=db_stress_fbcode $TASK_CREATION_TOOL',
|
||||
'user':'root',
|
||||
$PARSER
|
||||
},
|
||||
@@ -1264,9 +1248,6 @@ case $1 in
|
||||
lite)
|
||||
echo $LITE_BUILD_COMMANDS
|
||||
;;
|
||||
report_lite_binary_size)
|
||||
echo $REPORT_LITE_BINARY_SIZE_COMMANDS
|
||||
;;
|
||||
stress_crash)
|
||||
echo $STRESS_CRASH_TEST_COMMANDS
|
||||
;;
|
||||
@@ -1361,7 +1342,9 @@ case $1 in
|
||||
echo $REGRESSION_COMMANDS
|
||||
;;
|
||||
run_regression)
|
||||
set -e
|
||||
run_regression
|
||||
set +e
|
||||
;;
|
||||
java_build)
|
||||
echo $JAVA_BUILD_TEST_COMMANDS
|
||||
|
||||
Vendored
+1
-1
@@ -13,7 +13,7 @@ int main() {
|
||||
return 1;
|
||||
}
|
||||
#else
|
||||
#include <rocksdb/cache_bench_tool.h>
|
||||
#include "rocksdb/cache_bench_tool.h"
|
||||
int main(int argc, char** argv) {
|
||||
return ROCKSDB_NAMESPACE::cache_bench_tool(argc, argv);
|
||||
}
|
||||
|
||||
Vendored
+2
@@ -19,6 +19,7 @@ std::array<const char*, kNumCacheEntryRoles> kCacheEntryRoleToCamelString{{
|
||||
"IndexBlock",
|
||||
"OtherBlock",
|
||||
"WriteBuffer",
|
||||
"CompressionDictionaryBuildingBuffer",
|
||||
"Misc",
|
||||
}};
|
||||
|
||||
@@ -30,6 +31,7 @@ std::array<const char*, kNumCacheEntryRoles> kCacheEntryRoleToHyphenString{{
|
||||
"index-block",
|
||||
"other-block",
|
||||
"write-buffer",
|
||||
"compression-dictionary-building-buffer",
|
||||
"misc",
|
||||
}};
|
||||
|
||||
|
||||
Vendored
+5
@@ -14,6 +14,8 @@
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
// Classifications of block cache entries, for reporting statistics
|
||||
// Adding new enum to this class requires corresponding updates to
|
||||
// kCacheEntryRoleToCamelString and kCacheEntryRoleToHyphenString
|
||||
enum class CacheEntryRole {
|
||||
// Block-based table data block
|
||||
kDataBlock,
|
||||
@@ -29,6 +31,9 @@ enum class CacheEntryRole {
|
||||
kOtherBlock,
|
||||
// WriteBufferManager reservations to account for memtable usage
|
||||
kWriteBuffer,
|
||||
// BlockBasedTableBuilder reservations to account for
|
||||
// compression dictionary building buffer's memory usage
|
||||
kCompressionDictionaryBuildingBuffer,
|
||||
// Default bucket, for miscellaneous cache entries. Do not use for
|
||||
// entries that could potentially add up to large usage.
|
||||
kMisc,
|
||||
|
||||
Vendored
+1
@@ -15,6 +15,7 @@
|
||||
#include "rocksdb/cache.h"
|
||||
#include "rocksdb/status.h"
|
||||
#include "rocksdb/system_clock.h"
|
||||
#include "test_util/sync_point.h"
|
||||
#include "util/coding_lean.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
Vendored
+131
@@ -0,0 +1,131 @@
|
||||
// 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 "cache/cache_reservation_manager.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <cstddef>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
|
||||
#include "cache/cache_entry_roles.h"
|
||||
#include "rocksdb/cache.h"
|
||||
#include "rocksdb/slice.h"
|
||||
#include "rocksdb/status.h"
|
||||
#include "table/block_based/block_based_table_reader.h"
|
||||
#include "util/coding.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
CacheReservationManager::CacheReservationManager(std::shared_ptr<Cache> cache,
|
||||
bool delayed_decrease)
|
||||
: delayed_decrease_(delayed_decrease), cache_allocated_size_(0) {
|
||||
assert(cache != nullptr);
|
||||
cache_ = cache;
|
||||
std::memset(cache_key_, 0, kCacheKeyPrefixSize + kMaxVarint64Length);
|
||||
EncodeVarint64(cache_key_, cache_->NewId());
|
||||
}
|
||||
|
||||
CacheReservationManager::~CacheReservationManager() {
|
||||
for (auto* handle : dummy_handles_) {
|
||||
cache_->Release(handle, true);
|
||||
}
|
||||
}
|
||||
|
||||
template <CacheEntryRole R>
|
||||
Status CacheReservationManager::UpdateCacheReservation(
|
||||
std::size_t new_mem_used) {
|
||||
std::size_t cur_cache_allocated_size =
|
||||
cache_allocated_size_.load(std::memory_order_relaxed);
|
||||
if (new_mem_used == cur_cache_allocated_size) {
|
||||
return Status::OK();
|
||||
} else if (new_mem_used > cur_cache_allocated_size) {
|
||||
Status s = IncreaseCacheReservation<R>(new_mem_used);
|
||||
return s;
|
||||
} else {
|
||||
// In delayed decrease mode, we don't decrease cache reservation
|
||||
// untill the memory usage is less than 3/4 of what we reserve
|
||||
// in the cache.
|
||||
// We do this because
|
||||
// (1) Dummy entry insertion is expensive in block cache
|
||||
// (2) Delayed releasing previously inserted dummy entries can save such
|
||||
// expensive dummy entry insertion on memory increase in the near future,
|
||||
// which is likely to happen when the memory usage is greater than or equal
|
||||
// to 3/4 of what we reserve
|
||||
if (delayed_decrease_ && new_mem_used >= cur_cache_allocated_size / 4 * 3) {
|
||||
return Status::OK();
|
||||
} else {
|
||||
Status s = DecreaseCacheReservation(new_mem_used);
|
||||
return s;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Explicitly instantiate templates for "CacheEntryRole" values we use.
|
||||
// This makes it possible to keep the template definitions in the .cc file.
|
||||
template Status CacheReservationManager::UpdateCacheReservation<
|
||||
CacheEntryRole::kWriteBuffer>(std::size_t new_mem_used);
|
||||
template Status CacheReservationManager::UpdateCacheReservation<
|
||||
CacheEntryRole::kCompressionDictionaryBuildingBuffer>(
|
||||
std::size_t new_mem_used);
|
||||
// For cache reservation manager unit tests
|
||||
template Status CacheReservationManager::UpdateCacheReservation<
|
||||
CacheEntryRole::kMisc>(std::size_t new_mem_used);
|
||||
|
||||
template <CacheEntryRole R>
|
||||
Status CacheReservationManager::IncreaseCacheReservation(
|
||||
std::size_t new_mem_used) {
|
||||
Status return_status = Status::OK();
|
||||
while (new_mem_used > cache_allocated_size_.load(std::memory_order_relaxed)) {
|
||||
Cache::Handle* handle = nullptr;
|
||||
return_status = cache_->Insert(GetNextCacheKey(), nullptr, kSizeDummyEntry,
|
||||
GetNoopDeleterForRole<R>(), &handle);
|
||||
|
||||
if (return_status != Status::OK()) {
|
||||
return return_status;
|
||||
}
|
||||
|
||||
dummy_handles_.push_back(handle);
|
||||
cache_allocated_size_ += kSizeDummyEntry;
|
||||
}
|
||||
return return_status;
|
||||
}
|
||||
|
||||
Status CacheReservationManager::DecreaseCacheReservation(
|
||||
std::size_t new_mem_used) {
|
||||
Status return_status = Status::OK();
|
||||
|
||||
// Decrease to the smallest multiple of kSizeDummyEntry that is greater than
|
||||
// or equal to new_mem_used We do addition instead of new_mem_used <=
|
||||
// cache_allocated_size_.load(std::memory_order_relaxed) - kSizeDummyEntry to
|
||||
// avoid underflow of size_t when cache_allocated_size_ = 0
|
||||
while (new_mem_used + kSizeDummyEntry <=
|
||||
cache_allocated_size_.load(std::memory_order_relaxed)) {
|
||||
assert(!dummy_handles_.empty());
|
||||
auto* handle = dummy_handles_.back();
|
||||
cache_->Release(handle, true);
|
||||
dummy_handles_.pop_back();
|
||||
cache_allocated_size_ -= kSizeDummyEntry;
|
||||
}
|
||||
return return_status;
|
||||
}
|
||||
|
||||
std::size_t CacheReservationManager::GetTotalReservedCacheSize() {
|
||||
return cache_allocated_size_.load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
Slice CacheReservationManager::GetNextCacheKey() {
|
||||
// Calling this function will have the side-effect of changing the
|
||||
// underlying cache_key_ that is shared among other keys generated from this
|
||||
// fucntion. Therefore please make sure the previous keys are saved/copied
|
||||
// before calling this function.
|
||||
std::memset(cache_key_ + kCacheKeyPrefixSize, 0, kMaxVarint64Length);
|
||||
char* end =
|
||||
EncodeVarint64(cache_key_ + kCacheKeyPrefixSize, next_cache_key_id_++);
|
||||
return Slice(cache_key_, static_cast<std::size_t>(end - cache_key_));
|
||||
}
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
Vendored
+98
@@ -0,0 +1,98 @@
|
||||
// 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.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "cache/cache_entry_roles.h"
|
||||
#include "rocksdb/cache.h"
|
||||
#include "rocksdb/slice.h"
|
||||
#include "rocksdb/status.h"
|
||||
#include "table/block_based/block_based_table_reader.h"
|
||||
#include "util/coding.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
// CacheReservationManager is for reserving cache space for the memory used
|
||||
// through inserting/releasing dummy entries in the cache.
|
||||
// This class is not thread-safe.
|
||||
class CacheReservationManager {
|
||||
public:
|
||||
// Construct a CacheReservationManager
|
||||
// @param cache The cache where dummy entries are inserted and released for
|
||||
// reserving cache space
|
||||
// @param delayed_decrease If set true, then dummy entries won't be released
|
||||
// immediately when memory usage decreases.
|
||||
// Instead, it will be released when the memory usage
|
||||
// decreases to 3/4 of what we have reserved so far.
|
||||
// This is for saving some future dummy entry
|
||||
// insertion when memory usage increases are likely to
|
||||
// happen in the near future.
|
||||
explicit CacheReservationManager(std::shared_ptr<Cache> cache,
|
||||
bool delayed_decrease = false);
|
||||
|
||||
// no copy constructor, copy assignment, move constructor, move assignment
|
||||
CacheReservationManager(const CacheReservationManager &) = delete;
|
||||
CacheReservationManager &operator=(const CacheReservationManager &) = delete;
|
||||
CacheReservationManager(CacheReservationManager &&) = delete;
|
||||
CacheReservationManager &operator=(CacheReservationManager &&) = delete;
|
||||
|
||||
~CacheReservationManager();
|
||||
|
||||
template <CacheEntryRole R>
|
||||
|
||||
// Insert and release dummy entries in the cache to
|
||||
// match the size of total dummy entries with the smallest multiple of
|
||||
// kSizeDummyEntry that is greater than or equal to new_mem_used
|
||||
//
|
||||
// Insert dummy entries if new_memory_used > cache_allocated_size_;
|
||||
//
|
||||
// Release dummy entries if new_memory_used < cache_allocated_size_
|
||||
// (and new_memory_used < cache_allocated_size_ * 3/4
|
||||
// when delayed_decrease is set true);
|
||||
//
|
||||
// Keey dummy entries the same if (1) new_memory_used == cache_allocated_size_
|
||||
// or (2) new_memory_used is in the interval of
|
||||
// [cache_allocated_size_ * 3/4, cache_allocated_size) when delayed_decrease
|
||||
// is set true.
|
||||
//
|
||||
// @param new_memory_used The number of bytes used by new memory
|
||||
// @return On inserting dummy entries, it returns Status::OK() if all dummy
|
||||
// entry insertions succeed. Otherwise, it returns the first non-ok status;
|
||||
// On releasing dummy entries, it always returns Status::OK().
|
||||
// On keeping dummy entries the same, it always returns Status::OK().
|
||||
Status UpdateCacheReservation(std::size_t new_memory_used);
|
||||
std::size_t GetTotalReservedCacheSize();
|
||||
|
||||
private:
|
||||
static constexpr std::size_t kSizeDummyEntry = 256 * 1024;
|
||||
// The key will be longer than keys for blocks in SST files so they won't
|
||||
// conflict.
|
||||
static const std::size_t kCacheKeyPrefixSize =
|
||||
BlockBasedTable::kMaxCacheKeyPrefixSize + kMaxVarint64Length;
|
||||
|
||||
Slice GetNextCacheKey();
|
||||
template <CacheEntryRole R>
|
||||
Status IncreaseCacheReservation(std::size_t new_mem_used);
|
||||
Status DecreaseCacheReservation(std::size_t new_mem_used);
|
||||
|
||||
std::shared_ptr<Cache> cache_;
|
||||
bool delayed_decrease_;
|
||||
std::atomic<std::size_t> cache_allocated_size_;
|
||||
std::vector<Cache::Handle *> dummy_handles_;
|
||||
std::uint64_t next_cache_key_id_ = 0;
|
||||
// The non-prefix part will be updated according to the ID to use.
|
||||
char cache_key_[kCacheKeyPrefixSize + kMaxVarint64Length];
|
||||
};
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
+411
@@ -0,0 +1,411 @@
|
||||
// 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 "cache/cache_reservation_manager.h"
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
|
||||
#include "cache/cache_entry_roles.h"
|
||||
#include "rocksdb/cache.h"
|
||||
#include "rocksdb/slice.h"
|
||||
#include "table/block_based/block_based_table_reader.h"
|
||||
#include "test_util/testharness.h"
|
||||
#include "util/coding.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
class CacheReservationManagerTest : public ::testing::Test {
|
||||
protected:
|
||||
static constexpr std::size_t kOneGigabyte = 1024 * 1024 * 1024;
|
||||
static constexpr int kNumShardBits = 0; // 2^0 shard
|
||||
|
||||
static constexpr std::size_t kSizeDummyEntry = 256 * 1024;
|
||||
static const std::size_t kCacheKeyPrefixSize =
|
||||
BlockBasedTable::kMaxCacheKeyPrefixSize + kMaxVarint64Length;
|
||||
static constexpr std::size_t kMetaDataChargeOverhead = 10000;
|
||||
|
||||
std::shared_ptr<Cache> cache = NewLRUCache(kOneGigabyte, kNumShardBits);
|
||||
std::unique_ptr<CacheReservationManager> test_cache_rev_mng;
|
||||
|
||||
CacheReservationManagerTest() {
|
||||
test_cache_rev_mng.reset(new CacheReservationManager(cache));
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(CacheReservationManagerTest, GenerateCacheKey) {
|
||||
// The first cache reservation manager owning the cache will have
|
||||
// cache->NewId() = 1
|
||||
constexpr std::size_t kCacheNewId = 1;
|
||||
// The first key generated inside of cache reservation manager will have
|
||||
// next_cache_key_id = 0
|
||||
constexpr std::size_t kCacheKeyId = 0;
|
||||
|
||||
char expected_cache_key[kCacheKeyPrefixSize + kMaxVarint64Length];
|
||||
std::memset(expected_cache_key, 0, kCacheKeyPrefixSize + kMaxVarint64Length);
|
||||
|
||||
EncodeVarint64(expected_cache_key, kCacheNewId);
|
||||
char* end =
|
||||
EncodeVarint64(expected_cache_key + kCacheKeyPrefixSize, kCacheKeyId);
|
||||
Slice expected_cache_key_slice(
|
||||
expected_cache_key, static_cast<std::size_t>(end - expected_cache_key));
|
||||
|
||||
std::size_t new_mem_used = 1 * kSizeDummyEntry;
|
||||
Status s =
|
||||
test_cache_rev_mng
|
||||
->UpdateCacheReservation<ROCKSDB_NAMESPACE::CacheEntryRole::kMisc>(
|
||||
new_mem_used);
|
||||
ASSERT_EQ(s, Status::OK());
|
||||
ASSERT_GE(cache->GetPinnedUsage(), 1 * kSizeDummyEntry);
|
||||
ASSERT_LT(cache->GetPinnedUsage(),
|
||||
1 * kSizeDummyEntry + kMetaDataChargeOverhead);
|
||||
|
||||
Cache::Handle* handle = cache->Lookup(expected_cache_key_slice);
|
||||
EXPECT_NE(handle, nullptr)
|
||||
<< "Failed to generate the cache key for the dummy entry correctly";
|
||||
// Clean up the returned handle from Lookup() to prevent memory leak
|
||||
cache->Release(handle);
|
||||
}
|
||||
|
||||
TEST_F(CacheReservationManagerTest, KeepCacheReservationTheSame) {
|
||||
std::size_t new_mem_used = 1 * kSizeDummyEntry;
|
||||
Status s =
|
||||
test_cache_rev_mng
|
||||
->UpdateCacheReservation<ROCKSDB_NAMESPACE::CacheEntryRole::kMisc>(
|
||||
new_mem_used);
|
||||
ASSERT_EQ(s, Status::OK());
|
||||
ASSERT_EQ(test_cache_rev_mng->GetTotalReservedCacheSize(),
|
||||
1 * kSizeDummyEntry);
|
||||
std::size_t initial_pinned_usage = cache->GetPinnedUsage();
|
||||
ASSERT_GE(initial_pinned_usage, 1 * kSizeDummyEntry);
|
||||
ASSERT_LT(initial_pinned_usage,
|
||||
1 * kSizeDummyEntry + kMetaDataChargeOverhead);
|
||||
|
||||
s = test_cache_rev_mng
|
||||
->UpdateCacheReservation<ROCKSDB_NAMESPACE::CacheEntryRole::kMisc>(
|
||||
new_mem_used);
|
||||
EXPECT_EQ(s, Status::OK())
|
||||
<< "Failed to keep cache reservation the same when new_mem_used equals "
|
||||
"to current cache reservation";
|
||||
EXPECT_EQ(test_cache_rev_mng->GetTotalReservedCacheSize(),
|
||||
1 * kSizeDummyEntry)
|
||||
<< "Failed to bookkeep correctly when new_mem_used equals to current "
|
||||
"cache reservation";
|
||||
EXPECT_EQ(cache->GetPinnedUsage(), initial_pinned_usage)
|
||||
<< "Failed to keep underlying dummy entries the same when new_mem_used "
|
||||
"equals to current cache reservation";
|
||||
}
|
||||
|
||||
TEST_F(CacheReservationManagerTest,
|
||||
IncreaseCacheReservationByMultiplesOfDummyEntrySize) {
|
||||
std::size_t new_mem_used = 2 * kSizeDummyEntry;
|
||||
Status s =
|
||||
test_cache_rev_mng
|
||||
->UpdateCacheReservation<ROCKSDB_NAMESPACE::CacheEntryRole::kMisc>(
|
||||
new_mem_used);
|
||||
EXPECT_EQ(s, Status::OK())
|
||||
<< "Failed to increase cache reservation correctly";
|
||||
EXPECT_EQ(test_cache_rev_mng->GetTotalReservedCacheSize(),
|
||||
2 * kSizeDummyEntry)
|
||||
<< "Failed to bookkeep cache reservation increase correctly";
|
||||
EXPECT_GE(cache->GetPinnedUsage(), 2 * kSizeDummyEntry)
|
||||
<< "Failed to increase underlying dummy entries in cache correctly";
|
||||
EXPECT_LT(cache->GetPinnedUsage(),
|
||||
2 * kSizeDummyEntry + kMetaDataChargeOverhead)
|
||||
<< "Failed to increase underlying dummy entries in cache correctly";
|
||||
}
|
||||
|
||||
TEST_F(CacheReservationManagerTest,
|
||||
IncreaseCacheReservationNotByMultiplesOfDummyEntrySize) {
|
||||
std::size_t new_mem_used = 2 * kSizeDummyEntry + kSizeDummyEntry / 2;
|
||||
Status s =
|
||||
test_cache_rev_mng
|
||||
->UpdateCacheReservation<ROCKSDB_NAMESPACE::CacheEntryRole::kMisc>(
|
||||
new_mem_used);
|
||||
EXPECT_EQ(s, Status::OK())
|
||||
<< "Failed to increase cache reservation correctly";
|
||||
EXPECT_EQ(test_cache_rev_mng->GetTotalReservedCacheSize(),
|
||||
3 * kSizeDummyEntry)
|
||||
<< "Failed to bookkeep cache reservation increase correctly";
|
||||
EXPECT_GE(cache->GetPinnedUsage(), 3 * kSizeDummyEntry)
|
||||
<< "Failed to increase underlying dummy entries in cache correctly";
|
||||
EXPECT_LT(cache->GetPinnedUsage(),
|
||||
3 * kSizeDummyEntry + kMetaDataChargeOverhead)
|
||||
<< "Failed to increase underlying dummy entries in cache correctly";
|
||||
}
|
||||
|
||||
TEST(CacheReservationManagerIncreaseReservcationOnFullCacheTest,
|
||||
IncreaseCacheReservationOnFullCache) {
|
||||
constexpr std::size_t kOneMegabyte = 1024 * 1024;
|
||||
constexpr std::size_t kOneGigabyte = 1024 * 1024 * 1024;
|
||||
constexpr std::size_t kSizeDummyEntry = 256 * 1024;
|
||||
constexpr std::size_t kMetaDataChargeOverhead = 10000;
|
||||
|
||||
LRUCacheOptions lo;
|
||||
lo.capacity = kOneMegabyte;
|
||||
lo.num_shard_bits = 0; // 2^0 shard
|
||||
lo.strict_capacity_limit = true;
|
||||
std::shared_ptr<Cache> cache = NewLRUCache(lo);
|
||||
std::unique_ptr<CacheReservationManager> test_cache_rev_mng(
|
||||
new CacheReservationManager(cache));
|
||||
|
||||
std::size_t new_mem_used = kOneMegabyte + 1;
|
||||
Status s =
|
||||
test_cache_rev_mng
|
||||
->UpdateCacheReservation<ROCKSDB_NAMESPACE::CacheEntryRole::kMisc>(
|
||||
new_mem_used);
|
||||
EXPECT_EQ(s, Status::Incomplete())
|
||||
<< "Failed to return status to indicate failure of dummy entry insertion "
|
||||
"during cache reservation on full cache";
|
||||
EXPECT_GE(test_cache_rev_mng->GetTotalReservedCacheSize(),
|
||||
1 * kSizeDummyEntry)
|
||||
<< "Failed to bookkeep correctly before cache resevation failure happens "
|
||||
"due to full cache";
|
||||
EXPECT_LE(test_cache_rev_mng->GetTotalReservedCacheSize(), kOneMegabyte)
|
||||
<< "Failed to bookkeep correctly (i.e, bookkeep only successful dummy "
|
||||
"entry insertions) when encountering cache resevation failure due to "
|
||||
"full cache";
|
||||
EXPECT_GE(cache->GetPinnedUsage(), 1 * kSizeDummyEntry)
|
||||
<< "Failed to insert underlying dummy entries correctly when "
|
||||
"encountering cache resevation failure due to full cache";
|
||||
EXPECT_LE(cache->GetPinnedUsage(), kOneMegabyte)
|
||||
<< "Failed to insert underlying dummy entries correctly when "
|
||||
"encountering cache resevation failure due to full cache";
|
||||
|
||||
new_mem_used = kOneMegabyte / 2; // 2 dummy entries
|
||||
s = test_cache_rev_mng
|
||||
->UpdateCacheReservation<ROCKSDB_NAMESPACE::CacheEntryRole::kMisc>(
|
||||
new_mem_used);
|
||||
EXPECT_EQ(s, Status::OK())
|
||||
<< "Failed to decrease cache reservation after encountering cache "
|
||||
"reservation failure due to full cache";
|
||||
EXPECT_EQ(test_cache_rev_mng->GetTotalReservedCacheSize(),
|
||||
2 * kSizeDummyEntry)
|
||||
<< "Failed to bookkeep cache reservation decrease correctly after "
|
||||
"encountering cache reservation due to full cache";
|
||||
EXPECT_GE(cache->GetPinnedUsage(), 2 * kSizeDummyEntry)
|
||||
<< "Failed to release underlying dummy entries correctly on cache "
|
||||
"reservation decrease after encountering cache resevation failure due "
|
||||
"to full cache";
|
||||
EXPECT_LT(cache->GetPinnedUsage(),
|
||||
2 * kSizeDummyEntry + kMetaDataChargeOverhead)
|
||||
<< "Failed to release underlying dummy entries correctly on cache "
|
||||
"reservation decrease after encountering cache resevation failure due "
|
||||
"to full cache";
|
||||
|
||||
// Create cache full again for subsequent tests
|
||||
new_mem_used = kOneMegabyte + 1;
|
||||
s = test_cache_rev_mng
|
||||
->UpdateCacheReservation<ROCKSDB_NAMESPACE::CacheEntryRole::kMisc>(
|
||||
new_mem_used);
|
||||
EXPECT_EQ(s, Status::Incomplete())
|
||||
<< "Failed to return status to indicate failure of dummy entry insertion "
|
||||
"during cache reservation on full cache";
|
||||
EXPECT_GE(test_cache_rev_mng->GetTotalReservedCacheSize(),
|
||||
1 * kSizeDummyEntry)
|
||||
<< "Failed to bookkeep correctly before cache resevation failure happens "
|
||||
"due to full cache";
|
||||
EXPECT_LE(test_cache_rev_mng->GetTotalReservedCacheSize(), kOneMegabyte)
|
||||
<< "Failed to bookkeep correctly (i.e, bookkeep only successful dummy "
|
||||
"entry insertions) when encountering cache resevation failure due to "
|
||||
"full cache";
|
||||
EXPECT_GE(cache->GetPinnedUsage(), 1 * kSizeDummyEntry)
|
||||
<< "Failed to insert underlying dummy entries correctly when "
|
||||
"encountering cache resevation failure due to full cache";
|
||||
EXPECT_LE(cache->GetPinnedUsage(), kOneMegabyte)
|
||||
<< "Failed to insert underlying dummy entries correctly when "
|
||||
"encountering cache resevation failure due to full cache";
|
||||
|
||||
// Increase cache capacity so the previously failed insertion can fully
|
||||
// succeed
|
||||
cache->SetCapacity(kOneGigabyte);
|
||||
new_mem_used = kOneMegabyte + 1;
|
||||
s = test_cache_rev_mng
|
||||
->UpdateCacheReservation<ROCKSDB_NAMESPACE::CacheEntryRole::kMisc>(
|
||||
new_mem_used);
|
||||
EXPECT_EQ(s, Status::OK())
|
||||
<< "Failed to increase cache reservation after increasing cache capacity "
|
||||
"and mitigating cache full error";
|
||||
EXPECT_EQ(test_cache_rev_mng->GetTotalReservedCacheSize(),
|
||||
5 * kSizeDummyEntry)
|
||||
<< "Failed to bookkeep cache reservation increase correctly after "
|
||||
"increasing cache capacity and mitigating cache full error";
|
||||
EXPECT_GE(cache->GetPinnedUsage(), 5 * kSizeDummyEntry)
|
||||
<< "Failed to insert underlying dummy entries correctly after increasing "
|
||||
"cache capacity and mitigating cache full error";
|
||||
EXPECT_LT(cache->GetPinnedUsage(),
|
||||
5 * kSizeDummyEntry + kMetaDataChargeOverhead)
|
||||
<< "Failed to insert underlying dummy entries correctly after increasing "
|
||||
"cache capacity and mitigating cache full error";
|
||||
}
|
||||
|
||||
TEST_F(CacheReservationManagerTest,
|
||||
DecreaseCacheReservationByMultiplesOfDummyEntrySize) {
|
||||
std::size_t new_mem_used = 2 * kSizeDummyEntry;
|
||||
Status s =
|
||||
test_cache_rev_mng
|
||||
->UpdateCacheReservation<ROCKSDB_NAMESPACE::CacheEntryRole::kMisc>(
|
||||
new_mem_used);
|
||||
ASSERT_EQ(s, Status::OK());
|
||||
ASSERT_EQ(test_cache_rev_mng->GetTotalReservedCacheSize(),
|
||||
2 * kSizeDummyEntry);
|
||||
ASSERT_GE(cache->GetPinnedUsage(), 2 * kSizeDummyEntry);
|
||||
ASSERT_LT(cache->GetPinnedUsage(),
|
||||
2 * kSizeDummyEntry + kMetaDataChargeOverhead);
|
||||
|
||||
new_mem_used = 1 * kSizeDummyEntry;
|
||||
s = test_cache_rev_mng
|
||||
->UpdateCacheReservation<ROCKSDB_NAMESPACE::CacheEntryRole::kMisc>(
|
||||
new_mem_used);
|
||||
EXPECT_EQ(s, Status::OK())
|
||||
<< "Failed to decrease cache reservation correctly";
|
||||
EXPECT_EQ(test_cache_rev_mng->GetTotalReservedCacheSize(),
|
||||
1 * kSizeDummyEntry)
|
||||
<< "Failed to bookkeep cache reservation decrease correctly";
|
||||
EXPECT_GE(cache->GetPinnedUsage(), 1 * kSizeDummyEntry)
|
||||
<< "Failed to decrease underlying dummy entries in cache correctly";
|
||||
EXPECT_LT(cache->GetPinnedUsage(),
|
||||
1 * kSizeDummyEntry + kMetaDataChargeOverhead)
|
||||
<< "Failed to decrease underlying dummy entries in cache correctly";
|
||||
}
|
||||
|
||||
TEST_F(CacheReservationManagerTest,
|
||||
DecreaseCacheReservationNotByMultiplesOfDummyEntrySize) {
|
||||
std::size_t new_mem_used = 2 * kSizeDummyEntry;
|
||||
Status s =
|
||||
test_cache_rev_mng
|
||||
->UpdateCacheReservation<ROCKSDB_NAMESPACE::CacheEntryRole::kMisc>(
|
||||
new_mem_used);
|
||||
ASSERT_EQ(s, Status::OK());
|
||||
ASSERT_EQ(test_cache_rev_mng->GetTotalReservedCacheSize(),
|
||||
2 * kSizeDummyEntry);
|
||||
ASSERT_GE(cache->GetPinnedUsage(), 2 * kSizeDummyEntry);
|
||||
ASSERT_LT(cache->GetPinnedUsage(),
|
||||
2 * kSizeDummyEntry + kMetaDataChargeOverhead);
|
||||
|
||||
new_mem_used = kSizeDummyEntry / 2;
|
||||
s = test_cache_rev_mng
|
||||
->UpdateCacheReservation<ROCKSDB_NAMESPACE::CacheEntryRole::kMisc>(
|
||||
new_mem_used);
|
||||
EXPECT_EQ(s, Status::OK())
|
||||
<< "Failed to decrease cache reservation correctly";
|
||||
EXPECT_EQ(test_cache_rev_mng->GetTotalReservedCacheSize(),
|
||||
1 * kSizeDummyEntry)
|
||||
<< "Failed to bookkeep cache reservation decrease correctly";
|
||||
EXPECT_GE(cache->GetPinnedUsage(), 1 * kSizeDummyEntry)
|
||||
<< "Failed to decrease underlying dummy entries in cache correctly";
|
||||
EXPECT_LT(cache->GetPinnedUsage(),
|
||||
1 * kSizeDummyEntry + kMetaDataChargeOverhead)
|
||||
<< "Failed to decrease underlying dummy entries in cache correctly";
|
||||
}
|
||||
|
||||
TEST(CacheReservationManagerWithDelayedDecreaseTest,
|
||||
DecreaseCacheReservationWithDelayedDecrease) {
|
||||
constexpr std::size_t kOneGigabyte = 1024 * 1024 * 1024;
|
||||
constexpr std::size_t kSizeDummyEntry = 256 * 1024;
|
||||
constexpr std::size_t kMetaDataChargeOverhead = 10000;
|
||||
|
||||
LRUCacheOptions lo;
|
||||
lo.capacity = kOneGigabyte;
|
||||
lo.num_shard_bits = 0;
|
||||
std::shared_ptr<Cache> cache = NewLRUCache(lo);
|
||||
std::unique_ptr<CacheReservationManager> test_cache_rev_mng(
|
||||
new CacheReservationManager(cache, true /* delayed_decrease */));
|
||||
|
||||
std::size_t new_mem_used = 8 * kSizeDummyEntry;
|
||||
Status s =
|
||||
test_cache_rev_mng
|
||||
->UpdateCacheReservation<ROCKSDB_NAMESPACE::CacheEntryRole::kMisc>(
|
||||
new_mem_used);
|
||||
ASSERT_EQ(s, Status::OK());
|
||||
ASSERT_EQ(test_cache_rev_mng->GetTotalReservedCacheSize(),
|
||||
8 * kSizeDummyEntry);
|
||||
std::size_t initial_pinned_usage = cache->GetPinnedUsage();
|
||||
ASSERT_GE(initial_pinned_usage, 8 * kSizeDummyEntry);
|
||||
ASSERT_LT(initial_pinned_usage,
|
||||
8 * kSizeDummyEntry + kMetaDataChargeOverhead);
|
||||
|
||||
new_mem_used = 6 * kSizeDummyEntry;
|
||||
s = test_cache_rev_mng
|
||||
->UpdateCacheReservation<ROCKSDB_NAMESPACE::CacheEntryRole::kMisc>(
|
||||
new_mem_used);
|
||||
EXPECT_EQ(s, Status::OK()) << "Failed to delay decreasing cache reservation";
|
||||
EXPECT_EQ(test_cache_rev_mng->GetTotalReservedCacheSize(),
|
||||
8 * kSizeDummyEntry)
|
||||
<< "Failed to bookkeep correctly when delaying cache reservation "
|
||||
"decrease";
|
||||
EXPECT_EQ(cache->GetPinnedUsage(), initial_pinned_usage)
|
||||
<< "Failed to delay decreasing underlying dummy entries in cache";
|
||||
|
||||
new_mem_used = 7 * kSizeDummyEntry;
|
||||
s = test_cache_rev_mng
|
||||
->UpdateCacheReservation<ROCKSDB_NAMESPACE::CacheEntryRole::kMisc>(
|
||||
new_mem_used);
|
||||
EXPECT_EQ(s, Status::OK()) << "Failed to delay decreasing cache reservation";
|
||||
EXPECT_EQ(test_cache_rev_mng->GetTotalReservedCacheSize(),
|
||||
8 * kSizeDummyEntry)
|
||||
<< "Failed to bookkeep correctly when delaying cache reservation "
|
||||
"decrease";
|
||||
EXPECT_EQ(cache->GetPinnedUsage(), initial_pinned_usage)
|
||||
<< "Failed to delay decreasing underlying dummy entries in cache";
|
||||
|
||||
new_mem_used = 6 * kSizeDummyEntry - 1;
|
||||
s = test_cache_rev_mng
|
||||
->UpdateCacheReservation<ROCKSDB_NAMESPACE::CacheEntryRole::kMisc>(
|
||||
new_mem_used);
|
||||
EXPECT_EQ(s, Status::OK())
|
||||
<< "Failed to decrease cache reservation correctly when new_mem_used < "
|
||||
"GetTotalReservedCacheSize() * 3 / 4 on delayed decrease mode";
|
||||
EXPECT_EQ(test_cache_rev_mng->GetTotalReservedCacheSize(),
|
||||
6 * kSizeDummyEntry)
|
||||
<< "Failed to bookkeep correctly when new_mem_used < "
|
||||
"GetTotalReservedCacheSize() * 3 / 4 on delayed decrease mode";
|
||||
EXPECT_GE(cache->GetPinnedUsage(), 6 * kSizeDummyEntry)
|
||||
<< "Failed to decrease underlying dummy entries in cache when "
|
||||
"new_mem_used < GetTotalReservedCacheSize() * 3 / 4 on delayed "
|
||||
"decrease mode";
|
||||
EXPECT_LT(cache->GetPinnedUsage(),
|
||||
6 * kSizeDummyEntry + kMetaDataChargeOverhead)
|
||||
<< "Failed to decrease underlying dummy entries in cache when "
|
||||
"new_mem_used < GetTotalReservedCacheSize() * 3 / 4 on delayed "
|
||||
"decrease mode";
|
||||
}
|
||||
|
||||
TEST(CacheReservationManagerDestructorTest,
|
||||
ReleaseRemainingDummyEntriesOnDestruction) {
|
||||
constexpr std::size_t kOneGigabyte = 1024 * 1024 * 1024;
|
||||
constexpr std::size_t kSizeDummyEntry = 256 * 1024;
|
||||
constexpr std::size_t kMetaDataChargeOverhead = 10000;
|
||||
|
||||
LRUCacheOptions lo;
|
||||
lo.capacity = kOneGigabyte;
|
||||
lo.num_shard_bits = 0;
|
||||
std::shared_ptr<Cache> cache = NewLRUCache(lo);
|
||||
{
|
||||
std::unique_ptr<CacheReservationManager> test_cache_rev_mng(
|
||||
new CacheReservationManager(cache));
|
||||
std::size_t new_mem_used = 1 * kSizeDummyEntry;
|
||||
Status s =
|
||||
test_cache_rev_mng
|
||||
->UpdateCacheReservation<ROCKSDB_NAMESPACE::CacheEntryRole::kMisc>(
|
||||
new_mem_used);
|
||||
ASSERT_EQ(s, Status::OK());
|
||||
ASSERT_GE(cache->GetPinnedUsage(), 1 * kSizeDummyEntry);
|
||||
ASSERT_LT(cache->GetPinnedUsage(),
|
||||
1 * kSizeDummyEntry + kMetaDataChargeOverhead);
|
||||
}
|
||||
EXPECT_EQ(cache->GetPinnedUsage(), 0 * kSizeDummyEntry)
|
||||
<< "Failed to release remaining underlying dummy entries in cache in "
|
||||
"CacheReservationManager's destructor";
|
||||
}
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
Vendored
+1
-1
@@ -260,7 +260,7 @@ struct CleanupContext {
|
||||
class ClockCacheShard final : public CacheShard {
|
||||
public:
|
||||
// Hash map type.
|
||||
typedef tbb::concurrent_hash_map<CacheKey, CacheHandle*, CacheKey> HashTable;
|
||||
using HashTable = tbb::concurrent_hash_map<CacheKey, CacheHandle*, CacheKey>;
|
||||
|
||||
ClockCacheShard();
|
||||
~ClockCacheShard() override;
|
||||
|
||||
Vendored
+5
-6
@@ -360,7 +360,10 @@ Status LRUCacheShard::InsertItem(LRUHandle* e, Cache::Handle** handle,
|
||||
if (handle == nullptr) {
|
||||
LRU_Insert(e);
|
||||
} else {
|
||||
e->Ref();
|
||||
// If caller already holds a ref, no need to take one here
|
||||
if (!e->HasRefs()) {
|
||||
e->Ref();
|
||||
}
|
||||
*handle = reinterpret_cast<Cache::Handle*>(e);
|
||||
}
|
||||
}
|
||||
@@ -398,11 +401,7 @@ void LRUCacheShard::Promote(LRUHandle* e) {
|
||||
if (e->value) {
|
||||
Cache::Handle* handle = reinterpret_cast<Cache::Handle*>(e);
|
||||
Status s = InsertItem(e, &handle, /*free_handle_on_fail=*/false);
|
||||
if (s.ok()) {
|
||||
// InsertItem would have taken a reference on the item, so decrement it
|
||||
// here as we expect the caller to already hold a reference
|
||||
e->Unref();
|
||||
} else {
|
||||
if (!s.ok()) {
|
||||
// Item is in memory, but not accounted against the cache capacity.
|
||||
// When the handle is released, the item should get deleted
|
||||
assert(!e->InCache());
|
||||
|
||||
Vendored
+668
-4
@@ -15,9 +15,11 @@
|
||||
#include "rocksdb/cache.h"
|
||||
#include "rocksdb/io_status.h"
|
||||
#include "rocksdb/sst_file_manager.h"
|
||||
#include "rocksdb/utilities/cache_dump_load.h"
|
||||
#include "test_util/testharness.h"
|
||||
#include "util/coding.h"
|
||||
#include "util/random.h"
|
||||
#include "utilities/cache_dump_load_impl.h"
|
||||
#include "utilities/fault_injection_fs.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
@@ -693,7 +695,7 @@ TEST_F(DBSecondaryCacheTest, TestSecondaryCacheCorrectness1) {
|
||||
}
|
||||
|
||||
ASSERT_OK(Flush());
|
||||
// After Flush is successful, RocksDB do the paranoid check for the new
|
||||
// After Flush is successful, RocksDB will do the paranoid check for the new
|
||||
// SST file. Meta blocks are always cached in the block cache and they
|
||||
// will not be evicted. When block_2 is cache miss and read out, it is
|
||||
// inserted to the block cache. Note that, block_1 is never successfully
|
||||
@@ -787,7 +789,7 @@ TEST_F(DBSecondaryCacheTest, TestSecondaryCacheCorrectness2) {
|
||||
}
|
||||
|
||||
ASSERT_OK(Flush());
|
||||
// After Flush is successful, RocksDB do the paranoid check for the new
|
||||
// After Flush is successful, RocksDB will do the paranoid check for the new
|
||||
// SST file. Meta blocks are always cached in the block cache and they
|
||||
// will not be evicted. When block_2 is cache miss and read out, it is
|
||||
// inserted to the block cache. Thefore, block_1 is evicted from block
|
||||
@@ -881,7 +883,7 @@ TEST_F(DBSecondaryCacheTest, NoSecondaryCacheInsertion) {
|
||||
}
|
||||
|
||||
ASSERT_OK(Flush());
|
||||
// After Flush is successful, RocksDB do the paranoid check for the new
|
||||
// After Flush is successful, RocksDB will do the paranoid check for the new
|
||||
// SST file. Meta blocks are always cached in the block cache and they
|
||||
// will not be evicted. Now, block cache is large enough, it cache
|
||||
// both block_1 and block_2. When first time read block_1 and block_2
|
||||
@@ -983,7 +985,7 @@ TEST_F(DBSecondaryCacheTest, SecondaryCacheFailureTest) {
|
||||
}
|
||||
|
||||
ASSERT_OK(Flush());
|
||||
// After Flush is successful, RocksDB do the paranoid check for the new
|
||||
// After Flush is successful, RocksDB will do the paranoid check for the new
|
||||
// SST file. Meta blocks are always cached in the block cache and they
|
||||
// will not be evicted. When block_2 is cache miss and read out, it is
|
||||
// inserted to the block cache. Note that, block_1 is never successfully
|
||||
@@ -1182,6 +1184,668 @@ TEST_F(DBSecondaryCacheTest, TestSecondaryCacheMultiGet) {
|
||||
Destroy(options);
|
||||
}
|
||||
|
||||
class LRUCacheWithStat : public LRUCache {
|
||||
public:
|
||||
LRUCacheWithStat(
|
||||
size_t _capacity, int _num_shard_bits, bool _strict_capacity_limit,
|
||||
double _high_pri_pool_ratio,
|
||||
std::shared_ptr<MemoryAllocator> _memory_allocator = nullptr,
|
||||
bool _use_adaptive_mutex = kDefaultToAdaptiveMutex,
|
||||
CacheMetadataChargePolicy _metadata_charge_policy =
|
||||
kDontChargeCacheMetadata,
|
||||
const std::shared_ptr<SecondaryCache>& _secondary_cache = nullptr)
|
||||
: LRUCache(_capacity, _num_shard_bits, _strict_capacity_limit,
|
||||
_high_pri_pool_ratio, _memory_allocator, _use_adaptive_mutex,
|
||||
_metadata_charge_policy, _secondary_cache) {
|
||||
insert_count_ = 0;
|
||||
lookup_count_ = 0;
|
||||
}
|
||||
~LRUCacheWithStat() {}
|
||||
|
||||
Status Insert(const Slice& key, void* value, size_t charge, DeleterFn deleter,
|
||||
Handle** handle, Priority priority) override {
|
||||
insert_count_++;
|
||||
return LRUCache::Insert(key, value, charge, deleter, handle, priority);
|
||||
}
|
||||
Status Insert(const Slice& key, void* value, const CacheItemHelper* helper,
|
||||
size_t chargge, Handle** handle = nullptr,
|
||||
Priority priority = Priority::LOW) override {
|
||||
insert_count_++;
|
||||
return LRUCache::Insert(key, value, helper, chargge, handle, priority);
|
||||
}
|
||||
Handle* Lookup(const Slice& key, Statistics* stats) override {
|
||||
lookup_count_++;
|
||||
return LRUCache::Lookup(key, stats);
|
||||
}
|
||||
Handle* Lookup(const Slice& key, const CacheItemHelper* helper,
|
||||
const CreateCallback& create_cb, Priority priority, bool wait,
|
||||
Statistics* stats = nullptr) override {
|
||||
lookup_count_++;
|
||||
return LRUCache::Lookup(key, helper, create_cb, priority, wait, stats);
|
||||
}
|
||||
|
||||
uint32_t GetInsertCount() { return insert_count_; }
|
||||
uint32_t GetLookupcount() { return lookup_count_; }
|
||||
void ResetCount() {
|
||||
insert_count_ = 0;
|
||||
lookup_count_ = 0;
|
||||
}
|
||||
|
||||
private:
|
||||
uint32_t insert_count_;
|
||||
uint32_t lookup_count_;
|
||||
};
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
|
||||
TEST_F(DBSecondaryCacheTest, LRUCacheDumpLoadBasic) {
|
||||
LRUCacheOptions cache_opts(1024 * 1024, 0, false, 0.5, nullptr,
|
||||
kDefaultToAdaptiveMutex, kDontChargeCacheMetadata);
|
||||
LRUCacheWithStat* tmp_cache = new LRUCacheWithStat(
|
||||
cache_opts.capacity, cache_opts.num_shard_bits,
|
||||
cache_opts.strict_capacity_limit, cache_opts.high_pri_pool_ratio,
|
||||
cache_opts.memory_allocator, cache_opts.use_adaptive_mutex,
|
||||
cache_opts.metadata_charge_policy, cache_opts.secondary_cache);
|
||||
std::shared_ptr<Cache> cache(tmp_cache);
|
||||
BlockBasedTableOptions table_options;
|
||||
table_options.block_cache = cache;
|
||||
table_options.block_size = 4 * 1024;
|
||||
Options options = GetDefaultOptions();
|
||||
options.create_if_missing = true;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
options.env = fault_env_.get();
|
||||
DestroyAndReopen(options);
|
||||
fault_fs_->SetFailGetUniqueId(true);
|
||||
|
||||
Random rnd(301);
|
||||
const int N = 256;
|
||||
std::vector<std::string> value;
|
||||
char buf[1000];
|
||||
memset(buf, 'a', 1000);
|
||||
value.resize(N);
|
||||
for (int i = 0; i < N; i++) {
|
||||
// std::string p_v = rnd.RandomString(1000);
|
||||
std::string p_v(buf, 1000);
|
||||
value[i] = p_v;
|
||||
ASSERT_OK(Put(Key(i), p_v));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
Compact("a", "z");
|
||||
|
||||
// do th eread for all the key value pairs, so all the blocks should be in
|
||||
// cache
|
||||
uint32_t start_insert = tmp_cache->GetInsertCount();
|
||||
uint32_t start_lookup = tmp_cache->GetLookupcount();
|
||||
std::string v;
|
||||
for (int i = 0; i < N; i++) {
|
||||
v = Get(Key(i));
|
||||
ASSERT_EQ(v, value[i]);
|
||||
}
|
||||
uint32_t dump_insert = tmp_cache->GetInsertCount() - start_insert;
|
||||
uint32_t dump_lookup = tmp_cache->GetLookupcount() - start_lookup;
|
||||
ASSERT_EQ(63,
|
||||
static_cast<int>(dump_insert)); // the insert in the block cache
|
||||
ASSERT_EQ(256,
|
||||
static_cast<int>(dump_lookup)); // the lookup in the block cache
|
||||
// We have enough blocks in the block cache
|
||||
|
||||
CacheDumpOptions cd_options;
|
||||
cd_options.clock = fault_env_->GetSystemClock().get();
|
||||
std::string dump_path = db_->GetName() + "/cache_dump";
|
||||
std::unique_ptr<CacheDumpWriter> dump_writer;
|
||||
Status s = NewToFileCacheDumpWriter(fault_fs_, FileOptions(), dump_path,
|
||||
&dump_writer);
|
||||
ASSERT_OK(s);
|
||||
std::unique_ptr<CacheDumper> cache_dumper;
|
||||
s = NewDefaultCacheDumper(cd_options, cache, std::move(dump_writer),
|
||||
&cache_dumper);
|
||||
ASSERT_OK(s);
|
||||
std::vector<DB*> db_list;
|
||||
db_list.push_back(db_);
|
||||
s = cache_dumper->SetDumpFilter(db_list);
|
||||
ASSERT_OK(s);
|
||||
s = cache_dumper->DumpCacheEntriesToWriter();
|
||||
ASSERT_OK(s);
|
||||
cache_dumper.reset();
|
||||
|
||||
// we have a new cache it is empty, then, before we do the Get, we do the
|
||||
// dumpload
|
||||
std::shared_ptr<TestSecondaryCache> secondary_cache =
|
||||
std::make_shared<TestSecondaryCache>(2048 * 1024);
|
||||
cache_opts.secondary_cache = secondary_cache;
|
||||
tmp_cache = new LRUCacheWithStat(
|
||||
cache_opts.capacity, cache_opts.num_shard_bits,
|
||||
cache_opts.strict_capacity_limit, cache_opts.high_pri_pool_ratio,
|
||||
cache_opts.memory_allocator, cache_opts.use_adaptive_mutex,
|
||||
cache_opts.metadata_charge_policy, cache_opts.secondary_cache);
|
||||
std::shared_ptr<Cache> cache_new(tmp_cache);
|
||||
table_options.block_cache = cache_new;
|
||||
table_options.block_size = 4 * 1024;
|
||||
options.create_if_missing = true;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
options.env = fault_env_.get();
|
||||
|
||||
// start to load the data to new block cache
|
||||
start_insert = secondary_cache->num_inserts();
|
||||
start_lookup = secondary_cache->num_lookups();
|
||||
std::unique_ptr<CacheDumpReader> dump_reader;
|
||||
s = NewFromFileCacheDumpReader(fault_fs_, FileOptions(), dump_path,
|
||||
&dump_reader);
|
||||
ASSERT_OK(s);
|
||||
std::unique_ptr<CacheDumpedLoader> cache_loader;
|
||||
s = NewDefaultCacheDumpedLoader(cd_options, table_options, secondary_cache,
|
||||
std::move(dump_reader), &cache_loader);
|
||||
ASSERT_OK(s);
|
||||
s = cache_loader->RestoreCacheEntriesToSecondaryCache();
|
||||
ASSERT_OK(s);
|
||||
uint32_t load_insert = secondary_cache->num_inserts() - start_insert;
|
||||
uint32_t load_lookup = secondary_cache->num_lookups() - start_lookup;
|
||||
// check the number we inserted
|
||||
ASSERT_EQ(64, static_cast<int>(load_insert));
|
||||
ASSERT_EQ(0, static_cast<int>(load_lookup));
|
||||
ASSERT_OK(s);
|
||||
|
||||
Reopen(options);
|
||||
|
||||
// After load, we do the Get again
|
||||
start_insert = secondary_cache->num_inserts();
|
||||
start_lookup = secondary_cache->num_lookups();
|
||||
uint32_t cache_insert = tmp_cache->GetInsertCount();
|
||||
uint32_t cache_lookup = tmp_cache->GetLookupcount();
|
||||
for (int i = 0; i < N; i++) {
|
||||
v = Get(Key(i));
|
||||
ASSERT_EQ(v, value[i]);
|
||||
}
|
||||
uint32_t final_insert = secondary_cache->num_inserts() - start_insert;
|
||||
uint32_t final_lookup = secondary_cache->num_lookups() - start_lookup;
|
||||
// no insert to secondary cache
|
||||
ASSERT_EQ(0, static_cast<int>(final_insert));
|
||||
// lookup the secondary to get all blocks
|
||||
ASSERT_EQ(64, static_cast<int>(final_lookup));
|
||||
uint32_t block_insert = tmp_cache->GetInsertCount() - cache_insert;
|
||||
uint32_t block_lookup = tmp_cache->GetLookupcount() - cache_lookup;
|
||||
// Check the new block cache insert and lookup, should be no insert since all
|
||||
// blocks are from the secondary cache.
|
||||
ASSERT_EQ(0, static_cast<int>(block_insert));
|
||||
ASSERT_EQ(256, static_cast<int>(block_lookup));
|
||||
|
||||
fault_fs_->SetFailGetUniqueId(false);
|
||||
Destroy(options);
|
||||
}
|
||||
|
||||
TEST_F(DBSecondaryCacheTest, LRUCacheDumpLoadWithFilter) {
|
||||
LRUCacheOptions cache_opts(1024 * 1024, 0, false, 0.5, nullptr,
|
||||
kDefaultToAdaptiveMutex, kDontChargeCacheMetadata);
|
||||
LRUCacheWithStat* tmp_cache = new LRUCacheWithStat(
|
||||
cache_opts.capacity, cache_opts.num_shard_bits,
|
||||
cache_opts.strict_capacity_limit, cache_opts.high_pri_pool_ratio,
|
||||
cache_opts.memory_allocator, cache_opts.use_adaptive_mutex,
|
||||
cache_opts.metadata_charge_policy, cache_opts.secondary_cache);
|
||||
std::shared_ptr<Cache> cache(tmp_cache);
|
||||
BlockBasedTableOptions table_options;
|
||||
table_options.block_cache = cache;
|
||||
table_options.block_size = 4 * 1024;
|
||||
Options options = GetDefaultOptions();
|
||||
options.create_if_missing = true;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
options.env = fault_env_.get();
|
||||
std::string dbname1 = test::PerThreadDBPath("db_1");
|
||||
ASSERT_OK(DestroyDB(dbname1, options));
|
||||
DB* db1 = nullptr;
|
||||
ASSERT_OK(DB::Open(options, dbname1, &db1));
|
||||
std::string dbname2 = test::PerThreadDBPath("db_2");
|
||||
ASSERT_OK(DestroyDB(dbname2, options));
|
||||
DB* db2 = nullptr;
|
||||
ASSERT_OK(DB::Open(options, dbname2, &db2));
|
||||
fault_fs_->SetFailGetUniqueId(true);
|
||||
|
||||
// write the KVs to db1
|
||||
Random rnd(301);
|
||||
const int N = 256;
|
||||
std::vector<std::string> value1;
|
||||
WriteOptions wo;
|
||||
char buf[1000];
|
||||
memset(buf, 'a', 1000);
|
||||
value1.resize(N);
|
||||
for (int i = 0; i < N; i++) {
|
||||
std::string p_v(buf, 1000);
|
||||
value1[i] = p_v;
|
||||
ASSERT_OK(db1->Put(wo, Key(i), p_v));
|
||||
}
|
||||
ASSERT_OK(db1->Flush(FlushOptions()));
|
||||
Slice bg("a");
|
||||
Slice ed("b");
|
||||
ASSERT_OK(db1->CompactRange(CompactRangeOptions(), &bg, &ed));
|
||||
|
||||
// Write the KVs to DB2
|
||||
std::vector<std::string> value2;
|
||||
memset(buf, 'b', 1000);
|
||||
value2.resize(N);
|
||||
for (int i = 0; i < N; i++) {
|
||||
std::string p_v(buf, 1000);
|
||||
value2[i] = p_v;
|
||||
ASSERT_OK(db2->Put(wo, Key(i), p_v));
|
||||
}
|
||||
ASSERT_OK(db2->Flush(FlushOptions()));
|
||||
ASSERT_OK(db2->CompactRange(CompactRangeOptions(), &bg, &ed));
|
||||
|
||||
// do th eread for all the key value pairs, so all the blocks should be in
|
||||
// cache
|
||||
uint32_t start_insert = tmp_cache->GetInsertCount();
|
||||
uint32_t start_lookup = tmp_cache->GetLookupcount();
|
||||
ReadOptions ro;
|
||||
std::string v;
|
||||
for (int i = 0; i < N; i++) {
|
||||
ASSERT_OK(db1->Get(ro, Key(i), &v));
|
||||
ASSERT_EQ(v, value1[i]);
|
||||
}
|
||||
for (int i = 0; i < N; i++) {
|
||||
ASSERT_OK(db2->Get(ro, Key(i), &v));
|
||||
ASSERT_EQ(v, value2[i]);
|
||||
}
|
||||
uint32_t dump_insert = tmp_cache->GetInsertCount() - start_insert;
|
||||
uint32_t dump_lookup = tmp_cache->GetLookupcount() - start_lookup;
|
||||
ASSERT_EQ(128,
|
||||
static_cast<int>(dump_insert)); // the insert in the block cache
|
||||
ASSERT_EQ(512,
|
||||
static_cast<int>(dump_lookup)); // the lookup in the block cache
|
||||
// We have enough blocks in the block cache
|
||||
|
||||
CacheDumpOptions cd_options;
|
||||
cd_options.clock = fault_env_->GetSystemClock().get();
|
||||
std::string dump_path = db1->GetName() + "/cache_dump";
|
||||
std::unique_ptr<CacheDumpWriter> dump_writer;
|
||||
Status s = NewToFileCacheDumpWriter(fault_fs_, FileOptions(), dump_path,
|
||||
&dump_writer);
|
||||
ASSERT_OK(s);
|
||||
std::unique_ptr<CacheDumper> cache_dumper;
|
||||
s = NewDefaultCacheDumper(cd_options, cache, std::move(dump_writer),
|
||||
&cache_dumper);
|
||||
ASSERT_OK(s);
|
||||
std::vector<DB*> db_list;
|
||||
db_list.push_back(db1);
|
||||
s = cache_dumper->SetDumpFilter(db_list);
|
||||
ASSERT_OK(s);
|
||||
s = cache_dumper->DumpCacheEntriesToWriter();
|
||||
ASSERT_OK(s);
|
||||
cache_dumper.reset();
|
||||
|
||||
// we have a new cache it is empty, then, before we do the Get, we do the
|
||||
// dumpload
|
||||
std::shared_ptr<TestSecondaryCache> secondary_cache =
|
||||
std::make_shared<TestSecondaryCache>(2048 * 1024);
|
||||
cache_opts.secondary_cache = secondary_cache;
|
||||
tmp_cache = new LRUCacheWithStat(
|
||||
cache_opts.capacity, cache_opts.num_shard_bits,
|
||||
cache_opts.strict_capacity_limit, cache_opts.high_pri_pool_ratio,
|
||||
cache_opts.memory_allocator, cache_opts.use_adaptive_mutex,
|
||||
cache_opts.metadata_charge_policy, cache_opts.secondary_cache);
|
||||
std::shared_ptr<Cache> cache_new(tmp_cache);
|
||||
table_options.block_cache = cache_new;
|
||||
table_options.block_size = 4 * 1024;
|
||||
options.create_if_missing = true;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
options.env = fault_env_.get();
|
||||
|
||||
// Start the cache loading process
|
||||
start_insert = secondary_cache->num_inserts();
|
||||
start_lookup = secondary_cache->num_lookups();
|
||||
std::unique_ptr<CacheDumpReader> dump_reader;
|
||||
s = NewFromFileCacheDumpReader(fault_fs_, FileOptions(), dump_path,
|
||||
&dump_reader);
|
||||
ASSERT_OK(s);
|
||||
std::unique_ptr<CacheDumpedLoader> cache_loader;
|
||||
s = NewDefaultCacheDumpedLoader(cd_options, table_options, secondary_cache,
|
||||
std::move(dump_reader), &cache_loader);
|
||||
ASSERT_OK(s);
|
||||
s = cache_loader->RestoreCacheEntriesToSecondaryCache();
|
||||
ASSERT_OK(s);
|
||||
uint32_t load_insert = secondary_cache->num_inserts() - start_insert;
|
||||
uint32_t load_lookup = secondary_cache->num_lookups() - start_lookup;
|
||||
// check the number we inserted
|
||||
ASSERT_EQ(64, static_cast<int>(load_insert));
|
||||
ASSERT_EQ(0, static_cast<int>(load_lookup));
|
||||
ASSERT_OK(s);
|
||||
|
||||
ASSERT_OK(db1->Close());
|
||||
delete db1;
|
||||
ASSERT_OK(DB::Open(options, dbname1, &db1));
|
||||
|
||||
// After load, we do the Get again. To validate the cache, we do not allow any
|
||||
// I/O, so we set the file system to false.
|
||||
IOStatus error_msg = IOStatus::IOError("Retryable IO Error");
|
||||
fault_fs_->SetFilesystemActive(false, error_msg);
|
||||
start_insert = secondary_cache->num_inserts();
|
||||
start_lookup = secondary_cache->num_lookups();
|
||||
uint32_t cache_insert = tmp_cache->GetInsertCount();
|
||||
uint32_t cache_lookup = tmp_cache->GetLookupcount();
|
||||
for (int i = 0; i < N; i++) {
|
||||
ASSERT_OK(db1->Get(ro, Key(i), &v));
|
||||
ASSERT_EQ(v, value1[i]);
|
||||
}
|
||||
uint32_t final_insert = secondary_cache->num_inserts() - start_insert;
|
||||
uint32_t final_lookup = secondary_cache->num_lookups() - start_lookup;
|
||||
// no insert to secondary cache
|
||||
ASSERT_EQ(0, static_cast<int>(final_insert));
|
||||
// lookup the secondary to get all blocks
|
||||
ASSERT_EQ(64, static_cast<int>(final_lookup));
|
||||
uint32_t block_insert = tmp_cache->GetInsertCount() - cache_insert;
|
||||
uint32_t block_lookup = tmp_cache->GetLookupcount() - cache_lookup;
|
||||
// Check the new block cache insert and lookup, should be no insert since all
|
||||
// blocks are from the secondary cache.
|
||||
ASSERT_EQ(0, static_cast<int>(block_insert));
|
||||
ASSERT_EQ(256, static_cast<int>(block_lookup));
|
||||
fault_fs_->SetFailGetUniqueId(false);
|
||||
fault_fs_->SetFilesystemActive(true);
|
||||
delete db1;
|
||||
delete db2;
|
||||
ASSERT_OK(DestroyDB(dbname1, options));
|
||||
ASSERT_OK(DestroyDB(dbname2, options));
|
||||
}
|
||||
|
||||
// Test the option not to use the secondary cache in a certain DB.
|
||||
TEST_F(DBSecondaryCacheTest, TestSecondaryCacheOptionBasic) {
|
||||
LRUCacheOptions opts(4 * 1024, 0, false, 0.5, nullptr,
|
||||
kDefaultToAdaptiveMutex, kDontChargeCacheMetadata);
|
||||
std::shared_ptr<TestSecondaryCache> secondary_cache(
|
||||
new TestSecondaryCache(2048 * 1024));
|
||||
opts.secondary_cache = secondary_cache;
|
||||
std::shared_ptr<Cache> cache = NewLRUCache(opts);
|
||||
BlockBasedTableOptions table_options;
|
||||
table_options.block_cache = cache;
|
||||
table_options.block_size = 4 * 1024;
|
||||
Options options = GetDefaultOptions();
|
||||
options.create_if_missing = true;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
options.env = fault_env_.get();
|
||||
fault_fs_->SetFailGetUniqueId(true);
|
||||
options.lowest_used_cache_tier = CacheTier::kVolatileTier;
|
||||
|
||||
// Set the file paranoid check, so after flush, the file will be read
|
||||
// all the blocks will be accessed.
|
||||
options.paranoid_file_checks = true;
|
||||
DestroyAndReopen(options);
|
||||
std::string session_id;
|
||||
ASSERT_OK(db_->GetDbSessionId(session_id));
|
||||
secondary_cache->SetDbSessionId(session_id);
|
||||
Random rnd(301);
|
||||
const int N = 6;
|
||||
for (int i = 0; i < N; i++) {
|
||||
std::string p_v = rnd.RandomString(1007);
|
||||
ASSERT_OK(Put(Key(i), p_v));
|
||||
}
|
||||
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
for (int i = 0; i < N; i++) {
|
||||
std::string p_v = rnd.RandomString(1007);
|
||||
ASSERT_OK(Put(Key(i + 70), p_v));
|
||||
}
|
||||
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
// Flush will trigger the paranoid check and read blocks. But only block cache
|
||||
// will be read. No operations for secondary cache.
|
||||
ASSERT_EQ(secondary_cache->num_inserts(), 0u);
|
||||
ASSERT_EQ(secondary_cache->num_lookups(), 0u);
|
||||
|
||||
Compact("a", "z");
|
||||
|
||||
// Compaction will also insert and evict blocks, no operations to the block
|
||||
// cache. No operations for secondary cache.
|
||||
ASSERT_EQ(secondary_cache->num_inserts(), 0u);
|
||||
ASSERT_EQ(secondary_cache->num_lookups(), 0u);
|
||||
|
||||
std::string v = Get(Key(0));
|
||||
ASSERT_EQ(1007, v.size());
|
||||
|
||||
// Check the data in first block. Cache miss, direclty read from SST file.
|
||||
ASSERT_EQ(secondary_cache->num_inserts(), 0u);
|
||||
ASSERT_EQ(secondary_cache->num_lookups(), 0u);
|
||||
|
||||
v = Get(Key(5));
|
||||
ASSERT_EQ(1007, v.size());
|
||||
|
||||
// Check the second block.
|
||||
ASSERT_EQ(secondary_cache->num_inserts(), 0u);
|
||||
ASSERT_EQ(secondary_cache->num_lookups(), 0u);
|
||||
|
||||
v = Get(Key(5));
|
||||
ASSERT_EQ(1007, v.size());
|
||||
|
||||
// block cache hit
|
||||
ASSERT_EQ(secondary_cache->num_inserts(), 0u);
|
||||
ASSERT_EQ(secondary_cache->num_lookups(), 0u);
|
||||
|
||||
v = Get(Key(70));
|
||||
ASSERT_EQ(1007, v.size());
|
||||
|
||||
// Check the first block in the second SST file. Cache miss and trigger SST
|
||||
// file read. No operations for secondary cache.
|
||||
ASSERT_EQ(secondary_cache->num_inserts(), 0u);
|
||||
ASSERT_EQ(secondary_cache->num_lookups(), 0u);
|
||||
|
||||
v = Get(Key(75));
|
||||
ASSERT_EQ(1007, v.size());
|
||||
|
||||
// Check the second block in the second SST file. Cache miss and trigger SST
|
||||
// file read. No operations for secondary cache.
|
||||
ASSERT_EQ(secondary_cache->num_inserts(), 0u);
|
||||
ASSERT_EQ(secondary_cache->num_lookups(), 0u);
|
||||
|
||||
Destroy(options);
|
||||
}
|
||||
|
||||
// We disable the secondary cache in DBOptions at first. Close and reopen the DB
|
||||
// with new options, which set the lowest_used_cache_tier to
|
||||
// kNonVolatileBlockTier. So secondary cache will be used.
|
||||
TEST_F(DBSecondaryCacheTest, TestSecondaryCacheOptionChange) {
|
||||
LRUCacheOptions opts(4 * 1024, 0, false, 0.5, nullptr,
|
||||
kDefaultToAdaptiveMutex, kDontChargeCacheMetadata);
|
||||
std::shared_ptr<TestSecondaryCache> secondary_cache(
|
||||
new TestSecondaryCache(2048 * 1024));
|
||||
opts.secondary_cache = secondary_cache;
|
||||
std::shared_ptr<Cache> cache = NewLRUCache(opts);
|
||||
BlockBasedTableOptions table_options;
|
||||
table_options.block_cache = cache;
|
||||
table_options.block_size = 4 * 1024;
|
||||
Options options = GetDefaultOptions();
|
||||
options.create_if_missing = true;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
options.env = fault_env_.get();
|
||||
fault_fs_->SetFailGetUniqueId(true);
|
||||
options.lowest_used_cache_tier = CacheTier::kVolatileTier;
|
||||
|
||||
// Set the file paranoid check, so after flush, the file will be read
|
||||
// all the blocks will be accessed.
|
||||
options.paranoid_file_checks = true;
|
||||
DestroyAndReopen(options);
|
||||
std::string session_id;
|
||||
ASSERT_OK(db_->GetDbSessionId(session_id));
|
||||
secondary_cache->SetDbSessionId(session_id);
|
||||
Random rnd(301);
|
||||
const int N = 6;
|
||||
for (int i = 0; i < N; i++) {
|
||||
std::string p_v = rnd.RandomString(1007);
|
||||
ASSERT_OK(Put(Key(i), p_v));
|
||||
}
|
||||
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
for (int i = 0; i < N; i++) {
|
||||
std::string p_v = rnd.RandomString(1007);
|
||||
ASSERT_OK(Put(Key(i + 70), p_v));
|
||||
}
|
||||
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
// Flush will trigger the paranoid check and read blocks. But only block cache
|
||||
// will be read.
|
||||
ASSERT_EQ(secondary_cache->num_inserts(), 0u);
|
||||
ASSERT_EQ(secondary_cache->num_lookups(), 0u);
|
||||
|
||||
Compact("a", "z");
|
||||
|
||||
// Compaction will also insert and evict blocks, no operations to the block
|
||||
// cache.
|
||||
ASSERT_EQ(secondary_cache->num_inserts(), 0u);
|
||||
ASSERT_EQ(secondary_cache->num_lookups(), 0u);
|
||||
|
||||
std::string v = Get(Key(0));
|
||||
ASSERT_EQ(1007, v.size());
|
||||
|
||||
// Check the data in first block. Cache miss, direclty read from SST file.
|
||||
ASSERT_EQ(secondary_cache->num_inserts(), 0u);
|
||||
ASSERT_EQ(secondary_cache->num_lookups(), 0u);
|
||||
|
||||
v = Get(Key(5));
|
||||
ASSERT_EQ(1007, v.size());
|
||||
|
||||
// Check the second block.
|
||||
ASSERT_EQ(secondary_cache->num_inserts(), 0u);
|
||||
ASSERT_EQ(secondary_cache->num_lookups(), 0u);
|
||||
|
||||
v = Get(Key(5));
|
||||
ASSERT_EQ(1007, v.size());
|
||||
|
||||
// block cache hit
|
||||
ASSERT_EQ(secondary_cache->num_inserts(), 0u);
|
||||
ASSERT_EQ(secondary_cache->num_lookups(), 0u);
|
||||
|
||||
// Change the option to enable secondary cache after we Reopen the DB
|
||||
options.lowest_used_cache_tier = CacheTier::kNonVolatileBlockTier;
|
||||
Reopen(options);
|
||||
|
||||
v = Get(Key(70));
|
||||
ASSERT_EQ(1007, v.size());
|
||||
|
||||
// Enable the secondary cache, trigger lookup of the first block in second SST
|
||||
ASSERT_EQ(secondary_cache->num_inserts(), 0u);
|
||||
ASSERT_EQ(secondary_cache->num_lookups(), 1u);
|
||||
|
||||
v = Get(Key(75));
|
||||
ASSERT_EQ(1007, v.size());
|
||||
|
||||
// trigger lookup of the second block in second SST
|
||||
ASSERT_EQ(secondary_cache->num_inserts(), 0u);
|
||||
ASSERT_EQ(secondary_cache->num_lookups(), 2u);
|
||||
Destroy(options);
|
||||
}
|
||||
|
||||
// Two DB test. We create 2 DBs sharing the same block cache and secondary
|
||||
// cache. We diable the secondary cache option for DB2.
|
||||
TEST_F(DBSecondaryCacheTest, TestSecondaryCacheOptionTwoDB) {
|
||||
LRUCacheOptions opts(4 * 1024, 0, false, 0.5, nullptr,
|
||||
kDefaultToAdaptiveMutex, kDontChargeCacheMetadata);
|
||||
std::shared_ptr<TestSecondaryCache> secondary_cache(
|
||||
new TestSecondaryCache(2048 * 1024));
|
||||
opts.secondary_cache = secondary_cache;
|
||||
std::shared_ptr<Cache> cache = NewLRUCache(opts);
|
||||
BlockBasedTableOptions table_options;
|
||||
table_options.block_cache = cache;
|
||||
table_options.block_size = 4 * 1024;
|
||||
Options options = GetDefaultOptions();
|
||||
options.create_if_missing = true;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
options.env = fault_env_.get();
|
||||
options.paranoid_file_checks = true;
|
||||
std::string dbname1 = test::PerThreadDBPath("db_t_1");
|
||||
ASSERT_OK(DestroyDB(dbname1, options));
|
||||
DB* db1 = nullptr;
|
||||
ASSERT_OK(DB::Open(options, dbname1, &db1));
|
||||
std::string dbname2 = test::PerThreadDBPath("db_t_2");
|
||||
ASSERT_OK(DestroyDB(dbname2, options));
|
||||
DB* db2 = nullptr;
|
||||
Options options2 = options;
|
||||
options2.lowest_used_cache_tier = CacheTier::kVolatileTier;
|
||||
ASSERT_OK(DB::Open(options2, dbname2, &db2));
|
||||
fault_fs_->SetFailGetUniqueId(true);
|
||||
|
||||
// Set the file paranoid check, so after flush, the file will be read
|
||||
// all the blocks will be accessed.
|
||||
std::string session_id;
|
||||
ASSERT_OK(db1->GetDbSessionId(session_id));
|
||||
secondary_cache->SetDbSessionId(session_id);
|
||||
|
||||
WriteOptions wo;
|
||||
Random rnd(301);
|
||||
const int N = 6;
|
||||
for (int i = 0; i < N; i++) {
|
||||
std::string p_v = rnd.RandomString(1007);
|
||||
ASSERT_OK(db1->Put(wo, Key(i), p_v));
|
||||
}
|
||||
|
||||
ASSERT_EQ(secondary_cache->num_inserts(), 0u);
|
||||
ASSERT_EQ(secondary_cache->num_lookups(), 0u);
|
||||
ASSERT_OK(db1->Flush(FlushOptions()));
|
||||
|
||||
ASSERT_EQ(secondary_cache->num_inserts(), 0u);
|
||||
ASSERT_EQ(secondary_cache->num_lookups(), 2u);
|
||||
|
||||
for (int i = 0; i < N; i++) {
|
||||
std::string p_v = rnd.RandomString(1007);
|
||||
ASSERT_OK(db2->Put(wo, Key(i), p_v));
|
||||
}
|
||||
|
||||
// No change in the secondary cache, since it is disabled in DB2
|
||||
ASSERT_EQ(secondary_cache->num_inserts(), 0u);
|
||||
ASSERT_EQ(secondary_cache->num_lookups(), 2u);
|
||||
ASSERT_OK(db2->Flush(FlushOptions()));
|
||||
ASSERT_EQ(secondary_cache->num_inserts(), 1u);
|
||||
ASSERT_EQ(secondary_cache->num_lookups(), 2u);
|
||||
|
||||
Slice bg("a");
|
||||
Slice ed("b");
|
||||
ASSERT_OK(db1->CompactRange(CompactRangeOptions(), &bg, &ed));
|
||||
ASSERT_OK(db2->CompactRange(CompactRangeOptions(), &bg, &ed));
|
||||
|
||||
ASSERT_EQ(secondary_cache->num_inserts(), 1u);
|
||||
ASSERT_EQ(secondary_cache->num_lookups(), 2u);
|
||||
|
||||
ReadOptions ro;
|
||||
std::string v;
|
||||
ASSERT_OK(db1->Get(ro, Key(0), &v));
|
||||
ASSERT_EQ(1007, v.size());
|
||||
|
||||
// DB 1 has lookup block 1 and it is miss in block cache, trigger secondary
|
||||
// cache lookup
|
||||
ASSERT_EQ(secondary_cache->num_inserts(), 1u);
|
||||
ASSERT_EQ(secondary_cache->num_lookups(), 3u);
|
||||
|
||||
ASSERT_OK(db1->Get(ro, Key(5), &v));
|
||||
ASSERT_EQ(1007, v.size());
|
||||
|
||||
// DB 1 lookup the second block and it is miss in block cache, trigger
|
||||
// secondary cache lookup
|
||||
ASSERT_EQ(secondary_cache->num_inserts(), 1u);
|
||||
ASSERT_EQ(secondary_cache->num_lookups(), 4u);
|
||||
|
||||
ASSERT_OK(db2->Get(ro, Key(0), &v));
|
||||
ASSERT_EQ(1007, v.size());
|
||||
|
||||
// For db2, it is not enabled with secondary cache, so no search in the
|
||||
// secondary cache
|
||||
ASSERT_EQ(secondary_cache->num_inserts(), 1u);
|
||||
ASSERT_EQ(secondary_cache->num_lookups(), 4u);
|
||||
|
||||
ASSERT_OK(db2->Get(ro, Key(5), &v));
|
||||
ASSERT_EQ(1007, v.size());
|
||||
|
||||
// For db2, it is not enabled with secondary cache, so no search in the
|
||||
// secondary cache
|
||||
ASSERT_EQ(secondary_cache->num_inserts(), 1u);
|
||||
ASSERT_EQ(secondary_cache->num_lookups(), 4u);
|
||||
|
||||
fault_fs_->SetFailGetUniqueId(false);
|
||||
fault_fs_->SetFilesystemActive(true);
|
||||
delete db1;
|
||||
delete db2;
|
||||
ASSERT_OK(DestroyDB(dbname1, options));
|
||||
ASSERT_OK(DestroyDB(dbname2, options));
|
||||
}
|
||||
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
@@ -24,7 +24,7 @@ mkdir -p $COVERAGE_DIR
|
||||
|
||||
# Find all gcno files to generate the coverage report
|
||||
|
||||
PYTHON=${1:-`which python`}
|
||||
PYTHON=${1:-`which python3`}
|
||||
echo -e "Using $PYTHON"
|
||||
GCNO_FILES=`find $ROOT -name "*.gcno"`
|
||||
$GCOV --preserve-paths --relative-only --no-output $GCNO_FILES 2>/dev/null |
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
#include <string>
|
||||
#include "db/db_impl/db_impl.h"
|
||||
#include "db/db_iter.h"
|
||||
#include "db/dbformat.h"
|
||||
#include "db/range_del_aggregator.h"
|
||||
#include "memory/arena.h"
|
||||
#include "options/cf_options.h"
|
||||
@@ -34,7 +33,13 @@ class Version;
|
||||
// the same as the inner DBIter.
|
||||
class ArenaWrappedDBIter : public Iterator {
|
||||
public:
|
||||
virtual ~ArenaWrappedDBIter() { db_iter_->~DBIter(); }
|
||||
~ArenaWrappedDBIter() override {
|
||||
if (db_iter_ != nullptr) {
|
||||
db_iter_->~DBIter();
|
||||
} else {
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
|
||||
// Get the arena to be used to allocate memory for DBIter to be wrapped,
|
||||
// as well as child iterators in it.
|
||||
@@ -90,7 +95,7 @@ class ArenaWrappedDBIter : public Iterator {
|
||||
}
|
||||
|
||||
private:
|
||||
DBIter* db_iter_;
|
||||
DBIter* db_iter_ = nullptr;
|
||||
Arena arena_;
|
||||
uint64_t sv_number_;
|
||||
ColumnFamilyData* cfd_ = nullptr;
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#include "db/dbformat.h"
|
||||
#include "test_util/testharness.h"
|
||||
#include "test_util/testutil.h"
|
||||
#include "util/vector_iterator.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
@@ -67,7 +68,7 @@ TEST(BlobCountingIteratorTest, CountBlobs) {
|
||||
|
||||
assert(keys.size() == values.size());
|
||||
|
||||
test::VectorIterator input(keys, values);
|
||||
VectorIterator input(keys, values);
|
||||
BlobGarbageMeter blob_garbage_meter;
|
||||
|
||||
BlobCountingIterator blob_counter(&input, &blob_garbage_meter);
|
||||
@@ -307,7 +308,7 @@ TEST(BlobCountingIteratorTest, CorruptBlobIndex) {
|
||||
|
||||
assert(keys.size() == values.size());
|
||||
|
||||
test::VectorIterator input(keys, values);
|
||||
VectorIterator input(keys, values);
|
||||
BlobGarbageMeter blob_garbage_meter;
|
||||
|
||||
BlobCountingIterator blob_counter(&input, &blob_garbage_meter);
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include "db/blob/blob_index.h"
|
||||
#include "db/blob/blob_log_format.h"
|
||||
#include "db/blob/blob_log_writer.h"
|
||||
#include "db/event_helpers.h"
|
||||
#include "db/version_set.h"
|
||||
#include "file/filename.h"
|
||||
#include "file/read_write_util.h"
|
||||
@@ -36,13 +37,14 @@ BlobFileBuilder::BlobFileBuilder(
|
||||
Env::WriteLifeTimeHint write_hint,
|
||||
const std::shared_ptr<IOTracer>& io_tracer,
|
||||
BlobFileCompletionCallback* blob_callback,
|
||||
BlobFileCreationReason creation_reason,
|
||||
std::vector<std::string>* blob_file_paths,
|
||||
std::vector<BlobFileAddition>* blob_file_additions)
|
||||
: BlobFileBuilder([versions]() { return versions->NewFileNumber(); }, fs,
|
||||
immutable_options, mutable_cf_options, file_options,
|
||||
job_id, column_family_id, column_family_name, io_priority,
|
||||
write_hint, io_tracer, blob_callback, blob_file_paths,
|
||||
blob_file_additions) {}
|
||||
write_hint, io_tracer, blob_callback, creation_reason,
|
||||
blob_file_paths, blob_file_additions) {}
|
||||
|
||||
BlobFileBuilder::BlobFileBuilder(
|
||||
std::function<uint64_t()> file_number_generator, FileSystem* fs,
|
||||
@@ -53,6 +55,7 @@ BlobFileBuilder::BlobFileBuilder(
|
||||
Env::WriteLifeTimeHint write_hint,
|
||||
const std::shared_ptr<IOTracer>& io_tracer,
|
||||
BlobFileCompletionCallback* blob_callback,
|
||||
BlobFileCreationReason creation_reason,
|
||||
std::vector<std::string>* blob_file_paths,
|
||||
std::vector<BlobFileAddition>* blob_file_additions)
|
||||
: file_number_generator_(std::move(file_number_generator)),
|
||||
@@ -69,6 +72,7 @@ BlobFileBuilder::BlobFileBuilder(
|
||||
write_hint_(write_hint),
|
||||
io_tracer_(io_tracer),
|
||||
blob_callback_(blob_callback),
|
||||
creation_reason_(creation_reason),
|
||||
blob_file_paths_(blob_file_paths),
|
||||
blob_file_additions_(blob_file_additions),
|
||||
blob_count_(0),
|
||||
@@ -161,6 +165,11 @@ Status BlobFileBuilder::OpenBlobFileIfNeeded() {
|
||||
std::string blob_file_path =
|
||||
BlobFileName(immutable_options_->cf_paths.front().path, blob_file_number);
|
||||
|
||||
if (blob_callback_) {
|
||||
blob_callback_->OnBlobFileCreationStarted(
|
||||
blob_file_path, column_family_name_, job_id_, creation_reason_);
|
||||
}
|
||||
|
||||
std::unique_ptr<FSWritableFile> file;
|
||||
|
||||
{
|
||||
@@ -305,6 +314,13 @@ Status BlobFileBuilder::CloseBlobFile() {
|
||||
|
||||
const uint64_t blob_file_number = writer_->get_log_number();
|
||||
|
||||
if (blob_callback_) {
|
||||
s = blob_callback_->OnBlobFileCompleted(
|
||||
blob_file_paths_->back(), column_family_name_, job_id_,
|
||||
blob_file_number, creation_reason_, s, checksum_value, checksum_method,
|
||||
blob_count_, blob_bytes_);
|
||||
}
|
||||
|
||||
assert(blob_file_additions_);
|
||||
blob_file_additions_->emplace_back(blob_file_number, blob_count_, blob_bytes_,
|
||||
std::move(checksum_method),
|
||||
@@ -316,9 +332,6 @@ Status BlobFileBuilder::CloseBlobFile() {
|
||||
" total blobs, %" PRIu64 " total bytes",
|
||||
column_family_name_.c_str(), job_id_, blob_file_number,
|
||||
blob_count_, blob_bytes_);
|
||||
if (blob_callback_) {
|
||||
s = blob_callback_->OnBlobFileCompleted(blob_file_paths_->back());
|
||||
}
|
||||
|
||||
writer_.reset();
|
||||
blob_count_ = 0;
|
||||
@@ -340,15 +353,18 @@ Status BlobFileBuilder::CloseBlobFileIfNeeded() {
|
||||
return CloseBlobFile();
|
||||
}
|
||||
|
||||
void BlobFileBuilder::Abandon() {
|
||||
void BlobFileBuilder::Abandon(const Status& s) {
|
||||
if (!IsBlobFileOpen()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (blob_callback_) {
|
||||
// BlobFileBuilder::Abandon() is called because of error while writing to
|
||||
// Blob files. So we can ignore the below error.
|
||||
blob_callback_->OnBlobFileCompleted(blob_file_paths_->back())
|
||||
blob_callback_
|
||||
->OnBlobFileCompleted(blob_file_paths_->back(), column_family_name_,
|
||||
job_id_, writer_->get_log_number(),
|
||||
creation_reason_, s, "", "", blob_count_,
|
||||
blob_bytes_)
|
||||
.PermitUncheckedError();
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include "rocksdb/compression_type.h"
|
||||
#include "rocksdb/env.h"
|
||||
#include "rocksdb/rocksdb_namespace.h"
|
||||
#include "rocksdb/types.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
@@ -41,6 +42,7 @@ class BlobFileBuilder {
|
||||
Env::WriteLifeTimeHint write_hint,
|
||||
const std::shared_ptr<IOTracer>& io_tracer,
|
||||
BlobFileCompletionCallback* blob_callback,
|
||||
BlobFileCreationReason creation_reason,
|
||||
std::vector<std::string>* blob_file_paths,
|
||||
std::vector<BlobFileAddition>* blob_file_additions);
|
||||
|
||||
@@ -54,6 +56,7 @@ class BlobFileBuilder {
|
||||
Env::WriteLifeTimeHint write_hint,
|
||||
const std::shared_ptr<IOTracer>& io_tracer,
|
||||
BlobFileCompletionCallback* blob_callback,
|
||||
BlobFileCreationReason creation_reason,
|
||||
std::vector<std::string>* blob_file_paths,
|
||||
std::vector<BlobFileAddition>* blob_file_additions);
|
||||
|
||||
@@ -64,7 +67,7 @@ class BlobFileBuilder {
|
||||
|
||||
Status Add(const Slice& key, const Slice& value, std::string* blob_index);
|
||||
Status Finish();
|
||||
void Abandon();
|
||||
void Abandon(const Status& s);
|
||||
|
||||
private:
|
||||
bool IsBlobFileOpen() const;
|
||||
@@ -89,6 +92,7 @@ class BlobFileBuilder {
|
||||
Env::WriteLifeTimeHint write_hint_;
|
||||
std::shared_ptr<IOTracer> io_tracer_;
|
||||
BlobFileCompletionCallback* blob_callback_;
|
||||
BlobFileCreationReason creation_reason_;
|
||||
std::vector<std::string>* blob_file_paths_;
|
||||
std::vector<BlobFileAddition>* blob_file_additions_;
|
||||
std::unique_ptr<BlobLogWriter> writer_;
|
||||
|
||||
@@ -39,9 +39,10 @@ class TestFileNumberGenerator {
|
||||
|
||||
class BlobFileBuilderTest : public testing::Test {
|
||||
protected:
|
||||
BlobFileBuilderTest() : mock_env_(Env::Default()) {
|
||||
fs_ = mock_env_.GetFileSystem().get();
|
||||
clock_ = mock_env_.GetSystemClock().get();
|
||||
BlobFileBuilderTest() {
|
||||
mock_env_.reset(MockEnv::Create(Env::Default()));
|
||||
fs_ = mock_env_->GetFileSystem().get();
|
||||
clock_ = mock_env_->GetSystemClock().get();
|
||||
}
|
||||
|
||||
void VerifyBlobFile(uint64_t blob_file_number,
|
||||
@@ -108,7 +109,7 @@ class BlobFileBuilderTest : public testing::Test {
|
||||
ASSERT_EQ(footer.expiration_range, ExpirationRange());
|
||||
}
|
||||
|
||||
MockEnv mock_env_;
|
||||
std::unique_ptr<Env> mock_env_;
|
||||
FileSystem* fs_;
|
||||
SystemClock* clock_;
|
||||
FileOptions file_options_;
|
||||
@@ -123,11 +124,11 @@ TEST_F(BlobFileBuilderTest, BuildAndCheckOneFile) {
|
||||
|
||||
Options options;
|
||||
options.cf_paths.emplace_back(
|
||||
test::PerThreadDBPath(&mock_env_,
|
||||
test::PerThreadDBPath(mock_env_.get(),
|
||||
"BlobFileBuilderTest_BuildAndCheckOneFile"),
|
||||
0);
|
||||
options.enable_blob_files = true;
|
||||
options.env = &mock_env_;
|
||||
options.env = mock_env_.get();
|
||||
|
||||
ImmutableOptions immutable_options(options);
|
||||
MutableCFOptions mutable_cf_options(options);
|
||||
@@ -145,7 +146,7 @@ TEST_F(BlobFileBuilderTest, BuildAndCheckOneFile) {
|
||||
TestFileNumberGenerator(), fs_, &immutable_options, &mutable_cf_options,
|
||||
&file_options_, job_id, column_family_id, column_family_name, io_priority,
|
||||
write_hint, nullptr /*IOTracer*/, nullptr /*BlobFileCompletionCallback*/,
|
||||
&blob_file_paths, &blob_file_additions);
|
||||
BlobFileCreationReason::kFlush, &blob_file_paths, &blob_file_additions);
|
||||
|
||||
std::vector<std::pair<std::string, std::string>> expected_key_value_pairs(
|
||||
number_of_blobs);
|
||||
@@ -206,12 +207,12 @@ TEST_F(BlobFileBuilderTest, BuildAndCheckMultipleFiles) {
|
||||
|
||||
Options options;
|
||||
options.cf_paths.emplace_back(
|
||||
test::PerThreadDBPath(&mock_env_,
|
||||
test::PerThreadDBPath(mock_env_.get(),
|
||||
"BlobFileBuilderTest_BuildAndCheckMultipleFiles"),
|
||||
0);
|
||||
options.enable_blob_files = true;
|
||||
options.blob_file_size = value_size;
|
||||
options.env = &mock_env_;
|
||||
options.env = mock_env_.get();
|
||||
|
||||
ImmutableOptions immutable_options(options);
|
||||
MutableCFOptions mutable_cf_options(options);
|
||||
@@ -229,7 +230,7 @@ TEST_F(BlobFileBuilderTest, BuildAndCheckMultipleFiles) {
|
||||
TestFileNumberGenerator(), fs_, &immutable_options, &mutable_cf_options,
|
||||
&file_options_, job_id, column_family_id, column_family_name, io_priority,
|
||||
write_hint, nullptr /*IOTracer*/, nullptr /*BlobFileCompletionCallback*/,
|
||||
&blob_file_paths, &blob_file_additions);
|
||||
BlobFileCreationReason::kFlush, &blob_file_paths, &blob_file_additions);
|
||||
|
||||
std::vector<std::pair<std::string, std::string>> expected_key_value_pairs(
|
||||
number_of_blobs);
|
||||
@@ -293,11 +294,12 @@ TEST_F(BlobFileBuilderTest, InlinedValues) {
|
||||
|
||||
Options options;
|
||||
options.cf_paths.emplace_back(
|
||||
test::PerThreadDBPath(&mock_env_, "BlobFileBuilderTest_InlinedValues"),
|
||||
test::PerThreadDBPath(mock_env_.get(),
|
||||
"BlobFileBuilderTest_InlinedValues"),
|
||||
0);
|
||||
options.enable_blob_files = true;
|
||||
options.min_blob_size = 1024;
|
||||
options.env = &mock_env_;
|
||||
options.env = mock_env_.get();
|
||||
|
||||
ImmutableOptions immutable_options(options);
|
||||
MutableCFOptions mutable_cf_options(options);
|
||||
@@ -315,7 +317,7 @@ TEST_F(BlobFileBuilderTest, InlinedValues) {
|
||||
TestFileNumberGenerator(), fs_, &immutable_options, &mutable_cf_options,
|
||||
&file_options_, job_id, column_family_id, column_family_name, io_priority,
|
||||
write_hint, nullptr /*IOTracer*/, nullptr /*BlobFileCompletionCallback*/,
|
||||
&blob_file_paths, &blob_file_additions);
|
||||
BlobFileCreationReason::kFlush, &blob_file_paths, &blob_file_additions);
|
||||
|
||||
for (size_t i = 0; i < number_of_blobs; ++i) {
|
||||
const std::string key = std::to_string(i);
|
||||
@@ -347,10 +349,11 @@ TEST_F(BlobFileBuilderTest, Compression) {
|
||||
|
||||
Options options;
|
||||
options.cf_paths.emplace_back(
|
||||
test::PerThreadDBPath(&mock_env_, "BlobFileBuilderTest_Compression"), 0);
|
||||
test::PerThreadDBPath(mock_env_.get(), "BlobFileBuilderTest_Compression"),
|
||||
0);
|
||||
options.enable_blob_files = true;
|
||||
options.blob_compression_type = kSnappyCompression;
|
||||
options.env = &mock_env_;
|
||||
options.env = mock_env_.get();
|
||||
|
||||
ImmutableOptions immutable_options(options);
|
||||
MutableCFOptions mutable_cf_options(options);
|
||||
@@ -368,7 +371,7 @@ TEST_F(BlobFileBuilderTest, Compression) {
|
||||
TestFileNumberGenerator(), fs_, &immutable_options, &mutable_cf_options,
|
||||
&file_options_, job_id, column_family_id, column_family_name, io_priority,
|
||||
write_hint, nullptr /*IOTracer*/, nullptr /*BlobFileCompletionCallback*/,
|
||||
&blob_file_paths, &blob_file_additions);
|
||||
BlobFileCreationReason::kFlush, &blob_file_paths, &blob_file_additions);
|
||||
|
||||
const std::string key("1");
|
||||
const std::string uncompressed_value(value_size, 'x');
|
||||
@@ -429,11 +432,12 @@ TEST_F(BlobFileBuilderTest, CompressionError) {
|
||||
|
||||
Options options;
|
||||
options.cf_paths.emplace_back(
|
||||
test::PerThreadDBPath(&mock_env_, "BlobFileBuilderTest_CompressionError"),
|
||||
test::PerThreadDBPath(mock_env_.get(),
|
||||
"BlobFileBuilderTest_CompressionError"),
|
||||
0);
|
||||
options.enable_blob_files = true;
|
||||
options.blob_compression_type = kSnappyCompression;
|
||||
options.env = &mock_env_;
|
||||
options.env = mock_env_.get();
|
||||
ImmutableOptions immutable_options(options);
|
||||
MutableCFOptions mutable_cf_options(options);
|
||||
|
||||
@@ -450,7 +454,7 @@ TEST_F(BlobFileBuilderTest, CompressionError) {
|
||||
TestFileNumberGenerator(), fs_, &immutable_options, &mutable_cf_options,
|
||||
&file_options_, job_id, column_family_id, column_family_name, io_priority,
|
||||
write_hint, nullptr /*IOTracer*/, nullptr /*BlobFileCompletionCallback*/,
|
||||
&blob_file_paths, &blob_file_additions);
|
||||
BlobFileCreationReason::kFlush, &blob_file_paths, &blob_file_additions);
|
||||
|
||||
SyncPoint::GetInstance()->SetCallBack("CompressData:TamperWithReturnValue",
|
||||
[](void* arg) {
|
||||
@@ -506,11 +510,12 @@ TEST_F(BlobFileBuilderTest, Checksum) {
|
||||
|
||||
Options options;
|
||||
options.cf_paths.emplace_back(
|
||||
test::PerThreadDBPath(&mock_env_, "BlobFileBuilderTest_Checksum"), 0);
|
||||
test::PerThreadDBPath(mock_env_.get(), "BlobFileBuilderTest_Checksum"),
|
||||
0);
|
||||
options.enable_blob_files = true;
|
||||
options.file_checksum_gen_factory =
|
||||
std::make_shared<DummyFileChecksumGenFactory>();
|
||||
options.env = &mock_env_;
|
||||
options.env = mock_env_.get();
|
||||
|
||||
ImmutableOptions immutable_options(options);
|
||||
MutableCFOptions mutable_cf_options(options);
|
||||
@@ -528,7 +533,7 @@ TEST_F(BlobFileBuilderTest, Checksum) {
|
||||
TestFileNumberGenerator(), fs_, &immutable_options, &mutable_cf_options,
|
||||
&file_options_, job_id, column_family_id, column_family_name, io_priority,
|
||||
write_hint, nullptr /*IOTracer*/, nullptr /*BlobFileCompletionCallback*/,
|
||||
&blob_file_paths, &blob_file_additions);
|
||||
BlobFileCreationReason::kFlush, &blob_file_paths, &blob_file_additions);
|
||||
|
||||
const std::string key("1");
|
||||
const std::string value("deadbeef");
|
||||
@@ -575,12 +580,12 @@ class BlobFileBuilderIOErrorTest
|
||||
: public testing::Test,
|
||||
public testing::WithParamInterface<std::string> {
|
||||
protected:
|
||||
BlobFileBuilderIOErrorTest()
|
||||
: mock_env_(Env::Default()),
|
||||
fs_(mock_env_.GetFileSystem().get()),
|
||||
sync_point_(GetParam()) {}
|
||||
BlobFileBuilderIOErrorTest() : sync_point_(GetParam()) {
|
||||
mock_env_.reset(MockEnv::Create(Env::Default()));
|
||||
fs_ = mock_env_->GetFileSystem().get();
|
||||
}
|
||||
|
||||
MockEnv mock_env_;
|
||||
std::unique_ptr<Env> mock_env_;
|
||||
FileSystem* fs_;
|
||||
FileOptions file_options_;
|
||||
std::string sync_point_;
|
||||
@@ -602,11 +607,12 @@ TEST_P(BlobFileBuilderIOErrorTest, IOError) {
|
||||
|
||||
Options options;
|
||||
options.cf_paths.emplace_back(
|
||||
test::PerThreadDBPath(&mock_env_, "BlobFileBuilderIOErrorTest_IOError"),
|
||||
test::PerThreadDBPath(mock_env_.get(),
|
||||
"BlobFileBuilderIOErrorTest_IOError"),
|
||||
0);
|
||||
options.enable_blob_files = true;
|
||||
options.blob_file_size = value_size;
|
||||
options.env = &mock_env_;
|
||||
options.env = mock_env_.get();
|
||||
|
||||
ImmutableOptions immutable_options(options);
|
||||
MutableCFOptions mutable_cf_options(options);
|
||||
@@ -624,7 +630,7 @@ TEST_P(BlobFileBuilderIOErrorTest, IOError) {
|
||||
TestFileNumberGenerator(), fs_, &immutable_options, &mutable_cf_options,
|
||||
&file_options_, job_id, column_family_id, column_family_name, io_priority,
|
||||
write_hint, nullptr /*IOTracer*/, nullptr /*BlobFileCompletionCallback*/,
|
||||
&blob_file_paths, &blob_file_additions);
|
||||
BlobFileCreationReason::kFlush, &blob_file_paths, &blob_file_additions);
|
||||
|
||||
SyncPoint::GetInstance()->SetCallBack(sync_point_, [this](void* arg) {
|
||||
Status* const s = static_cast<Status*>(arg);
|
||||
|
||||
@@ -84,17 +84,18 @@ void WriteBlobFile(uint32_t column_family_id,
|
||||
|
||||
class BlobFileCacheTest : public testing::Test {
|
||||
protected:
|
||||
BlobFileCacheTest() : mock_env_(Env::Default()) {}
|
||||
BlobFileCacheTest() { mock_env_.reset(MockEnv::Create(Env::Default())); }
|
||||
|
||||
MockEnv mock_env_;
|
||||
std::unique_ptr<Env> mock_env_;
|
||||
};
|
||||
|
||||
TEST_F(BlobFileCacheTest, GetBlobFileReader) {
|
||||
Options options;
|
||||
options.env = &mock_env_;
|
||||
options.env = mock_env_.get();
|
||||
options.statistics = CreateDBStatistics();
|
||||
options.cf_paths.emplace_back(
|
||||
test::PerThreadDBPath(&mock_env_, "BlobFileCacheTest_GetBlobFileReader"),
|
||||
test::PerThreadDBPath(mock_env_.get(),
|
||||
"BlobFileCacheTest_GetBlobFileReader"),
|
||||
0);
|
||||
options.enable_blob_files = true;
|
||||
|
||||
@@ -135,10 +136,10 @@ TEST_F(BlobFileCacheTest, GetBlobFileReader) {
|
||||
|
||||
TEST_F(BlobFileCacheTest, GetBlobFileReader_Race) {
|
||||
Options options;
|
||||
options.env = &mock_env_;
|
||||
options.env = mock_env_.get();
|
||||
options.statistics = CreateDBStatistics();
|
||||
options.cf_paths.emplace_back(
|
||||
test::PerThreadDBPath(&mock_env_,
|
||||
test::PerThreadDBPath(mock_env_.get(),
|
||||
"BlobFileCacheTest_GetBlobFileReader_Race"),
|
||||
0);
|
||||
options.enable_blob_files = true;
|
||||
@@ -187,10 +188,10 @@ TEST_F(BlobFileCacheTest, GetBlobFileReader_Race) {
|
||||
|
||||
TEST_F(BlobFileCacheTest, GetBlobFileReader_IOError) {
|
||||
Options options;
|
||||
options.env = &mock_env_;
|
||||
options.env = mock_env_.get();
|
||||
options.statistics = CreateDBStatistics();
|
||||
options.cf_paths.emplace_back(
|
||||
test::PerThreadDBPath(&mock_env_,
|
||||
test::PerThreadDBPath(mock_env_.get(),
|
||||
"BlobFileCacheTest_GetBlobFileReader_IOError"),
|
||||
0);
|
||||
options.enable_blob_files = true;
|
||||
@@ -221,10 +222,10 @@ TEST_F(BlobFileCacheTest, GetBlobFileReader_IOError) {
|
||||
|
||||
TEST_F(BlobFileCacheTest, GetBlobFileReader_CacheFull) {
|
||||
Options options;
|
||||
options.env = &mock_env_;
|
||||
options.env = mock_env_.get();
|
||||
options.statistics = CreateDBStatistics();
|
||||
options.cf_paths.emplace_back(
|
||||
test::PerThreadDBPath(&mock_env_,
|
||||
test::PerThreadDBPath(mock_env_.get(),
|
||||
"BlobFileCacheTest_GetBlobFileReader_CacheFull"),
|
||||
0);
|
||||
options.enable_blob_files = true;
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "db/error_handler.h"
|
||||
#include "db/event_helpers.h"
|
||||
#include "file/sst_file_manager_impl.h"
|
||||
#include "rocksdb/status.h"
|
||||
|
||||
@@ -16,23 +17,51 @@ namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
class BlobFileCompletionCallback {
|
||||
public:
|
||||
#ifdef ROCKSDB_LITE
|
||||
BlobFileCompletionCallback(SstFileManager* /*sst_file_manager*/,
|
||||
InstrumentedMutex* /*mutex*/,
|
||||
ErrorHandler* /*error_handler*/) {}
|
||||
Status OnBlobFileCompleted(const std::string& /*file_name*/) {
|
||||
return Status::OK();
|
||||
}
|
||||
BlobFileCompletionCallback(
|
||||
SstFileManager* sst_file_manager, InstrumentedMutex* mutex,
|
||||
ErrorHandler* error_handler, EventLogger* event_logger,
|
||||
const std::vector<std::shared_ptr<EventListener>>& listeners,
|
||||
const std::string& dbname)
|
||||
: event_logger_(event_logger), listeners_(listeners), dbname_(dbname) {
|
||||
#ifndef ROCKSDB_LITE
|
||||
sst_file_manager_ = sst_file_manager;
|
||||
mutex_ = mutex;
|
||||
error_handler_ = error_handler;
|
||||
#else
|
||||
BlobFileCompletionCallback(SstFileManager* sst_file_manager,
|
||||
InstrumentedMutex* mutex,
|
||||
ErrorHandler* error_handler)
|
||||
: sst_file_manager_(sst_file_manager),
|
||||
mutex_(mutex),
|
||||
error_handler_(error_handler) {}
|
||||
(void)sst_file_manager;
|
||||
(void)mutex;
|
||||
(void)error_handler;
|
||||
#endif // ROCKSDB_LITE
|
||||
}
|
||||
|
||||
Status OnBlobFileCompleted(const std::string& file_name) {
|
||||
void OnBlobFileCreationStarted(const std::string& file_name,
|
||||
const std::string& column_family_name,
|
||||
int job_id,
|
||||
BlobFileCreationReason creation_reason) {
|
||||
#ifndef ROCKSDB_LITE
|
||||
// Notify the listeners.
|
||||
EventHelpers::NotifyBlobFileCreationStarted(listeners_, dbname_,
|
||||
column_family_name, file_name,
|
||||
job_id, creation_reason);
|
||||
#else
|
||||
(void)file_name;
|
||||
(void)column_family_name;
|
||||
(void)job_id;
|
||||
(void)creation_reason;
|
||||
#endif
|
||||
}
|
||||
|
||||
Status OnBlobFileCompleted(const std::string& file_name,
|
||||
const std::string& column_family_name, int job_id,
|
||||
uint64_t file_number,
|
||||
BlobFileCreationReason creation_reason,
|
||||
const Status& report_status,
|
||||
const std::string& checksum_value,
|
||||
const std::string& checksum_method,
|
||||
uint64_t blob_count, uint64_t blob_bytes) {
|
||||
Status s;
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
auto sfm = static_cast<SstFileManagerImpl*>(sst_file_manager_);
|
||||
if (sfm) {
|
||||
// Report new blob files to SstFileManagerImpl
|
||||
@@ -45,13 +74,28 @@ class BlobFileCompletionCallback {
|
||||
error_handler_->SetBGError(s, BackgroundErrorReason::kFlush);
|
||||
}
|
||||
}
|
||||
#endif // !ROCKSDB_LITE
|
||||
|
||||
// Notify the listeners.
|
||||
EventHelpers::LogAndNotifyBlobFileCreationFinished(
|
||||
event_logger_, listeners_, dbname_, column_family_name, file_name,
|
||||
job_id, file_number, creation_reason,
|
||||
(!report_status.ok() ? report_status : s),
|
||||
(checksum_value.empty() ? kUnknownFileChecksum : checksum_value),
|
||||
(checksum_method.empty() ? kUnknownFileChecksumFuncName
|
||||
: checksum_method),
|
||||
blob_count, blob_bytes);
|
||||
return s;
|
||||
}
|
||||
|
||||
private:
|
||||
#ifndef ROCKSDB_LITE
|
||||
SstFileManager* sst_file_manager_;
|
||||
InstrumentedMutex* mutex_;
|
||||
ErrorHandler* error_handler_;
|
||||
#endif // ROCKSDB_LITE
|
||||
EventLogger* event_logger_;
|
||||
std::vector<std::shared_ptr<EventListener>> listeners_;
|
||||
std::string dbname_;
|
||||
};
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
@@ -351,6 +351,124 @@ Status BlobFileReader::GetBlob(const ReadOptions& read_options,
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
void BlobFileReader::MultiGetBlob(
|
||||
const ReadOptions& read_options,
|
||||
const autovector<std::reference_wrapper<const Slice>>& user_keys,
|
||||
const autovector<uint64_t>& offsets,
|
||||
const autovector<uint64_t>& value_sizes, autovector<Status*>& statuses,
|
||||
autovector<PinnableSlice*>& values, uint64_t* bytes_read) const {
|
||||
const size_t num_blobs = user_keys.size();
|
||||
assert(num_blobs > 0);
|
||||
assert(num_blobs == offsets.size());
|
||||
assert(num_blobs == value_sizes.size());
|
||||
assert(num_blobs == statuses.size());
|
||||
assert(num_blobs == values.size());
|
||||
|
||||
#ifndef NDEBUG
|
||||
for (size_t i = 0; i < offsets.size() - 1; ++i) {
|
||||
assert(offsets[i] <= offsets[i + 1]);
|
||||
}
|
||||
#endif // !NDEBUG
|
||||
|
||||
std::vector<FSReadRequest> read_reqs(num_blobs);
|
||||
autovector<uint64_t> adjustments;
|
||||
uint64_t total_len = 0;
|
||||
for (size_t i = 0; i < num_blobs; ++i) {
|
||||
const size_t key_size = user_keys[i].get().size();
|
||||
assert(IsValidBlobOffset(offsets[i], key_size, value_sizes[i], file_size_));
|
||||
const uint64_t adjustment =
|
||||
read_options.verify_checksums
|
||||
? BlobLogRecord::CalculateAdjustmentForRecordHeader(key_size)
|
||||
: 0;
|
||||
assert(offsets[i] >= adjustment);
|
||||
adjustments.push_back(adjustment);
|
||||
read_reqs[i].offset = offsets[i] - adjustment;
|
||||
read_reqs[i].len = value_sizes[i] + adjustment;
|
||||
total_len += read_reqs[i].len;
|
||||
}
|
||||
|
||||
RecordTick(statistics_, BLOB_DB_BLOB_FILE_BYTES_READ, total_len);
|
||||
|
||||
Buffer buf;
|
||||
AlignedBuf aligned_buf;
|
||||
|
||||
Status s;
|
||||
bool direct_io = file_reader_->use_direct_io();
|
||||
if (direct_io) {
|
||||
for (size_t i = 0; i < read_reqs.size(); ++i) {
|
||||
read_reqs[i].scratch = nullptr;
|
||||
}
|
||||
} else {
|
||||
buf.reset(new char[total_len]);
|
||||
std::ptrdiff_t pos = 0;
|
||||
for (size_t i = 0; i < read_reqs.size(); ++i) {
|
||||
read_reqs[i].scratch = buf.get() + pos;
|
||||
pos += read_reqs[i].len;
|
||||
}
|
||||
}
|
||||
TEST_SYNC_POINT("BlobFileReader::MultiGetBlob:ReadFromFile");
|
||||
s = file_reader_->MultiRead(IOOptions(), read_reqs.data(), read_reqs.size(),
|
||||
direct_io ? &aligned_buf : nullptr);
|
||||
if (!s.ok()) {
|
||||
for (auto& req : read_reqs) {
|
||||
req.status.PermitUncheckedError();
|
||||
}
|
||||
for (size_t i = 0; i < num_blobs; ++i) {
|
||||
assert(statuses[i]);
|
||||
*statuses[i] = s;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
assert(s.ok());
|
||||
for (size_t i = 0; i < num_blobs; ++i) {
|
||||
auto& req = read_reqs[i];
|
||||
assert(statuses[i]);
|
||||
if (req.status.ok() && req.result.size() != req.len) {
|
||||
req.status = IOStatus::Corruption("Failed to read data from blob file");
|
||||
}
|
||||
*statuses[i] = req.status;
|
||||
}
|
||||
|
||||
if (read_options.verify_checksums) {
|
||||
for (size_t i = 0; i < num_blobs; ++i) {
|
||||
assert(statuses[i]);
|
||||
if (!statuses[i]->ok()) {
|
||||
continue;
|
||||
}
|
||||
const Slice& record_slice = read_reqs[i].result;
|
||||
s = VerifyBlob(record_slice, user_keys[i], value_sizes[i]);
|
||||
if (!s.ok()) {
|
||||
assert(statuses[i]);
|
||||
*statuses[i] = s;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < num_blobs; ++i) {
|
||||
assert(statuses[i]);
|
||||
if (!statuses[i]->ok()) {
|
||||
continue;
|
||||
}
|
||||
const Slice& record_slice = read_reqs[i].result;
|
||||
const Slice value_slice(record_slice.data() + adjustments[i],
|
||||
value_sizes[i]);
|
||||
s = UncompressBlobIfNeeded(value_slice, compression_type_, clock_,
|
||||
statistics_, values[i]);
|
||||
if (!s.ok()) {
|
||||
*statuses[i] = s;
|
||||
}
|
||||
}
|
||||
|
||||
if (bytes_read) {
|
||||
uint64_t total_bytes = 0;
|
||||
for (const auto& req : read_reqs) {
|
||||
total_bytes += req.result.size();
|
||||
}
|
||||
*bytes_read = total_bytes;
|
||||
}
|
||||
}
|
||||
|
||||
Status BlobFileReader::VerifyBlob(const Slice& record_slice,
|
||||
const Slice& user_key, uint64_t value_size) {
|
||||
BlobLogRecord record;
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include "file/random_access_file_reader.h"
|
||||
#include "rocksdb/compression_type.h"
|
||||
#include "rocksdb/rocksdb_namespace.h"
|
||||
#include "util/autovector.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
@@ -43,6 +44,18 @@ class BlobFileReader {
|
||||
CompressionType compression_type, PinnableSlice* value,
|
||||
uint64_t* bytes_read) const;
|
||||
|
||||
// offsets must be sorted in ascending order by caller.
|
||||
void MultiGetBlob(
|
||||
const ReadOptions& read_options,
|
||||
const autovector<std::reference_wrapper<const Slice>>& user_keys,
|
||||
const autovector<uint64_t>& offsets,
|
||||
const autovector<uint64_t>& value_sizes, autovector<Status*>& statuses,
|
||||
autovector<PinnableSlice*>& values, uint64_t* bytes_read) const;
|
||||
|
||||
CompressionType GetCompressionType() const { return compression_type_; }
|
||||
|
||||
uint64_t GetFileSize() const { return file_size_; }
|
||||
|
||||
private:
|
||||
BlobFileReader(std::unique_ptr<RandomAccessFileReader>&& file_reader,
|
||||
uint64_t file_size, CompressionType compression_type,
|
||||
|
||||
@@ -27,23 +27,23 @@ namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
namespace {
|
||||
|
||||
// Creates a test blob file with a single blob in it. Note: this method
|
||||
// makes it possible to test various corner cases by allowing the caller
|
||||
// to specify the contents of various blob file header/footer fields.
|
||||
// Creates a test blob file with `num` blobs in it.
|
||||
void WriteBlobFile(const ImmutableOptions& immutable_options,
|
||||
uint32_t column_family_id, bool has_ttl,
|
||||
const ExpirationRange& expiration_range_header,
|
||||
const ExpirationRange& expiration_range_footer,
|
||||
uint64_t blob_file_number, const Slice& key,
|
||||
const Slice& blob, CompressionType compression_type,
|
||||
uint64_t* blob_offset, uint64_t* blob_size) {
|
||||
uint64_t blob_file_number, const std::vector<Slice>& keys,
|
||||
const std::vector<Slice>& blobs, CompressionType compression,
|
||||
std::vector<uint64_t>& blob_offsets,
|
||||
std::vector<uint64_t>& blob_sizes) {
|
||||
assert(!immutable_options.cf_paths.empty());
|
||||
assert(blob_offset);
|
||||
assert(blob_size);
|
||||
size_t num = keys.size();
|
||||
assert(num == blobs.size());
|
||||
assert(num == blob_offsets.size());
|
||||
assert(num == blob_sizes.size());
|
||||
|
||||
const std::string blob_file_path =
|
||||
BlobFileName(immutable_options.cf_paths.front().path, blob_file_number);
|
||||
|
||||
std::unique_ptr<FSWritableFile> file;
|
||||
ASSERT_OK(NewWritableFile(immutable_options.fs.get(), blob_file_path, &file,
|
||||
FileOptions()));
|
||||
@@ -59,64 +59,90 @@ void WriteBlobFile(const ImmutableOptions& immutable_options,
|
||||
statistics, blob_file_number, use_fsync,
|
||||
do_flush);
|
||||
|
||||
BlobLogHeader header(column_family_id, compression_type, has_ttl,
|
||||
BlobLogHeader header(column_family_id, compression, has_ttl,
|
||||
expiration_range_header);
|
||||
|
||||
ASSERT_OK(blob_log_writer.WriteHeader(header));
|
||||
|
||||
std::string compressed_blob;
|
||||
Slice blob_to_write;
|
||||
|
||||
if (compression_type == kNoCompression) {
|
||||
blob_to_write = blob;
|
||||
*blob_size = blob.size();
|
||||
std::vector<std::string> compressed_blobs(num);
|
||||
std::vector<Slice> blobs_to_write(num);
|
||||
if (kNoCompression == compression) {
|
||||
for (size_t i = 0; i < num; ++i) {
|
||||
blobs_to_write[i] = blobs[i];
|
||||
blob_sizes[i] = blobs[i].size();
|
||||
}
|
||||
} else {
|
||||
CompressionOptions opts;
|
||||
CompressionContext context(compression_type);
|
||||
CompressionContext context(compression);
|
||||
constexpr uint64_t sample_for_compression = 0;
|
||||
|
||||
CompressionInfo info(opts, context, CompressionDict::GetEmptyDict(),
|
||||
compression_type, sample_for_compression);
|
||||
compression, sample_for_compression);
|
||||
|
||||
constexpr uint32_t compression_format_version = 2;
|
||||
|
||||
ASSERT_TRUE(
|
||||
CompressData(blob, info, compression_format_version, &compressed_blob));
|
||||
|
||||
blob_to_write = compressed_blob;
|
||||
*blob_size = compressed_blob.size();
|
||||
for (size_t i = 0; i < num; ++i) {
|
||||
ASSERT_TRUE(CompressData(blobs[i], info, compression_format_version,
|
||||
&compressed_blobs[i]));
|
||||
blobs_to_write[i] = compressed_blobs[i];
|
||||
blob_sizes[i] = compressed_blobs[i].size();
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t key_offset = 0;
|
||||
|
||||
ASSERT_OK(
|
||||
blob_log_writer.AddRecord(key, blob_to_write, &key_offset, blob_offset));
|
||||
for (size_t i = 0; i < num; ++i) {
|
||||
uint64_t key_offset = 0;
|
||||
ASSERT_OK(blob_log_writer.AddRecord(keys[i], blobs_to_write[i], &key_offset,
|
||||
&blob_offsets[i]));
|
||||
}
|
||||
|
||||
BlobLogFooter footer;
|
||||
footer.blob_count = 1;
|
||||
footer.blob_count = num;
|
||||
footer.expiration_range = expiration_range_footer;
|
||||
|
||||
std::string checksum_method;
|
||||
std::string checksum_value;
|
||||
|
||||
ASSERT_OK(
|
||||
blob_log_writer.AppendFooter(footer, &checksum_method, &checksum_value));
|
||||
}
|
||||
|
||||
// Creates a test blob file with a single blob in it. Note: this method
|
||||
// makes it possible to test various corner cases by allowing the caller
|
||||
// to specify the contents of various blob file header/footer fields.
|
||||
void WriteBlobFile(const ImmutableOptions& immutable_options,
|
||||
uint32_t column_family_id, bool has_ttl,
|
||||
const ExpirationRange& expiration_range_header,
|
||||
const ExpirationRange& expiration_range_footer,
|
||||
uint64_t blob_file_number, const Slice& key,
|
||||
const Slice& blob, CompressionType compression,
|
||||
uint64_t* blob_offset, uint64_t* blob_size) {
|
||||
std::vector<Slice> keys{key};
|
||||
std::vector<Slice> blobs{blob};
|
||||
std::vector<uint64_t> blob_offsets{0};
|
||||
std::vector<uint64_t> blob_sizes{0};
|
||||
WriteBlobFile(immutable_options, column_family_id, has_ttl,
|
||||
expiration_range_header, expiration_range_footer,
|
||||
blob_file_number, keys, blobs, compression, blob_offsets,
|
||||
blob_sizes);
|
||||
if (blob_offset) {
|
||||
*blob_offset = blob_offsets[0];
|
||||
}
|
||||
if (blob_size) {
|
||||
*blob_size = blob_sizes[0];
|
||||
}
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
class BlobFileReaderTest : public testing::Test {
|
||||
protected:
|
||||
BlobFileReaderTest() : mock_env_(Env::Default()) {}
|
||||
|
||||
MockEnv mock_env_;
|
||||
BlobFileReaderTest() { mock_env_.reset(MockEnv::Create(Env::Default())); }
|
||||
std::unique_ptr<Env> mock_env_;
|
||||
};
|
||||
|
||||
TEST_F(BlobFileReaderTest, CreateReaderAndGetBlob) {
|
||||
Options options;
|
||||
options.env = &mock_env_;
|
||||
options.env = mock_env_.get();
|
||||
options.cf_paths.emplace_back(
|
||||
test::PerThreadDBPath(&mock_env_,
|
||||
test::PerThreadDBPath(mock_env_.get(),
|
||||
"BlobFileReaderTest_CreateReaderAndGetBlob"),
|
||||
0);
|
||||
options.enable_blob_files = true;
|
||||
@@ -127,15 +153,19 @@ TEST_F(BlobFileReaderTest, CreateReaderAndGetBlob) {
|
||||
constexpr bool has_ttl = false;
|
||||
constexpr ExpirationRange expiration_range;
|
||||
constexpr uint64_t blob_file_number = 1;
|
||||
constexpr char key[] = "key";
|
||||
constexpr char blob[] = "blob";
|
||||
constexpr size_t num_blobs = 3;
|
||||
const std::vector<std::string> key_strs = {"key1", "key2", "key3"};
|
||||
const std::vector<std::string> blob_strs = {"blob1", "blob2", "blob3"};
|
||||
|
||||
uint64_t blob_offset = 0;
|
||||
uint64_t blob_size = 0;
|
||||
const std::vector<Slice> keys = {key_strs[0], key_strs[1], key_strs[2]};
|
||||
const std::vector<Slice> blobs = {blob_strs[0], blob_strs[1], blob_strs[2]};
|
||||
|
||||
std::vector<uint64_t> blob_offsets(keys.size());
|
||||
std::vector<uint64_t> blob_sizes(keys.size());
|
||||
|
||||
WriteBlobFile(immutable_options, column_family_id, has_ttl, expiration_range,
|
||||
expiration_range, blob_file_number, key, blob, kNoCompression,
|
||||
&blob_offset, &blob_size);
|
||||
expiration_range, blob_file_number, keys, blobs, kNoCompression,
|
||||
blob_offsets, blob_sizes);
|
||||
|
||||
constexpr HistogramImpl* blob_file_read_hist = nullptr;
|
||||
|
||||
@@ -153,10 +183,36 @@ TEST_F(BlobFileReaderTest, CreateReaderAndGetBlob) {
|
||||
PinnableSlice value;
|
||||
uint64_t bytes_read = 0;
|
||||
|
||||
ASSERT_OK(reader->GetBlob(read_options, key, blob_offset, blob_size,
|
||||
kNoCompression, &value, &bytes_read));
|
||||
ASSERT_EQ(value, blob);
|
||||
ASSERT_EQ(bytes_read, blob_size);
|
||||
ASSERT_OK(reader->GetBlob(read_options, keys[0], blob_offsets[0],
|
||||
blob_sizes[0], kNoCompression, &value,
|
||||
&bytes_read));
|
||||
ASSERT_EQ(value, blobs[0]);
|
||||
ASSERT_EQ(bytes_read, blob_sizes[0]);
|
||||
|
||||
// MultiGetBlob
|
||||
bytes_read = 0;
|
||||
size_t total_size = 0;
|
||||
autovector<std::reference_wrapper<const Slice>> key_refs;
|
||||
for (const auto& key_ref : keys) {
|
||||
key_refs.emplace_back(std::cref(key_ref));
|
||||
}
|
||||
autovector<uint64_t> offsets{blob_offsets[0], blob_offsets[1],
|
||||
blob_offsets[2]};
|
||||
autovector<uint64_t> sizes{blob_sizes[0], blob_sizes[1], blob_sizes[2]};
|
||||
std::array<Status, num_blobs> statuses_buf;
|
||||
autovector<Status*> statuses{&statuses_buf[0], &statuses_buf[1],
|
||||
&statuses_buf[2]};
|
||||
std::array<PinnableSlice, num_blobs> value_buf;
|
||||
autovector<PinnableSlice*> values{&value_buf[0], &value_buf[1],
|
||||
&value_buf[2]};
|
||||
reader->MultiGetBlob(read_options, key_refs, offsets, sizes, statuses,
|
||||
values, &bytes_read);
|
||||
for (size_t i = 0; i < num_blobs; ++i) {
|
||||
ASSERT_OK(statuses_buf[i]);
|
||||
ASSERT_EQ(value_buf[i], blobs[i]);
|
||||
total_size += blob_sizes[i];
|
||||
}
|
||||
ASSERT_EQ(bytes_read, total_size);
|
||||
}
|
||||
|
||||
read_options.verify_checksums = true;
|
||||
@@ -165,14 +221,15 @@ TEST_F(BlobFileReaderTest, CreateReaderAndGetBlob) {
|
||||
PinnableSlice value;
|
||||
uint64_t bytes_read = 0;
|
||||
|
||||
ASSERT_OK(reader->GetBlob(read_options, key, blob_offset, blob_size,
|
||||
kNoCompression, &value, &bytes_read));
|
||||
ASSERT_EQ(value, blob);
|
||||
ASSERT_OK(reader->GetBlob(read_options, keys[1], blob_offsets[1],
|
||||
blob_sizes[1], kNoCompression, &value,
|
||||
&bytes_read));
|
||||
ASSERT_EQ(value, blobs[1]);
|
||||
|
||||
constexpr uint64_t key_size = sizeof(key) - 1;
|
||||
const uint64_t key_size = keys[1].size();
|
||||
ASSERT_EQ(bytes_read,
|
||||
BlobLogRecord::CalculateAdjustmentForRecordHeader(key_size) +
|
||||
blob_size);
|
||||
blob_sizes[1]);
|
||||
}
|
||||
|
||||
// Invalid offset (too close to start of file)
|
||||
@@ -181,8 +238,9 @@ TEST_F(BlobFileReaderTest, CreateReaderAndGetBlob) {
|
||||
uint64_t bytes_read = 0;
|
||||
|
||||
ASSERT_TRUE(reader
|
||||
->GetBlob(read_options, key, blob_offset - 1, blob_size,
|
||||
kNoCompression, &value, &bytes_read)
|
||||
->GetBlob(read_options, keys[0], blob_offsets[0] - 1,
|
||||
blob_sizes[0], kNoCompression, &value,
|
||||
&bytes_read)
|
||||
.IsCorruption());
|
||||
ASSERT_EQ(bytes_read, 0);
|
||||
}
|
||||
@@ -193,8 +251,9 @@ TEST_F(BlobFileReaderTest, CreateReaderAndGetBlob) {
|
||||
uint64_t bytes_read = 0;
|
||||
|
||||
ASSERT_TRUE(reader
|
||||
->GetBlob(read_options, key, blob_offset + 1, blob_size,
|
||||
kNoCompression, &value, &bytes_read)
|
||||
->GetBlob(read_options, keys[2], blob_offsets[2] + 1,
|
||||
blob_sizes[2], kNoCompression, &value,
|
||||
&bytes_read)
|
||||
.IsCorruption());
|
||||
ASSERT_EQ(bytes_read, 0);
|
||||
}
|
||||
@@ -205,8 +264,8 @@ TEST_F(BlobFileReaderTest, CreateReaderAndGetBlob) {
|
||||
uint64_t bytes_read = 0;
|
||||
|
||||
ASSERT_TRUE(reader
|
||||
->GetBlob(read_options, key, blob_offset, blob_size, kZSTD,
|
||||
&value, &bytes_read)
|
||||
->GetBlob(read_options, keys[0], blob_offsets[0],
|
||||
blob_sizes[0], kZSTD, &value, &bytes_read)
|
||||
.IsCorruption());
|
||||
ASSERT_EQ(bytes_read, 0);
|
||||
}
|
||||
@@ -219,23 +278,82 @@ TEST_F(BlobFileReaderTest, CreateReaderAndGetBlob) {
|
||||
|
||||
ASSERT_TRUE(reader
|
||||
->GetBlob(read_options, shorter_key,
|
||||
blob_offset - (sizeof(key) - sizeof(shorter_key)),
|
||||
blob_size, kNoCompression, &value, &bytes_read)
|
||||
blob_offsets[0] -
|
||||
(keys[0].size() - sizeof(shorter_key) + 1),
|
||||
blob_sizes[0], kNoCompression, &value,
|
||||
&bytes_read)
|
||||
.IsCorruption());
|
||||
ASSERT_EQ(bytes_read, 0);
|
||||
|
||||
// MultiGetBlob
|
||||
autovector<std::reference_wrapper<const Slice>> key_refs;
|
||||
for (const auto& key_ref : keys) {
|
||||
key_refs.emplace_back(std::cref(key_ref));
|
||||
}
|
||||
Slice shorter_key_slice(shorter_key, sizeof(shorter_key) - 1);
|
||||
key_refs[1] = std::cref(shorter_key_slice);
|
||||
|
||||
autovector<uint64_t> offsets{
|
||||
blob_offsets[0],
|
||||
blob_offsets[1] - (keys[1].size() - key_refs[1].get().size()),
|
||||
blob_offsets[2]};
|
||||
autovector<uint64_t> sizes{blob_sizes[0], blob_sizes[1], blob_sizes[2]};
|
||||
std::array<Status, num_blobs> statuses_buf;
|
||||
autovector<Status*> statuses{&statuses_buf[0], &statuses_buf[1],
|
||||
&statuses_buf[2]};
|
||||
std::array<PinnableSlice, num_blobs> value_buf;
|
||||
autovector<PinnableSlice*> values{&value_buf[0], &value_buf[1],
|
||||
&value_buf[2]};
|
||||
reader->MultiGetBlob(read_options, key_refs, offsets, sizes, statuses,
|
||||
values, &bytes_read);
|
||||
for (size_t i = 0; i < num_blobs; ++i) {
|
||||
if (i == 1) {
|
||||
ASSERT_TRUE(statuses_buf[i].IsCorruption());
|
||||
} else {
|
||||
ASSERT_OK(statuses_buf[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Incorrect key
|
||||
{
|
||||
constexpr char incorrect_key[] = "foo";
|
||||
constexpr char incorrect_key[] = "foo1";
|
||||
PinnableSlice value;
|
||||
uint64_t bytes_read = 0;
|
||||
|
||||
ASSERT_TRUE(reader
|
||||
->GetBlob(read_options, incorrect_key, blob_offset,
|
||||
blob_size, kNoCompression, &value, &bytes_read)
|
||||
->GetBlob(read_options, incorrect_key, blob_offsets[0],
|
||||
blob_sizes[0], kNoCompression, &value,
|
||||
&bytes_read)
|
||||
.IsCorruption());
|
||||
ASSERT_EQ(bytes_read, 0);
|
||||
|
||||
// MultiGetBlob
|
||||
autovector<std::reference_wrapper<const Slice>> key_refs;
|
||||
for (const auto& key_ref : keys) {
|
||||
key_refs.emplace_back(std::cref(key_ref));
|
||||
}
|
||||
Slice wrong_key_slice(incorrect_key, sizeof(incorrect_key) - 1);
|
||||
key_refs[2] = std::cref(wrong_key_slice);
|
||||
|
||||
autovector<uint64_t> offsets{blob_offsets[0], blob_offsets[1],
|
||||
blob_offsets[2]};
|
||||
autovector<uint64_t> sizes{blob_sizes[0], blob_sizes[1], blob_sizes[2]};
|
||||
std::array<Status, num_blobs> statuses_buf;
|
||||
autovector<Status*> statuses{&statuses_buf[0], &statuses_buf[1],
|
||||
&statuses_buf[2]};
|
||||
std::array<PinnableSlice, num_blobs> value_buf;
|
||||
autovector<PinnableSlice*> values{&value_buf[0], &value_buf[1],
|
||||
&value_buf[2]};
|
||||
reader->MultiGetBlob(read_options, key_refs, offsets, sizes, statuses,
|
||||
values, &bytes_read);
|
||||
for (size_t i = 0; i < num_blobs; ++i) {
|
||||
if (i == num_blobs - 1) {
|
||||
ASSERT_TRUE(statuses_buf[i].IsCorruption());
|
||||
} else {
|
||||
ASSERT_OK(statuses_buf[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Incorrect value size
|
||||
@@ -244,10 +362,35 @@ TEST_F(BlobFileReaderTest, CreateReaderAndGetBlob) {
|
||||
uint64_t bytes_read = 0;
|
||||
|
||||
ASSERT_TRUE(reader
|
||||
->GetBlob(read_options, key, blob_offset, blob_size + 1,
|
||||
kNoCompression, &value, &bytes_read)
|
||||
->GetBlob(read_options, keys[1], blob_offsets[1],
|
||||
blob_sizes[1] + 1, kNoCompression, &value,
|
||||
&bytes_read)
|
||||
.IsCorruption());
|
||||
ASSERT_EQ(bytes_read, 0);
|
||||
|
||||
// MultiGetBlob
|
||||
autovector<std::reference_wrapper<const Slice>> key_refs;
|
||||
for (const auto& key_ref : keys) {
|
||||
key_refs.emplace_back(std::cref(key_ref));
|
||||
}
|
||||
autovector<uint64_t> offsets{blob_offsets[0], blob_offsets[1],
|
||||
blob_offsets[2]};
|
||||
autovector<uint64_t> sizes{blob_sizes[0], blob_sizes[1] + 1, blob_sizes[2]};
|
||||
std::array<Status, num_blobs> statuses_buf;
|
||||
autovector<Status*> statuses{&statuses_buf[0], &statuses_buf[1],
|
||||
&statuses_buf[2]};
|
||||
std::array<PinnableSlice, num_blobs> value_buf;
|
||||
autovector<PinnableSlice*> values{&value_buf[0], &value_buf[1],
|
||||
&value_buf[2]};
|
||||
reader->MultiGetBlob(read_options, key_refs, offsets, sizes, statuses,
|
||||
values, &bytes_read);
|
||||
for (size_t i = 0; i < num_blobs; ++i) {
|
||||
if (i != 1) {
|
||||
ASSERT_OK(statuses_buf[i]);
|
||||
} else {
|
||||
ASSERT_TRUE(statuses_buf[i].IsCorruption());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -256,9 +399,10 @@ TEST_F(BlobFileReaderTest, Malformed) {
|
||||
// detect the error when we open it for reading
|
||||
|
||||
Options options;
|
||||
options.env = &mock_env_;
|
||||
options.env = mock_env_.get();
|
||||
options.cf_paths.emplace_back(
|
||||
test::PerThreadDBPath(&mock_env_, "BlobFileReaderTest_Malformed"), 0);
|
||||
test::PerThreadDBPath(mock_env_.get(), "BlobFileReaderTest_Malformed"),
|
||||
0);
|
||||
options.enable_blob_files = true;
|
||||
|
||||
ImmutableOptions immutable_options(options);
|
||||
@@ -308,9 +452,9 @@ TEST_F(BlobFileReaderTest, Malformed) {
|
||||
|
||||
TEST_F(BlobFileReaderTest, TTL) {
|
||||
Options options;
|
||||
options.env = &mock_env_;
|
||||
options.env = mock_env_.get();
|
||||
options.cf_paths.emplace_back(
|
||||
test::PerThreadDBPath(&mock_env_, "BlobFileReaderTest_TTL"), 0);
|
||||
test::PerThreadDBPath(mock_env_.get(), "BlobFileReaderTest_TTL"), 0);
|
||||
options.enable_blob_files = true;
|
||||
|
||||
ImmutableOptions immutable_options(options);
|
||||
@@ -342,9 +486,9 @@ TEST_F(BlobFileReaderTest, TTL) {
|
||||
|
||||
TEST_F(BlobFileReaderTest, ExpirationRangeInHeader) {
|
||||
Options options;
|
||||
options.env = &mock_env_;
|
||||
options.env = mock_env_.get();
|
||||
options.cf_paths.emplace_back(
|
||||
test::PerThreadDBPath(&mock_env_,
|
||||
test::PerThreadDBPath(mock_env_.get(),
|
||||
"BlobFileReaderTest_ExpirationRangeInHeader"),
|
||||
0);
|
||||
options.enable_blob_files = true;
|
||||
@@ -381,9 +525,9 @@ TEST_F(BlobFileReaderTest, ExpirationRangeInHeader) {
|
||||
|
||||
TEST_F(BlobFileReaderTest, ExpirationRangeInFooter) {
|
||||
Options options;
|
||||
options.env = &mock_env_;
|
||||
options.env = mock_env_.get();
|
||||
options.cf_paths.emplace_back(
|
||||
test::PerThreadDBPath(&mock_env_,
|
||||
test::PerThreadDBPath(mock_env_.get(),
|
||||
"BlobFileReaderTest_ExpirationRangeInFooter"),
|
||||
0);
|
||||
options.enable_blob_files = true;
|
||||
@@ -420,9 +564,9 @@ TEST_F(BlobFileReaderTest, ExpirationRangeInFooter) {
|
||||
|
||||
TEST_F(BlobFileReaderTest, IncorrectColumnFamily) {
|
||||
Options options;
|
||||
options.env = &mock_env_;
|
||||
options.env = mock_env_.get();
|
||||
options.cf_paths.emplace_back(
|
||||
test::PerThreadDBPath(&mock_env_,
|
||||
test::PerThreadDBPath(mock_env_.get(),
|
||||
"BlobFileReaderTest_IncorrectColumnFamily"),
|
||||
0);
|
||||
options.enable_blob_files = true;
|
||||
@@ -458,9 +602,10 @@ TEST_F(BlobFileReaderTest, IncorrectColumnFamily) {
|
||||
|
||||
TEST_F(BlobFileReaderTest, BlobCRCError) {
|
||||
Options options;
|
||||
options.env = &mock_env_;
|
||||
options.env = mock_env_.get();
|
||||
options.cf_paths.emplace_back(
|
||||
test::PerThreadDBPath(&mock_env_, "BlobFileReaderTest_BlobCRCError"), 0);
|
||||
test::PerThreadDBPath(mock_env_.get(), "BlobFileReaderTest_BlobCRCError"),
|
||||
0);
|
||||
options.enable_blob_files = true;
|
||||
|
||||
ImmutableOptions immutable_options(options);
|
||||
@@ -516,9 +661,10 @@ TEST_F(BlobFileReaderTest, Compression) {
|
||||
}
|
||||
|
||||
Options options;
|
||||
options.env = &mock_env_;
|
||||
options.env = mock_env_.get();
|
||||
options.cf_paths.emplace_back(
|
||||
test::PerThreadDBPath(&mock_env_, "BlobFileReaderTest_Compression"), 0);
|
||||
test::PerThreadDBPath(mock_env_.get(), "BlobFileReaderTest_Compression"),
|
||||
0);
|
||||
options.enable_blob_files = true;
|
||||
|
||||
ImmutableOptions immutable_options(options);
|
||||
@@ -582,9 +728,9 @@ TEST_F(BlobFileReaderTest, UncompressionError) {
|
||||
}
|
||||
|
||||
Options options;
|
||||
options.env = &mock_env_;
|
||||
options.env = mock_env_.get();
|
||||
options.cf_paths.emplace_back(
|
||||
test::PerThreadDBPath(&mock_env_,
|
||||
test::PerThreadDBPath(mock_env_.get(),
|
||||
"BlobFileReaderTest_UncompressionError"),
|
||||
0);
|
||||
options.enable_blob_files = true;
|
||||
@@ -641,13 +787,13 @@ class BlobFileReaderIOErrorTest
|
||||
: public testing::Test,
|
||||
public testing::WithParamInterface<std::string> {
|
||||
protected:
|
||||
BlobFileReaderIOErrorTest()
|
||||
: mock_env_(Env::Default()),
|
||||
fault_injection_env_(&mock_env_),
|
||||
sync_point_(GetParam()) {}
|
||||
BlobFileReaderIOErrorTest() : sync_point_(GetParam()) {
|
||||
mock_env_.reset(MockEnv::Create(Env::Default()));
|
||||
fault_injection_env_.reset(new FaultInjectionTestEnv(mock_env_.get()));
|
||||
}
|
||||
|
||||
MockEnv mock_env_;
|
||||
FaultInjectionTestEnv fault_injection_env_;
|
||||
std::unique_ptr<Env> mock_env_;
|
||||
std::unique_ptr<FaultInjectionTestEnv> fault_injection_env_;
|
||||
std::string sync_point_;
|
||||
};
|
||||
|
||||
@@ -663,9 +809,9 @@ TEST_P(BlobFileReaderIOErrorTest, IOError) {
|
||||
// Simulates an I/O error during the specified step
|
||||
|
||||
Options options;
|
||||
options.env = &fault_injection_env_;
|
||||
options.env = fault_injection_env_.get();
|
||||
options.cf_paths.emplace_back(
|
||||
test::PerThreadDBPath(&fault_injection_env_,
|
||||
test::PerThreadDBPath(fault_injection_env_.get(),
|
||||
"BlobFileReaderIOErrorTest_IOError"),
|
||||
0);
|
||||
options.enable_blob_files = true;
|
||||
@@ -687,8 +833,8 @@ TEST_P(BlobFileReaderIOErrorTest, IOError) {
|
||||
&blob_offset, &blob_size);
|
||||
|
||||
SyncPoint::GetInstance()->SetCallBack(sync_point_, [this](void* /* arg */) {
|
||||
fault_injection_env_.SetFilesystemActive(false,
|
||||
Status::IOError(sync_point_));
|
||||
fault_injection_env_->SetFilesystemActive(false,
|
||||
Status::IOError(sync_point_));
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
@@ -726,10 +872,11 @@ class BlobFileReaderDecodingErrorTest
|
||||
: public testing::Test,
|
||||
public testing::WithParamInterface<std::string> {
|
||||
protected:
|
||||
BlobFileReaderDecodingErrorTest()
|
||||
: mock_env_(Env::Default()), sync_point_(GetParam()) {}
|
||||
BlobFileReaderDecodingErrorTest() : sync_point_(GetParam()) {
|
||||
mock_env_.reset(MockEnv::Create(Env::Default()));
|
||||
}
|
||||
|
||||
MockEnv mock_env_;
|
||||
std::unique_ptr<Env> mock_env_;
|
||||
std::string sync_point_;
|
||||
};
|
||||
|
||||
@@ -741,9 +888,9 @@ INSTANTIATE_TEST_CASE_P(BlobFileReaderTest, BlobFileReaderDecodingErrorTest,
|
||||
|
||||
TEST_P(BlobFileReaderDecodingErrorTest, DecodingError) {
|
||||
Options options;
|
||||
options.env = &mock_env_;
|
||||
options.env = mock_env_.get();
|
||||
options.cf_paths.emplace_back(
|
||||
test::PerThreadDBPath(&mock_env_,
|
||||
test::PerThreadDBPath(mock_env_.get(),
|
||||
"BlobFileReaderDecodingErrorTest_DecodingError"),
|
||||
0);
|
||||
options.enable_blob_files = true;
|
||||
|
||||
@@ -52,6 +52,9 @@ class BlobIndex {
|
||||
|
||||
BlobIndex() : type_(Type::kUnknown) {}
|
||||
|
||||
BlobIndex(const BlobIndex&) = default;
|
||||
BlobIndex& operator=(const BlobIndex&) = default;
|
||||
|
||||
bool IsInlined() const { return type_ == Type::kInlinedTTL; }
|
||||
|
||||
bool HasTTL() const {
|
||||
|
||||
@@ -4,8 +4,10 @@
|
||||
// (found in the LICENSE.Apache file in the root directory).
|
||||
|
||||
#include <array>
|
||||
#include <sstream>
|
||||
|
||||
#include "db/blob/blob_index.h"
|
||||
#include "db/blob/blob_log_format.h"
|
||||
#include "db/db_test_util.h"
|
||||
#include "port/stack_trace.h"
|
||||
#include "test_util/sync_point.h"
|
||||
@@ -125,6 +127,237 @@ TEST_F(DBBlobBasicTest, MultiGetBlobs) {
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
TEST_F(DBBlobBasicTest, MultiGetWithDirectIO) {
|
||||
Options options = GetDefaultOptions();
|
||||
|
||||
// First, create an external SST file ["b"].
|
||||
const std::string file_path = dbname_ + "/test.sst";
|
||||
{
|
||||
SstFileWriter sst_file_writer(EnvOptions(), GetDefaultOptions());
|
||||
Status s = sst_file_writer.Open(file_path);
|
||||
ASSERT_OK(s);
|
||||
ASSERT_OK(sst_file_writer.Put("b", "b_value"));
|
||||
ASSERT_OK(sst_file_writer.Finish());
|
||||
}
|
||||
|
||||
options.enable_blob_files = true;
|
||||
options.min_blob_size = 1000;
|
||||
options.use_direct_reads = true;
|
||||
options.allow_ingest_behind = true;
|
||||
|
||||
// Open DB with fixed-prefix sst-partitioner so that compaction will cut
|
||||
// new table file when encountering a new key whose 1-byte prefix changes.
|
||||
constexpr size_t key_len = 1;
|
||||
options.sst_partitioner_factory =
|
||||
NewSstPartitionerFixedPrefixFactory(key_len);
|
||||
|
||||
Status s = TryReopen(options);
|
||||
if (s.IsInvalidArgument()) {
|
||||
ROCKSDB_GTEST_SKIP("This test requires direct IO support");
|
||||
return;
|
||||
}
|
||||
ASSERT_OK(s);
|
||||
|
||||
constexpr size_t num_keys = 3;
|
||||
constexpr size_t blob_size = 3000;
|
||||
|
||||
constexpr char first_key[] = "a";
|
||||
const std::string first_blob(blob_size, 'a');
|
||||
ASSERT_OK(Put(first_key, first_blob));
|
||||
|
||||
constexpr char second_key[] = "b";
|
||||
const std::string second_blob(2 * blob_size, 'b');
|
||||
ASSERT_OK(Put(second_key, second_blob));
|
||||
|
||||
constexpr char third_key[] = "d";
|
||||
const std::string third_blob(blob_size, 'd');
|
||||
ASSERT_OK(Put(third_key, third_blob));
|
||||
|
||||
// first_blob, second_blob and third_blob in the same blob file.
|
||||
// SST Blob file
|
||||
// L0 ["a", "b", "d"] |'aaaa', 'bbbb', 'dddd'|
|
||||
// | | | ^ ^ ^
|
||||
// | | | | | |
|
||||
// | | +---------|-------|--------+
|
||||
// | +-----------------|-------+
|
||||
// +-------------------------+
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
constexpr char fourth_key[] = "c";
|
||||
const std::string fourth_blob(blob_size, 'c');
|
||||
ASSERT_OK(Put(fourth_key, fourth_blob));
|
||||
// fourth_blob in another blob file.
|
||||
// SST Blob file SST Blob file
|
||||
// L0 ["a", "b", "d"] |'aaaa', 'bbbb', 'dddd'| ["c"] |'cccc'|
|
||||
// | | | ^ ^ ^ | ^
|
||||
// | | | | | | | |
|
||||
// | | +---------|-------|--------+ +-------+
|
||||
// | +-----------------|-------+
|
||||
// +-------------------------+
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), /*begin=*/nullptr,
|
||||
/*end=*/nullptr));
|
||||
|
||||
// Due to the above sst partitioner, we get 4 L1 files. The blob files are
|
||||
// unchanged.
|
||||
// |'aaaa', 'bbbb', 'dddd'| |'cccc'|
|
||||
// ^ ^ ^ ^
|
||||
// | | | |
|
||||
// L0 | | | |
|
||||
// L1 ["a"] ["b"] ["c"] | | ["d"] |
|
||||
// | | | | | |
|
||||
// | | +---------|-------|---------------+
|
||||
// | +-----------------|-------+
|
||||
// +-------------------------+
|
||||
ASSERT_EQ(4, NumTableFilesAtLevel(/*level=*/1));
|
||||
|
||||
{
|
||||
// Ingest the external SST file into bottommost level.
|
||||
std::vector<std::string> ext_files{file_path};
|
||||
IngestExternalFileOptions opts;
|
||||
opts.ingest_behind = true;
|
||||
ASSERT_OK(
|
||||
db_->IngestExternalFile(db_->DefaultColumnFamily(), ext_files, opts));
|
||||
}
|
||||
|
||||
// Now the database becomes as follows.
|
||||
// |'aaaa', 'bbbb', 'dddd'| |'cccc'|
|
||||
// ^ ^ ^ ^
|
||||
// | | | |
|
||||
// L0 | | | |
|
||||
// L1 ["a"] ["b"] ["c"] | | ["d"] |
|
||||
// | | | | | |
|
||||
// | | +---------|-------|---------------+
|
||||
// | +-----------------|-------+
|
||||
// +-------------------------+
|
||||
//
|
||||
// L6 ["b"]
|
||||
|
||||
{
|
||||
// Compact ["b"] to bottommost level.
|
||||
Slice begin = Slice(second_key);
|
||||
Slice end = Slice(second_key);
|
||||
CompactRangeOptions cro;
|
||||
cro.bottommost_level_compaction = BottommostLevelCompaction::kForce;
|
||||
ASSERT_OK(db_->CompactRange(cro, &begin, &end));
|
||||
}
|
||||
|
||||
// |'aaaa', 'bbbb', 'dddd'| |'cccc'|
|
||||
// ^ ^ ^ ^
|
||||
// | | | |
|
||||
// L0 | | | |
|
||||
// L1 ["a"] ["c"] | | ["d"] |
|
||||
// | | | | |
|
||||
// | +---------|-------|---------------+
|
||||
// | +-----------------|-------+
|
||||
// +-------|-----------------+
|
||||
// |
|
||||
// L6 ["b"]
|
||||
ASSERT_EQ(3, NumTableFilesAtLevel(/*level=*/1));
|
||||
ASSERT_EQ(1, NumTableFilesAtLevel(/*level=*/6));
|
||||
|
||||
bool called = false;
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"RandomAccessFileReader::MultiRead:AlignedReqs", [&](void* arg) {
|
||||
auto* aligned_reqs = static_cast<std::vector<FSReadRequest>*>(arg);
|
||||
assert(aligned_reqs);
|
||||
ASSERT_EQ(1, aligned_reqs->size());
|
||||
called = true;
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
std::array<Slice, num_keys> keys{{first_key, third_key, second_key}};
|
||||
|
||||
{
|
||||
std::array<PinnableSlice, num_keys> values;
|
||||
std::array<Status, num_keys> statuses;
|
||||
|
||||
// The MultiGet(), when constructing the KeyContexts, will process the keys
|
||||
// in such order: a, d, b. The reason is that ["a"] and ["d"] are in L1,
|
||||
// while ["b"] resides in L6.
|
||||
// Consequently, the original FSReadRequest list prepared by
|
||||
// Version::MultiGetblob() will be for "a", "d" and "b". It is unsorted as
|
||||
// follows:
|
||||
//
|
||||
// ["a", offset=30, len=3033],
|
||||
// ["d", offset=9096, len=3033],
|
||||
// ["b", offset=3063, len=6033]
|
||||
//
|
||||
// If we do not sort them before calling MultiRead() in DirectIO, then the
|
||||
// underlying IO merging logic will yield two requests.
|
||||
//
|
||||
// [offset=0, len=4096] (for "a")
|
||||
// [offset=0, len=12288] (result of merging the request for "d" and "b")
|
||||
//
|
||||
// We need to sort them in Version::MultiGetBlob() so that the underlying
|
||||
// IO merging logic in DirectIO mode works as expected. The correct
|
||||
// behavior will be one aligned request:
|
||||
//
|
||||
// [offset=0, len=12288]
|
||||
|
||||
db_->MultiGet(ReadOptions(), db_->DefaultColumnFamily(), num_keys, &keys[0],
|
||||
&values[0], &statuses[0]);
|
||||
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
|
||||
ASSERT_TRUE(called);
|
||||
|
||||
ASSERT_OK(statuses[0]);
|
||||
ASSERT_EQ(values[0], first_blob);
|
||||
|
||||
ASSERT_OK(statuses[1]);
|
||||
ASSERT_EQ(values[1], third_blob);
|
||||
|
||||
ASSERT_OK(statuses[2]);
|
||||
ASSERT_EQ(values[2], second_blob);
|
||||
}
|
||||
}
|
||||
#endif // !ROCKSDB_LITE
|
||||
|
||||
TEST_F(DBBlobBasicTest, MultiGetBlobsFromMultipleFiles) {
|
||||
Options options = GetDefaultOptions();
|
||||
options.enable_blob_files = true;
|
||||
options.min_blob_size = 0;
|
||||
|
||||
Reopen(options);
|
||||
|
||||
constexpr size_t kNumBlobFiles = 3;
|
||||
constexpr size_t kNumBlobsPerFile = 3;
|
||||
constexpr size_t kNumKeys = kNumBlobsPerFile * kNumBlobFiles;
|
||||
|
||||
std::vector<std::string> key_strs;
|
||||
std::vector<std::string> value_strs;
|
||||
for (size_t i = 0; i < kNumBlobFiles; ++i) {
|
||||
for (size_t j = 0; j < kNumBlobsPerFile; ++j) {
|
||||
std::string key = "key" + std::to_string(i) + "_" + std::to_string(j);
|
||||
std::string value =
|
||||
"value_as_blob" + std::to_string(i) + "_" + std::to_string(j);
|
||||
ASSERT_OK(Put(key, value));
|
||||
key_strs.push_back(key);
|
||||
value_strs.push_back(value);
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
assert(key_strs.size() == kNumKeys);
|
||||
std::array<Slice, kNumKeys> keys;
|
||||
for (size_t i = 0; i < keys.size(); ++i) {
|
||||
keys[i] = key_strs[i];
|
||||
}
|
||||
std::array<PinnableSlice, kNumKeys> values;
|
||||
std::array<Status, kNumKeys> statuses;
|
||||
db_->MultiGet(ReadOptions(), db_->DefaultColumnFamily(), kNumKeys, &keys[0],
|
||||
&values[0], &statuses[0]);
|
||||
|
||||
for (size_t i = 0; i < kNumKeys; ++i) {
|
||||
ASSERT_OK(statuses[i]);
|
||||
ASSERT_EQ(value_strs[i], values[i]);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBBlobBasicTest, GetBlob_CorruptIndex) {
|
||||
Options options = GetDefaultOptions();
|
||||
options.enable_blob_files = true;
|
||||
@@ -148,6 +381,83 @@ TEST_F(DBBlobBasicTest, GetBlob_CorruptIndex) {
|
||||
.IsCorruption());
|
||||
}
|
||||
|
||||
TEST_F(DBBlobBasicTest, MultiGetBlob_CorruptIndex) {
|
||||
Options options = GetDefaultOptions();
|
||||
options.enable_blob_files = true;
|
||||
options.min_blob_size = 0;
|
||||
options.create_if_missing = true;
|
||||
|
||||
DestroyAndReopen(options);
|
||||
|
||||
constexpr size_t kNumOfKeys = 3;
|
||||
std::array<std::string, kNumOfKeys> key_strs;
|
||||
std::array<std::string, kNumOfKeys> value_strs;
|
||||
std::array<Slice, kNumOfKeys + 1> keys;
|
||||
for (size_t i = 0; i < kNumOfKeys; ++i) {
|
||||
key_strs[i] = "foo" + std::to_string(i);
|
||||
value_strs[i] = "blob_value" + std::to_string(i);
|
||||
ASSERT_OK(Put(key_strs[i], value_strs[i]));
|
||||
keys[i] = key_strs[i];
|
||||
}
|
||||
|
||||
constexpr char key[] = "key";
|
||||
{
|
||||
// Fake a corrupt blob index.
|
||||
const std::string blob_index("foobar");
|
||||
WriteBatch batch;
|
||||
ASSERT_OK(WriteBatchInternal::PutBlobIndex(&batch, 0, key, blob_index));
|
||||
ASSERT_OK(db_->Write(WriteOptions(), &batch));
|
||||
keys[kNumOfKeys] = Slice(static_cast<const char*>(key), sizeof(key) - 1);
|
||||
}
|
||||
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
std::array<PinnableSlice, kNumOfKeys + 1> values;
|
||||
std::array<Status, kNumOfKeys + 1> statuses;
|
||||
db_->MultiGet(ReadOptions(), dbfull()->DefaultColumnFamily(), kNumOfKeys + 1,
|
||||
keys.data(), values.data(), statuses.data(),
|
||||
/*sorted_input=*/false);
|
||||
for (size_t i = 0; i < kNumOfKeys + 1; ++i) {
|
||||
if (i != kNumOfKeys) {
|
||||
ASSERT_OK(statuses[i]);
|
||||
ASSERT_EQ("blob_value" + std::to_string(i), values[i]);
|
||||
} else {
|
||||
ASSERT_TRUE(statuses[i].IsCorruption());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBBlobBasicTest, MultiGetBlob_ExceedSoftLimit) {
|
||||
Options options = GetDefaultOptions();
|
||||
options.enable_blob_files = true;
|
||||
options.min_blob_size = 0;
|
||||
|
||||
Reopen(options);
|
||||
|
||||
constexpr size_t kNumOfKeys = 3;
|
||||
std::array<std::string, kNumOfKeys> key_bufs;
|
||||
std::array<std::string, kNumOfKeys> value_bufs;
|
||||
std::array<Slice, kNumOfKeys> keys;
|
||||
for (size_t i = 0; i < kNumOfKeys; ++i) {
|
||||
key_bufs[i] = "foo" + std::to_string(i);
|
||||
value_bufs[i] = "blob_value" + std::to_string(i);
|
||||
ASSERT_OK(Put(key_bufs[i], value_bufs[i]));
|
||||
keys[i] = key_bufs[i];
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
std::array<PinnableSlice, kNumOfKeys> values;
|
||||
std::array<Status, kNumOfKeys> statuses;
|
||||
ReadOptions read_opts;
|
||||
read_opts.value_size_soft_limit = 1;
|
||||
db_->MultiGet(read_opts, dbfull()->DefaultColumnFamily(), kNumOfKeys,
|
||||
keys.data(), values.data(), statuses.data(),
|
||||
/*sorted_input=*/true);
|
||||
for (const auto& s : statuses) {
|
||||
ASSERT_TRUE(s.IsAborted());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBBlobBasicTest, GetBlob_InlinedTTLIndex) {
|
||||
constexpr uint64_t min_blob_size = 10;
|
||||
|
||||
@@ -367,6 +677,147 @@ TEST_F(DBBlobBasicTest, MultiGetMergeBlobWithPut) {
|
||||
ASSERT_EQ(values[2], "v2_0");
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
TEST_F(DBBlobBasicTest, Properties) {
|
||||
Options options = GetDefaultOptions();
|
||||
options.enable_blob_files = true;
|
||||
options.min_blob_size = 0;
|
||||
|
||||
Reopen(options);
|
||||
|
||||
constexpr char key1[] = "key1";
|
||||
constexpr size_t key1_size = sizeof(key1) - 1;
|
||||
|
||||
constexpr char key2[] = "key2";
|
||||
constexpr size_t key2_size = sizeof(key2) - 1;
|
||||
|
||||
constexpr char key3[] = "key3";
|
||||
constexpr size_t key3_size = sizeof(key3) - 1;
|
||||
|
||||
constexpr char blob[] = "0000000000";
|
||||
constexpr size_t blob_size = sizeof(blob) - 1;
|
||||
|
||||
ASSERT_OK(Put(key1, blob));
|
||||
ASSERT_OK(Put(key2, blob));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
constexpr size_t first_blob_file_expected_size =
|
||||
BlobLogHeader::kSize +
|
||||
BlobLogRecord::CalculateAdjustmentForRecordHeader(key1_size) + blob_size +
|
||||
BlobLogRecord::CalculateAdjustmentForRecordHeader(key2_size) + blob_size +
|
||||
BlobLogFooter::kSize;
|
||||
|
||||
ASSERT_OK(Put(key3, blob));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
constexpr size_t second_blob_file_expected_size =
|
||||
BlobLogHeader::kSize +
|
||||
BlobLogRecord::CalculateAdjustmentForRecordHeader(key3_size) + blob_size +
|
||||
BlobLogFooter::kSize;
|
||||
|
||||
constexpr size_t total_expected_size =
|
||||
first_blob_file_expected_size + second_blob_file_expected_size;
|
||||
|
||||
// Number of blob files
|
||||
uint64_t num_blob_files = 0;
|
||||
ASSERT_TRUE(
|
||||
db_->GetIntProperty(DB::Properties::kNumBlobFiles, &num_blob_files));
|
||||
ASSERT_EQ(num_blob_files, 2);
|
||||
|
||||
// Total size of live blob files
|
||||
uint64_t live_blob_file_size = 0;
|
||||
ASSERT_TRUE(db_->GetIntProperty(DB::Properties::kLiveBlobFileSize,
|
||||
&live_blob_file_size));
|
||||
ASSERT_EQ(live_blob_file_size, total_expected_size);
|
||||
|
||||
// Total size of all blob files across all versions
|
||||
// Note: this should be the same as above since we only have one
|
||||
// version at this point.
|
||||
uint64_t total_blob_file_size = 0;
|
||||
ASSERT_TRUE(db_->GetIntProperty(DB::Properties::kTotalBlobFileSize,
|
||||
&total_blob_file_size));
|
||||
ASSERT_EQ(total_blob_file_size, total_expected_size);
|
||||
|
||||
// Delete key2 to create some garbage
|
||||
ASSERT_OK(Delete(key2));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
constexpr Slice* begin = nullptr;
|
||||
constexpr Slice* end = nullptr;
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), begin, end));
|
||||
|
||||
constexpr size_t expected_garbage_size =
|
||||
BlobLogRecord::CalculateAdjustmentForRecordHeader(key2_size) + blob_size;
|
||||
|
||||
// Blob file stats
|
||||
std::string blob_stats;
|
||||
ASSERT_TRUE(db_->GetProperty(DB::Properties::kBlobStats, &blob_stats));
|
||||
|
||||
std::ostringstream oss;
|
||||
oss << "Number of blob files: 2\nTotal size of blob files: "
|
||||
<< total_expected_size
|
||||
<< "\nTotal size of garbage in blob files: " << expected_garbage_size
|
||||
<< '\n';
|
||||
|
||||
ASSERT_EQ(blob_stats, oss.str());
|
||||
}
|
||||
|
||||
TEST_F(DBBlobBasicTest, PropertiesMultiVersion) {
|
||||
Options options = GetDefaultOptions();
|
||||
options.enable_blob_files = true;
|
||||
options.min_blob_size = 0;
|
||||
|
||||
Reopen(options);
|
||||
|
||||
constexpr char key1[] = "key1";
|
||||
constexpr char key2[] = "key2";
|
||||
constexpr char key3[] = "key3";
|
||||
|
||||
constexpr size_t key_size = sizeof(key1) - 1;
|
||||
static_assert(sizeof(key2) - 1 == key_size, "unexpected size: key2");
|
||||
static_assert(sizeof(key3) - 1 == key_size, "unexpected size: key3");
|
||||
|
||||
constexpr char blob[] = "0000000000";
|
||||
constexpr size_t blob_size = sizeof(blob) - 1;
|
||||
|
||||
ASSERT_OK(Put(key1, blob));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
ASSERT_OK(Put(key2, blob));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
// Create an iterator to keep the current version alive
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(ReadOptions()));
|
||||
ASSERT_OK(iter->status());
|
||||
|
||||
// Note: the Delete and subsequent compaction results in the first blob file
|
||||
// not making it to the final version. (It is still part of the previous
|
||||
// version kept alive by the iterator though.) On the other hand, the Put
|
||||
// results in a third blob file.
|
||||
ASSERT_OK(Delete(key1));
|
||||
ASSERT_OK(Put(key3, blob));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
constexpr Slice* begin = nullptr;
|
||||
constexpr Slice* end = nullptr;
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), begin, end));
|
||||
|
||||
// Total size of all blob files across all versions: between the two versions,
|
||||
// we should have three blob files of the same size with one blob each.
|
||||
// The version kept alive by the iterator contains the first and the second
|
||||
// blob file, while the final version contains the second and the third blob
|
||||
// file. (The second blob file is thus shared by the two versions but should
|
||||
// be counted only once.)
|
||||
uint64_t total_blob_file_size = 0;
|
||||
ASSERT_TRUE(db_->GetIntProperty(DB::Properties::kTotalBlobFileSize,
|
||||
&total_blob_file_size));
|
||||
ASSERT_EQ(total_blob_file_size,
|
||||
3 * (BlobLogHeader::kSize +
|
||||
BlobLogRecord::CalculateAdjustmentForRecordHeader(key_size) +
|
||||
blob_size + BlobLogFooter::kSize));
|
||||
}
|
||||
#endif // !ROCKSDB_LITE
|
||||
|
||||
class DBBlobBasicIOErrorTest : public DBBlobBasicTest,
|
||||
public testing::WithParamInterface<std::string> {
|
||||
protected:
|
||||
@@ -379,11 +830,21 @@ class DBBlobBasicIOErrorTest : public DBBlobBasicTest,
|
||||
std::string sync_point_;
|
||||
};
|
||||
|
||||
class DBBlobBasicIOErrorMultiGetTest : public DBBlobBasicIOErrorTest {
|
||||
public:
|
||||
DBBlobBasicIOErrorMultiGetTest() : DBBlobBasicIOErrorTest() {}
|
||||
};
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(DBBlobBasicTest, DBBlobBasicIOErrorTest,
|
||||
::testing::ValuesIn(std::vector<std::string>{
|
||||
"BlobFileReader::OpenFile:NewRandomAccessFile",
|
||||
"BlobFileReader::GetBlob:ReadFromFile"}));
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(DBBlobBasicTest, DBBlobBasicIOErrorMultiGetTest,
|
||||
::testing::ValuesIn(std::vector<std::string>{
|
||||
"BlobFileReader::OpenFile:NewRandomAccessFile",
|
||||
"BlobFileReader::MultiGetBlob:ReadFromFile"}));
|
||||
|
||||
TEST_P(DBBlobBasicIOErrorTest, GetBlob_IOError) {
|
||||
Options options;
|
||||
options.env = fault_injection_env_.get();
|
||||
@@ -413,7 +874,7 @@ TEST_P(DBBlobBasicIOErrorTest, GetBlob_IOError) {
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
}
|
||||
|
||||
TEST_P(DBBlobBasicIOErrorTest, MultiGetBlobs_IOError) {
|
||||
TEST_P(DBBlobBasicIOErrorMultiGetTest, MultiGetBlobs_IOError) {
|
||||
Options options = GetDefaultOptions();
|
||||
options.env = fault_injection_env_.get();
|
||||
options.enable_blob_files = true;
|
||||
@@ -455,6 +916,53 @@ TEST_P(DBBlobBasicIOErrorTest, MultiGetBlobs_IOError) {
|
||||
ASSERT_TRUE(statuses[1].IsIOError());
|
||||
}
|
||||
|
||||
TEST_P(DBBlobBasicIOErrorMultiGetTest, MultipleBlobFiles) {
|
||||
Options options = GetDefaultOptions();
|
||||
options.env = fault_injection_env_.get();
|
||||
options.enable_blob_files = true;
|
||||
options.min_blob_size = 0;
|
||||
|
||||
Reopen(options);
|
||||
|
||||
constexpr size_t num_keys = 2;
|
||||
|
||||
constexpr char key1[] = "key1";
|
||||
constexpr char value1[] = "blob1";
|
||||
|
||||
ASSERT_OK(Put(key1, value1));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
constexpr char key2[] = "key2";
|
||||
constexpr char value2[] = "blob2";
|
||||
|
||||
ASSERT_OK(Put(key2, value2));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
std::array<Slice, num_keys> keys{{key1, key2}};
|
||||
std::array<PinnableSlice, num_keys> values;
|
||||
std::array<Status, num_keys> statuses;
|
||||
|
||||
bool first_blob_file = true;
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
sync_point_, [&first_blob_file, this](void* /* arg */) {
|
||||
if (first_blob_file) {
|
||||
first_blob_file = false;
|
||||
return;
|
||||
}
|
||||
fault_injection_env_->SetFilesystemActive(false,
|
||||
Status::IOError(sync_point_));
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
db_->MultiGet(ReadOptions(), db_->DefaultColumnFamily(), num_keys,
|
||||
keys.data(), values.data(), statuses.data());
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
ASSERT_OK(statuses[0]);
|
||||
ASSERT_EQ(value1, values[0]);
|
||||
ASSERT_TRUE(statuses[1].IsIOError());
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
class ReadBlobCompactionFilter : public CompactionFilter {
|
||||
@@ -510,8 +1018,17 @@ TEST_P(DBBlobBasicIOErrorTest, CompactionFilterReadBlob_IOError) {
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
#ifdef ROCKSDB_UNITTESTS_WITH_CUSTOM_OBJECTS_FROM_STATIC_LIBS
|
||||
extern "C" {
|
||||
void RegisterCustomObjects(int argc, char** argv);
|
||||
}
|
||||
#else
|
||||
void RegisterCustomObjects(int /*argc*/, char** /*argv*/) {}
|
||||
#endif // !ROCKSDB_UNITTESTS_WITH_CUSTOM_OBJECTS_FROM_STATIC_LIBS
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
ROCKSDB_NAMESPACE::port::InstallStackTraceHandler();
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
RegisterCustomObjects(argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
@@ -8,7 +8,6 @@
|
||||
#include "db/db_test_util.h"
|
||||
#include "port/stack_trace.h"
|
||||
#include "test_util/sync_point.h"
|
||||
#include "utilities/fault_injection_env.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
@@ -593,8 +592,17 @@ TEST_F(DBBlobCompactionTest, MergeBlobWithBase) {
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
#ifdef ROCKSDB_UNITTESTS_WITH_CUSTOM_OBJECTS_FROM_STATIC_LIBS
|
||||
extern "C" {
|
||||
void RegisterCustomObjects(int argc, char** argv);
|
||||
}
|
||||
#else
|
||||
void RegisterCustomObjects(int /*argc*/, char** /*argv*/) {}
|
||||
#endif // !ROCKSDB_UNITTESTS_WITH_CUSTOM_OBJECTS_FROM_STATIC_LIBS
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
ROCKSDB_NAMESPACE::port::InstallStackTraceHandler();
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
RegisterCustomObjects(argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
@@ -74,8 +74,17 @@ TEST_F(DBBlobCorruptionTest, VerifyWholeBlobFileChecksum) {
|
||||
#endif // !ROCKSDB_LITE
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
#ifdef ROCKSDB_UNITTESTS_WITH_CUSTOM_OBJECTS_FROM_STATIC_LIBS
|
||||
extern "C" {
|
||||
void RegisterCustomObjects(int argc, char** argv);
|
||||
}
|
||||
#else
|
||||
void RegisterCustomObjects(int /*argc*/, char** /*argv*/) {}
|
||||
#endif // !ROCKSDB_UNITTESTS_WITH_CUSTOM_OBJECTS_FROM_STATIC_LIBS
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
ROCKSDB_NAMESPACE::port::InstallStackTraceHandler();
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
RegisterCustomObjects(argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
@@ -564,8 +564,17 @@ TEST_F(DBBlobIndexTest, IntegratedBlobIterate) {
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
#ifdef ROCKSDB_UNITTESTS_WITH_CUSTOM_OBJECTS_FROM_STATIC_LIBS
|
||||
extern "C" {
|
||||
void RegisterCustomObjects(int argc, char** argv);
|
||||
}
|
||||
#else
|
||||
void RegisterCustomObjects(int /*argc*/, char** /*argv*/) {}
|
||||
#endif // !ROCKSDB_UNITTESTS_WITH_CUSTOM_OBJECTS_FROM_STATIC_LIBS
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
ROCKSDB_NAMESPACE::port::InstallStackTraceHandler();
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
RegisterCustomObjects(argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
+9
-9
@@ -15,7 +15,6 @@
|
||||
|
||||
#include "db/blob/blob_file_builder.h"
|
||||
#include "db/compaction/compaction_iterator.h"
|
||||
#include "db/dbformat.h"
|
||||
#include "db/event_helpers.h"
|
||||
#include "db/internal_stats.h"
|
||||
#include "db/merge_helper.h"
|
||||
@@ -65,7 +64,8 @@ Status BuildTable(
|
||||
SequenceNumber earliest_write_conflict_snapshot,
|
||||
SnapshotChecker* snapshot_checker, bool paranoid_file_checks,
|
||||
InternalStats* internal_stats, IOStatus* io_status,
|
||||
const std::shared_ptr<IOTracer>& io_tracer, EventLogger* event_logger,
|
||||
const std::shared_ptr<IOTracer>& io_tracer,
|
||||
BlobFileCreationReason blob_creation_reason, EventLogger* event_logger,
|
||||
int job_id, const Env::IOPriority io_priority,
|
||||
TableProperties* table_properties, Env::WriteLifeTimeHint write_hint,
|
||||
const std::string* full_history_ts_low,
|
||||
@@ -178,12 +178,12 @@ Status BuildTable(
|
||||
|
||||
std::unique_ptr<BlobFileBuilder> blob_file_builder(
|
||||
(mutable_cf_options.enable_blob_files && blob_file_additions)
|
||||
? new BlobFileBuilder(versions, fs, &ioptions, &mutable_cf_options,
|
||||
&file_options, job_id,
|
||||
tboptions.column_family_id,
|
||||
tboptions.column_family_name, io_priority,
|
||||
write_hint, io_tracer, blob_callback,
|
||||
&blob_file_paths, blob_file_additions)
|
||||
? new BlobFileBuilder(
|
||||
versions, fs, &ioptions, &mutable_cf_options, &file_options,
|
||||
job_id, tboptions.column_family_id,
|
||||
tboptions.column_family_name, io_priority, write_hint,
|
||||
io_tracer, blob_callback, blob_creation_reason,
|
||||
&blob_file_paths, blob_file_additions)
|
||||
: nullptr);
|
||||
|
||||
CompactionIterator c_iter(
|
||||
@@ -311,7 +311,7 @@ Status BuildTable(
|
||||
if (s.ok()) {
|
||||
s = blob_file_builder->Finish();
|
||||
} else {
|
||||
blob_file_builder->Abandon();
|
||||
blob_file_builder->Abandon(s);
|
||||
}
|
||||
blob_file_builder.reset();
|
||||
}
|
||||
|
||||
@@ -60,6 +60,7 @@ extern Status BuildTable(
|
||||
SnapshotChecker* snapshot_checker, bool paranoid_file_checks,
|
||||
InternalStats* internal_stats, IOStatus* io_status,
|
||||
const std::shared_ptr<IOTracer>& io_tracer,
|
||||
BlobFileCreationReason blob_creation_reason,
|
||||
EventLogger* event_logger = nullptr, int job_id = 0,
|
||||
const Env::IOPriority io_priority = Env::IO_HIGH,
|
||||
TableProperties* table_properties = nullptr,
|
||||
|
||||
@@ -2751,6 +2751,15 @@ double rocksdb_options_get_blob_gc_age_cutoff(rocksdb_options_t* opt) {
|
||||
return opt->rep.blob_garbage_collection_age_cutoff;
|
||||
}
|
||||
|
||||
void rocksdb_options_set_blob_gc_force_threshold(rocksdb_options_t* opt,
|
||||
double val) {
|
||||
opt->rep.blob_garbage_collection_force_threshold = val;
|
||||
}
|
||||
|
||||
double rocksdb_options_get_blob_gc_force_threshold(rocksdb_options_t* opt) {
|
||||
return opt->rep.blob_garbage_collection_force_threshold;
|
||||
}
|
||||
|
||||
void rocksdb_options_set_num_levels(rocksdb_options_t* opt, int n) {
|
||||
opt->rep.num_levels = n;
|
||||
}
|
||||
|
||||
+5
-2
@@ -1793,8 +1793,11 @@ int main(int argc, char** argv) {
|
||||
rocksdb_options_set_enable_blob_gc(o, 1);
|
||||
CheckCondition(1 == rocksdb_options_get_enable_blob_gc(o));
|
||||
|
||||
rocksdb_options_set_blob_gc_age_cutoff(o, 0.75);
|
||||
CheckCondition(0.75 == rocksdb_options_get_blob_gc_age_cutoff(o));
|
||||
rocksdb_options_set_blob_gc_age_cutoff(o, 0.5);
|
||||
CheckCondition(0.5 == rocksdb_options_get_blob_gc_age_cutoff(o));
|
||||
|
||||
rocksdb_options_set_blob_gc_force_threshold(o, 0.75);
|
||||
CheckCondition(0.75 == rocksdb_options_get_blob_gc_force_threshold(o));
|
||||
|
||||
// Create a copy that should be equal to the original.
|
||||
rocksdb_options_t* copy;
|
||||
|
||||
+31
-14
@@ -29,7 +29,7 @@
|
||||
#include "db/version_set.h"
|
||||
#include "db/write_controller.h"
|
||||
#include "file/sst_file_manager_impl.h"
|
||||
#include "memtable/hash_skiplist_rep.h"
|
||||
#include "logging/logging.h"
|
||||
#include "monitoring/thread_status_util.h"
|
||||
#include "options/options_helper.h"
|
||||
#include "port/port.h"
|
||||
@@ -203,7 +203,7 @@ Status CheckCFPathsSupported(const DBOptions& db_options,
|
||||
namespace {
|
||||
const uint64_t kDefaultTtl = 0xfffffffffffffffe;
|
||||
const uint64_t kDefaultPeriodicCompSecs = 0xfffffffffffffffe;
|
||||
}; // namespace
|
||||
} // namespace
|
||||
|
||||
ColumnFamilyOptions SanitizeOptions(const ImmutableDBOptions& db_options,
|
||||
const ColumnFamilyOptions& src) {
|
||||
@@ -349,12 +349,18 @@ ColumnFamilyOptions SanitizeOptions(const ImmutableDBOptions& db_options,
|
||||
}
|
||||
|
||||
if (result.level_compaction_dynamic_level_bytes) {
|
||||
if (result.compaction_style != kCompactionStyleLevel ||
|
||||
result.cf_paths.size() > 1U) {
|
||||
// 1. level_compaction_dynamic_level_bytes only makes sense for
|
||||
// level-based compaction.
|
||||
// 2. we don't yet know how to make both of this feature and multiple
|
||||
// DB path work.
|
||||
if (result.compaction_style != kCompactionStyleLevel) {
|
||||
ROCKS_LOG_WARN(db_options.info_log.get(),
|
||||
"level_compaction_dynamic_level_bytes only makes sense"
|
||||
"for level-based compaction");
|
||||
result.level_compaction_dynamic_level_bytes = false;
|
||||
} else if (result.cf_paths.size() > 1U) {
|
||||
// we don't yet know how to make both of this feature and multiple
|
||||
// DB path work.
|
||||
ROCKS_LOG_WARN(db_options.info_log.get(),
|
||||
"multiple cf_paths/db_paths and"
|
||||
"level_compaction_dynamic_level_bytes"
|
||||
"can't be used together");
|
||||
result.level_compaction_dynamic_level_bytes = false;
|
||||
}
|
||||
}
|
||||
@@ -1049,6 +1055,10 @@ uint64_t ColumnFamilyData::GetTotalSstFilesSize() const {
|
||||
return VersionSet::GetTotalSstFilesSize(dummy_versions_);
|
||||
}
|
||||
|
||||
uint64_t ColumnFamilyData::GetTotalBlobFileSize() const {
|
||||
return VersionSet::GetTotalBlobFileSize(dummy_versions_);
|
||||
}
|
||||
|
||||
uint64_t ColumnFamilyData::GetLiveSstFilesSize() const {
|
||||
return current_->GetSstFilesSize();
|
||||
}
|
||||
@@ -1348,12 +1358,19 @@ Status ColumnFamilyData::ValidateOptions(
|
||||
}
|
||||
}
|
||||
|
||||
if (cf_options.enable_blob_garbage_collection &&
|
||||
(cf_options.blob_garbage_collection_age_cutoff < 0.0 ||
|
||||
cf_options.blob_garbage_collection_age_cutoff > 1.0)) {
|
||||
return Status::InvalidArgument(
|
||||
"The age cutoff for blob garbage collection should be in the range "
|
||||
"[0.0, 1.0].");
|
||||
if (cf_options.enable_blob_garbage_collection) {
|
||||
if (cf_options.blob_garbage_collection_age_cutoff < 0.0 ||
|
||||
cf_options.blob_garbage_collection_age_cutoff > 1.0) {
|
||||
return Status::InvalidArgument(
|
||||
"The age cutoff for blob garbage collection should be in the range "
|
||||
"[0.0, 1.0].");
|
||||
}
|
||||
if (cf_options.blob_garbage_collection_force_threshold < 0.0 ||
|
||||
cf_options.blob_garbage_collection_force_threshold > 1.0) {
|
||||
return Status::InvalidArgument(
|
||||
"The garbage ratio threshold for forcing blob garbage collection "
|
||||
"should be in the range [0.0, 1.0].");
|
||||
}
|
||||
}
|
||||
|
||||
if (cf_options.compaction_style == kCompactionStyleFIFO &&
|
||||
|
||||
@@ -357,6 +357,7 @@ class ColumnFamilyData {
|
||||
uint64_t GetNumLiveVersions() const; // REQUIRE: DB mutex held
|
||||
uint64_t GetTotalSstFilesSize() const; // REQUIRE: DB mutex held
|
||||
uint64_t GetLiveSstFilesSize() const; // REQUIRE: DB mutex held
|
||||
uint64_t GetTotalBlobFileSize() const; // REQUIRE: DB mutex held
|
||||
void SetMemtable(MemTable* new_mem) {
|
||||
uint64_t memtable_id = last_memtable_id_.fetch_add(1) + 1;
|
||||
new_mem->SetID(memtable_id);
|
||||
|
||||
@@ -2975,7 +2975,8 @@ TEST_P(ColumnFamilyTest, FlushCloseWALFiles) {
|
||||
SpecialEnv env(Env::Default());
|
||||
db_options_.env = &env;
|
||||
db_options_.max_background_flushes = 1;
|
||||
column_family_options_.memtable_factory.reset(new SpecialSkipListFactory(2));
|
||||
column_family_options_.memtable_factory.reset(
|
||||
test::NewSpecialSkipListFactory(2));
|
||||
Open();
|
||||
CreateColumnFamilies({"one"});
|
||||
ASSERT_OK(Put(1, "fodor", "mirko"));
|
||||
@@ -3020,7 +3021,8 @@ TEST_P(ColumnFamilyTest, IteratorCloseWALFile1) {
|
||||
SpecialEnv env(Env::Default());
|
||||
db_options_.env = &env;
|
||||
db_options_.max_background_flushes = 1;
|
||||
column_family_options_.memtable_factory.reset(new SpecialSkipListFactory(2));
|
||||
column_family_options_.memtable_factory.reset(
|
||||
test::NewSpecialSkipListFactory(2));
|
||||
Open();
|
||||
CreateColumnFamilies({"one"});
|
||||
ASSERT_OK(Put(1, "fodor", "mirko"));
|
||||
@@ -3071,7 +3073,8 @@ TEST_P(ColumnFamilyTest, IteratorCloseWALFile2) {
|
||||
env.SetBackgroundThreads(2, Env::HIGH);
|
||||
db_options_.env = &env;
|
||||
db_options_.max_background_flushes = 1;
|
||||
column_family_options_.memtable_factory.reset(new SpecialSkipListFactory(2));
|
||||
column_family_options_.memtable_factory.reset(
|
||||
test::NewSpecialSkipListFactory(2));
|
||||
Open();
|
||||
CreateColumnFamilies({"one"});
|
||||
ASSERT_OK(Put(1, "fodor", "mirko"));
|
||||
@@ -3129,7 +3132,8 @@ TEST_P(ColumnFamilyTest, ForwardIteratorCloseWALFile) {
|
||||
env.SetBackgroundThreads(2, Env::HIGH);
|
||||
db_options_.env = &env;
|
||||
db_options_.max_background_flushes = 1;
|
||||
column_family_options_.memtable_factory.reset(new SpecialSkipListFactory(3));
|
||||
column_family_options_.memtable_factory.reset(
|
||||
test::NewSpecialSkipListFactory(3));
|
||||
column_family_options_.level0_file_num_compaction_trigger = 2;
|
||||
Open();
|
||||
CreateColumnFamilies({"one"});
|
||||
@@ -3403,6 +3407,30 @@ TEST(ColumnFamilyTest, ValidateBlobGCCutoff) {
|
||||
.IsInvalidArgument());
|
||||
}
|
||||
|
||||
TEST(ColumnFamilyTest, ValidateBlobGCForceThreshold) {
|
||||
DBOptions db_options;
|
||||
|
||||
ColumnFamilyOptions cf_options;
|
||||
cf_options.enable_blob_garbage_collection = true;
|
||||
|
||||
cf_options.blob_garbage_collection_force_threshold = -0.5;
|
||||
ASSERT_TRUE(ColumnFamilyData::ValidateOptions(db_options, cf_options)
|
||||
.IsInvalidArgument());
|
||||
|
||||
cf_options.blob_garbage_collection_force_threshold = 0.0;
|
||||
ASSERT_OK(ColumnFamilyData::ValidateOptions(db_options, cf_options));
|
||||
|
||||
cf_options.blob_garbage_collection_force_threshold = 0.5;
|
||||
ASSERT_OK(ColumnFamilyData::ValidateOptions(db_options, cf_options));
|
||||
|
||||
cf_options.blob_garbage_collection_force_threshold = 1.0;
|
||||
ASSERT_OK(ColumnFamilyData::ValidateOptions(db_options, cf_options));
|
||||
|
||||
cf_options.blob_garbage_collection_force_threshold = 1.5;
|
||||
ASSERT_TRUE(ColumnFamilyData::ValidateOptions(db_options, cf_options)
|
||||
.IsInvalidArgument());
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
#ifdef ROCKSDB_UNITTESTS_WITH_CUSTOM_OBJECTS_FROM_STATIC_LIBS
|
||||
|
||||
@@ -10,22 +10,24 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "db/dbformat.h"
|
||||
#include "rocksdb/comparator.h"
|
||||
#include "test_util/testharness.h"
|
||||
#include "test_util/testutil.h"
|
||||
#include "util/vector_iterator.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
// A vector iterator which does its own bounds checking. This is for testing the
|
||||
// optimizations in the clipping iterator where we bypass the bounds checking if
|
||||
// the input iterator has already performed it.
|
||||
class BoundsCheckingVectorIterator : public test::VectorIterator {
|
||||
class BoundsCheckingVectorIterator : public VectorIterator {
|
||||
public:
|
||||
BoundsCheckingVectorIterator(const std::vector<std::string>& keys,
|
||||
const std::vector<std::string>& values,
|
||||
const Slice* start, const Slice* end,
|
||||
const Comparator* cmp)
|
||||
: VectorIterator(keys, values), start_(start), end_(end), cmp_(cmp) {
|
||||
: VectorIterator(keys, values, cmp), start_(start), end_(end), cmp_(cmp) {
|
||||
assert(cmp_);
|
||||
}
|
||||
|
||||
@@ -105,7 +107,7 @@ TEST_P(ClippingIteratorTest, Clip) {
|
||||
use_bounds_checking_vec_it
|
||||
? new BoundsCheckingVectorIterator(input_keys, input_values, &start,
|
||||
&end, BytewiseComparator())
|
||||
: new test::VectorIterator(input_keys, input_values));
|
||||
: new VectorIterator(input_keys, input_values, BytewiseComparator()));
|
||||
|
||||
ClippingIterator clip(input.get(), &start, &end, BytewiseComparator());
|
||||
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include "rocksdb/rocksdb_namespace.h"
|
||||
|
||||
struct CompactionIterationStats {
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include "db/blob/blob_file_builder.h"
|
||||
#include "db/blob/blob_index.h"
|
||||
#include "db/snapshot_checker.h"
|
||||
#include "logging/logging.h"
|
||||
#include "port/likely.h"
|
||||
#include "rocksdb/listener.h"
|
||||
#include "table/internal_iterator.h"
|
||||
@@ -543,9 +544,21 @@ void CompactionIterator::NextFromInput() {
|
||||
// we can choose how to handle such a combinations of operations. We will
|
||||
// try to compact out as much as we can in these cases.
|
||||
// We will report counts on these anomalous cases.
|
||||
//
|
||||
// Note: If timestamp is enabled, then record will be eligible for
|
||||
// deletion, only if, along with above conditions (Rule 1 and Rule 2)
|
||||
// full_history_ts_low_ is specified and timestamp for that key is less
|
||||
// than *full_history_ts_low_. If it's not eligible for deletion, then we
|
||||
// will output the SingleDelete. For Optimization 3 also, if
|
||||
// full_history_ts_low_ is specified and timestamp for the key is less
|
||||
// than *full_history_ts_low_ then only optimization will be applied.
|
||||
|
||||
// The easiest way to process a SingleDelete during iteration is to peek
|
||||
// ahead at the next key.
|
||||
const bool is_timestamp_eligible_for_gc =
|
||||
(timestamp_size_ == 0 ||
|
||||
(full_history_ts_low_ && cmp_with_history_ts_low_ < 0));
|
||||
|
||||
ParsedInternalKey next_ikey;
|
||||
AdvanceInputIter();
|
||||
|
||||
@@ -554,7 +567,7 @@ void CompactionIterator::NextFromInput() {
|
||||
if (input_.Valid() &&
|
||||
ParseInternalKey(input_.key(), &next_ikey, allow_data_in_errors_)
|
||||
.ok() &&
|
||||
cmp_->Equal(ikey_.user_key, next_ikey.user_key)) {
|
||||
cmp_->EqualWithoutTimestamp(ikey_.user_key, next_ikey.user_key)) {
|
||||
#ifndef NDEBUG
|
||||
const Compaction* c =
|
||||
compaction_ ? compaction_->real_compaction() : nullptr;
|
||||
@@ -562,7 +575,6 @@ void CompactionIterator::NextFromInput() {
|
||||
TEST_SYNC_POINT_CALLBACK(
|
||||
"CompactionIterator::NextFromInput:SingleDelete:1",
|
||||
const_cast<Compaction*>(c));
|
||||
|
||||
// Check whether the next key belongs to the same snapshot as the
|
||||
// SingleDelete.
|
||||
if (prev_snapshot == 0 ||
|
||||
@@ -570,15 +582,20 @@ void CompactionIterator::NextFromInput() {
|
||||
TEST_SYNC_POINT_CALLBACK(
|
||||
"CompactionIterator::NextFromInput:SingleDelete:2", nullptr);
|
||||
if (next_ikey.type == kTypeSingleDeletion) {
|
||||
// We encountered two SingleDeletes in a row. This could be due to
|
||||
// unexpected user input.
|
||||
// Skip the first SingleDelete and let the next iteration decide how
|
||||
// to handle the second SingleDelete
|
||||
// We encountered two SingleDeletes for same key in a row. This
|
||||
// could be due to unexpected user input. Skip the first
|
||||
// SingleDelete and let the next iteration decide how to handle the
|
||||
// second SingleDelete
|
||||
|
||||
// First SingleDelete has been skipped since we already called
|
||||
// input_.Next().
|
||||
++iter_stats_.num_record_drop_obsolete;
|
||||
++iter_stats_.num_single_del_mismatch;
|
||||
} else if (!is_timestamp_eligible_for_gc) {
|
||||
// We cannot drop the SingleDelete as timestamp is enabled, and
|
||||
// timestamp of this key is greater than or equal to
|
||||
// *full_history_ts_low_. We will output the SingleDelete.
|
||||
valid_ = true;
|
||||
} else if (has_outputted_key_ ||
|
||||
DefinitelyInSnapshot(ikey_.sequence,
|
||||
earliest_write_conflict_snapshot_)) {
|
||||
@@ -635,7 +652,8 @@ void CompactionIterator::NextFromInput() {
|
||||
if (compaction_ != nullptr &&
|
||||
DefinitelyInSnapshot(ikey_.sequence, earliest_snapshot_) &&
|
||||
compaction_->KeyNotExistsBeyondOutputLevel(ikey_.user_key,
|
||||
&level_ptrs_)) {
|
||||
&level_ptrs_) &&
|
||||
is_timestamp_eligible_for_gc) {
|
||||
// Key doesn't exist outside of this range.
|
||||
// Can compact out this SingleDelete.
|
||||
++iter_stats_.num_record_drop_obsolete;
|
||||
@@ -987,8 +1005,12 @@ void CompactionIterator::PrepareOutput() {
|
||||
ikeyNotNeededForIncrementalSnapshot() && bottommost_level_ &&
|
||||
DefinitelyInSnapshot(ikey_.sequence, earliest_snapshot_) &&
|
||||
ikey_.type != kTypeMerge) {
|
||||
assert(ikey_.type != kTypeDeletion && ikey_.type != kTypeSingleDeletion);
|
||||
if (ikey_.type == kTypeDeletion || ikey_.type == kTypeSingleDeletion) {
|
||||
assert(ikey_.type != kTypeDeletion);
|
||||
assert(ikey_.type != kTypeSingleDeletion ||
|
||||
(timestamp_size_ || full_history_ts_low_));
|
||||
if (ikey_.type == kTypeDeletion ||
|
||||
(ikey_.type == kTypeSingleDeletion &&
|
||||
(!timestamp_size_ || !full_history_ts_low_))) {
|
||||
ROCKS_LOG_FATAL(info_log_,
|
||||
"Unexpected key type %d for seq-zero optimization",
|
||||
ikey_.type);
|
||||
|
||||
@@ -3,15 +3,17 @@
|
||||
// COPYING file in the root directory) and Apache 2.0 License
|
||||
// (found in the LICENSE.Apache file in the root directory).
|
||||
|
||||
#include "db/compaction/compaction_iterator.h"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "db/compaction/compaction_iterator.h"
|
||||
#include "db/dbformat.h"
|
||||
#include "port/port.h"
|
||||
#include "test_util/testharness.h"
|
||||
#include "test_util/testutil.h"
|
||||
#include "util/string_util.h"
|
||||
#include "util/vector_iterator.h"
|
||||
#include "utilities/merge_operators.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
@@ -86,7 +88,7 @@ class FilterAllKeysCompactionFilter : public CompactionFilter {
|
||||
const char* Name() const override { return "AllKeysCompactionFilter"; }
|
||||
};
|
||||
|
||||
class LoggingForwardVectorIterator : public InternalIterator {
|
||||
class LoggingForwardVectorIterator : public VectorIterator {
|
||||
public:
|
||||
struct Action {
|
||||
enum class Type {
|
||||
@@ -108,22 +110,19 @@ class LoggingForwardVectorIterator : public InternalIterator {
|
||||
|
||||
LoggingForwardVectorIterator(const std::vector<std::string>& keys,
|
||||
const std::vector<std::string>& values)
|
||||
: keys_(keys), values_(values), current_(keys.size()) {
|
||||
assert(keys_.size() == values_.size());
|
||||
: VectorIterator(keys, values) {
|
||||
current_ = keys_.size();
|
||||
}
|
||||
|
||||
bool Valid() const override { return current_ < keys_.size(); }
|
||||
|
||||
void SeekToFirst() override {
|
||||
log.emplace_back(Action::Type::SEEK_TO_FIRST);
|
||||
current_ = 0;
|
||||
VectorIterator::SeekToFirst();
|
||||
}
|
||||
void SeekToLast() override { assert(false); }
|
||||
|
||||
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();
|
||||
VectorIterator::Seek(target);
|
||||
}
|
||||
|
||||
void SeekForPrev(const Slice& /*target*/) override { assert(false); }
|
||||
@@ -131,27 +130,20 @@ class LoggingForwardVectorIterator : public InternalIterator {
|
||||
void Next() override {
|
||||
assert(Valid());
|
||||
log.emplace_back(Action::Type::NEXT);
|
||||
current_++;
|
||||
VectorIterator::Next();
|
||||
}
|
||||
void Prev() override { assert(false); }
|
||||
|
||||
Slice key() const override {
|
||||
assert(Valid());
|
||||
return Slice(keys_[current_]);
|
||||
return VectorIterator::key();
|
||||
}
|
||||
Slice value() const override {
|
||||
assert(Valid());
|
||||
return Slice(values_[current_]);
|
||||
return VectorIterator::value();
|
||||
}
|
||||
|
||||
Status status() const override { return Status::OK(); }
|
||||
|
||||
std::vector<Action> log;
|
||||
|
||||
private:
|
||||
std::vector<std::string> keys_;
|
||||
std::vector<std::string> values_;
|
||||
size_t current_;
|
||||
};
|
||||
|
||||
class FakeCompaction : public CompactionIterator::CompactionProxy {
|
||||
@@ -244,7 +236,7 @@ class CompactionIteratorTest : public testing::TestWithParam<bool> {
|
||||
bool key_not_exists_beyond_output_level = false,
|
||||
const std::string* full_history_ts_low = nullptr) {
|
||||
std::unique_ptr<InternalIterator> unfragmented_range_del_iter(
|
||||
new test::VectorIterator(range_del_ks, range_del_vs));
|
||||
new VectorIterator(range_del_ks, range_del_vs, &icmp_));
|
||||
auto tombstone_list = std::make_shared<FragmentedRangeTombstoneList>(
|
||||
std::move(unfragmented_range_del_iter), icmp_);
|
||||
std::unique_ptr<FragmentedRangeTombstoneIterator> range_del_iter(
|
||||
@@ -1258,6 +1250,101 @@ TEST_P(CompactionIteratorTsGcTest, RewriteTs) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(CompactionIteratorTsGcTest, SingleDeleteNoKeyEligibleForGC) {
|
||||
constexpr char user_key[][2] = {{'a', '\0'}, {'b', '\0'}};
|
||||
const std::vector<std::string> input_keys = {
|
||||
test::KeyStr(/*ts=*/104, user_key[0], /*seq=*/4, kTypeSingleDeletion),
|
||||
test::KeyStr(/*ts=*/103, user_key[0], /*seq=*/3, kTypeValue),
|
||||
test::KeyStr(/*ts=*/102, user_key[1], /*seq=*/2, kTypeValue)};
|
||||
const std::vector<std::string> input_values = {"", "a3", "b2"};
|
||||
std::string full_history_ts_low;
|
||||
// All keys' timestamps are newer than or equal to 102, thus none of them
|
||||
// will be eligible for GC.
|
||||
PutFixed64(&full_history_ts_low, 102);
|
||||
const std::vector<std::string>& expected_keys = input_keys;
|
||||
const std::vector<std::string>& expected_values = input_values;
|
||||
const std::vector<std::pair<bool, bool>> params = {
|
||||
{false, false}, {false, true}, {true, true}};
|
||||
for (const std::pair<bool, bool>& param : params) {
|
||||
const bool bottommost_level = param.first;
|
||||
const bool key_not_exists_beyond_output_level = param.second;
|
||||
RunTest(input_keys, input_values, expected_keys, expected_values,
|
||||
/*last_committed_seq=*/kMaxSequenceNumber,
|
||||
/*merge_operator=*/nullptr, /*compaction_filter=*/nullptr,
|
||||
bottommost_level,
|
||||
/*earliest_write_conflict_snapshot=*/kMaxSequenceNumber,
|
||||
key_not_exists_beyond_output_level, &full_history_ts_low);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(CompactionIteratorTsGcTest, SingleDeleteDropTombstones) {
|
||||
constexpr char user_key[] = "a";
|
||||
const std::vector<std::string> input_keys = {
|
||||
test::KeyStr(/*ts=*/103, user_key, /*seq=*/4, kTypeSingleDeletion),
|
||||
test::KeyStr(/*ts=*/102, user_key, /*seq=*/3, kTypeValue),
|
||||
test::KeyStr(/*ts=*/101, user_key, /*seq=*/2, kTypeSingleDeletion),
|
||||
test::KeyStr(/*ts=*/100, user_key, /*seq=*/1, kTypeValue)};
|
||||
const std::vector<std::string> input_values = {"", "a2", "", "a0"};
|
||||
const std::vector<std::string> expected_keys = {input_keys[0], input_keys[1]};
|
||||
const std::vector<std::string> expected_values = {"", "a2"};
|
||||
|
||||
// Take a snapshot at seq 2.
|
||||
AddSnapshot(2);
|
||||
{
|
||||
const std::vector<std::pair<bool, bool>> params = {
|
||||
{false, false}, {false, true}, {true, true}};
|
||||
for (const std::pair<bool, bool>& param : params) {
|
||||
const bool bottommost_level = param.first;
|
||||
const bool key_not_exists_beyond_output_level = param.second;
|
||||
std::string full_history_ts_low;
|
||||
PutFixed64(&full_history_ts_low, 102);
|
||||
RunTest(input_keys, input_values, expected_keys, expected_values,
|
||||
/*last_committed_seq=*/kMaxSequenceNumber,
|
||||
/*merge_operator=*/nullptr, /*compaction_filter=*/nullptr,
|
||||
bottommost_level,
|
||||
/*earliest_write_conflict_snapshot=*/kMaxSequenceNumber,
|
||||
key_not_exists_beyond_output_level, &full_history_ts_low);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(CompactionIteratorTsGcTest, SingleDeleteAllKeysOlderThanThreshold) {
|
||||
constexpr char user_key[][2] = {{'a', '\0'}, {'b', '\0'}};
|
||||
const std::vector<std::string> input_keys = {
|
||||
test::KeyStr(/*ts=*/103, user_key[0], /*seq=*/4, kTypeSingleDeletion),
|
||||
test::KeyStr(/*ts=*/102, user_key[0], /*seq=*/3, kTypeValue),
|
||||
test::KeyStr(/*ts=*/104, user_key[1], /*seq=*/5, kTypeValue)};
|
||||
const std::vector<std::string> input_values = {"", "a2", "b5"};
|
||||
std::string full_history_ts_low;
|
||||
PutFixed64(&full_history_ts_low, std::numeric_limits<uint64_t>::max());
|
||||
{
|
||||
// With a snapshot at seq 3, both the deletion marker and the key at 3 must
|
||||
// be preserved.
|
||||
AddSnapshot(3);
|
||||
const std::vector<std::string> expected_keys = {
|
||||
input_keys[0], input_keys[1], input_keys[2]};
|
||||
const std::vector<std::string> expected_values = {"", "a2", "b5"};
|
||||
RunTest(input_keys, input_values, expected_keys, expected_values,
|
||||
/*last_committed_seq=*/kMaxSequenceNumber,
|
||||
/*merge_operator=*/nullptr, /*compaction_filter=*/nullptr,
|
||||
/*bottommost_level=*/false,
|
||||
/*earliest_write_conflict_snapshot=*/kMaxSequenceNumber,
|
||||
/*key_not_exists_beyond_output_level=*/false, &full_history_ts_low);
|
||||
ClearSnapshots();
|
||||
}
|
||||
{
|
||||
// No snapshot.
|
||||
const std::vector<std::string> expected_keys = {input_keys[2]};
|
||||
const std::vector<std::string> expected_values = {"b5"};
|
||||
RunTest(input_keys, input_values, expected_keys, expected_values,
|
||||
/*last_committed_seq=*/kMaxSequenceNumber,
|
||||
/*merge_operator=*/nullptr, /*compaction_filter=*/nullptr,
|
||||
/*bottommost_level=*/false,
|
||||
/*earliest_write_conflict_snapshot=*/kMaxSequenceNumber,
|
||||
/*key_not_exists_beyond_output_level=*/false, &full_history_ts_low);
|
||||
}
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(CompactionIteratorTsGcTestInstance,
|
||||
CompactionIteratorTsGcTest,
|
||||
testing::Values(true, false));
|
||||
|
||||
@@ -109,6 +109,8 @@ const char* GetCompactionReasonString(CompactionReason compaction_reason) {
|
||||
return "PeriodicCompaction";
|
||||
case CompactionReason::kChangeTemperature:
|
||||
return "ChangeTemperature";
|
||||
case CompactionReason::kForcedBlobGC:
|
||||
return "ForcedBlobGC";
|
||||
case CompactionReason::kNumOfReasons:
|
||||
// fall through
|
||||
default:
|
||||
@@ -217,6 +219,7 @@ struct CompactionJob::SubcompactionState {
|
||||
&compaction->column_family_data()->internal_comparator();
|
||||
const std::vector<FileMetaData*>& grandparents = compaction->grandparents();
|
||||
|
||||
bool grandparant_file_switched = false;
|
||||
// Scan to find earliest grandparent file that contains key.
|
||||
while (grandparent_index < grandparents.size() &&
|
||||
icmp->Compare(internal_key,
|
||||
@@ -224,6 +227,7 @@ struct CompactionJob::SubcompactionState {
|
||||
0) {
|
||||
if (seen_key) {
|
||||
overlapped_bytes += grandparents[grandparent_index]->fd.GetFileSize();
|
||||
grandparant_file_switched = true;
|
||||
}
|
||||
assert(grandparent_index + 1 >= grandparents.size() ||
|
||||
icmp->Compare(
|
||||
@@ -233,8 +237,8 @@ struct CompactionJob::SubcompactionState {
|
||||
}
|
||||
seen_key = true;
|
||||
|
||||
if (overlapped_bytes + curr_file_size >
|
||||
compaction->max_compaction_bytes()) {
|
||||
if (grandparant_file_switched && overlapped_bytes + curr_file_size >
|
||||
compaction->max_compaction_bytes()) {
|
||||
// Too much overlap for current output; start new output
|
||||
overlapped_bytes = 0;
|
||||
return true;
|
||||
@@ -932,7 +936,8 @@ Status CompactionJob::Install(const MutableCFOptions& mutable_cf_options) {
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
void CompactionJob::ProcessKeyValueCompactionWithCompactionService(
|
||||
CompactionServiceJobStatus
|
||||
CompactionJob::ProcessKeyValueCompactionWithCompactionService(
|
||||
SubcompactionState* sub_compact) {
|
||||
assert(sub_compact);
|
||||
assert(sub_compact->compaction);
|
||||
@@ -969,7 +974,7 @@ void CompactionJob::ProcessKeyValueCompactionWithCompactionService(
|
||||
Status s = compaction_input.Write(&compaction_input_binary);
|
||||
if (!s.ok()) {
|
||||
sub_compact->status = s;
|
||||
return;
|
||||
return CompactionServiceJobStatus::kFailure;
|
||||
}
|
||||
|
||||
std::ostringstream input_files_oss;
|
||||
@@ -984,39 +989,77 @@ void CompactionJob::ProcessKeyValueCompactionWithCompactionService(
|
||||
"[%s] [JOB %d] Starting remote compaction (output level: %d): %s",
|
||||
compaction_input.column_family.name.c_str(), job_id_,
|
||||
compaction_input.output_level, input_files_oss.str().c_str());
|
||||
CompactionServiceJobInfo info(dbname_, db_id_, db_session_id_,
|
||||
GetCompactionId(sub_compact), thread_pri_);
|
||||
CompactionServiceJobStatus compaction_status =
|
||||
db_options_.compaction_service->Start(compaction_input_binary,
|
||||
GetCompactionId(sub_compact));
|
||||
if (compaction_status != CompactionServiceJobStatus::kSuccess) {
|
||||
sub_compact->status =
|
||||
Status::Incomplete("CompactionService failed to start compaction job.");
|
||||
return;
|
||||
db_options_.compaction_service->StartV2(info, compaction_input_binary);
|
||||
switch (compaction_status) {
|
||||
case CompactionServiceJobStatus::kSuccess:
|
||||
break;
|
||||
case CompactionServiceJobStatus::kFailure:
|
||||
sub_compact->status = Status::Incomplete(
|
||||
"CompactionService failed to start compaction job.");
|
||||
ROCKS_LOG_WARN(db_options_.info_log,
|
||||
"[%s] [JOB %d] Remote compaction failed to start.",
|
||||
compaction_input.column_family.name.c_str(), job_id_);
|
||||
return compaction_status;
|
||||
case CompactionServiceJobStatus::kUseLocal:
|
||||
ROCKS_LOG_INFO(
|
||||
db_options_.info_log,
|
||||
"[%s] [JOB %d] Remote compaction fallback to local by API Start.",
|
||||
compaction_input.column_family.name.c_str(), job_id_);
|
||||
return compaction_status;
|
||||
default:
|
||||
assert(false); // unknown status
|
||||
break;
|
||||
}
|
||||
|
||||
ROCKS_LOG_INFO(db_options_.info_log,
|
||||
"[%s] [JOB %d] Waiting for remote compaction...",
|
||||
compaction_input.column_family.name.c_str(), job_id_);
|
||||
std::string compaction_result_binary;
|
||||
compaction_status = db_options_.compaction_service->WaitForComplete(
|
||||
GetCompactionId(sub_compact), &compaction_result_binary);
|
||||
compaction_status = db_options_.compaction_service->WaitForCompleteV2(
|
||||
info, &compaction_result_binary);
|
||||
|
||||
if (compaction_status == CompactionServiceJobStatus::kUseLocal) {
|
||||
ROCKS_LOG_INFO(db_options_.info_log,
|
||||
"[%s] [JOB %d] Remote compaction fallback to local by API "
|
||||
"WaitForComplete.",
|
||||
compaction_input.column_family.name.c_str(), job_id_);
|
||||
return compaction_status;
|
||||
}
|
||||
|
||||
CompactionServiceResult compaction_result;
|
||||
s = CompactionServiceResult::Read(compaction_result_binary,
|
||||
&compaction_result);
|
||||
if (compaction_status != CompactionServiceJobStatus::kSuccess) {
|
||||
sub_compact->status =
|
||||
s.ok() ? compaction_result.status
|
||||
: Status::Incomplete(
|
||||
"CompactionService failed to run compaction job.");
|
||||
compaction_result.status.PermitUncheckedError();
|
||||
|
||||
if (compaction_status == CompactionServiceJobStatus::kFailure) {
|
||||
if (s.ok()) {
|
||||
if (compaction_result.status.ok()) {
|
||||
sub_compact->status = Status::Incomplete(
|
||||
"CompactionService failed to run the compaction job (even though "
|
||||
"the internal status is okay).");
|
||||
} else {
|
||||
// set the current sub compaction status with the status returned from
|
||||
// remote
|
||||
sub_compact->status = compaction_result.status;
|
||||
}
|
||||
} else {
|
||||
sub_compact->status = Status::Incomplete(
|
||||
"CompactionService failed to run the compaction job (and no valid "
|
||||
"result is returned).");
|
||||
compaction_result.status.PermitUncheckedError();
|
||||
}
|
||||
ROCKS_LOG_WARN(db_options_.info_log,
|
||||
"[%s] [JOB %d] Remote compaction failed, status: %s",
|
||||
compaction_input.column_family.name.c_str(), job_id_,
|
||||
s.ToString().c_str());
|
||||
return;
|
||||
"[%s] [JOB %d] Remote compaction failed.",
|
||||
compaction_input.column_family.name.c_str(), job_id_);
|
||||
return compaction_status;
|
||||
}
|
||||
|
||||
if (!s.ok()) {
|
||||
sub_compact->status = s;
|
||||
compaction_result.status.PermitUncheckedError();
|
||||
return;
|
||||
return CompactionServiceJobStatus::kFailure;
|
||||
}
|
||||
sub_compact->status = compaction_result.status;
|
||||
|
||||
@@ -1036,7 +1079,7 @@ void CompactionJob::ProcessKeyValueCompactionWithCompactionService(
|
||||
|
||||
if (!s.ok()) {
|
||||
sub_compact->status = s;
|
||||
return;
|
||||
return CompactionServiceJobStatus::kFailure;
|
||||
}
|
||||
|
||||
for (const auto& file : compaction_result.output_files) {
|
||||
@@ -1047,7 +1090,7 @@ void CompactionJob::ProcessKeyValueCompactionWithCompactionService(
|
||||
s = fs_->RenameFile(src_file, tgt_file, IOOptions(), nullptr);
|
||||
if (!s.ok()) {
|
||||
sub_compact->status = s;
|
||||
return;
|
||||
return CompactionServiceJobStatus::kFailure;
|
||||
}
|
||||
|
||||
FileMetaData meta;
|
||||
@@ -1055,7 +1098,7 @@ void CompactionJob::ProcessKeyValueCompactionWithCompactionService(
|
||||
s = fs_->GetFileSize(tgt_file, IOOptions(), &file_size, nullptr);
|
||||
if (!s.ok()) {
|
||||
sub_compact->status = s;
|
||||
return;
|
||||
return CompactionServiceJobStatus::kFailure;
|
||||
}
|
||||
meta.fd = FileDescriptor(file_num, compaction->output_path_id(), file_size,
|
||||
file.smallest_seqno, file.largest_seqno);
|
||||
@@ -1074,8 +1117,10 @@ void CompactionJob::ProcessKeyValueCompactionWithCompactionService(
|
||||
sub_compact->num_output_records = compaction_result.num_output_records;
|
||||
sub_compact->approx_size = compaction_input.approx_size; // is this used?
|
||||
sub_compact->total_bytes = compaction_result.total_bytes;
|
||||
IOSTATS_ADD(bytes_written, compaction_result.bytes_written);
|
||||
IOSTATS_ADD(bytes_read, compaction_result.bytes_read);
|
||||
RecordTick(stats_, REMOTE_COMPACT_READ_BYTES, compaction_result.bytes_read);
|
||||
RecordTick(stats_, REMOTE_COMPACT_WRITE_BYTES,
|
||||
compaction_result.bytes_written);
|
||||
return CompactionServiceJobStatus::kSuccess;
|
||||
}
|
||||
#endif // !ROCKSDB_LITE
|
||||
|
||||
@@ -1085,7 +1130,14 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
if (db_options_.compaction_service) {
|
||||
return ProcessKeyValueCompactionWithCompactionService(sub_compact);
|
||||
CompactionServiceJobStatus comp_status =
|
||||
ProcessKeyValueCompactionWithCompactionService(sub_compact);
|
||||
if (comp_status == CompactionServiceJobStatus::kSuccess ||
|
||||
comp_status == CompactionServiceJobStatus::kFailure) {
|
||||
return;
|
||||
}
|
||||
// fallback to local compaction
|
||||
assert(comp_status == CompactionServiceJobStatus::kUseLocal);
|
||||
}
|
||||
#endif // !ROCKSDB_LITE
|
||||
|
||||
@@ -1209,13 +1261,13 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
|
||||
|
||||
std::unique_ptr<BlobFileBuilder> blob_file_builder(
|
||||
mutable_cf_options->enable_blob_files
|
||||
? new BlobFileBuilder(versions_, fs_.get(),
|
||||
sub_compact->compaction->immutable_options(),
|
||||
mutable_cf_options, &file_options_, job_id_,
|
||||
cfd->GetID(), cfd->GetName(),
|
||||
Env::IOPriority::IO_LOW, write_hint_,
|
||||
io_tracer_, blob_callback_, &blob_file_paths,
|
||||
&sub_compact->blob_file_additions)
|
||||
? new BlobFileBuilder(
|
||||
versions_, fs_.get(),
|
||||
sub_compact->compaction->immutable_options(),
|
||||
mutable_cf_options, &file_options_, job_id_, cfd->GetID(),
|
||||
cfd->GetName(), Env::IOPriority::IO_LOW, write_hint_,
|
||||
io_tracer_, blob_callback_, BlobFileCreationReason::kCompaction,
|
||||
&blob_file_paths, &sub_compact->blob_file_additions)
|
||||
: nullptr);
|
||||
|
||||
TEST_SYNC_POINT("CompactionJob::Run():Inprogress");
|
||||
@@ -1426,7 +1478,7 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
|
||||
if (status.ok()) {
|
||||
status = blob_file_builder->Finish();
|
||||
} else {
|
||||
blob_file_builder->Abandon();
|
||||
blob_file_builder->Abandon(status);
|
||||
}
|
||||
blob_file_builder.reset();
|
||||
}
|
||||
@@ -2201,6 +2253,12 @@ std::string CompactionServiceCompactionJob::GetTableFileName(
|
||||
return MakeTableFileName(output_path_, file_number);
|
||||
}
|
||||
|
||||
void CompactionServiceCompactionJob::RecordCompactionIOStats() {
|
||||
compaction_result_->bytes_read += IOSTATS(bytes_read);
|
||||
compaction_result_->bytes_written += IOSTATS(bytes_written);
|
||||
CompactionJob::RecordCompactionIOStats();
|
||||
}
|
||||
|
||||
CompactionServiceCompactionJob::CompactionServiceCompactionJob(
|
||||
int job_id, Compaction* compaction, const ImmutableDBOptions& db_options,
|
||||
const MutableDBOptions& mutable_db_options, const FileOptions& file_options,
|
||||
@@ -2291,9 +2349,6 @@ Status CompactionServiceCompactionJob::Run() {
|
||||
// Finish up all book-keeping to unify the subcompaction results
|
||||
AggregateStatistics();
|
||||
UpdateCompactionStats();
|
||||
|
||||
compaction_result_->bytes_written = IOSTATS(bytes_written);
|
||||
compaction_result_->bytes_read = IOSTATS(bytes_read);
|
||||
RecordCompactionIOStats();
|
||||
|
||||
LogFlush(db_options_.info_log);
|
||||
|
||||
@@ -20,7 +20,6 @@
|
||||
#include "db/blob/blob_file_completion_callback.h"
|
||||
#include "db/column_family.h"
|
||||
#include "db/compaction/compaction_iterator.h"
|
||||
#include "db/dbformat.h"
|
||||
#include "db/flush_scheduler.h"
|
||||
#include "db/internal_stats.h"
|
||||
#include "db/job_context.h"
|
||||
@@ -117,7 +116,7 @@ class CompactionJob {
|
||||
void AggregateStatistics();
|
||||
void UpdateCompactionStats();
|
||||
void LogCompaction();
|
||||
void RecordCompactionIOStats();
|
||||
virtual void RecordCompactionIOStats();
|
||||
void CleanupCompaction();
|
||||
|
||||
// Call compaction filter. Then iterate through input and compact the
|
||||
@@ -146,7 +145,7 @@ class CompactionJob {
|
||||
// consecutive groups such that each group has a similar size.
|
||||
void GenSubcompactionBoundaries();
|
||||
|
||||
void ProcessKeyValueCompactionWithCompactionService(
|
||||
CompactionServiceJobStatus ProcessKeyValueCompactionWithCompactionService(
|
||||
SubcompactionState* sub_compact);
|
||||
|
||||
// update the thread status for starting a compaction.
|
||||
@@ -304,10 +303,10 @@ struct CompactionServiceResult {
|
||||
std::string output_path;
|
||||
|
||||
// some statistics about the compaction
|
||||
uint64_t num_output_records;
|
||||
uint64_t total_bytes;
|
||||
uint64_t bytes_read;
|
||||
uint64_t bytes_written;
|
||||
uint64_t num_output_records = 0;
|
||||
uint64_t total_bytes = 0;
|
||||
uint64_t bytes_read = 0;
|
||||
uint64_t bytes_written = 0;
|
||||
CompactionJobStats stats;
|
||||
|
||||
// serialization interface to read and write the object
|
||||
@@ -347,6 +346,9 @@ class CompactionServiceCompactionJob : private CompactionJob {
|
||||
|
||||
IOStatus io_status() const { return CompactionJob::io_status(); }
|
||||
|
||||
protected:
|
||||
void RecordCompactionIOStats() override;
|
||||
|
||||
private:
|
||||
// Get table file name in output_path
|
||||
std::string GetTableFileName(uint64_t file_number) override;
|
||||
|
||||
@@ -24,7 +24,6 @@
|
||||
#include "db/write_batch_internal.h"
|
||||
#include "env/mock_env.h"
|
||||
#include "file/filename.h"
|
||||
#include "memtable/hash_linklist_rep.h"
|
||||
#include "monitoring/statistics.h"
|
||||
#include "monitoring/thread_status_util.h"
|
||||
#include "port/stack_trace.h"
|
||||
@@ -269,10 +268,10 @@ class CompactionJobStatsTest : public testing::Test,
|
||||
if (cf == 0) {
|
||||
// default cfd
|
||||
EXPECT_TRUE(db_->GetProperty(
|
||||
"rocksdb.num-files-at-level" + NumberToString(level), &property));
|
||||
"rocksdb.num-files-at-level" + ToString(level), &property));
|
||||
} else {
|
||||
EXPECT_TRUE(db_->GetProperty(
|
||||
handles_[cf], "rocksdb.num-files-at-level" + NumberToString(level),
|
||||
handles_[cf], "rocksdb.num-files-at-level" + ToString(level),
|
||||
&property));
|
||||
}
|
||||
return atoi(property.c_str());
|
||||
|
||||
@@ -1120,7 +1120,9 @@ TEST_F(CompactionJobTest, InputSerialization) {
|
||||
input.snapshots.emplace_back(rnd64.Uniform(UINT64_MAX));
|
||||
}
|
||||
while (!rnd.OneIn(10)) {
|
||||
input.input_files.emplace_back(rnd.RandomString(rnd.Uniform(kStrMaxLen)));
|
||||
input.input_files.emplace_back(rnd.RandomString(
|
||||
rnd.Uniform(kStrMaxLen - 1) +
|
||||
1)); // input file name should have at least one character
|
||||
}
|
||||
input.output_level = 4;
|
||||
input.has_begin = rnd.OneIn(2);
|
||||
@@ -1291,13 +1293,16 @@ TEST_F(CompactionJobTimestampTest, GCDisabled) {
|
||||
auto file1 =
|
||||
mock::MakeMockFile({{KeyStr("a", 10, ValueType::kTypeValue, 100), "a10"},
|
||||
{KeyStr("a", 9, ValueType::kTypeValue, 99), "a9"},
|
||||
{KeyStr("b", 8, ValueType::kTypeValue, 98), "b8"}});
|
||||
{KeyStr("b", 8, ValueType::kTypeValue, 98), "b8"},
|
||||
{KeyStr("d", 7, ValueType::kTypeValue, 97), "d7"}});
|
||||
|
||||
AddMockFile(file1);
|
||||
|
||||
auto file2 = mock::MakeMockFile(
|
||||
{{KeyStr("b", 7, ValueType::kTypeDeletionWithTimestamp, 97), ""},
|
||||
{KeyStr("c", 6, ValueType::kTypeDeletionWithTimestamp, 96), ""},
|
||||
{KeyStr("c", 5, ValueType::kTypeValue, 95), "c5"}});
|
||||
{{KeyStr("b", 6, ValueType::kTypeDeletionWithTimestamp, 96), ""},
|
||||
{KeyStr("c", 5, ValueType::kTypeDeletionWithTimestamp, 95), ""},
|
||||
{KeyStr("c", 4, ValueType::kTypeValue, 94), "c5"},
|
||||
{KeyStr("d", 3, ValueType::kTypeSingleDeletion, 93), ""}});
|
||||
AddMockFile(file2);
|
||||
|
||||
SetLastSequence(10);
|
||||
@@ -1306,9 +1311,11 @@ TEST_F(CompactionJobTimestampTest, GCDisabled) {
|
||||
{{KeyStr("a", 10, ValueType::kTypeValue, 100), "a10"},
|
||||
{KeyStr("a", 9, ValueType::kTypeValue, 99), "a9"},
|
||||
{KeyStr("b", 8, ValueType::kTypeValue, 98), "b8"},
|
||||
{KeyStr("b", 7, ValueType::kTypeDeletionWithTimestamp, 97), ""},
|
||||
{KeyStr("c", 6, ValueType::kTypeDeletionWithTimestamp, 96), ""},
|
||||
{KeyStr("c", 5, ValueType::kTypeValue, 95), "c5"}});
|
||||
{KeyStr("b", 6, ValueType::kTypeDeletionWithTimestamp, 96), ""},
|
||||
{KeyStr("c", 5, ValueType::kTypeDeletionWithTimestamp, 95), ""},
|
||||
{KeyStr("c", 4, ValueType::kTypeValue, 94), "c5"},
|
||||
{KeyStr("d", 7, ValueType::kTypeValue, 97), "d7"},
|
||||
{KeyStr("d", 3, ValueType::kTypeSingleDeletion, 93), ""}});
|
||||
const auto& files = cfd_->current()->storage_info()->LevelFiles(0);
|
||||
RunCompaction({files}, expected_results);
|
||||
}
|
||||
@@ -1346,19 +1353,21 @@ TEST_F(CompactionJobTimestampTest, AllKeysExpired) {
|
||||
|
||||
auto file1 = mock::MakeMockFile(
|
||||
{{KeyStr("a", 5, ValueType::kTypeDeletionWithTimestamp, 100), ""},
|
||||
{KeyStr("b", 6, ValueType::kTypeValue, 99), "b6"}});
|
||||
{KeyStr("b", 6, ValueType::kTypeSingleDeletion, 99), ""},
|
||||
{KeyStr("c", 7, ValueType::kTypeValue, 98), "c7"}});
|
||||
AddMockFile(file1);
|
||||
|
||||
auto file2 = mock::MakeMockFile(
|
||||
{{KeyStr("a", 4, ValueType::kTypeValue, 98), "a4"},
|
||||
{KeyStr("b", 3, ValueType::kTypeDeletionWithTimestamp, 97), ""},
|
||||
{KeyStr("b", 2, ValueType::kTypeValue, 96), "b2"}});
|
||||
{{KeyStr("a", 4, ValueType::kTypeValue, 97), "a4"},
|
||||
{KeyStr("b", 3, ValueType::kTypeValue, 96), "b3"},
|
||||
{KeyStr("c", 2, ValueType::kTypeDeletionWithTimestamp, 95), ""},
|
||||
{KeyStr("c", 1, ValueType::kTypeValue, 94), "c1"}});
|
||||
AddMockFile(file2);
|
||||
|
||||
SetLastSequence(6);
|
||||
SetLastSequence(7);
|
||||
|
||||
auto expected_results =
|
||||
mock::MakeMockFile({{KeyStr("b", 0, ValueType::kTypeValue, 0), "b6"}});
|
||||
mock::MakeMockFile({{KeyStr("c", 0, ValueType::kTypeValue, 0), "c7"}});
|
||||
const auto& files = cfd_->current()->storage_info()->LevelFiles(0);
|
||||
|
||||
full_history_ts_low_ = encode_u64_ts_(std::numeric_limits<uint64_t>::max());
|
||||
|
||||
@@ -15,9 +15,11 @@
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "db/column_family.h"
|
||||
#include "file/filename.h"
|
||||
#include "logging/log_buffer.h"
|
||||
#include "logging/logging.h"
|
||||
#include "monitoring/statistics.h"
|
||||
#include "test_util/sync_point.h"
|
||||
#include "util/random.h"
|
||||
@@ -552,10 +554,14 @@ void CompactionPicker::GetGrandparents(
|
||||
InternalKey start, limit;
|
||||
GetRange(inputs, output_level_inputs, &start, &limit);
|
||||
// Compute the set of grandparent files that overlap this compaction
|
||||
// (parent == level+1; grandparent == level+2)
|
||||
if (output_level_inputs.level + 1 < NumberLevels()) {
|
||||
vstorage->GetOverlappingInputs(output_level_inputs.level + 1, &start,
|
||||
&limit, grandparents);
|
||||
// (parent == level+1; grandparent == level+2 or the first
|
||||
// level after that has overlapping files)
|
||||
for (int level = output_level_inputs.level + 1; level < NumberLevels();
|
||||
level++) {
|
||||
vstorage->GetOverlappingInputs(level, &start, &limit, grandparents);
|
||||
if (!grandparents->empty()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,8 +13,10 @@
|
||||
#include <cinttypes>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "db/column_family.h"
|
||||
#include "logging/log_buffer.h"
|
||||
#include "logging/logging.h"
|
||||
#include "util/string_util.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
@@ -31,6 +31,9 @@ bool LevelCompactionPicker::NeedsCompaction(
|
||||
if (!vstorage->FilesMarkedForCompaction().empty()) {
|
||||
return true;
|
||||
}
|
||||
if (!vstorage->FilesMarkedForForcedBlobGC().empty()) {
|
||||
return true;
|
||||
}
|
||||
for (int i = 0; i <= vstorage->MaxInputLevel(); i++) {
|
||||
if (vstorage->CompactionScore(i) >= 1) {
|
||||
return true;
|
||||
@@ -248,6 +251,13 @@ void LevelCompactionBuilder::SetupInitialFiles() {
|
||||
compaction_reason_ = CompactionReason::kPeriodicCompaction;
|
||||
return;
|
||||
}
|
||||
|
||||
// Forced blob garbage collection
|
||||
PickFileToCompact(vstorage_->FilesMarkedForForcedBlobGC(), false);
|
||||
if (!start_level_inputs_.empty()) {
|
||||
compaction_reason_ = CompactionReason::kForcedBlobGC;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
bool LevelCompactionBuilder::SetupOtherL0FilesIfNeeded() {
|
||||
@@ -385,7 +395,7 @@ uint32_t LevelCompactionBuilder::GetPathId(
|
||||
if (ioptions.level_compaction_dynamic_level_bytes) {
|
||||
// Currently, level_compaction_dynamic_level_bytes is ignored when
|
||||
// multiple db paths are specified. https://github.com/facebook/
|
||||
// rocksdb/blob/master/db/column_family.cc.
|
||||
// rocksdb/blob/main/db/column_family.cc.
|
||||
// Still, adding this check to avoid accidentally using
|
||||
// max_bytes_for_level_multiplier_additional
|
||||
level_size = static_cast<uint64_t>(
|
||||
|
||||
@@ -733,6 +733,221 @@ TEST_F(CompactionPickerTest, UniversalPeriodicCompaction6) {
|
||||
ASSERT_EQ(4, compaction->output_level());
|
||||
}
|
||||
|
||||
TEST_F(CompactionPickerTest, UniversalIncrementalSpace1) {
|
||||
const uint64_t kFileSize = 100000;
|
||||
|
||||
mutable_cf_options_.max_compaction_bytes = 555555;
|
||||
mutable_cf_options_.compaction_options_universal.incremental = true;
|
||||
mutable_cf_options_.compaction_options_universal
|
||||
.max_size_amplification_percent = 30;
|
||||
UniversalCompactionPicker universal_compaction_picker(ioptions_, &icmp_);
|
||||
|
||||
NewVersionStorage(5, kCompactionStyleUniversal);
|
||||
|
||||
Add(0, 1U, "150", "200", kFileSize, 0, 500, 550);
|
||||
Add(2, 2U, "010", "080", kFileSize, 0, 200, 251);
|
||||
Add(3, 5U, "310", "380", kFileSize, 0, 200, 251);
|
||||
Add(3, 6U, "410", "880", kFileSize, 0, 200, 251);
|
||||
Add(3, 7U, "910", "980", 1, 0, 200, 251);
|
||||
Add(4, 10U, "201", "250", kFileSize, 0, 101, 150);
|
||||
Add(4, 11U, "301", "350", kFileSize, 0, 101, 150);
|
||||
Add(4, 12U, "401", "450", kFileSize, 0, 101, 150);
|
||||
Add(4, 13U, "501", "750", kFileSize, 0, 101, 150);
|
||||
Add(4, 14U, "801", "850", kFileSize, 0, 101, 150);
|
||||
Add(4, 15U, "901", "950", kFileSize, 0, 101, 150);
|
||||
// Add(4, 15U, "960", "970", kFileSize, 0, 101, 150);
|
||||
|
||||
UpdateVersionStorageInfo();
|
||||
|
||||
std::unique_ptr<Compaction> compaction(
|
||||
universal_compaction_picker.PickCompaction(
|
||||
cf_name_, mutable_cf_options_, mutable_db_options_, vstorage_.get(),
|
||||
&log_buffer_));
|
||||
ASSERT_TRUE(compaction);
|
||||
ASSERT_EQ(4, compaction->output_level());
|
||||
ASSERT_EQ(3, compaction->start_level());
|
||||
ASSERT_EQ(2U, compaction->num_input_files(0));
|
||||
ASSERT_EQ(5U, compaction->input(0, 0)->fd.GetNumber());
|
||||
ASSERT_EQ(6U, compaction->input(0, 1)->fd.GetNumber());
|
||||
// ASSERT_EQ(4U, compaction->num_input_files(1));
|
||||
ASSERT_EQ(11U, compaction->input(1, 0)->fd.GetNumber());
|
||||
ASSERT_EQ(12U, compaction->input(1, 1)->fd.GetNumber());
|
||||
ASSERT_EQ(13U, compaction->input(1, 2)->fd.GetNumber());
|
||||
ASSERT_EQ(14U, compaction->input(1, 3)->fd.GetNumber());
|
||||
}
|
||||
|
||||
TEST_F(CompactionPickerTest, UniversalIncrementalSpace2) {
|
||||
const uint64_t kFileSize = 100000;
|
||||
|
||||
mutable_cf_options_.max_compaction_bytes = 400000;
|
||||
mutable_cf_options_.compaction_options_universal.incremental = true;
|
||||
mutable_cf_options_.compaction_options_universal
|
||||
.max_size_amplification_percent = 30;
|
||||
UniversalCompactionPicker universal_compaction_picker(ioptions_, &icmp_);
|
||||
|
||||
NewVersionStorage(5, kCompactionStyleUniversal);
|
||||
|
||||
Add(0, 1U, "150", "200", kFileSize, 0, 500, 550);
|
||||
Add(1, 2U, "010", "080", kFileSize, 0, 200, 251);
|
||||
Add(2, 5U, "310", "380", kFileSize, 0, 200, 251);
|
||||
Add(2, 6U, "410", "880", kFileSize, 0, 200, 251);
|
||||
Add(2, 7U, "910", "980", kFileSize, 0, 200, 251);
|
||||
Add(4, 10U, "201", "250", kFileSize, 0, 101, 150);
|
||||
Add(4, 11U, "301", "350", kFileSize, 0, 101, 150);
|
||||
Add(4, 12U, "401", "450", kFileSize, 0, 101, 150);
|
||||
Add(4, 13U, "501", "750", kFileSize, 0, 101, 150);
|
||||
Add(4, 14U, "801", "850", kFileSize, 0, 101, 150);
|
||||
Add(4, 15U, "901", "950", kFileSize, 0, 101, 150);
|
||||
|
||||
UpdateVersionStorageInfo();
|
||||
|
||||
std::unique_ptr<Compaction> compaction(
|
||||
universal_compaction_picker.PickCompaction(
|
||||
cf_name_, mutable_cf_options_, mutable_db_options_, vstorage_.get(),
|
||||
&log_buffer_));
|
||||
ASSERT_TRUE(compaction);
|
||||
ASSERT_EQ(4, compaction->output_level());
|
||||
ASSERT_EQ(2, compaction->start_level());
|
||||
ASSERT_EQ(1U, compaction->num_input_files(0));
|
||||
ASSERT_EQ(7U, compaction->input(0, 0)->fd.GetNumber());
|
||||
ASSERT_EQ(1U, compaction->num_input_files(1));
|
||||
ASSERT_EQ(15U, compaction->input(1, 0)->fd.GetNumber());
|
||||
}
|
||||
|
||||
TEST_F(CompactionPickerTest, UniversalIncrementalSpace3) {
|
||||
// Test bottom level files falling between gaps between two upper level
|
||||
// files
|
||||
const uint64_t kFileSize = 100000;
|
||||
|
||||
mutable_cf_options_.max_compaction_bytes = 300000;
|
||||
mutable_cf_options_.compaction_options_universal.incremental = true;
|
||||
mutable_cf_options_.compaction_options_universal
|
||||
.max_size_amplification_percent = 30;
|
||||
UniversalCompactionPicker universal_compaction_picker(ioptions_, &icmp_);
|
||||
|
||||
NewVersionStorage(5, kCompactionStyleUniversal);
|
||||
|
||||
Add(0, 1U, "150", "200", kFileSize, 0, 500, 550);
|
||||
Add(2, 2U, "010", "080", kFileSize, 0, 200, 251);
|
||||
Add(3, 5U, "000", "180", kFileSize, 0, 200, 251);
|
||||
Add(3, 6U, "181", "190", kFileSize, 0, 200, 251);
|
||||
Add(3, 7U, "710", "810", kFileSize, 0, 200, 251);
|
||||
Add(3, 8U, "820", "830", kFileSize, 0, 200, 251);
|
||||
Add(3, 9U, "900", "991", kFileSize, 0, 200, 251);
|
||||
Add(4, 10U, "201", "250", kFileSize, 0, 101, 150);
|
||||
Add(4, 11U, "301", "350", kFileSize, 0, 101, 150);
|
||||
Add(4, 12U, "401", "450", kFileSize, 0, 101, 150);
|
||||
Add(4, 13U, "501", "750", kFileSize, 0, 101, 150);
|
||||
Add(4, 14U, "801", "850", kFileSize, 0, 101, 150);
|
||||
Add(4, 15U, "901", "950", kFileSize, 0, 101, 150);
|
||||
|
||||
UpdateVersionStorageInfo();
|
||||
|
||||
std::unique_ptr<Compaction> compaction(
|
||||
universal_compaction_picker.PickCompaction(
|
||||
cf_name_, mutable_cf_options_, mutable_db_options_, vstorage_.get(),
|
||||
&log_buffer_));
|
||||
ASSERT_TRUE(compaction);
|
||||
ASSERT_EQ(4, compaction->output_level());
|
||||
ASSERT_EQ(2, compaction->start_level());
|
||||
ASSERT_EQ(1U, compaction->num_input_files(0));
|
||||
ASSERT_EQ(2U, compaction->input(0, 0)->fd.GetNumber());
|
||||
ASSERT_EQ(2U, compaction->num_input_files(1));
|
||||
ASSERT_EQ(5U, compaction->input(1, 0)->fd.GetNumber());
|
||||
ASSERT_EQ(6U, compaction->input(1, 1)->fd.GetNumber());
|
||||
ASSERT_EQ(0, compaction->num_input_files(2));
|
||||
}
|
||||
|
||||
TEST_F(CompactionPickerTest, UniversalIncrementalSpace4) {
|
||||
// Test compaction candidates always cover many files.
|
||||
const uint64_t kFileSize = 100000;
|
||||
|
||||
mutable_cf_options_.max_compaction_bytes = 3200000;
|
||||
mutable_cf_options_.compaction_options_universal.incremental = true;
|
||||
mutable_cf_options_.compaction_options_universal
|
||||
.max_size_amplification_percent = 30;
|
||||
UniversalCompactionPicker universal_compaction_picker(ioptions_, &icmp_);
|
||||
|
||||
NewVersionStorage(5, kCompactionStyleUniversal);
|
||||
|
||||
Add(0, 1U, "150", "200", kFileSize, 0, 500, 550);
|
||||
Add(2, 2U, "010", "080", kFileSize, 0, 200, 251);
|
||||
|
||||
// Generate files like following:
|
||||
// L3: (1101, 1180) (1201, 1280) ... (7901, 7908)
|
||||
// L4: (1130, 1150) (1160, 1210) (1230, 1250) (1260 1310) ... (7960, 8010)
|
||||
for (int i = 11; i < 79; i++) {
|
||||
Add(3, 100 + i * 3, ToString(i * 100).c_str(),
|
||||
ToString(i * 100 + 80).c_str(), kFileSize, 0, 200, 251);
|
||||
// Add a tie breaker
|
||||
if (i == 66) {
|
||||
Add(3, 10000U, "6690", "6699", kFileSize, 0, 200, 251);
|
||||
}
|
||||
|
||||
Add(4, 100 + i * 3 + 1, ToString(i * 100 + 30).c_str(),
|
||||
ToString(i * 100 + 50).c_str(), kFileSize, 0, 200, 251);
|
||||
Add(4, 100 + i * 3 + 2, ToString(i * 100 + 60).c_str(),
|
||||
ToString(i * 100 + 110).c_str(), kFileSize, 0, 200, 251);
|
||||
}
|
||||
UpdateVersionStorageInfo();
|
||||
|
||||
std::unique_ptr<Compaction> compaction(
|
||||
universal_compaction_picker.PickCompaction(
|
||||
cf_name_, mutable_cf_options_, mutable_db_options_, vstorage_.get(),
|
||||
&log_buffer_));
|
||||
ASSERT_TRUE(compaction);
|
||||
ASSERT_EQ(4, compaction->output_level());
|
||||
ASSERT_EQ(3, compaction->start_level());
|
||||
ASSERT_EQ(6U, compaction->num_input_files(0));
|
||||
ASSERT_EQ(100 + 62U * 3, compaction->input(0, 0)->fd.GetNumber());
|
||||
ASSERT_EQ(10000U, compaction->input(0, 5)->fd.GetNumber());
|
||||
ASSERT_EQ(11, compaction->num_input_files(1));
|
||||
}
|
||||
|
||||
TEST_F(CompactionPickerTest, UniversalIncrementalSpace5) {
|
||||
// Test compaction candidates always cover many files with some single
|
||||
// files larger than size threshold.
|
||||
const uint64_t kFileSize = 100000;
|
||||
|
||||
mutable_cf_options_.max_compaction_bytes = 3200000;
|
||||
mutable_cf_options_.compaction_options_universal.incremental = true;
|
||||
mutable_cf_options_.compaction_options_universal
|
||||
.max_size_amplification_percent = 30;
|
||||
UniversalCompactionPicker universal_compaction_picker(ioptions_, &icmp_);
|
||||
|
||||
NewVersionStorage(5, kCompactionStyleUniversal);
|
||||
|
||||
Add(0, 1U, "150", "200", kFileSize, 0, 500, 550);
|
||||
Add(2, 2U, "010", "080", kFileSize, 0, 200, 251);
|
||||
|
||||
// Generate files like following:
|
||||
// L3: (1101, 1180) (1201, 1280) ... (7901, 7908)
|
||||
// L4: (1130, 1150) (1160, 1210) (1230, 1250) (1260 1310) ... (7960, 8010)
|
||||
for (int i = 11; i < 70; i++) {
|
||||
Add(3, 100 + i * 3, ToString(i * 100).c_str(),
|
||||
ToString(i * 100 + 80).c_str(),
|
||||
i % 10 == 9 ? kFileSize * 100 : kFileSize, 0, 200, 251);
|
||||
|
||||
Add(4, 100 + i * 3 + 1, ToString(i * 100 + 30).c_str(),
|
||||
ToString(i * 100 + 50).c_str(), kFileSize, 0, 200, 251);
|
||||
Add(4, 100 + i * 3 + 2, ToString(i * 100 + 60).c_str(),
|
||||
ToString(i * 100 + 110).c_str(), kFileSize, 0, 200, 251);
|
||||
}
|
||||
UpdateVersionStorageInfo();
|
||||
|
||||
std::unique_ptr<Compaction> compaction(
|
||||
universal_compaction_picker.PickCompaction(
|
||||
cf_name_, mutable_cf_options_, mutable_db_options_, vstorage_.get(),
|
||||
&log_buffer_));
|
||||
ASSERT_TRUE(compaction);
|
||||
ASSERT_EQ(4, compaction->output_level());
|
||||
ASSERT_EQ(3, compaction->start_level());
|
||||
ASSERT_EQ(6U, compaction->num_input_files(0));
|
||||
ASSERT_EQ(100 + 14 * 3, compaction->input(0, 0)->fd.GetNumber());
|
||||
ASSERT_EQ(100 + 19 * 3, compaction->input(0, 5)->fd.GetNumber());
|
||||
ASSERT_EQ(13, compaction->num_input_files(1));
|
||||
}
|
||||
|
||||
TEST_F(CompactionPickerTest, NeedsCompactionFIFO) {
|
||||
NewVersionStorage(1, kCompactionStyleFIFO);
|
||||
const int kFileCount =
|
||||
|
||||
@@ -15,9 +15,11 @@
|
||||
#include <queue>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include "db/column_family.h"
|
||||
#include "file/filename.h"
|
||||
#include "logging/log_buffer.h"
|
||||
#include "logging/logging.h"
|
||||
#include "monitoring/statistics.h"
|
||||
#include "test_util/sync_point.h"
|
||||
#include "util/random.h"
|
||||
@@ -87,6 +89,14 @@ class UniversalCompactionBuilder {
|
||||
// Pick Universal compaction to limit space amplification.
|
||||
Compaction* PickCompactionToReduceSizeAmp();
|
||||
|
||||
// Try to pick incremental compaction to reduce space amplification.
|
||||
// It will return null if it cannot find a fanout within the threshold.
|
||||
// Fanout is defined as
|
||||
// total size of files to compact at output level
|
||||
// --------------------------------------------------
|
||||
// total size of files to compact at other levels
|
||||
Compaction* PickIncrementalForReduceSizeAmp(double fanout_threshold);
|
||||
|
||||
Compaction* PickDeleteTriggeredCompaction();
|
||||
|
||||
// Form a compaction from the sorted run indicated by start_index to the
|
||||
@@ -102,12 +112,14 @@ class UniversalCompactionBuilder {
|
||||
// because some files are being compacted.
|
||||
Compaction* PickPeriodicCompaction();
|
||||
|
||||
// Used in universal compaction when the enabled_trivial_move
|
||||
// Used in universal compaction when the allow_trivial_move
|
||||
// option is set. Checks whether there are any overlapping files
|
||||
// in the input. Returns true if the input files are non
|
||||
// overlapping.
|
||||
bool IsInputFilesNonOverlapping(Compaction* c);
|
||||
|
||||
uint64_t GetMaxOverlappingBytes() const;
|
||||
|
||||
const ImmutableOptions& ioptions_;
|
||||
const InternalKeyComparator* icmp_;
|
||||
double score_;
|
||||
@@ -157,9 +169,9 @@ struct SmallestKeyHeapComparator {
|
||||
const Comparator* ucmp_;
|
||||
};
|
||||
|
||||
typedef std::priority_queue<InputFileInfo, std::vector<InputFileInfo>,
|
||||
SmallestKeyHeapComparator>
|
||||
SmallestKeyHeap;
|
||||
using SmallestKeyHeap =
|
||||
std::priority_queue<InputFileInfo, std::vector<InputFileInfo>,
|
||||
SmallestKeyHeapComparator>;
|
||||
|
||||
// This function creates the heap that is used to find if the files are
|
||||
// overlapping during universal compaction when the allow_trivial_move
|
||||
@@ -712,6 +724,19 @@ Compaction* UniversalCompactionBuilder::PickCompactionToReduceSortedRuns(
|
||||
cf_name_.c_str(), file_num_buf);
|
||||
}
|
||||
|
||||
std::vector<FileMetaData*> grandparents;
|
||||
// Include grandparents for potential file cutting in incremental
|
||||
// mode. It is for aligning file cutting boundaries across levels,
|
||||
// so that subsequent compactions can pick files with aligned
|
||||
// buffer.
|
||||
// Single files are only picked up in incremental mode, so that
|
||||
// there is no need for full range.
|
||||
if (mutable_cf_options_.compaction_options_universal.incremental &&
|
||||
first_index_after < sorted_runs_.size() &&
|
||||
sorted_runs_[first_index_after].level > 1) {
|
||||
grandparents = vstorage_->LevelFiles(sorted_runs_[first_index_after].level);
|
||||
}
|
||||
|
||||
CompactionReason compaction_reason;
|
||||
if (max_number_of_files_to_compact == UINT_MAX) {
|
||||
compaction_reason = CompactionReason::kUniversalSizeRatio;
|
||||
@@ -723,14 +748,14 @@ Compaction* UniversalCompactionBuilder::PickCompactionToReduceSortedRuns(
|
||||
std::move(inputs), output_level,
|
||||
MaxFileSizeForLevel(mutable_cf_options_, output_level,
|
||||
kCompactionStyleUniversal),
|
||||
LLONG_MAX, path_id,
|
||||
GetMaxOverlappingBytes(), path_id,
|
||||
GetCompressionType(ioptions_, vstorage_, mutable_cf_options_, start_level,
|
||||
1, enable_compression),
|
||||
GetCompressionOptions(mutable_cf_options_, vstorage_, start_level,
|
||||
enable_compression),
|
||||
Temperature::kUnknown,
|
||||
/* max_subcompactions */ 0, /* grandparents */ {}, /* is manual */ false,
|
||||
score_, false /* deletion_compaction */, compaction_reason);
|
||||
/* max_subcompactions */ 0, grandparents, /* is manual */ false, score_,
|
||||
false /* deletion_compaction */, compaction_reason);
|
||||
}
|
||||
|
||||
// Look at overall size amplification. If size amplification
|
||||
@@ -786,6 +811,8 @@ Compaction* UniversalCompactionBuilder::PickCompactionToReduceSizeAmp() {
|
||||
for (size_t loop = start_index; loop + 1 < sorted_runs_.size(); loop++) {
|
||||
sr = &sorted_runs_[loop];
|
||||
if (sr->being_compacted) {
|
||||
// TODO with incremental compaction is supported, we might want to
|
||||
// schedule some incremental compactions in parallel if needed.
|
||||
char file_num_buf[kFormatFileNumberBufSize];
|
||||
sr->Dump(file_num_buf, sizeof(file_num_buf), true);
|
||||
ROCKS_LOG_BUFFER(
|
||||
@@ -819,16 +846,250 @@ Compaction* UniversalCompactionBuilder::PickCompactionToReduceSizeAmp() {
|
||||
" earliest-file-size %" PRIu64,
|
||||
cf_name_.c_str(), candidate_size, earliest_file_size);
|
||||
}
|
||||
// Since incremental compaction can't include more than second last
|
||||
// level, it can introduce penalty, compared to full compaction. We
|
||||
// hard code the pentalty to be 80%. If we end up with a compaction
|
||||
// fanout higher than 80% of full level compactions, we fall back
|
||||
// to full level compaction.
|
||||
// The 80% threshold is arbitrary and can be adjusted or made
|
||||
// configurable in the future.
|
||||
// This also prevent the case when compaction falls behind and we
|
||||
// need to compact more levels for compactions to catch up.
|
||||
if (mutable_cf_options_.compaction_options_universal.incremental) {
|
||||
double fanout_threshold = static_cast<double>(earliest_file_size) /
|
||||
static_cast<double>(candidate_size) * 1.8;
|
||||
Compaction* picked = PickIncrementalForReduceSizeAmp(fanout_threshold);
|
||||
if (picked != nullptr) {
|
||||
// As the feature is still incremental, picking incremental compaction
|
||||
// might fail and we will fall bck to compacting full level.
|
||||
return picked;
|
||||
}
|
||||
}
|
||||
return PickCompactionToOldest(start_index,
|
||||
CompactionReason::kUniversalSizeAmplification);
|
||||
}
|
||||
|
||||
Compaction* UniversalCompactionBuilder::PickIncrementalForReduceSizeAmp(
|
||||
double fanout_threshold) {
|
||||
// Try find all potential compactions with total size just over
|
||||
// options.max_compaction_size / 2, and take the one with the lowest
|
||||
// fanout (defined in declaration of the function).
|
||||
// This is done by having a sliding window of the files at the second
|
||||
// lowest level, and keep expanding while finding overlapping in the
|
||||
// last level. Once total size exceeds the size threshold, calculate
|
||||
// the fanout value. And then shrinking from the small side of the
|
||||
// window. Keep doing it until the end.
|
||||
// Finally, we try to include upper level files if they fall into
|
||||
// the range.
|
||||
//
|
||||
// Note that it is a similar problem as leveled compaction's
|
||||
// kMinOverlappingRatio priority, but instead of picking single files
|
||||
// we expand to a target compaction size. The reason is that in
|
||||
// leveled compaction, actual fanout value tends to high, e.g. 10, so
|
||||
// even with single file in down merging level, the extra size
|
||||
// compacted in boundary files is at a lower ratio. But here users
|
||||
// often have size of second last level size to be 1/4, 1/3 or even
|
||||
// 1/2 of the bottommost level, so picking single file in second most
|
||||
// level will cause significant waste, which is not desirable.
|
||||
//
|
||||
// This algorithm has lots of room to improve to pick more efficient
|
||||
// compactions.
|
||||
assert(sorted_runs_.size() >= 2);
|
||||
int second_last_level = sorted_runs_[sorted_runs_.size() - 2].level;
|
||||
if (second_last_level == 0) {
|
||||
// Can't split Level 0.
|
||||
return nullptr;
|
||||
}
|
||||
int output_level = sorted_runs_.back().level;
|
||||
const std::vector<FileMetaData*>& bottom_files =
|
||||
vstorage_->LevelFiles(output_level);
|
||||
const std::vector<FileMetaData*>& files =
|
||||
vstorage_->LevelFiles(second_last_level);
|
||||
assert(!bottom_files.empty());
|
||||
assert(!files.empty());
|
||||
|
||||
// std::unordered_map<uint64_t, uint64_t> file_to_order;
|
||||
|
||||
int picked_start_idx = 0;
|
||||
int picked_end_idx = 0;
|
||||
double picked_fanout = fanout_threshold;
|
||||
|
||||
// Use half target compaction bytes as anchor to stop growing second most
|
||||
// level files, and reserve growing space for more overlapping bottom level,
|
||||
// clean cut, files from other levels, etc.
|
||||
uint64_t comp_thres_size = mutable_cf_options_.max_compaction_bytes / 2;
|
||||
int start_idx = 0;
|
||||
int bottom_end_idx = 0;
|
||||
int bottom_start_idx = 0;
|
||||
uint64_t non_bottom_size = 0;
|
||||
uint64_t bottom_size = 0;
|
||||
bool end_bottom_size_counted = false;
|
||||
for (int end_idx = 0; end_idx < static_cast<int>(files.size()); end_idx++) {
|
||||
FileMetaData* end_file = files[end_idx];
|
||||
|
||||
// Include bottom most level files smaller than the current second
|
||||
// last level file.
|
||||
int num_skipped = 0;
|
||||
while (bottom_end_idx < static_cast<int>(bottom_files.size()) &&
|
||||
icmp_->Compare(bottom_files[bottom_end_idx]->largest,
|
||||
end_file->smallest) < 0) {
|
||||
if (!end_bottom_size_counted) {
|
||||
bottom_size += bottom_files[bottom_end_idx]->fd.file_size;
|
||||
}
|
||||
bottom_end_idx++;
|
||||
end_bottom_size_counted = false;
|
||||
num_skipped++;
|
||||
}
|
||||
|
||||
if (num_skipped > 1) {
|
||||
// At least a file in the bottom most level falls into the file gap. No
|
||||
// reason to include the file. We cut the range and start a new sliding
|
||||
// window.
|
||||
start_idx = end_idx;
|
||||
}
|
||||
|
||||
if (start_idx == end_idx) {
|
||||
// new sliding window.
|
||||
non_bottom_size = 0;
|
||||
bottom_size = 0;
|
||||
bottom_start_idx = bottom_end_idx;
|
||||
end_bottom_size_counted = false;
|
||||
}
|
||||
|
||||
non_bottom_size += end_file->fd.file_size;
|
||||
|
||||
// Include all overlapping files in bottom level.
|
||||
while (bottom_end_idx < static_cast<int>(bottom_files.size()) &&
|
||||
icmp_->Compare(bottom_files[bottom_end_idx]->smallest,
|
||||
end_file->largest) < 0) {
|
||||
if (!end_bottom_size_counted) {
|
||||
bottom_size += bottom_files[bottom_end_idx]->fd.file_size;
|
||||
end_bottom_size_counted = true;
|
||||
}
|
||||
if (icmp_->Compare(bottom_files[bottom_end_idx]->largest,
|
||||
end_file->largest) > 0) {
|
||||
// next level file cross large boundary of current file.
|
||||
break;
|
||||
}
|
||||
bottom_end_idx++;
|
||||
end_bottom_size_counted = false;
|
||||
}
|
||||
|
||||
if ((non_bottom_size + bottom_size > comp_thres_size ||
|
||||
end_idx == static_cast<int>(files.size()) - 1) &&
|
||||
non_bottom_size > 0) { // Do we alow 0 size file at all?
|
||||
// If it is a better compaction, remember it in picked* variables.
|
||||
double fanout = static_cast<double>(bottom_size) /
|
||||
static_cast<double>(non_bottom_size);
|
||||
if (fanout < picked_fanout) {
|
||||
picked_start_idx = start_idx;
|
||||
picked_end_idx = end_idx;
|
||||
picked_fanout = fanout;
|
||||
}
|
||||
// Shrink from the start end to under comp_thres_size
|
||||
while (non_bottom_size + bottom_size > comp_thres_size &&
|
||||
start_idx <= end_idx) {
|
||||
non_bottom_size -= files[start_idx]->fd.file_size;
|
||||
start_idx++;
|
||||
if (start_idx < static_cast<int>(files.size())) {
|
||||
while (bottom_start_idx <= bottom_end_idx &&
|
||||
icmp_->Compare(bottom_files[bottom_start_idx]->largest,
|
||||
files[start_idx]->smallest) < 0) {
|
||||
bottom_size -= bottom_files[bottom_start_idx]->fd.file_size;
|
||||
bottom_start_idx++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (picked_fanout >= fanout_threshold) {
|
||||
assert(picked_fanout == fanout_threshold);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::vector<CompactionInputFiles> inputs;
|
||||
CompactionInputFiles bottom_level_inputs;
|
||||
CompactionInputFiles second_last_level_inputs;
|
||||
second_last_level_inputs.level = second_last_level;
|
||||
bottom_level_inputs.level = output_level;
|
||||
for (int i = picked_start_idx; i <= picked_end_idx; i++) {
|
||||
if (files[i]->being_compacted) {
|
||||
return nullptr;
|
||||
}
|
||||
second_last_level_inputs.files.push_back(files[i]);
|
||||
}
|
||||
assert(!second_last_level_inputs.empty());
|
||||
if (!picker_->ExpandInputsToCleanCut(cf_name_, vstorage_,
|
||||
&second_last_level_inputs,
|
||||
/*next_smallest=*/nullptr)) {
|
||||
return nullptr;
|
||||
}
|
||||
// We might be able to avoid this binary search if we save and expand
|
||||
// from bottom_start_idx and bottom_end_idx, but for now, we use
|
||||
// SetupOtherInputs() for simplicity.
|
||||
int parent_index = -1; // Create and use bottom_start_idx?
|
||||
if (!picker_->SetupOtherInputs(cf_name_, mutable_cf_options_, vstorage_,
|
||||
&second_last_level_inputs,
|
||||
&bottom_level_inputs, &parent_index,
|
||||
/*base_index=*/-1)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Try to include files in upper levels if they fall into the range.
|
||||
// Since we need to go from lower level up and this is in the reverse
|
||||
// order, compared to level order, we first write to an reversed
|
||||
// data structure and finally copy them to compaction inputs.
|
||||
InternalKey smallest, largest;
|
||||
picker_->GetRange(second_last_level_inputs, &smallest, &largest);
|
||||
std::vector<CompactionInputFiles> inputs_reverse;
|
||||
for (auto it = ++(++sorted_runs_.rbegin()); it != sorted_runs_.rend(); it++) {
|
||||
SortedRun& sr = *it;
|
||||
if (sr.level == 0) {
|
||||
break;
|
||||
}
|
||||
std::vector<FileMetaData*> level_inputs;
|
||||
vstorage_->GetCleanInputsWithinInterval(sr.level, &smallest, &largest,
|
||||
&level_inputs);
|
||||
if (!level_inputs.empty()) {
|
||||
inputs_reverse.push_back({});
|
||||
inputs_reverse.back().level = sr.level;
|
||||
inputs_reverse.back().files = level_inputs;
|
||||
picker_->GetRange(inputs_reverse.back(), &smallest, &largest);
|
||||
}
|
||||
}
|
||||
for (auto it = inputs_reverse.rbegin(); it != inputs_reverse.rend(); it++) {
|
||||
inputs.push_back(*it);
|
||||
}
|
||||
|
||||
inputs.push_back(second_last_level_inputs);
|
||||
inputs.push_back(bottom_level_inputs);
|
||||
|
||||
// TODO support multi paths?
|
||||
uint32_t path_id = 0;
|
||||
return new Compaction(
|
||||
vstorage_, ioptions_, mutable_cf_options_, mutable_db_options_,
|
||||
std::move(inputs), output_level,
|
||||
MaxFileSizeForLevel(mutable_cf_options_, output_level,
|
||||
kCompactionStyleUniversal),
|
||||
GetMaxOverlappingBytes(), path_id,
|
||||
GetCompressionType(ioptions_, vstorage_, mutable_cf_options_,
|
||||
output_level, 1, true /* enable_compression */),
|
||||
GetCompressionOptions(mutable_cf_options_, vstorage_, output_level,
|
||||
true /* enable_compression */),
|
||||
Temperature::kUnknown,
|
||||
/* max_subcompactions */ 0, /* grandparents */ {}, /* is manual */ false,
|
||||
score_, false /* deletion_compaction */,
|
||||
CompactionReason::kUniversalSizeAmplification);
|
||||
}
|
||||
|
||||
// Pick files marked for compaction. Typically, files are marked by
|
||||
// CompactOnDeleteCollector due to the presence of tombstones.
|
||||
Compaction* UniversalCompactionBuilder::PickDeleteTriggeredCompaction() {
|
||||
CompactionInputFiles start_level_inputs;
|
||||
int output_level;
|
||||
std::vector<CompactionInputFiles> inputs;
|
||||
std::vector<FileMetaData*> grandparents;
|
||||
|
||||
if (vstorage_->num_levels() == 1) {
|
||||
// This is single level universal. Since we're basically trying to reclaim
|
||||
@@ -935,6 +1196,9 @@ Compaction* UniversalCompactionBuilder::PickDeleteTriggeredCompaction() {
|
||||
if (picker_->FilesRangeOverlapWithCompaction(inputs, output_level)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
picker_->GetGrandparents(vstorage_, start_level_inputs,
|
||||
output_level_inputs, &grandparents);
|
||||
} else {
|
||||
inputs.push_back(start_level_inputs);
|
||||
}
|
||||
@@ -952,13 +1216,13 @@ Compaction* UniversalCompactionBuilder::PickDeleteTriggeredCompaction() {
|
||||
std::move(inputs), output_level,
|
||||
MaxFileSizeForLevel(mutable_cf_options_, output_level,
|
||||
kCompactionStyleUniversal),
|
||||
/* max_grandparent_overlap_bytes */ LLONG_MAX, path_id,
|
||||
/* max_grandparent_overlap_bytes */ GetMaxOverlappingBytes(), path_id,
|
||||
GetCompressionType(ioptions_, vstorage_, mutable_cf_options_,
|
||||
output_level, 1),
|
||||
GetCompressionOptions(mutable_cf_options_, vstorage_, output_level),
|
||||
Temperature::kUnknown,
|
||||
/* max_subcompactions */ 0, /* grandparents */ {}, /* is manual */ false,
|
||||
score_, false /* deletion_compaction */,
|
||||
/* max_subcompactions */ 0, grandparents, /* is manual */ false, score_,
|
||||
false /* deletion_compaction */,
|
||||
CompactionReason::kFilesMarkedForCompaction);
|
||||
}
|
||||
|
||||
@@ -1026,7 +1290,7 @@ Compaction* UniversalCompactionBuilder::PickCompactionToOldest(
|
||||
std::move(inputs), output_level,
|
||||
MaxFileSizeForLevel(mutable_cf_options_, output_level,
|
||||
kCompactionStyleUniversal),
|
||||
LLONG_MAX, path_id,
|
||||
GetMaxOverlappingBytes(), path_id,
|
||||
GetCompressionType(ioptions_, vstorage_, mutable_cf_options_,
|
||||
output_level, 1, true /* enable_compression */),
|
||||
GetCompressionOptions(mutable_cf_options_, vstorage_, output_level,
|
||||
@@ -1101,6 +1365,17 @@ Compaction* UniversalCompactionBuilder::PickPeriodicCompaction() {
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
uint64_t UniversalCompactionBuilder::GetMaxOverlappingBytes() const {
|
||||
if (!mutable_cf_options_.compaction_options_universal.incremental) {
|
||||
return port::kMaxUint64;
|
||||
} else {
|
||||
// Try to align cutting boundary with files at the next level if the
|
||||
// file isn't end up with 1/2 of target size, or it would overlap
|
||||
// with two full size files at the next level.
|
||||
return mutable_cf_options_.target_file_size_base / 2 * 3;
|
||||
}
|
||||
}
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
#endif // !ROCKSDB_LITE
|
||||
@@ -10,13 +10,54 @@
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
class MyTestCompactionService : public CompactionService {
|
||||
class TestCompactionServiceBase {
|
||||
public:
|
||||
MyTestCompactionService(const std::string& db_path, Options& options,
|
||||
std::shared_ptr<Statistics> statistics = nullptr)
|
||||
: db_path_(db_path), options_(options), statistics_(statistics) {}
|
||||
virtual int GetCompactionNum() = 0;
|
||||
|
||||
static const char* kClassName() { return "MyTestCompactionService"; }
|
||||
void OverrideStartStatus(CompactionServiceJobStatus s) {
|
||||
is_override_start_status = true;
|
||||
override_start_status = s;
|
||||
}
|
||||
|
||||
void OverrideWaitStatus(CompactionServiceJobStatus s) {
|
||||
is_override_wait_status = true;
|
||||
override_wait_status = s;
|
||||
}
|
||||
|
||||
void OverrideWaitResult(std::string str) {
|
||||
is_override_wait_result = true;
|
||||
override_wait_result = std::move(str);
|
||||
}
|
||||
|
||||
void ResetOverride() {
|
||||
is_override_wait_result = false;
|
||||
is_override_start_status = false;
|
||||
is_override_wait_status = false;
|
||||
}
|
||||
|
||||
virtual ~TestCompactionServiceBase() = default;
|
||||
|
||||
protected:
|
||||
bool is_override_start_status = false;
|
||||
CompactionServiceJobStatus override_start_status =
|
||||
CompactionServiceJobStatus::kFailure;
|
||||
bool is_override_wait_status = false;
|
||||
CompactionServiceJobStatus override_wait_status =
|
||||
CompactionServiceJobStatus::kFailure;
|
||||
bool is_override_wait_result = false;
|
||||
std::string override_wait_result;
|
||||
};
|
||||
|
||||
class MyTestCompactionServiceLegacy : public CompactionService,
|
||||
public TestCompactionServiceBase {
|
||||
public:
|
||||
MyTestCompactionServiceLegacy(std::string db_path, Options& options,
|
||||
std::shared_ptr<Statistics>& statistics)
|
||||
: db_path_(std::move(db_path)),
|
||||
options_(options),
|
||||
statistics_(statistics) {}
|
||||
|
||||
static const char* kClassName() { return "MyTestCompactionServiceLegacy"; }
|
||||
|
||||
const char* Name() const override { return kClassName(); }
|
||||
|
||||
@@ -25,7 +66,9 @@ class MyTestCompactionService : public CompactionService {
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
jobs_.emplace(job_id, compaction_service_input);
|
||||
CompactionServiceJobStatus s = CompactionServiceJobStatus::kSuccess;
|
||||
TEST_SYNC_POINT_CALLBACK("MyTestCompactionService::Start::End", &s);
|
||||
if (is_override_start_status) {
|
||||
return override_start_status;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
@@ -42,6 +85,10 @@ class MyTestCompactionService : public CompactionService {
|
||||
jobs_.erase(i);
|
||||
}
|
||||
|
||||
if (is_override_wait_status) {
|
||||
return override_wait_status;
|
||||
}
|
||||
|
||||
CompactionServiceOptionsOverride options_override;
|
||||
options_override.env = options_.env;
|
||||
options_override.file_checksum_gen_factory =
|
||||
@@ -59,8 +106,9 @@ class MyTestCompactionService : public CompactionService {
|
||||
Status s = DB::OpenAndCompact(
|
||||
db_path_, db_path_ + "/" + ROCKSDB_NAMESPACE::ToString(job_id),
|
||||
compaction_input, compaction_service_result, options_override);
|
||||
TEST_SYNC_POINT_CALLBACK("MyTestCompactionService::WaitForComplete::End",
|
||||
compaction_service_result);
|
||||
if (is_override_wait_result) {
|
||||
*compaction_service_result = override_wait_result;
|
||||
}
|
||||
compaction_num_.fetch_add(1);
|
||||
if (s.ok()) {
|
||||
return CompactionServiceJobStatus::kSuccess;
|
||||
@@ -69,7 +117,7 @@ class MyTestCompactionService : public CompactionService {
|
||||
}
|
||||
}
|
||||
|
||||
int GetCompactionNum() { return compaction_num_.load(); }
|
||||
int GetCompactionNum() override { return compaction_num_.load(); }
|
||||
|
||||
private:
|
||||
InstrumentedMutex mutex_;
|
||||
@@ -80,12 +128,144 @@ class MyTestCompactionService : public CompactionService {
|
||||
std::shared_ptr<Statistics> statistics_;
|
||||
};
|
||||
|
||||
class CompactionServiceTest : public DBTestBase {
|
||||
class MyTestCompactionService : public CompactionService,
|
||||
public TestCompactionServiceBase {
|
||||
public:
|
||||
MyTestCompactionService(std::string db_path, Options& options,
|
||||
std::shared_ptr<Statistics>& statistics)
|
||||
: db_path_(std::move(db_path)),
|
||||
options_(options),
|
||||
statistics_(statistics),
|
||||
start_info_("na", "na", "na", 0, Env::TOTAL),
|
||||
wait_info_("na", "na", "na", 0, Env::TOTAL) {}
|
||||
|
||||
static const char* kClassName() { return "MyTestCompactionService"; }
|
||||
|
||||
const char* Name() const override { return kClassName(); }
|
||||
|
||||
CompactionServiceJobStatus StartV2(
|
||||
const CompactionServiceJobInfo& info,
|
||||
const std::string& compaction_service_input) override {
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
start_info_ = info;
|
||||
assert(info.db_name == db_path_);
|
||||
jobs_.emplace(info.job_id, compaction_service_input);
|
||||
CompactionServiceJobStatus s = CompactionServiceJobStatus::kSuccess;
|
||||
if (is_override_start_status) {
|
||||
return override_start_status;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
CompactionServiceJobStatus WaitForCompleteV2(
|
||||
const CompactionServiceJobInfo& info,
|
||||
std::string* compaction_service_result) override {
|
||||
std::string compaction_input;
|
||||
assert(info.db_name == db_path_);
|
||||
{
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
wait_info_ = info;
|
||||
auto i = jobs_.find(info.job_id);
|
||||
if (i == jobs_.end()) {
|
||||
return CompactionServiceJobStatus::kFailure;
|
||||
}
|
||||
compaction_input = std::move(i->second);
|
||||
jobs_.erase(i);
|
||||
}
|
||||
|
||||
if (is_override_wait_status) {
|
||||
return override_wait_status;
|
||||
}
|
||||
|
||||
CompactionServiceOptionsOverride options_override;
|
||||
options_override.env = options_.env;
|
||||
options_override.file_checksum_gen_factory =
|
||||
options_.file_checksum_gen_factory;
|
||||
options_override.comparator = options_.comparator;
|
||||
options_override.merge_operator = options_.merge_operator;
|
||||
options_override.compaction_filter = options_.compaction_filter;
|
||||
options_override.compaction_filter_factory =
|
||||
options_.compaction_filter_factory;
|
||||
options_override.prefix_extractor = options_.prefix_extractor;
|
||||
options_override.table_factory = options_.table_factory;
|
||||
options_override.sst_partitioner_factory = options_.sst_partitioner_factory;
|
||||
options_override.statistics = statistics_;
|
||||
|
||||
Status s = DB::OpenAndCompact(
|
||||
db_path_, db_path_ + "/" + ROCKSDB_NAMESPACE::ToString(info.job_id),
|
||||
compaction_input, compaction_service_result, options_override);
|
||||
if (is_override_wait_result) {
|
||||
*compaction_service_result = override_wait_result;
|
||||
}
|
||||
compaction_num_.fetch_add(1);
|
||||
if (s.ok()) {
|
||||
return CompactionServiceJobStatus::kSuccess;
|
||||
} else {
|
||||
return CompactionServiceJobStatus::kFailure;
|
||||
}
|
||||
}
|
||||
|
||||
int GetCompactionNum() override { return compaction_num_.load(); }
|
||||
|
||||
CompactionServiceJobInfo GetCompactionInfoForStart() { return start_info_; }
|
||||
CompactionServiceJobInfo GetCompactionInfoForWait() { return wait_info_; }
|
||||
|
||||
private:
|
||||
InstrumentedMutex mutex_;
|
||||
std::atomic_int compaction_num_{0};
|
||||
std::map<uint64_t, std::string> jobs_;
|
||||
const std::string db_path_;
|
||||
Options options_;
|
||||
std::shared_ptr<Statistics> statistics_;
|
||||
CompactionServiceJobInfo start_info_;
|
||||
CompactionServiceJobInfo wait_info_;
|
||||
};
|
||||
|
||||
// This is only for listing test classes
|
||||
enum TestCompactionServiceType {
|
||||
MyTestCompactionServiceType,
|
||||
MyTestCompactionServiceLegacyType,
|
||||
};
|
||||
|
||||
class CompactionServiceTest
|
||||
: public DBTestBase,
|
||||
public testing::WithParamInterface<TestCompactionServiceType> {
|
||||
public:
|
||||
explicit CompactionServiceTest()
|
||||
: DBTestBase("compaction_service_test", true) {}
|
||||
|
||||
protected:
|
||||
void ReopenWithCompactionService(Options* options) {
|
||||
options->env = env_;
|
||||
primary_statistics_ = CreateDBStatistics();
|
||||
options->statistics = primary_statistics_;
|
||||
compactor_statistics_ = CreateDBStatistics();
|
||||
TestCompactionServiceType cs_type = GetParam();
|
||||
switch (cs_type) {
|
||||
case MyTestCompactionServiceType:
|
||||
compaction_service_ = std::make_shared<MyTestCompactionService>(
|
||||
dbname_, *options, compactor_statistics_);
|
||||
break;
|
||||
case MyTestCompactionServiceLegacyType:
|
||||
compaction_service_ = std::make_shared<MyTestCompactionServiceLegacy>(
|
||||
dbname_, *options, compactor_statistics_);
|
||||
break;
|
||||
default:
|
||||
assert(false);
|
||||
}
|
||||
options->compaction_service = compaction_service_;
|
||||
DestroyAndReopen(*options);
|
||||
}
|
||||
|
||||
Statistics* GetCompactorStatistics() { return compactor_statistics_.get(); }
|
||||
|
||||
Statistics* GetPrimaryStatistics() { return primary_statistics_.get(); }
|
||||
|
||||
TestCompactionServiceBase* GetCompactionService() {
|
||||
CompactionService* cs = compaction_service_.get();
|
||||
return dynamic_cast<TestCompactionServiceBase*>(cs);
|
||||
}
|
||||
|
||||
void GenerateTestData() {
|
||||
// Generate 20 files @ L2
|
||||
for (int i = 0; i < 20; i++) {
|
||||
@@ -119,17 +299,19 @@ class CompactionServiceTest : public DBTestBase {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr<Statistics> compactor_statistics_;
|
||||
std::shared_ptr<Statistics> primary_statistics_;
|
||||
std::shared_ptr<CompactionService> compaction_service_;
|
||||
};
|
||||
|
||||
TEST_F(CompactionServiceTest, BasicCompactions) {
|
||||
TEST_P(CompactionServiceTest, BasicCompactions) {
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
options.statistics = CreateDBStatistics();
|
||||
std::shared_ptr<Statistics> compactor_statistics = CreateDBStatistics();
|
||||
options.compaction_service = std::make_shared<MyTestCompactionService>(
|
||||
dbname_, options, compactor_statistics);
|
||||
ReopenWithCompactionService(&options);
|
||||
|
||||
DestroyAndReopen(options);
|
||||
Statistics* primary_statistics = GetPrimaryStatistics();
|
||||
Statistics* compactor_statistics = GetCompactorStatistics();
|
||||
|
||||
for (int i = 0; i < 20; i++) {
|
||||
for (int j = 0; j < 10; j++) {
|
||||
@@ -157,21 +339,32 @@ TEST_F(CompactionServiceTest, BasicCompactions) {
|
||||
ASSERT_EQ(result, "value_new" + ToString(i));
|
||||
}
|
||||
}
|
||||
auto my_cs =
|
||||
dynamic_cast<MyTestCompactionService*>(options.compaction_service.get());
|
||||
auto my_cs = GetCompactionService();
|
||||
ASSERT_GE(my_cs->GetCompactionNum(), 1);
|
||||
|
||||
// make sure the compaction statistics is only recorded on remote side
|
||||
ASSERT_GE(
|
||||
compactor_statistics->getTickerCount(COMPACTION_KEY_DROP_NEWER_ENTRY), 1);
|
||||
ASSERT_EQ(options.statistics->getTickerCount(COMPACTION_KEY_DROP_NEWER_ENTRY),
|
||||
// make sure the compaction statistics is only recorded on the remote side
|
||||
ASSERT_GE(compactor_statistics->getTickerCount(COMPACT_WRITE_BYTES), 1);
|
||||
ASSERT_GE(compactor_statistics->getTickerCount(COMPACT_READ_BYTES), 1);
|
||||
ASSERT_EQ(primary_statistics->getTickerCount(COMPACT_WRITE_BYTES), 0);
|
||||
// even with remote compaction, primary host still needs to read SST files to
|
||||
// `verify_table()`.
|
||||
ASSERT_GE(primary_statistics->getTickerCount(COMPACT_READ_BYTES), 1);
|
||||
// all the compaction write happens on the remote side
|
||||
ASSERT_EQ(primary_statistics->getTickerCount(REMOTE_COMPACT_WRITE_BYTES),
|
||||
compactor_statistics->getTickerCount(COMPACT_WRITE_BYTES));
|
||||
ASSERT_GE(primary_statistics->getTickerCount(REMOTE_COMPACT_READ_BYTES), 1);
|
||||
ASSERT_GT(primary_statistics->getTickerCount(COMPACT_READ_BYTES),
|
||||
primary_statistics->getTickerCount(REMOTE_COMPACT_READ_BYTES));
|
||||
// compactor is already the remote side, which doesn't have remote
|
||||
ASSERT_EQ(compactor_statistics->getTickerCount(REMOTE_COMPACT_READ_BYTES), 0);
|
||||
ASSERT_EQ(compactor_statistics->getTickerCount(REMOTE_COMPACT_WRITE_BYTES),
|
||||
0);
|
||||
|
||||
// Test failed compaction
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImplSecondary::CompactWithoutInstallation::End", [&](void* status) {
|
||||
// override job status
|
||||
Status* s = static_cast<Status*>(status);
|
||||
auto s = static_cast<Status*>(status);
|
||||
*s = Status::Aborted("MyTestCompactionService failed to compact!");
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
@@ -200,17 +393,13 @@ TEST_F(CompactionServiceTest, BasicCompactions) {
|
||||
ASSERT_TRUE(s.IsAborted());
|
||||
}
|
||||
|
||||
TEST_F(CompactionServiceTest, ManualCompaction) {
|
||||
TEST_P(CompactionServiceTest, ManualCompaction) {
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
options.disable_auto_compactions = true;
|
||||
options.compaction_service =
|
||||
std::make_shared<MyTestCompactionService>(dbname_, options);
|
||||
DestroyAndReopen(options);
|
||||
ReopenWithCompactionService(&options);
|
||||
GenerateTestData();
|
||||
|
||||
auto my_cs =
|
||||
dynamic_cast<MyTestCompactionService*>(options.compaction_service.get());
|
||||
auto my_cs = GetCompactionService();
|
||||
|
||||
std::string start_str = Key(15);
|
||||
std::string end_str = Key(45);
|
||||
@@ -241,22 +430,15 @@ TEST_F(CompactionServiceTest, ManualCompaction) {
|
||||
VerifyTestData();
|
||||
}
|
||||
|
||||
TEST_F(CompactionServiceTest, FailedToStart) {
|
||||
TEST_P(CompactionServiceTest, FailedToStart) {
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
options.disable_auto_compactions = true;
|
||||
options.compaction_service =
|
||||
std::make_shared<MyTestCompactionService>(dbname_, options);
|
||||
DestroyAndReopen(options);
|
||||
ReopenWithCompactionService(&options);
|
||||
|
||||
GenerateTestData();
|
||||
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"MyTestCompactionService::Start::End", [&](void* status) {
|
||||
// override job status
|
||||
auto s = static_cast<CompactionServiceJobStatus*>(status);
|
||||
*s = CompactionServiceJobStatus::kFailure;
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
auto my_cs = GetCompactionService();
|
||||
my_cs->OverrideStartStatus(CompactionServiceJobStatus::kFailure);
|
||||
|
||||
std::string start_str = Key(15);
|
||||
std::string end_str = Key(45);
|
||||
@@ -266,22 +448,15 @@ TEST_F(CompactionServiceTest, FailedToStart) {
|
||||
ASSERT_TRUE(s.IsIncomplete());
|
||||
}
|
||||
|
||||
TEST_F(CompactionServiceTest, InvalidResult) {
|
||||
TEST_P(CompactionServiceTest, InvalidResult) {
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
options.disable_auto_compactions = true;
|
||||
options.compaction_service =
|
||||
std::make_shared<MyTestCompactionService>(dbname_, options);
|
||||
DestroyAndReopen(options);
|
||||
ReopenWithCompactionService(&options);
|
||||
|
||||
GenerateTestData();
|
||||
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"MyTestCompactionService::WaitForComplete::End", [&](void* result) {
|
||||
// override job status
|
||||
auto result_str = static_cast<std::string*>(result);
|
||||
*result_str = "Invalid Str";
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
auto my_cs = GetCompactionService();
|
||||
my_cs->OverrideWaitResult("Invalid Str");
|
||||
|
||||
std::string start_str = Key(15);
|
||||
std::string end_str = Key(45);
|
||||
@@ -291,21 +466,17 @@ TEST_F(CompactionServiceTest, InvalidResult) {
|
||||
ASSERT_FALSE(s.ok());
|
||||
}
|
||||
|
||||
TEST_F(CompactionServiceTest, SubCompaction) {
|
||||
TEST_P(CompactionServiceTest, SubCompaction) {
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
options.max_subcompactions = 10;
|
||||
options.target_file_size_base = 1 << 10; // 1KB
|
||||
options.disable_auto_compactions = true;
|
||||
options.compaction_service =
|
||||
std::make_shared<MyTestCompactionService>(dbname_, options);
|
||||
ReopenWithCompactionService(&options);
|
||||
|
||||
DestroyAndReopen(options);
|
||||
GenerateTestData();
|
||||
VerifyTestData();
|
||||
|
||||
auto my_cs =
|
||||
dynamic_cast<MyTestCompactionService*>(options.compaction_service.get());
|
||||
auto my_cs = GetCompactionService();
|
||||
int compaction_num_before = my_cs->GetCompactionNum();
|
||||
|
||||
auto cro = CompactRangeOptions();
|
||||
@@ -334,16 +505,12 @@ class PartialDeleteCompactionFilter : public CompactionFilter {
|
||||
const char* Name() const override { return "PartialDeleteCompactionFilter"; }
|
||||
};
|
||||
|
||||
TEST_F(CompactionServiceTest, CompactionFilter) {
|
||||
TEST_P(CompactionServiceTest, CompactionFilter) {
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
std::unique_ptr<CompactionFilter> delete_comp_filter(
|
||||
new PartialDeleteCompactionFilter());
|
||||
options.compaction_filter = delete_comp_filter.get();
|
||||
options.compaction_service =
|
||||
std::make_shared<MyTestCompactionService>(dbname_, options);
|
||||
|
||||
DestroyAndReopen(options);
|
||||
ReopenWithCompactionService(&options);
|
||||
|
||||
for (int i = 0; i < 20; i++) {
|
||||
for (int j = 0; j < 10; j++) {
|
||||
@@ -375,18 +542,13 @@ TEST_F(CompactionServiceTest, CompactionFilter) {
|
||||
ASSERT_EQ(result, "value_new" + ToString(i));
|
||||
}
|
||||
}
|
||||
auto my_cs =
|
||||
dynamic_cast<MyTestCompactionService*>(options.compaction_service.get());
|
||||
auto my_cs = GetCompactionService();
|
||||
ASSERT_GE(my_cs->GetCompactionNum(), 1);
|
||||
}
|
||||
|
||||
TEST_F(CompactionServiceTest, Snapshot) {
|
||||
TEST_P(CompactionServiceTest, Snapshot) {
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
options.compaction_service =
|
||||
std::make_shared<MyTestCompactionService>(dbname_, options);
|
||||
|
||||
DestroyAndReopen(options);
|
||||
ReopenWithCompactionService(&options);
|
||||
|
||||
ASSERT_OK(Put(Key(1), "value1"));
|
||||
ASSERT_OK(Put(Key(2), "value1"));
|
||||
@@ -398,23 +560,18 @@ TEST_F(CompactionServiceTest, Snapshot) {
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
||||
auto my_cs =
|
||||
dynamic_cast<MyTestCompactionService*>(options.compaction_service.get());
|
||||
auto my_cs = GetCompactionService();
|
||||
ASSERT_GE(my_cs->GetCompactionNum(), 1);
|
||||
ASSERT_EQ("value1", Get(Key(1), s1));
|
||||
ASSERT_EQ("value2", Get(Key(1)));
|
||||
db_->ReleaseSnapshot(s1);
|
||||
}
|
||||
|
||||
TEST_F(CompactionServiceTest, ConcurrentCompaction) {
|
||||
TEST_P(CompactionServiceTest, ConcurrentCompaction) {
|
||||
Options options = CurrentOptions();
|
||||
options.level0_file_num_compaction_trigger = 100;
|
||||
options.env = env_;
|
||||
options.compaction_service =
|
||||
std::make_shared<MyTestCompactionService>(dbname_, options);
|
||||
options.max_background_jobs = 20;
|
||||
|
||||
DestroyAndReopen(options);
|
||||
ReopenWithCompactionService(&options);
|
||||
GenerateTestData();
|
||||
|
||||
ColumnFamilyMetaData meta;
|
||||
@@ -442,12 +599,211 @@ TEST_F(CompactionServiceTest, ConcurrentCompaction) {
|
||||
ASSERT_EQ(result, "value_new" + ToString(i));
|
||||
}
|
||||
}
|
||||
auto my_cs =
|
||||
dynamic_cast<MyTestCompactionService*>(options.compaction_service.get());
|
||||
auto my_cs = GetCompactionService();
|
||||
ASSERT_EQ(my_cs->GetCompactionNum(), 10);
|
||||
ASSERT_EQ(FilesPerLevel(), "0,0,10");
|
||||
}
|
||||
|
||||
TEST_P(CompactionServiceTest, CompactionInfo) {
|
||||
// only test compaction info for new compaction service interface
|
||||
if (GetParam() != MyTestCompactionServiceType) {
|
||||
return;
|
||||
}
|
||||
|
||||
Options options = CurrentOptions();
|
||||
ReopenWithCompactionService(&options);
|
||||
|
||||
for (int i = 0; i < 20; i++) {
|
||||
for (int j = 0; j < 10; j++) {
|
||||
int key_id = i * 10 + j;
|
||||
ASSERT_OK(Put(Key(key_id), "value" + ToString(key_id)));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
|
||||
for (int i = 0; i < 10; i++) {
|
||||
for (int j = 0; j < 10; j++) {
|
||||
int key_id = i * 20 + j * 2;
|
||||
ASSERT_OK(Put(Key(key_id), "value_new" + ToString(key_id)));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
auto my_cs =
|
||||
static_cast_with_check<MyTestCompactionService>(GetCompactionService());
|
||||
uint64_t comp_num = my_cs->GetCompactionNum();
|
||||
ASSERT_GE(comp_num, 1);
|
||||
|
||||
CompactionServiceJobInfo info = my_cs->GetCompactionInfoForStart();
|
||||
ASSERT_EQ(dbname_, info.db_name);
|
||||
std::string db_id, db_session_id;
|
||||
ASSERT_OK(db_->GetDbIdentity(db_id));
|
||||
ASSERT_EQ(db_id, info.db_id);
|
||||
ASSERT_OK(db_->GetDbSessionId(db_session_id));
|
||||
ASSERT_EQ(db_session_id, info.db_session_id);
|
||||
ASSERT_EQ(Env::LOW, info.priority);
|
||||
info = my_cs->GetCompactionInfoForWait();
|
||||
ASSERT_EQ(dbname_, info.db_name);
|
||||
ASSERT_EQ(db_id, info.db_id);
|
||||
ASSERT_EQ(db_session_id, info.db_session_id);
|
||||
ASSERT_EQ(Env::LOW, info.priority);
|
||||
|
||||
// Test priority USER
|
||||
ColumnFamilyMetaData meta;
|
||||
db_->GetColumnFamilyMetaData(&meta);
|
||||
SstFileMetaData file = meta.levels[1].files[0];
|
||||
ASSERT_OK(db_->CompactFiles(CompactionOptions(),
|
||||
{file.db_path + "/" + file.name}, 2));
|
||||
info = my_cs->GetCompactionInfoForStart();
|
||||
ASSERT_EQ(Env::USER, info.priority);
|
||||
info = my_cs->GetCompactionInfoForWait();
|
||||
ASSERT_EQ(Env::USER, info.priority);
|
||||
|
||||
// Test priority BOTTOM
|
||||
env_->SetBackgroundThreads(1, Env::BOTTOM);
|
||||
options.num_levels = 2;
|
||||
ReopenWithCompactionService(&options);
|
||||
my_cs =
|
||||
static_cast_with_check<MyTestCompactionService>(GetCompactionService());
|
||||
|
||||
for (int i = 0; i < 20; i++) {
|
||||
for (int j = 0; j < 10; j++) {
|
||||
int key_id = i * 10 + j;
|
||||
ASSERT_OK(Put(Key(key_id), "value" + ToString(key_id)));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
for (int j = 0; j < 10; j++) {
|
||||
int key_id = i * 20 + j * 2;
|
||||
ASSERT_OK(Put(Key(key_id), "value_new" + ToString(key_id)));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
info = my_cs->GetCompactionInfoForStart();
|
||||
ASSERT_EQ(Env::BOTTOM, info.priority);
|
||||
info = my_cs->GetCompactionInfoForWait();
|
||||
ASSERT_EQ(Env::BOTTOM, info.priority);
|
||||
}
|
||||
|
||||
TEST_P(CompactionServiceTest, FallbackLocalAuto) {
|
||||
Options options = CurrentOptions();
|
||||
ReopenWithCompactionService(&options);
|
||||
|
||||
auto my_cs = GetCompactionService();
|
||||
Statistics* compactor_statistics = GetCompactorStatistics();
|
||||
Statistics* primary_statistics = GetPrimaryStatistics();
|
||||
uint64_t compactor_write_bytes =
|
||||
compactor_statistics->getTickerCount(COMPACT_WRITE_BYTES);
|
||||
uint64_t primary_write_bytes =
|
||||
primary_statistics->getTickerCount(COMPACT_WRITE_BYTES);
|
||||
|
||||
my_cs->OverrideStartStatus(CompactionServiceJobStatus::kUseLocal);
|
||||
|
||||
for (int i = 0; i < 20; i++) {
|
||||
for (int j = 0; j < 10; j++) {
|
||||
int key_id = i * 10 + j;
|
||||
ASSERT_OK(Put(Key(key_id), "value" + ToString(key_id)));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
|
||||
for (int i = 0; i < 10; i++) {
|
||||
for (int j = 0; j < 10; j++) {
|
||||
int key_id = i * 20 + j * 2;
|
||||
ASSERT_OK(Put(Key(key_id), "value_new" + ToString(key_id)));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
|
||||
// verify result
|
||||
for (int i = 0; i < 200; i++) {
|
||||
auto result = Get(Key(i));
|
||||
if (i % 2) {
|
||||
ASSERT_EQ(result, "value" + ToString(i));
|
||||
} else {
|
||||
ASSERT_EQ(result, "value_new" + ToString(i));
|
||||
}
|
||||
}
|
||||
|
||||
ASSERT_EQ(my_cs->GetCompactionNum(), 0);
|
||||
|
||||
// make sure the compaction statistics is only recorded on the local side
|
||||
ASSERT_EQ(compactor_statistics->getTickerCount(COMPACT_WRITE_BYTES),
|
||||
compactor_write_bytes);
|
||||
ASSERT_GT(primary_statistics->getTickerCount(COMPACT_WRITE_BYTES),
|
||||
primary_write_bytes);
|
||||
ASSERT_EQ(primary_statistics->getTickerCount(REMOTE_COMPACT_READ_BYTES), 0);
|
||||
ASSERT_EQ(primary_statistics->getTickerCount(REMOTE_COMPACT_WRITE_BYTES), 0);
|
||||
}
|
||||
|
||||
TEST_P(CompactionServiceTest, FallbackLocalManual) {
|
||||
Options options = CurrentOptions();
|
||||
options.disable_auto_compactions = true;
|
||||
ReopenWithCompactionService(&options);
|
||||
|
||||
GenerateTestData();
|
||||
VerifyTestData();
|
||||
|
||||
auto my_cs = GetCompactionService();
|
||||
Statistics* compactor_statistics = GetCompactorStatistics();
|
||||
Statistics* primary_statistics = GetPrimaryStatistics();
|
||||
uint64_t compactor_write_bytes =
|
||||
compactor_statistics->getTickerCount(COMPACT_WRITE_BYTES);
|
||||
uint64_t primary_write_bytes =
|
||||
primary_statistics->getTickerCount(COMPACT_WRITE_BYTES);
|
||||
|
||||
// re-enable remote compaction
|
||||
my_cs->ResetOverride();
|
||||
std::string start_str = Key(15);
|
||||
std::string end_str = Key(45);
|
||||
Slice start(start_str);
|
||||
Slice end(end_str);
|
||||
uint64_t comp_num = my_cs->GetCompactionNum();
|
||||
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), &start, &end));
|
||||
ASSERT_GE(my_cs->GetCompactionNum(), comp_num + 1);
|
||||
// make sure the compaction statistics is only recorded on the remote side
|
||||
ASSERT_GT(compactor_statistics->getTickerCount(COMPACT_WRITE_BYTES),
|
||||
compactor_write_bytes);
|
||||
ASSERT_EQ(primary_statistics->getTickerCount(REMOTE_COMPACT_WRITE_BYTES),
|
||||
compactor_statistics->getTickerCount(COMPACT_WRITE_BYTES));
|
||||
ASSERT_EQ(primary_statistics->getTickerCount(COMPACT_WRITE_BYTES),
|
||||
primary_write_bytes);
|
||||
|
||||
// return run local again with API WaitForComplete
|
||||
my_cs->OverrideWaitStatus(CompactionServiceJobStatus::kUseLocal);
|
||||
start_str = Key(120);
|
||||
start = start_str;
|
||||
comp_num = my_cs->GetCompactionNum();
|
||||
compactor_write_bytes =
|
||||
compactor_statistics->getTickerCount(COMPACT_WRITE_BYTES);
|
||||
primary_write_bytes = primary_statistics->getTickerCount(COMPACT_WRITE_BYTES);
|
||||
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), &start, nullptr));
|
||||
ASSERT_EQ(my_cs->GetCompactionNum(),
|
||||
comp_num); // no remote compaction is run
|
||||
// make sure the compaction statistics is only recorded on the local side
|
||||
ASSERT_EQ(compactor_statistics->getTickerCount(COMPACT_WRITE_BYTES),
|
||||
compactor_write_bytes);
|
||||
ASSERT_GT(primary_statistics->getTickerCount(COMPACT_WRITE_BYTES),
|
||||
primary_write_bytes);
|
||||
ASSERT_EQ(primary_statistics->getTickerCount(REMOTE_COMPACT_WRITE_BYTES),
|
||||
compactor_write_bytes);
|
||||
|
||||
// verify result after 2 manual compactions
|
||||
VerifyTestData();
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(
|
||||
CompactionServiceTest, CompactionServiceTest,
|
||||
::testing::Values(
|
||||
TestCompactionServiceType::MyTestCompactionServiceType,
|
||||
TestCompactionServiceType::MyTestCompactionServiceLegacyType));
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
#ifdef ROCKSDB_UNITTESTS_WITH_CUSTOM_OBJECTS_FROM_STATIC_LIBS
|
||||
|
||||
@@ -8,7 +8,24 @@
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "rocksdb/utilities/customizable_util.h"
|
||||
#include "rocksdb/utilities/object_registry.h"
|
||||
#include "rocksdb/utilities/options_type.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
static std::unordered_map<std::string, OptionTypeInfo>
|
||||
sst_fixed_prefix_type_info = {
|
||||
#ifndef ROCKSDB_LITE
|
||||
{"length",
|
||||
{0, OptionType::kSizeT, OptionVerificationType::kNormal,
|
||||
OptionTypeFlags::kNone}},
|
||||
#endif // ROCKSDB_LITE
|
||||
};
|
||||
|
||||
SstPartitionerFixedPrefixFactory::SstPartitionerFixedPrefixFactory(size_t len)
|
||||
: len_(len) {
|
||||
RegisterOptions("Length", &len_, &sst_fixed_prefix_type_info);
|
||||
}
|
||||
|
||||
PartitionerResult SstPartitionerFixedPrefix::ShouldPartition(
|
||||
const PartitionerRequest& request) {
|
||||
@@ -41,4 +58,33 @@ std::shared_ptr<SstPartitionerFactory> NewSstPartitionerFixedPrefixFactory(
|
||||
return std::make_shared<SstPartitionerFixedPrefixFactory>(prefix_len);
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
namespace {
|
||||
static int RegisterSstPartitionerFactories(ObjectLibrary& library,
|
||||
const std::string& /*arg*/) {
|
||||
library.Register<SstPartitionerFactory>(
|
||||
SstPartitionerFixedPrefixFactory::kClassName(),
|
||||
[](const std::string& /*uri*/,
|
||||
std::unique_ptr<SstPartitionerFactory>* guard,
|
||||
std::string* /* errmsg */) {
|
||||
guard->reset(new SstPartitionerFixedPrefixFactory(0));
|
||||
return guard->get();
|
||||
});
|
||||
return 1;
|
||||
}
|
||||
} // namespace
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
Status SstPartitionerFactory::CreateFromString(
|
||||
const ConfigOptions& options, const std::string& value,
|
||||
std::shared_ptr<SstPartitionerFactory>* result) {
|
||||
#ifndef ROCKSDB_LITE
|
||||
static std::once_flag once;
|
||||
std::call_once(once, [&]() {
|
||||
RegisterSstPartitionerFactories(*(ObjectLibrary::Default().get()), "");
|
||||
});
|
||||
#endif // ROCKSDB_LITE
|
||||
return LoadSharedObject<SstPartitionerFactory>(options, value, nullptr,
|
||||
result);
|
||||
}
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
@@ -6,6 +6,7 @@
|
||||
#ifndef ROCKSDB_LITE
|
||||
|
||||
#include "db/db_impl/db_impl.h"
|
||||
#include "db/db_test_util.h"
|
||||
#include "rocksdb/db.h"
|
||||
#include "rocksdb/env.h"
|
||||
#include "table/cuckoo/cuckoo_table_factory.h"
|
||||
@@ -94,8 +95,8 @@ class CuckooTableDBTest : public testing::Test {
|
||||
|
||||
int NumTableFilesAtLevel(int level) {
|
||||
std::string property;
|
||||
EXPECT_TRUE(db_->GetProperty(
|
||||
"rocksdb.num-files-at-level" + NumberToString(level), &property));
|
||||
EXPECT_TRUE(db_->GetProperty("rocksdb.num-files-at-level" + ToString(level),
|
||||
&property));
|
||||
return atoi(property.c_str());
|
||||
}
|
||||
|
||||
@@ -133,6 +134,7 @@ TEST_F(CuckooTableDBTest, Flush) {
|
||||
|
||||
TablePropertiesCollection ptc;
|
||||
ASSERT_OK(reinterpret_cast<DB*>(dbfull())->GetPropertiesOfAllTables(&ptc));
|
||||
VerifySstUniqueIds(ptc);
|
||||
ASSERT_EQ(1U, ptc.size());
|
||||
ASSERT_EQ(3U, ptc.begin()->second->num_entries);
|
||||
ASSERT_EQ("1", FilesPerLevel());
|
||||
@@ -149,6 +151,7 @@ TEST_F(CuckooTableDBTest, Flush) {
|
||||
ASSERT_OK(dbfull()->TEST_FlushMemTable());
|
||||
|
||||
ASSERT_OK(reinterpret_cast<DB*>(dbfull())->GetPropertiesOfAllTables(&ptc));
|
||||
VerifySstUniqueIds(ptc);
|
||||
ASSERT_EQ(2U, ptc.size());
|
||||
auto row = ptc.begin();
|
||||
ASSERT_EQ(3U, row->second->num_entries);
|
||||
@@ -166,6 +169,7 @@ TEST_F(CuckooTableDBTest, Flush) {
|
||||
ASSERT_OK(Delete("key4"));
|
||||
ASSERT_OK(dbfull()->TEST_FlushMemTable());
|
||||
ASSERT_OK(reinterpret_cast<DB*>(dbfull())->GetPropertiesOfAllTables(&ptc));
|
||||
VerifySstUniqueIds(ptc);
|
||||
ASSERT_EQ(3U, ptc.size());
|
||||
row = ptc.begin();
|
||||
ASSERT_EQ(3U, row->second->num_entries);
|
||||
@@ -190,6 +194,7 @@ TEST_F(CuckooTableDBTest, FlushWithDuplicateKeys) {
|
||||
|
||||
TablePropertiesCollection ptc;
|
||||
ASSERT_OK(reinterpret_cast<DB*>(dbfull())->GetPropertiesOfAllTables(&ptc));
|
||||
VerifySstUniqueIds(ptc);
|
||||
ASSERT_EQ(1U, ptc.size());
|
||||
ASSERT_EQ(2U, ptc.begin()->second->num_entries);
|
||||
ASSERT_EQ("1", FilesPerLevel());
|
||||
|
||||
+29
-12
@@ -8,7 +8,6 @@
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
|
||||
#include <cstring>
|
||||
#include <regex>
|
||||
|
||||
#include "db/db_test_util.h"
|
||||
#include "port/stack_trace.h"
|
||||
@@ -69,11 +68,10 @@ TEST_F(DBBasicTest, UniqueSession) {
|
||||
ASSERT_EQ(sid2, sid4);
|
||||
|
||||
// Expected compact format for session ids (see notes in implementation)
|
||||
std::regex expected("[0-9A-Z]{20}");
|
||||
const std::string match("match");
|
||||
EXPECT_EQ(match, std::regex_replace(sid1, expected, match));
|
||||
EXPECT_EQ(match, std::regex_replace(sid2, expected, match));
|
||||
EXPECT_EQ(match, std::regex_replace(sid3, expected, match));
|
||||
TestRegex expected("[0-9A-Z]{20}");
|
||||
EXPECT_MATCHES_REGEX(sid1, expected);
|
||||
EXPECT_MATCHES_REGEX(sid2, expected);
|
||||
EXPECT_MATCHES_REGEX(sid3, expected);
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
Close();
|
||||
@@ -1132,9 +1130,15 @@ TEST_F(DBBasicTest, DBCloseFlushError) {
|
||||
ASSERT_OK(Put("key3", "value3"));
|
||||
fault_injection_env->SetFilesystemActive(false);
|
||||
Status s = dbfull()->Close();
|
||||
fault_injection_env->SetFilesystemActive(true);
|
||||
ASSERT_NE(s, Status::OK());
|
||||
|
||||
// retry should return the same error
|
||||
s = dbfull()->Close();
|
||||
ASSERT_NE(s, Status::OK());
|
||||
fault_injection_env->SetFilesystemActive(true);
|
||||
// retry close() is no-op even the system is back. Could be improved if
|
||||
// Close() is retry-able: #9029
|
||||
s = dbfull()->Close();
|
||||
ASSERT_NE(s, Status::OK());
|
||||
Destroy(options);
|
||||
}
|
||||
|
||||
@@ -2593,6 +2597,21 @@ TEST_F(DBBasicTest, ManifestChecksumMismatch) {
|
||||
ASSERT_TRUE(s.IsCorruption());
|
||||
}
|
||||
|
||||
TEST_F(DBBasicTest, ConcurrentlyCloseDB) {
|
||||
Options options = CurrentOptions();
|
||||
DestroyAndReopen(options);
|
||||
std::vector<std::thread> workers;
|
||||
for (int i = 0; i < 10; i++) {
|
||||
workers.push_back(std::thread([&]() {
|
||||
auto s = db_->Close();
|
||||
ASSERT_OK(s);
|
||||
}));
|
||||
}
|
||||
for (auto& w : workers) {
|
||||
w.join();
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
class DBBasicTestTrackWal : public DBTestBase,
|
||||
public testing::WithParamInterface<bool> {
|
||||
@@ -3237,13 +3256,11 @@ INSTANTIATE_TEST_CASE_P(ParallelIO, DBBasicTestWithParallelIO,
|
||||
// Forward declaration
|
||||
class DeadlineFS;
|
||||
|
||||
class DeadlineRandomAccessFile : public FSRandomAccessFileWrapper {
|
||||
class DeadlineRandomAccessFile : public FSRandomAccessFileOwnerWrapper {
|
||||
public:
|
||||
DeadlineRandomAccessFile(DeadlineFS& fs,
|
||||
std::unique_ptr<FSRandomAccessFile>& file)
|
||||
: FSRandomAccessFileWrapper(file.get()),
|
||||
fs_(fs),
|
||||
file_(std::move(file)) {}
|
||||
: FSRandomAccessFileOwnerWrapper(std::move(file)), fs_(fs) {}
|
||||
|
||||
IOStatus Read(uint64_t offset, size_t len, const IOOptions& opts,
|
||||
Slice* result, char* scratch,
|
||||
|
||||
@@ -505,6 +505,11 @@ TEST_F(DBBlockCacheTest, WarmCacheWithDataBlocksDuringFlush) {
|
||||
ASSERT_EQ(0, options.statistics->getTickerCount(BLOCK_CACHE_DATA_MISS));
|
||||
ASSERT_EQ(i, options.statistics->getTickerCount(BLOCK_CACHE_DATA_HIT));
|
||||
}
|
||||
// Verify compaction not counted
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), /*begin=*/nullptr,
|
||||
/*end=*/nullptr));
|
||||
EXPECT_EQ(kNumBlocks,
|
||||
options.statistics->getTickerCount(BLOCK_CACHE_DATA_ADD));
|
||||
}
|
||||
|
||||
// This test cache data, index and filter blocks during flush.
|
||||
@@ -542,6 +547,18 @@ TEST_F(DBBlockCacheTest, WarmCacheWithBlocksDuringFlush) {
|
||||
ASSERT_EQ(i * 2,
|
||||
options.statistics->getTickerCount(BLOCK_CACHE_FILTER_HIT));
|
||||
}
|
||||
// Verify compaction not counted
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), /*begin=*/nullptr,
|
||||
/*end=*/nullptr));
|
||||
EXPECT_EQ(kNumBlocks,
|
||||
options.statistics->getTickerCount(BLOCK_CACHE_DATA_ADD));
|
||||
// Index and filter blocks are automatically warmed when the new table file
|
||||
// is automatically opened at the end of compaction. This is not easily
|
||||
// disabled so results in the new index and filter blocks being warmed.
|
||||
EXPECT_EQ(1 + kNumBlocks,
|
||||
options.statistics->getTickerCount(BLOCK_CACHE_INDEX_ADD));
|
||||
EXPECT_EQ(1 + kNumBlocks,
|
||||
options.statistics->getTickerCount(BLOCK_CACHE_FILTER_ADD));
|
||||
}
|
||||
|
||||
TEST_F(DBBlockCacheTest, DynamicallyWarmCacheDuringFlush) {
|
||||
|
||||
+17
-17
@@ -15,6 +15,7 @@
|
||||
#include "port/stack_trace.h"
|
||||
#include "rocksdb/perf_context.h"
|
||||
#include "table/block_based/filter_policy_internal.h"
|
||||
#include "test_util/testutil.h"
|
||||
#include "util/string_util.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
@@ -1819,8 +1820,8 @@ TEST_F(DBBloomFilterTest, DynamicBloomFilterUpperBound) {
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_USEFUL), 0);
|
||||
}
|
||||
ASSERT_OK(dbfull()->SetOptions({{"prefix_extractor", "fixed:5"}}));
|
||||
ASSERT_EQ(0, strcmp(dbfull()->GetOptions().prefix_extractor->Name(),
|
||||
"rocksdb.FixedPrefix.5"));
|
||||
ASSERT_EQ(dbfull()->GetOptions().prefix_extractor->AsString(),
|
||||
"rocksdb.FixedPrefix.5");
|
||||
{
|
||||
// BF changed, [abcdxx00, abce) is a valid bound, will trigger BF read
|
||||
Slice upper_bound("abce");
|
||||
@@ -1939,8 +1940,8 @@ TEST_F(DBBloomFilterTest, DynamicBloomFilterMultipleSST) {
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED), 1);
|
||||
|
||||
ASSERT_OK(dbfull()->SetOptions({{"prefix_extractor", "capped:3"}}));
|
||||
ASSERT_EQ(0, strcmp(dbfull()->GetOptions().prefix_extractor->Name(),
|
||||
"rocksdb.CappedPrefix.3"));
|
||||
ASSERT_EQ(dbfull()->GetOptions().prefix_extractor->AsString(),
|
||||
"rocksdb.CappedPrefix.3");
|
||||
read_options.iterate_upper_bound = &upper_bound;
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options));
|
||||
ASSERT_EQ(CountIter(iter, "foo"), 2);
|
||||
@@ -1973,8 +1974,8 @@ TEST_F(DBBloomFilterTest, DynamicBloomFilterMultipleSST) {
|
||||
}
|
||||
|
||||
ASSERT_OK(dbfull()->SetOptions({{"prefix_extractor", "fixed:2"}}));
|
||||
ASSERT_EQ(0, strcmp(dbfull()->GetOptions().prefix_extractor->Name(),
|
||||
"rocksdb.FixedPrefix.2"));
|
||||
ASSERT_EQ(dbfull()->GetOptions().prefix_extractor->AsString(),
|
||||
"rocksdb.FixedPrefix.2");
|
||||
// third SST with fixed:2 BF
|
||||
ASSERT_OK(Put("foo6", "bar6"));
|
||||
ASSERT_OK(Put("foo7", "bar7"));
|
||||
@@ -2021,8 +2022,8 @@ TEST_F(DBBloomFilterTest, DynamicBloomFilterMultipleSST) {
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_USEFUL), 3);
|
||||
}
|
||||
ASSERT_OK(dbfull()->SetOptions({{"prefix_extractor", "capped:3"}}));
|
||||
ASSERT_EQ(0, strcmp(dbfull()->GetOptions().prefix_extractor->Name(),
|
||||
"rocksdb.CappedPrefix.3"));
|
||||
ASSERT_EQ(dbfull()->GetOptions().prefix_extractor->AsString(),
|
||||
"rocksdb.CappedPrefix.3");
|
||||
{
|
||||
std::unique_ptr<Iterator> iter_all(db_->NewIterator(read_options));
|
||||
ASSERT_EQ(CountIter(iter_all, "foo"), 6);
|
||||
@@ -2062,9 +2063,8 @@ TEST_F(DBBloomFilterTest, DynamicBloomFilterNewColumnFamily) {
|
||||
// create a new CF and set prefix_extractor dynamically
|
||||
options.prefix_extractor.reset(NewCappedPrefixTransform(3));
|
||||
CreateColumnFamilies({"ramen_dojo_" + std::to_string(iteration)}, options);
|
||||
ASSERT_EQ(0,
|
||||
strcmp(dbfull()->GetOptions(handles_[2]).prefix_extractor->Name(),
|
||||
"rocksdb.CappedPrefix.3"));
|
||||
ASSERT_EQ(dbfull()->GetOptions(handles_[2]).prefix_extractor->AsString(),
|
||||
"rocksdb.CappedPrefix.3");
|
||||
ASSERT_OK(Put(2, "foo3", "bar3"));
|
||||
ASSERT_OK(Put(2, "foo4", "bar4"));
|
||||
ASSERT_OK(Put(2, "foo5", "bar5"));
|
||||
@@ -2080,9 +2080,8 @@ TEST_F(DBBloomFilterTest, DynamicBloomFilterNewColumnFamily) {
|
||||
}
|
||||
ASSERT_OK(
|
||||
dbfull()->SetOptions(handles_[2], {{"prefix_extractor", "fixed:2"}}));
|
||||
ASSERT_EQ(0,
|
||||
strcmp(dbfull()->GetOptions(handles_[2]).prefix_extractor->Name(),
|
||||
"rocksdb.FixedPrefix.2"));
|
||||
ASSERT_EQ(dbfull()->GetOptions(handles_[2]).prefix_extractor->AsString(),
|
||||
"rocksdb.FixedPrefix.2");
|
||||
{
|
||||
std::unique_ptr<Iterator> iter(
|
||||
db_->NewIterator(read_options, handles_[2]));
|
||||
@@ -2147,8 +2146,8 @@ TEST_F(DBBloomFilterTest, DynamicBloomFilterOptions) {
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_USEFUL), 0);
|
||||
|
||||
ASSERT_OK(dbfull()->SetOptions({{"prefix_extractor", "capped:3"}}));
|
||||
ASSERT_EQ(0, strcmp(dbfull()->GetOptions().prefix_extractor->Name(),
|
||||
"rocksdb.CappedPrefix.3"));
|
||||
ASSERT_EQ(dbfull()->GetOptions().prefix_extractor->AsString(),
|
||||
"rocksdb.CappedPrefix.3");
|
||||
{
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options));
|
||||
// "fp*" should be skipped
|
||||
@@ -2171,7 +2170,8 @@ TEST_F(DBBloomFilterTest, SeekForPrevWithPartitionedFilters) {
|
||||
Options options = CurrentOptions();
|
||||
constexpr size_t kNumKeys = 10000;
|
||||
static_assert(kNumKeys <= 10000, "kNumKeys have to be <= 10000");
|
||||
options.memtable_factory.reset(new SpecialSkipListFactory(kNumKeys + 10));
|
||||
options.memtable_factory.reset(
|
||||
test::NewSpecialSkipListFactory(kNumKeys + 10));
|
||||
options.create_if_missing = true;
|
||||
constexpr size_t kPrefixLength = 4;
|
||||
options.prefix_extractor.reset(NewFixedPrefixTransform(kPrefixLength));
|
||||
|
||||
+63
-12
@@ -19,6 +19,7 @@
|
||||
#include "rocksdb/sst_file_writer.h"
|
||||
#include "rocksdb/utilities/convenience.h"
|
||||
#include "test_util/sync_point.h"
|
||||
#include "test_util/testutil.h"
|
||||
#include "util/concurrent_task_limiter_impl.h"
|
||||
#include "util/random.h"
|
||||
#include "utilities/fault_injection_env.h"
|
||||
@@ -805,7 +806,8 @@ TEST_P(DBCompactionTestWithParam, CompactionTrigger) {
|
||||
options.num_levels = 3;
|
||||
options.level0_file_num_compaction_trigger = 3;
|
||||
options.max_subcompactions = max_subcompactions_;
|
||||
options.memtable_factory.reset(new SpecialSkipListFactory(kNumKeysPerFile));
|
||||
options.memtable_factory.reset(
|
||||
test::NewSpecialSkipListFactory(kNumKeysPerFile));
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
|
||||
Random rnd(301);
|
||||
@@ -852,7 +854,8 @@ TEST_F(DBCompactionTest, BGCompactionsAllowed) {
|
||||
options.level0_slowdown_writes_trigger = 20;
|
||||
options.soft_pending_compaction_bytes_limit = 1 << 30; // Infinitely large
|
||||
options.max_background_compactions = 3;
|
||||
options.memtable_factory.reset(new SpecialSkipListFactory(kNumKeysPerFile));
|
||||
options.memtable_factory.reset(
|
||||
test::NewSpecialSkipListFactory(kNumKeysPerFile));
|
||||
|
||||
// Block all threads in thread pool.
|
||||
const size_t kTotalTasks = 4;
|
||||
@@ -2094,7 +2097,7 @@ TEST_P(DBCompactionTestWithParam, LevelCompactionThirdPath) {
|
||||
options.db_paths.emplace_back(dbname_ + "_2", 4 * 1024 * 1024);
|
||||
options.db_paths.emplace_back(dbname_ + "_3", 1024 * 1024 * 1024);
|
||||
options.memtable_factory.reset(
|
||||
new SpecialSkipListFactory(KNumKeysByGenerateNewFile - 1));
|
||||
test::NewSpecialSkipListFactory(KNumKeysByGenerateNewFile - 1));
|
||||
options.compaction_style = kCompactionStyleLevel;
|
||||
options.write_buffer_size = 110 << 10; // 110KB
|
||||
options.arena_block_size = 4 << 10;
|
||||
@@ -2203,7 +2206,7 @@ TEST_P(DBCompactionTestWithParam, LevelCompactionPathUse) {
|
||||
options.db_paths.emplace_back(dbname_ + "_2", 4 * 1024 * 1024);
|
||||
options.db_paths.emplace_back(dbname_ + "_3", 1024 * 1024 * 1024);
|
||||
options.memtable_factory.reset(
|
||||
new SpecialSkipListFactory(KNumKeysByGenerateNewFile - 1));
|
||||
test::NewSpecialSkipListFactory(KNumKeysByGenerateNewFile - 1));
|
||||
options.compaction_style = kCompactionStyleLevel;
|
||||
options.write_buffer_size = 110 << 10; // 110KB
|
||||
options.arena_block_size = 4 << 10;
|
||||
@@ -2313,7 +2316,7 @@ TEST_P(DBCompactionTestWithParam, LevelCompactionCFPathUse) {
|
||||
options.db_paths.emplace_back(dbname_ + "_2", 4 * 1024 * 1024);
|
||||
options.db_paths.emplace_back(dbname_ + "_3", 1024 * 1024 * 1024);
|
||||
options.memtable_factory.reset(
|
||||
new SpecialSkipListFactory(KNumKeysByGenerateNewFile - 1));
|
||||
test::NewSpecialSkipListFactory(KNumKeysByGenerateNewFile - 1));
|
||||
options.compaction_style = kCompactionStyleLevel;
|
||||
options.write_buffer_size = 110 << 10; // 110KB
|
||||
options.arena_block_size = 4 << 10;
|
||||
@@ -3012,7 +3015,7 @@ TEST_P(DBCompactionTestWithParam, CompressLevelCompaction) {
|
||||
}
|
||||
Options options = CurrentOptions();
|
||||
options.memtable_factory.reset(
|
||||
new SpecialSkipListFactory(KNumKeysByGenerateNewFile - 1));
|
||||
test::NewSpecialSkipListFactory(KNumKeysByGenerateNewFile - 1));
|
||||
options.compaction_style = kCompactionStyleLevel;
|
||||
options.write_buffer_size = 110 << 10; // 110KB
|
||||
options.arena_block_size = 4 << 10;
|
||||
@@ -4815,7 +4818,8 @@ TEST_F(DBCompactionTest, CompactionLimiter) {
|
||||
options.level0_slowdown_writes_trigger = 64;
|
||||
options.level0_stop_writes_trigger = 64;
|
||||
options.max_background_jobs = kMaxBackgroundThreads; // Enough threads
|
||||
options.memtable_factory.reset(new SpecialSkipListFactory(kNumKeysPerFile));
|
||||
options.memtable_factory.reset(
|
||||
test::NewSpecialSkipListFactory(kNumKeysPerFile));
|
||||
options.max_write_buffer_number = 10; // Enough memtables
|
||||
DestroyAndReopen(options);
|
||||
|
||||
@@ -4975,7 +4979,7 @@ TEST_P(DBCompactionDirectIOTest, DirectIO) {
|
||||
options.create_if_missing = true;
|
||||
options.disable_auto_compactions = true;
|
||||
options.use_direct_io_for_flush_and_compaction = GetParam();
|
||||
options.env = new MockEnv(Env::Default());
|
||||
options.env = MockEnv::Create(Env::Default());
|
||||
Reopen(options);
|
||||
bool readahead = false;
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
@@ -5251,6 +5255,7 @@ TEST_F(DBCompactionTest, ManualCompactionMax) {
|
||||
generate_sst_func();
|
||||
uint64_t total_size = (l1_avg_size * 10) + (l2_avg_size * 100);
|
||||
opts.max_compaction_bytes = total_size / num_split;
|
||||
opts.target_file_size_base = total_size / num_split;
|
||||
Reopen(opts);
|
||||
num_compactions.store(0);
|
||||
ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr));
|
||||
@@ -5258,6 +5263,7 @@ TEST_F(DBCompactionTest, ManualCompactionMax) {
|
||||
|
||||
// very small max_compaction_bytes, it should still move forward
|
||||
opts.max_compaction_bytes = l1_avg_size / 2;
|
||||
opts.target_file_size_base = l1_avg_size / 2;
|
||||
DestroyAndReopen(opts);
|
||||
generate_sst_func();
|
||||
num_compactions.store(0);
|
||||
@@ -5271,7 +5277,8 @@ TEST_F(DBCompactionTest, ManualCompactionMax) {
|
||||
generate_sst_func();
|
||||
total_size = (l1_avg_size * 10) + (l2_avg_size * 100);
|
||||
Status s = db_->SetOptions(
|
||||
{{"max_compaction_bytes", std::to_string(total_size / num_split)}});
|
||||
{{"max_compaction_bytes", std::to_string(total_size / num_split)},
|
||||
{"target_file_size_base", std::to_string(total_size / num_split)}});
|
||||
ASSERT_OK(s);
|
||||
|
||||
num_compactions.store(0);
|
||||
@@ -5338,7 +5345,8 @@ TEST_P(DBCompactionTestWithParam, FixFileIngestionCompactionDeadlock) {
|
||||
options.level0_file_num_compaction_trigger =
|
||||
options.level0_stop_writes_trigger;
|
||||
options.max_subcompactions = max_subcompactions_;
|
||||
options.memtable_factory.reset(new SpecialSkipListFactory(kNumKeysPerFile));
|
||||
options.memtable_factory.reset(
|
||||
test::NewSpecialSkipListFactory(kNumKeysPerFile));
|
||||
DestroyAndReopen(options);
|
||||
Random rnd(301);
|
||||
|
||||
@@ -5864,7 +5872,7 @@ TEST_F(DBCompactionTest, ChangeLevelCompactRangeConflictsWithManual) {
|
||||
// `Status::Incomplete`.
|
||||
Options options = CurrentOptions();
|
||||
options.memtable_factory.reset(
|
||||
new SpecialSkipListFactory(KNumKeysByGenerateNewFile - 1));
|
||||
test::NewSpecialSkipListFactory(KNumKeysByGenerateNewFile - 1));
|
||||
options.level0_file_num_compaction_trigger = 2;
|
||||
options.num_levels = 3;
|
||||
Reopen(options);
|
||||
@@ -5952,7 +5960,7 @@ TEST_F(DBCompactionTest, ChangeLevelErrorPathTest) {
|
||||
// succeeds
|
||||
Options options = CurrentOptions();
|
||||
options.memtable_factory.reset(
|
||||
new SpecialSkipListFactory(KNumKeysByGenerateNewFile - 1));
|
||||
test::NewSpecialSkipListFactory(KNumKeysByGenerateNewFile - 1));
|
||||
options.level0_file_num_compaction_trigger = 2;
|
||||
options.num_levels = 3;
|
||||
Reopen(options);
|
||||
@@ -6812,6 +6820,49 @@ TEST_F(DBCompactionTest, FIFOWarm) {
|
||||
Destroy(options);
|
||||
}
|
||||
|
||||
TEST_F(DBCompactionTest,
|
||||
DisableManualCompactionDoesNotWaitForDrainingAutomaticCompaction) {
|
||||
// When `CompactRangeOptions::exclusive_manual_compaction == true`, we wait
|
||||
// for automatic compactions to drain before starting the manual compaction.
|
||||
// This test verifies `DisableManualCompaction()` can cancel such a compaction
|
||||
// without waiting for the drain to complete.
|
||||
const int kNumL0Files = 4;
|
||||
|
||||
// Enforces manual compaction enters wait loop due to pending automatic
|
||||
// compaction.
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency(
|
||||
{{"DBImpl::BGWorkCompaction", "DBImpl::RunManualCompaction:NotScheduled"},
|
||||
{"DBImpl::RunManualCompaction:WaitScheduled",
|
||||
"BackgroundCallCompaction:0"}});
|
||||
// The automatic compaction will cancel the waiting manual compaction.
|
||||
// Completing this implies the cancellation did not wait on automatic
|
||||
// compactions to finish.
|
||||
bool callback_completed = false;
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"BackgroundCallCompaction:0", [&](void* /*arg*/) {
|
||||
db_->DisableManualCompaction();
|
||||
callback_completed = true;
|
||||
});
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
Options options = CurrentOptions();
|
||||
options.level0_file_num_compaction_trigger = kNumL0Files;
|
||||
Reopen(options);
|
||||
|
||||
for (int i = 0; i < kNumL0Files; ++i) {
|
||||
ASSERT_OK(Put(Key(1), "value1"));
|
||||
ASSERT_OK(Put(Key(2), "value2"));
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
|
||||
CompactRangeOptions cro;
|
||||
cro.exclusive_manual_compaction = true;
|
||||
ASSERT_TRUE(db_->CompactRange(cro, nullptr, nullptr).IsIncomplete());
|
||||
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
ASSERT_TRUE(callback_completed);
|
||||
}
|
||||
|
||||
#endif // !defined(ROCKSDB_LITE)
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
@@ -13,9 +13,9 @@
|
||||
#if !defined(ROCKSDB_LITE)
|
||||
|
||||
#include "db/db_test_util.h"
|
||||
#include "env/mock_env.h"
|
||||
#include "port/port.h"
|
||||
#include "port/stack_trace.h"
|
||||
#include "rocksdb/env.h"
|
||||
#include "util/random.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
@@ -30,7 +30,7 @@ TEST_F(DBTestDynamicLevel, DynamicLevelMaxBytesBase) {
|
||||
return;
|
||||
}
|
||||
// Use InMemoryEnv, or it would be too slow.
|
||||
std::unique_ptr<Env> env(new MockEnv(env_));
|
||||
std::unique_ptr<Env> env(NewMemEnv(env_));
|
||||
|
||||
const int kNKeys = 1000;
|
||||
int keys[kNKeys];
|
||||
|
||||
+286
-35
@@ -6,23 +6,67 @@
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
|
||||
#include <stdint.h>
|
||||
#include <algorithm>
|
||||
#include <cinttypes>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "db/db_impl/db_impl.h"
|
||||
#include "db/job_context.h"
|
||||
#include "db/version_set.h"
|
||||
#include "file/file_util.h"
|
||||
#include "file/filename.h"
|
||||
#include "logging/logging.h"
|
||||
#include "port/port.h"
|
||||
#include "rocksdb/db.h"
|
||||
#include "rocksdb/env.h"
|
||||
#include "rocksdb/metadata.h"
|
||||
#include "rocksdb/types.h"
|
||||
#include "test_util/sync_point.h"
|
||||
#include "util/file_checksum_helper.h"
|
||||
#include "util/mutexlock.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
Status DBImpl::FlushForGetLiveFiles() {
|
||||
mutex_.AssertHeld();
|
||||
|
||||
// flush all dirty data to disk.
|
||||
Status status;
|
||||
if (immutable_db_options_.atomic_flush) {
|
||||
autovector<ColumnFamilyData*> cfds;
|
||||
SelectColumnFamiliesForAtomicFlush(&cfds);
|
||||
mutex_.Unlock();
|
||||
status =
|
||||
AtomicFlushMemTables(cfds, FlushOptions(), FlushReason::kGetLiveFiles);
|
||||
if (status.IsColumnFamilyDropped()) {
|
||||
status = Status::OK();
|
||||
}
|
||||
mutex_.Lock();
|
||||
} else {
|
||||
for (auto cfd : *versions_->GetColumnFamilySet()) {
|
||||
if (cfd->IsDropped()) {
|
||||
continue;
|
||||
}
|
||||
cfd->Ref();
|
||||
mutex_.Unlock();
|
||||
status = FlushMemTable(cfd, FlushOptions(), FlushReason::kGetLiveFiles);
|
||||
TEST_SYNC_POINT("DBImpl::GetLiveFiles:1");
|
||||
TEST_SYNC_POINT("DBImpl::GetLiveFiles:2");
|
||||
mutex_.Lock();
|
||||
cfd->UnrefAndTryDelete();
|
||||
if (!status.ok() && !status.IsColumnFamilyDropped()) {
|
||||
break;
|
||||
} else if (status.IsColumnFamilyDropped()) {
|
||||
status = Status::OK();
|
||||
}
|
||||
}
|
||||
}
|
||||
versions_->GetColumnFamilySet()->FreeDeadColumnFamilies();
|
||||
return status;
|
||||
}
|
||||
|
||||
Status DBImpl::GetLiveFiles(std::vector<std::string>& ret,
|
||||
uint64_t* manifest_file_size,
|
||||
bool flush_memtable) {
|
||||
@@ -31,39 +75,7 @@ Status DBImpl::GetLiveFiles(std::vector<std::string>& ret,
|
||||
mutex_.Lock();
|
||||
|
||||
if (flush_memtable) {
|
||||
// flush all dirty data to disk.
|
||||
Status status;
|
||||
if (immutable_db_options_.atomic_flush) {
|
||||
autovector<ColumnFamilyData*> cfds;
|
||||
SelectColumnFamiliesForAtomicFlush(&cfds);
|
||||
mutex_.Unlock();
|
||||
status = AtomicFlushMemTables(cfds, FlushOptions(),
|
||||
FlushReason::kGetLiveFiles);
|
||||
if (status.IsColumnFamilyDropped()) {
|
||||
status = Status::OK();
|
||||
}
|
||||
mutex_.Lock();
|
||||
} else {
|
||||
for (auto cfd : *versions_->GetColumnFamilySet()) {
|
||||
if (cfd->IsDropped()) {
|
||||
continue;
|
||||
}
|
||||
cfd->Ref();
|
||||
mutex_.Unlock();
|
||||
status = FlushMemTable(cfd, FlushOptions(), FlushReason::kGetLiveFiles);
|
||||
TEST_SYNC_POINT("DBImpl::GetLiveFiles:1");
|
||||
TEST_SYNC_POINT("DBImpl::GetLiveFiles:2");
|
||||
mutex_.Lock();
|
||||
cfd->UnrefAndTryDelete();
|
||||
if (!status.ok() && !status.IsColumnFamilyDropped()) {
|
||||
break;
|
||||
} else if (status.IsColumnFamilyDropped()) {
|
||||
status = Status::OK();
|
||||
}
|
||||
}
|
||||
}
|
||||
versions_->GetColumnFamilySet()->FreeDeadColumnFamilies();
|
||||
|
||||
Status status = FlushForGetLiveFiles();
|
||||
if (!status.ok()) {
|
||||
mutex_.Unlock();
|
||||
ROCKS_LOG_ERROR(immutable_db_options_.info_log, "Cannot Flush data %s\n",
|
||||
@@ -154,6 +166,245 @@ Status DBImpl::GetCurrentWalFile(std::unique_ptr<LogFile>* current_log_file) {
|
||||
|
||||
return wal_manager_.GetLiveWalFile(current_logfile_number, current_log_file);
|
||||
}
|
||||
|
||||
Status DBImpl::GetLiveFilesStorageInfo(
|
||||
const LiveFilesStorageInfoOptions& opts,
|
||||
std::vector<LiveFileStorageInfo>* files) {
|
||||
// To avoid returning partial results, only move to ouput on success
|
||||
assert(files);
|
||||
files->clear();
|
||||
std::vector<LiveFileStorageInfo> results;
|
||||
|
||||
// NOTE: This implementation was largely migrated from Checkpoint.
|
||||
|
||||
Status s;
|
||||
VectorLogPtr live_wal_files;
|
||||
bool flush_memtable = true;
|
||||
if (!immutable_db_options_.allow_2pc) {
|
||||
if (opts.wal_size_for_flush == port::kMaxUint64) {
|
||||
flush_memtable = false;
|
||||
} else if (opts.wal_size_for_flush > 0) {
|
||||
// If out standing log files are small, we skip the flush.
|
||||
s = GetSortedWalFiles(live_wal_files);
|
||||
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
|
||||
// Don't flush column families if total log size is smaller than
|
||||
// log_size_for_flush. We copy the log files instead.
|
||||
// We may be able to cover 2PC case too.
|
||||
uint64_t total_wal_size = 0;
|
||||
for (auto& wal : live_wal_files) {
|
||||
total_wal_size += wal->SizeFileBytes();
|
||||
}
|
||||
if (total_wal_size < opts.wal_size_for_flush) {
|
||||
flush_memtable = false;
|
||||
}
|
||||
live_wal_files.clear();
|
||||
}
|
||||
}
|
||||
|
||||
// This is a modified version of GetLiveFiles, to get access to more
|
||||
// metadata.
|
||||
mutex_.Lock();
|
||||
if (flush_memtable) {
|
||||
Status status = FlushForGetLiveFiles();
|
||||
if (!status.ok()) {
|
||||
mutex_.Unlock();
|
||||
ROCKS_LOG_ERROR(immutable_db_options_.info_log, "Cannot Flush data %s\n",
|
||||
status.ToString().c_str());
|
||||
return status;
|
||||
}
|
||||
}
|
||||
|
||||
// Make a set of all of the live table and blob files
|
||||
for (auto cfd : *versions_->GetColumnFamilySet()) {
|
||||
if (cfd->IsDropped()) {
|
||||
continue;
|
||||
}
|
||||
VersionStorageInfo& vsi = *cfd->current()->storage_info();
|
||||
auto& cf_paths = cfd->ioptions()->cf_paths;
|
||||
|
||||
auto GetDir = [&](size_t path_id) {
|
||||
// Matching TableFileName() behavior
|
||||
if (path_id >= cf_paths.size()) {
|
||||
assert(false);
|
||||
return cf_paths.back().path;
|
||||
} else {
|
||||
return cf_paths[path_id].path;
|
||||
}
|
||||
};
|
||||
|
||||
for (int level = 0; level < vsi.num_levels(); ++level) {
|
||||
const auto& level_files = vsi.LevelFiles(level);
|
||||
for (const auto& meta : level_files) {
|
||||
assert(meta);
|
||||
|
||||
results.emplace_back();
|
||||
LiveFileStorageInfo& info = results.back();
|
||||
|
||||
info.relative_filename = MakeTableFileName(meta->fd.GetNumber());
|
||||
info.directory = GetDir(meta->fd.GetPathId());
|
||||
info.file_number = meta->fd.GetNumber();
|
||||
info.file_type = kTableFile;
|
||||
info.size = meta->fd.GetFileSize();
|
||||
if (opts.include_checksum_info) {
|
||||
info.file_checksum_func_name = meta->file_checksum_func_name;
|
||||
info.file_checksum = meta->file_checksum;
|
||||
if (info.file_checksum_func_name.empty()) {
|
||||
info.file_checksum_func_name = kUnknownFileChecksumFuncName;
|
||||
info.file_checksum = kUnknownFileChecksum;
|
||||
}
|
||||
}
|
||||
info.temperature = meta->temperature;
|
||||
}
|
||||
}
|
||||
const auto& blob_files = vsi.GetBlobFiles();
|
||||
for (const auto& pair : blob_files) {
|
||||
const auto& meta = pair.second;
|
||||
assert(meta);
|
||||
|
||||
results.emplace_back();
|
||||
LiveFileStorageInfo& info = results.back();
|
||||
|
||||
info.relative_filename = BlobFileName(meta->GetBlobFileNumber());
|
||||
info.directory = GetName(); // TODO?: support db_paths/cf_paths
|
||||
info.file_number = meta->GetBlobFileNumber();
|
||||
info.file_type = kBlobFile;
|
||||
info.size = meta->GetBlobFileSize();
|
||||
if (opts.include_checksum_info) {
|
||||
info.file_checksum_func_name = meta->GetChecksumMethod();
|
||||
info.file_checksum = meta->GetChecksumValue();
|
||||
if (info.file_checksum_func_name.empty()) {
|
||||
info.file_checksum_func_name = kUnknownFileChecksumFuncName;
|
||||
info.file_checksum = kUnknownFileChecksum;
|
||||
}
|
||||
}
|
||||
// TODO?: info.temperature
|
||||
}
|
||||
}
|
||||
|
||||
// Capture some final info before releasing mutex
|
||||
const uint64_t manifest_number = versions_->manifest_file_number();
|
||||
const uint64_t manifest_size = versions_->manifest_file_size();
|
||||
const uint64_t options_number = versions_->options_file_number();
|
||||
const uint64_t options_size = versions_->options_file_size_;
|
||||
const uint64_t min_log_num = MinLogNumberToKeep();
|
||||
|
||||
mutex_.Unlock();
|
||||
|
||||
std::string manifest_fname = DescriptorFileName(manifest_number);
|
||||
{ // MANIFEST
|
||||
results.emplace_back();
|
||||
LiveFileStorageInfo& info = results.back();
|
||||
|
||||
info.relative_filename = manifest_fname;
|
||||
info.directory = GetName();
|
||||
info.file_number = manifest_number;
|
||||
info.file_type = kDescriptorFile;
|
||||
info.size = manifest_size;
|
||||
info.trim_to_size = true;
|
||||
if (opts.include_checksum_info) {
|
||||
info.file_checksum_func_name = kUnknownFileChecksumFuncName;
|
||||
info.file_checksum = kUnknownFileChecksum;
|
||||
}
|
||||
}
|
||||
|
||||
{ // CURRENT
|
||||
results.emplace_back();
|
||||
LiveFileStorageInfo& info = results.back();
|
||||
|
||||
info.relative_filename = kCurrentFileName;
|
||||
info.directory = GetName();
|
||||
info.file_type = kCurrentFile;
|
||||
// CURRENT could be replaced so we have to record the contents we want
|
||||
// for it
|
||||
info.replacement_contents = manifest_fname + "\n";
|
||||
info.size = manifest_fname.size() + 1;
|
||||
if (opts.include_checksum_info) {
|
||||
info.file_checksum_func_name = kUnknownFileChecksumFuncName;
|
||||
info.file_checksum = kUnknownFileChecksum;
|
||||
}
|
||||
}
|
||||
|
||||
// The OPTIONS file number is zero in read-write mode when OPTIONS file
|
||||
// writing failed and the DB was configured with
|
||||
// `fail_if_options_file_error == false`. In read-only mode the OPTIONS file
|
||||
// number is zero when no OPTIONS file exist at all. In those cases we do not
|
||||
// record any OPTIONS file in the live file list.
|
||||
if (options_number != 0) {
|
||||
results.emplace_back();
|
||||
LiveFileStorageInfo& info = results.back();
|
||||
|
||||
info.relative_filename = OptionsFileName(options_number);
|
||||
info.directory = GetName();
|
||||
info.file_number = options_number;
|
||||
info.file_type = kOptionsFile;
|
||||
info.size = options_size;
|
||||
if (opts.include_checksum_info) {
|
||||
info.file_checksum_func_name = kUnknownFileChecksumFuncName;
|
||||
info.file_checksum = kUnknownFileChecksum;
|
||||
}
|
||||
}
|
||||
|
||||
// Some legacy testing stuff TODO: carefully clean up obsolete parts
|
||||
TEST_SYNC_POINT("CheckpointImpl::CreateCheckpoint:FlushDone");
|
||||
|
||||
TEST_SYNC_POINT("CheckpointImpl::CreateCheckpoint:SavedLiveFiles1");
|
||||
TEST_SYNC_POINT("CheckpointImpl::CreateCheckpoint:SavedLiveFiles2");
|
||||
|
||||
if (s.ok()) {
|
||||
s = FlushWAL(false /* sync */);
|
||||
}
|
||||
|
||||
TEST_SYNC_POINT("CheckpointImpl::CreateCustomCheckpoint:AfterGetLive1");
|
||||
TEST_SYNC_POINT("CheckpointImpl::CreateCustomCheckpoint:AfterGetLive2");
|
||||
|
||||
// if we have more than one column family, we need to also get WAL files
|
||||
if (s.ok()) {
|
||||
s = GetSortedWalFiles(live_wal_files);
|
||||
}
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
|
||||
size_t wal_size = live_wal_files.size();
|
||||
|
||||
ROCKS_LOG_INFO(immutable_db_options_.info_log,
|
||||
"Number of log files %" ROCKSDB_PRIszt, live_wal_files.size());
|
||||
|
||||
// Link WAL files. Copy exact size of last one because it is the only one
|
||||
// that has changes after the last flush.
|
||||
auto wal_dir = immutable_db_options_.GetWalDir();
|
||||
for (size_t i = 0; s.ok() && i < wal_size; ++i) {
|
||||
if ((live_wal_files[i]->Type() == kAliveLogFile) &&
|
||||
(!flush_memtable || live_wal_files[i]->LogNumber() >= min_log_num)) {
|
||||
results.emplace_back();
|
||||
LiveFileStorageInfo& info = results.back();
|
||||
auto f = live_wal_files[i]->PathName();
|
||||
assert(!f.empty() && f[0] == '/');
|
||||
info.relative_filename = f.substr(1);
|
||||
info.directory = wal_dir;
|
||||
info.file_number = live_wal_files[i]->LogNumber();
|
||||
info.file_type = kWalFile;
|
||||
info.size = live_wal_files[i]->SizeFileBytes();
|
||||
// Only last should need to be trimmed
|
||||
info.trim_to_size = (i + 1 == wal_size);
|
||||
if (opts.include_checksum_info) {
|
||||
info.file_checksum_func_name = kUnknownFileChecksumFuncName;
|
||||
info.file_checksum = kUnknownFileChecksum;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (s.ok()) {
|
||||
// Only move output on success
|
||||
*files = std::move(results);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
#endif // ROCKSDB_LITE
|
||||
+120
-3
@@ -18,6 +18,7 @@
|
||||
#include "port/stack_trace.h"
|
||||
#include "rocksdb/utilities/transaction_db.h"
|
||||
#include "test_util/sync_point.h"
|
||||
#include "test_util/testutil.h"
|
||||
#include "util/cast_util.h"
|
||||
#include "util/mutexlock.h"
|
||||
#include "utilities/fault_injection_env.h"
|
||||
@@ -139,7 +140,7 @@ TEST_F(DBFlushTest, FlushInLowPriThreadPool) {
|
||||
// scheduled in the low-pri (compaction) thread pool.
|
||||
Options options = CurrentOptions();
|
||||
options.level0_file_num_compaction_trigger = 4;
|
||||
options.memtable_factory.reset(new SpecialSkipListFactory(1));
|
||||
options.memtable_factory.reset(test::NewSpecialSkipListFactory(1));
|
||||
Reopen(options);
|
||||
env_->SetBackgroundThreads(0, Env::HIGH);
|
||||
|
||||
@@ -1380,7 +1381,7 @@ TEST_P(DBFlushDirectIOTest, DirectIO) {
|
||||
options.disable_auto_compactions = true;
|
||||
options.max_background_flushes = 2;
|
||||
options.use_direct_io_for_flush_and_compaction = GetParam();
|
||||
options.env = new MockEnv(Env::Default());
|
||||
options.env = MockEnv::Create(Env::Default());
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"BuildTable:create_file", [&](void* arg) {
|
||||
bool* use_direct_writes = static_cast<bool*>(arg);
|
||||
@@ -2320,7 +2321,7 @@ TEST_P(DBAtomicFlushTest, TriggerFlushAndClose) {
|
||||
options.create_if_missing = true;
|
||||
options.atomic_flush = atomic_flush;
|
||||
options.memtable_factory.reset(
|
||||
new SpecialSkipListFactory(kNumKeysTriggerFlush));
|
||||
test::NewSpecialSkipListFactory(kNumKeysTriggerFlush));
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
|
||||
for (int i = 0; i != kNumKeysTriggerFlush; ++i) {
|
||||
@@ -2439,6 +2440,122 @@ TEST_P(DBAtomicFlushTest, RollbackAfterFailToInstallResults) {
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
}
|
||||
|
||||
// In atomic flush, concurrent bg flush threads commit to the MANIFEST in
|
||||
// serial, in the order of their picked memtables for each column family.
|
||||
// Only when a bg flush thread finds out that its memtables are the earliest
|
||||
// unflushed ones for all the included column families will this bg flush
|
||||
// thread continue to commit to MANIFEST.
|
||||
// This unit test uses sync point to coordinate the execution of two bg threads
|
||||
// executing the same sequence of functions. The interleaving are as follows.
|
||||
// time bg1 bg2
|
||||
// | pick memtables to flush
|
||||
// | flush memtables cf1_m1, cf2_m1
|
||||
// | join MANIFEST write queue
|
||||
// | pick memtabls to flush
|
||||
// | flush memtables cf1_(m1+1)
|
||||
// | join MANIFEST write queue
|
||||
// | wait to write MANIFEST
|
||||
// | write MANIFEST
|
||||
// | IO error
|
||||
// | detect IO error and stop waiting
|
||||
// V
|
||||
TEST_P(DBAtomicFlushTest, BgThreadNoWaitAfterManifestError) {
|
||||
bool atomic_flush = GetParam();
|
||||
if (!atomic_flush) {
|
||||
return;
|
||||
}
|
||||
auto fault_injection_env = std::make_shared<FaultInjectionTestEnv>(env_);
|
||||
Options options = GetDefaultOptions();
|
||||
options.create_if_missing = true;
|
||||
options.atomic_flush = true;
|
||||
options.env = fault_injection_env.get();
|
||||
// Set a larger value than default so that RocksDB can schedule concurrent
|
||||
// background flush threads.
|
||||
options.max_background_jobs = 8;
|
||||
options.max_write_buffer_number = 8;
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
|
||||
assert(2 == handles_.size());
|
||||
|
||||
WriteOptions write_opts;
|
||||
write_opts.disableWAL = true;
|
||||
|
||||
ASSERT_OK(Put(0, "a", "v_0_a", write_opts));
|
||||
ASSERT_OK(Put(1, "a", "v_1_a", write_opts));
|
||||
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
|
||||
SyncPoint::GetInstance()->LoadDependency({
|
||||
{"BgFlushThr2:WaitToCommit", "BgFlushThr1:BeforeWriteManifest"},
|
||||
});
|
||||
|
||||
std::thread::id bg_flush_thr1, bg_flush_thr2;
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::BackgroundCallFlush:start", [&](void*) {
|
||||
if (bg_flush_thr1 == std::thread::id()) {
|
||||
bg_flush_thr1 = std::this_thread::get_id();
|
||||
} else if (bg_flush_thr2 == std::thread::id()) {
|
||||
bg_flush_thr2 = std::this_thread::get_id();
|
||||
}
|
||||
});
|
||||
|
||||
int called = 0;
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::AtomicFlushMemTablesToOutputFiles:WaitToCommit", [&](void* arg) {
|
||||
if (std::this_thread::get_id() == bg_flush_thr2) {
|
||||
const auto* ptr = reinterpret_cast<std::pair<Status, bool>*>(arg);
|
||||
assert(ptr);
|
||||
if (0 == called) {
|
||||
// When bg flush thread 2 reaches here for the first time.
|
||||
ASSERT_OK(ptr->first);
|
||||
ASSERT_TRUE(ptr->second);
|
||||
} else if (1 == called) {
|
||||
// When bg flush thread 2 reaches here for the second time.
|
||||
ASSERT_TRUE(ptr->first.IsIOError());
|
||||
ASSERT_FALSE(ptr->second);
|
||||
}
|
||||
++called;
|
||||
TEST_SYNC_POINT("BgFlushThr2:WaitToCommit");
|
||||
}
|
||||
});
|
||||
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"VersionSet::ProcessManifestWrites:BeforeWriteLastVersionEdit:0",
|
||||
[&](void*) {
|
||||
if (std::this_thread::get_id() == bg_flush_thr1) {
|
||||
TEST_SYNC_POINT("BgFlushThr1:BeforeWriteManifest");
|
||||
}
|
||||
});
|
||||
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"VersionSet::LogAndApply:WriteManifest", [&](void*) {
|
||||
if (std::this_thread::get_id() != bg_flush_thr1) {
|
||||
return;
|
||||
}
|
||||
ASSERT_OK(db_->Put(write_opts, "b", "v_1_b"));
|
||||
|
||||
FlushOptions flush_opts;
|
||||
flush_opts.wait = false;
|
||||
std::vector<ColumnFamilyHandle*> cfhs(1, db_->DefaultColumnFamily());
|
||||
ASSERT_OK(dbfull()->Flush(flush_opts, cfhs));
|
||||
});
|
||||
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"VersionSet::ProcessManifestWrites:AfterSyncManifest", [&](void* arg) {
|
||||
auto* ptr = reinterpret_cast<IOStatus*>(arg);
|
||||
assert(ptr);
|
||||
*ptr = IOStatus::IOError("Injected failure");
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
ASSERT_TRUE(dbfull()->Flush(FlushOptions(), handles_).IsIOError());
|
||||
|
||||
Close();
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(DBFlushDirectIOTest, DBFlushDirectIOTest,
|
||||
testing::Bool());
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
#include "db/db_impl/db_impl.h"
|
||||
#include "db/version_set.h"
|
||||
#include "logging/logging.h"
|
||||
#include "table/get_context.h"
|
||||
#include "util/cast_util.h"
|
||||
|
||||
|
||||
+78
-68
@@ -53,6 +53,7 @@
|
||||
#include "db/version_set.h"
|
||||
#include "db/write_batch_internal.h"
|
||||
#include "db/write_callback.h"
|
||||
#include "env/unique_id_gen.h"
|
||||
#include "file/file_util.h"
|
||||
#include "file/filename.h"
|
||||
#include "file/random_access_file_reader.h"
|
||||
@@ -60,8 +61,6 @@
|
||||
#include "logging/auto_roll_logger.h"
|
||||
#include "logging/log_buffer.h"
|
||||
#include "logging/logging.h"
|
||||
#include "memtable/hash_linklist_rep.h"
|
||||
#include "memtable/hash_skiplist_rep.h"
|
||||
#include "monitoring/in_memory_stats_history.h"
|
||||
#include "monitoring/instrumented_mutex.h"
|
||||
#include "monitoring/iostats_context_imp.h"
|
||||
@@ -93,6 +92,7 @@
|
||||
#include "table/sst_file_dumper.h"
|
||||
#include "table/table_builder.h"
|
||||
#include "table/two_level_iterator.h"
|
||||
#include "table/unique_id_impl.h"
|
||||
#include "test_util/sync_point.h"
|
||||
#include "trace_replay/trace_replay.h"
|
||||
#include "util/autovector.h"
|
||||
@@ -238,7 +238,8 @@ DBImpl::DBImpl(const DBOptions& options, const std::string& dbname,
|
||||
closed_(false),
|
||||
atomic_flush_install_cv_(&mutex_),
|
||||
blob_callback_(immutable_db_options_.sst_file_manager.get(), &mutex_,
|
||||
&error_handler_) {
|
||||
&error_handler_, &event_logger_,
|
||||
immutable_db_options_.listeners, dbname_) {
|
||||
// !batch_per_trx_ implies seq_per_batch_ because it is only unset for
|
||||
// WriteUnprepared, which should use seq_per_batch_.
|
||||
assert(batch_per_txn_ || seq_per_batch_);
|
||||
@@ -718,9 +719,11 @@ Status DBImpl::CloseHelper() {
|
||||
Status DBImpl::CloseImpl() { return CloseHelper(); }
|
||||
|
||||
DBImpl::~DBImpl() {
|
||||
InstrumentedMutexLock closing_lock_guard(&closing_mutex_);
|
||||
if (!closed_) {
|
||||
closed_ = true;
|
||||
CloseHelper().PermitUncheckedError();
|
||||
closing_status_ = CloseHelper();
|
||||
closing_status_.PermitUncheckedError();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -958,8 +961,13 @@ void DBImpl::DumpStats() {
|
||||
// Release DB mutex for gathering cache entry stats. Pass over all
|
||||
// column families for this first so that other stats are dumped
|
||||
// near-atomically.
|
||||
InstrumentedMutexUnlock u(&mutex_);
|
||||
cfd->internal_stats()->CollectCacheEntryStats(/*foreground=*/false);
|
||||
// Get a ref before unlocking
|
||||
cfd->Ref();
|
||||
{
|
||||
InstrumentedMutexUnlock u(&mutex_);
|
||||
cfd->internal_stats()->CollectCacheEntryStats(/*foreground=*/false);
|
||||
}
|
||||
cfd->UnrefAndTryDelete();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2157,7 +2165,7 @@ bool DBImpl::MultiCFSnapshot(
|
||||
// consecutive retries, it means the write rate is very high. In that case
|
||||
// its probably ok to take the mutex on the 3rd try so we can succeed for
|
||||
// sure
|
||||
static const int num_retries = 3;
|
||||
constexpr int num_retries = 3;
|
||||
for (int i = 0; i < num_retries; ++i) {
|
||||
last_try = (i == num_retries - 1);
|
||||
bool retry = false;
|
||||
@@ -2187,8 +2195,9 @@ bool DBImpl::MultiCFSnapshot(
|
||||
*snapshot = versions_->LastPublishedSequence();
|
||||
}
|
||||
} else {
|
||||
*snapshot = reinterpret_cast<const SnapshotImpl*>(read_options.snapshot)
|
||||
->number_;
|
||||
*snapshot =
|
||||
static_cast_with_check<const SnapshotImpl>(read_options.snapshot)
|
||||
->number_;
|
||||
}
|
||||
for (auto cf_iter = cf_list->begin(); cf_iter != cf_list->end();
|
||||
++cf_iter) {
|
||||
@@ -2389,17 +2398,9 @@ void DBImpl::PrepareMultiGetKeys(
|
||||
autovector<KeyContext*, MultiGetContext::MAX_BATCH_SIZE>* sorted_keys) {
|
||||
if (sorted_input) {
|
||||
#ifndef NDEBUG
|
||||
CompareKeyContext key_context_less;
|
||||
|
||||
for (size_t index = 1; index < sorted_keys->size(); ++index) {
|
||||
const KeyContext* const lhs = (*sorted_keys)[index - 1];
|
||||
const KeyContext* const rhs = (*sorted_keys)[index];
|
||||
|
||||
// lhs should be <= rhs, or in other words, rhs should NOT be < lhs
|
||||
assert(!key_context_less(rhs, lhs));
|
||||
}
|
||||
assert(std::is_sorted(sorted_keys->begin(), sorted_keys->end(),
|
||||
CompareKeyContext()));
|
||||
#endif
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -3110,7 +3111,7 @@ SnapshotImpl* DBImpl::GetSnapshotImpl(bool is_write_conflict_boundary,
|
||||
}
|
||||
|
||||
namespace {
|
||||
typedef autovector<ColumnFamilyData*, 2> CfdList;
|
||||
using CfdList = autovector<ColumnFamilyData*, 2>;
|
||||
bool CfdListContains(const CfdList& list, ColumnFamilyData* cfd) {
|
||||
for (const ColumnFamilyData* t : list) {
|
||||
if (t == cfd) {
|
||||
@@ -3933,7 +3934,8 @@ Status DBImpl::GetDbIdentityFromIdentityFile(std::string* identity) const {
|
||||
return s;
|
||||
}
|
||||
|
||||
// If last character is '\n' remove it from identity
|
||||
// If last character is '\n' remove it from identity. (Old implementations
|
||||
// of Env::GenerateUniqueId() would include a trailing '\n'.)
|
||||
if (identity->size() > 0 && identity->back() == '\n') {
|
||||
identity->pop_back();
|
||||
}
|
||||
@@ -3945,31 +3947,19 @@ Status DBImpl::GetDbSessionId(std::string& session_id) const {
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
std::string DBImpl::GenerateDbSessionId(Env* env) {
|
||||
// GenerateUniqueId() generates an identifier that has a negligible
|
||||
// probability of being duplicated, ~128 bits of entropy
|
||||
std::string uuid = env->GenerateUniqueId();
|
||||
std::string DBImpl::GenerateDbSessionId(Env*) {
|
||||
// See SemiStructuredUniqueIdGen for its desirable properties.
|
||||
static SemiStructuredUniqueIdGen gen;
|
||||
|
||||
// Hash and reformat that down to a more compact format, 20 characters
|
||||
// in base-36 ([0-9A-Z]), which is ~103 bits of entropy, which is enough
|
||||
// to expect no collisions across a billion servers each opening DBs
|
||||
// a million times (~2^50). Benefits vs. raw unique id:
|
||||
// * Save ~ dozen bytes per SST file
|
||||
// * Shorter shared backup file names (some platforms have low limits)
|
||||
// * Visually distinct from DB id format
|
||||
uint64_t a = NPHash64(uuid.data(), uuid.size(), 1234U);
|
||||
uint64_t b = NPHash64(uuid.data(), uuid.size(), 5678U);
|
||||
std::string db_session_id;
|
||||
db_session_id.resize(20);
|
||||
static const char* const base36 = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||
size_t i = 0;
|
||||
for (; i < 10U; ++i, a /= 36U) {
|
||||
db_session_id[i] = base36[a % 36];
|
||||
uint64_t lo, hi;
|
||||
gen.GenerateNext(&hi, &lo);
|
||||
if (lo == 0) {
|
||||
// Avoid emitting session ID with lo==0, so that SST unique
|
||||
// IDs can be more easily ensured non-zero
|
||||
gen.GenerateNext(&hi, &lo);
|
||||
assert(lo != 0);
|
||||
}
|
||||
for (; i < 20U; ++i, b /= 36U) {
|
||||
db_session_id[i] = base36[b % 36];
|
||||
}
|
||||
return db_session_id;
|
||||
return EncodeSessionId(hi, lo);
|
||||
}
|
||||
|
||||
void DBImpl::SetDbSessionId() {
|
||||
@@ -4014,19 +4004,20 @@ Status DB::DestroyColumnFamilyHandle(ColumnFamilyHandle* column_family) {
|
||||
DB::~DB() {}
|
||||
|
||||
Status DBImpl::Close() {
|
||||
if (!closed_) {
|
||||
{
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
// If there is unreleased snapshot, fail the close call
|
||||
if (!snapshots_.empty()) {
|
||||
return Status::Aborted("Cannot close DB with unreleased snapshot.");
|
||||
}
|
||||
}
|
||||
|
||||
closed_ = true;
|
||||
return CloseImpl();
|
||||
InstrumentedMutexLock closing_lock_guard(&closing_mutex_);
|
||||
if (closed_) {
|
||||
return closing_status_;
|
||||
}
|
||||
return Status::OK();
|
||||
{
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
// If there is unreleased snapshot, fail the close call
|
||||
if (!snapshots_.empty()) {
|
||||
return Status::Aborted("Cannot close DB with unreleased snapshot.");
|
||||
}
|
||||
}
|
||||
closing_status_ = CloseImpl();
|
||||
closed_ = true;
|
||||
return closing_status_;
|
||||
}
|
||||
|
||||
Status DB::ListColumnFamilies(const DBOptions& db_options,
|
||||
@@ -4067,8 +4058,10 @@ Status DestroyDB(const std::string& dbname, const Options& options,
|
||||
del = DestroyDB(path_to_delete, options);
|
||||
} else if (type == kTableFile || type == kWalFile ||
|
||||
type == kBlobFile) {
|
||||
del = DeleteDBFile(&soptions, path_to_delete, dbname,
|
||||
/*force_bg=*/false, /*force_fg=*/!wal_in_db_path);
|
||||
del = DeleteDBFile(
|
||||
&soptions, path_to_delete, dbname,
|
||||
/*force_bg=*/false,
|
||||
/*force_fg=*/(type == kWalFile) ? !wal_in_db_path : false);
|
||||
} else {
|
||||
del = env->DeleteFile(path_to_delete);
|
||||
}
|
||||
@@ -4290,11 +4283,16 @@ Status DBImpl::RenameTempFileToOptionsFile(const std::string& file_name) {
|
||||
uint64_t options_file_number = versions_->NewFileNumber();
|
||||
std::string options_file_name =
|
||||
OptionsFileName(GetName(), options_file_number);
|
||||
// Retry if the file name happen to conflict with an existing one.
|
||||
s = GetEnv()->RenameFile(file_name, options_file_name);
|
||||
uint64_t options_file_size = 0;
|
||||
s = GetEnv()->GetFileSize(file_name, &options_file_size);
|
||||
if (s.ok()) {
|
||||
// Retry if the file name happen to conflict with an existing one.
|
||||
s = GetEnv()->RenameFile(file_name, options_file_name);
|
||||
}
|
||||
if (s.ok()) {
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
versions_->options_file_number_ = options_file_number;
|
||||
versions_->options_file_size_ = options_file_size;
|
||||
}
|
||||
|
||||
if (0 == disable_delete_obsolete_files_) {
|
||||
@@ -4564,7 +4562,8 @@ Status DBImpl::IngestExternalFiles(
|
||||
SuperVersion* super_version = cfd->GetReferencedSuperVersion(this);
|
||||
Status es = ingestion_jobs[i].Prepare(
|
||||
args[i].external_files, args[i].files_checksums,
|
||||
args[i].files_checksum_func_names, start_file_number, super_version);
|
||||
args[i].files_checksum_func_names, args[i].file_temperature,
|
||||
start_file_number, super_version);
|
||||
// capture first error only
|
||||
if (!es.ok() && status.ok()) {
|
||||
status = es;
|
||||
@@ -4579,7 +4578,8 @@ Status DBImpl::IngestExternalFiles(
|
||||
SuperVersion* super_version = cfd->GetReferencedSuperVersion(this);
|
||||
Status es = ingestion_jobs[0].Prepare(
|
||||
args[0].external_files, args[0].files_checksums,
|
||||
args[0].files_checksum_func_names, next_file_number, super_version);
|
||||
args[0].files_checksum_func_names, args[0].file_temperature,
|
||||
next_file_number, super_version);
|
||||
if (!es.ok()) {
|
||||
status = es;
|
||||
}
|
||||
@@ -4687,14 +4687,11 @@ Status DBImpl::IngestExternalFiles(
|
||||
if (status.ok()) {
|
||||
int consumed_seqno_count =
|
||||
ingestion_jobs[0].ConsumedSequenceNumbersCount();
|
||||
#ifndef NDEBUG
|
||||
for (size_t i = 1; i != num_cfs; ++i) {
|
||||
assert(!!consumed_seqno_count ==
|
||||
!!ingestion_jobs[i].ConsumedSequenceNumbersCount());
|
||||
consumed_seqno_count +=
|
||||
ingestion_jobs[i].ConsumedSequenceNumbersCount();
|
||||
consumed_seqno_count =
|
||||
std::max(consumed_seqno_count,
|
||||
ingestion_jobs[i].ConsumedSequenceNumbersCount());
|
||||
}
|
||||
#endif
|
||||
if (consumed_seqno_count > 0) {
|
||||
const SequenceNumber last_seqno = versions_->LastSequence();
|
||||
versions_->SetLastAllocatedSequence(last_seqno + consumed_seqno_count);
|
||||
@@ -4936,6 +4933,11 @@ Status DBImpl::VerifyChecksum(const ReadOptions& read_options) {
|
||||
|
||||
Status DBImpl::VerifyChecksumInternal(const ReadOptions& read_options,
|
||||
bool use_file_checksum) {
|
||||
// `bytes_read` stat is enabled based on compile-time support and cannot
|
||||
// be dynamically toggled. So we do not need to worry about `PerfLevel`
|
||||
// here, unlike many other `IOStatsContext` / `PerfContext` stats.
|
||||
uint64_t prev_bytes_read = IOSTATS(bytes_read);
|
||||
|
||||
Status s;
|
||||
|
||||
if (use_file_checksum) {
|
||||
@@ -4990,6 +4992,9 @@ Status DBImpl::VerifyChecksumInternal(const ReadOptions& read_options,
|
||||
s = ROCKSDB_NAMESPACE::VerifySstFileChecksum(opts, file_options_,
|
||||
read_options, fname);
|
||||
}
|
||||
RecordTick(stats_, VERIFY_CHECKSUM_READ_BYTES,
|
||||
IOSTATS(bytes_read) - prev_bytes_read);
|
||||
prev_bytes_read = IOSTATS(bytes_read);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5004,6 +5009,9 @@ Status DBImpl::VerifyChecksumInternal(const ReadOptions& read_options,
|
||||
s = VerifyFullFileChecksum(meta->GetChecksumValue(),
|
||||
meta->GetChecksumMethod(), blob_file_name,
|
||||
read_options);
|
||||
RecordTick(stats_, VERIFY_CHECKSUM_READ_BYTES,
|
||||
IOSTATS(bytes_read) - prev_bytes_read);
|
||||
prev_bytes_read = IOSTATS(bytes_read);
|
||||
if (!s.ok()) {
|
||||
break;
|
||||
}
|
||||
@@ -5035,6 +5043,8 @@ Status DBImpl::VerifyChecksumInternal(const ReadOptions& read_options,
|
||||
cfd->UnrefAndTryDelete();
|
||||
}
|
||||
}
|
||||
RecordTick(stats_, VERIFY_CHECKSUM_READ_BYTES,
|
||||
IOSTATS(bytes_read) - prev_bytes_read);
|
||||
return s;
|
||||
}
|
||||
|
||||
|
||||
+16
-5
@@ -22,7 +22,6 @@
|
||||
#include "db/column_family.h"
|
||||
#include "db/compaction/compaction_iterator.h"
|
||||
#include "db/compaction/compaction_job.h"
|
||||
#include "db/dbformat.h"
|
||||
#include "db/error_handler.h"
|
||||
#include "db/event_helpers.h"
|
||||
#include "db/external_sst_file_ingestion_job.h"
|
||||
@@ -406,6 +405,10 @@ class DBImpl : public DB {
|
||||
virtual Status GetLiveFilesChecksumInfo(
|
||||
FileChecksumList* checksum_list) override;
|
||||
|
||||
virtual Status GetLiveFilesStorageInfo(
|
||||
const LiveFilesStorageInfoOptions& opts,
|
||||
std::vector<LiveFileStorageInfo>* files) override;
|
||||
|
||||
// Obtains the meta data of the specified column family of the DB.
|
||||
// TODO(yhchiang): output parameter is placed in the end in this codebase.
|
||||
virtual void GetColumnFamilyMetaData(ColumnFamilyHandle* column_family,
|
||||
@@ -1099,8 +1102,10 @@ class DBImpl : public DB {
|
||||
// Called from WriteBufferManager. This function changes the state_
|
||||
// to State::RUNNING indicating the stall is cleared and DB can proceed.
|
||||
void Signal() override {
|
||||
MutexLock lock(&state_mutex_);
|
||||
state_ = State::RUNNING;
|
||||
{
|
||||
MutexLock lock(&state_mutex_);
|
||||
state_ = State::RUNNING;
|
||||
}
|
||||
state_cv_.Signal();
|
||||
}
|
||||
|
||||
@@ -1230,6 +1235,8 @@ class DBImpl : public DB {
|
||||
#ifndef ROCKSDB_LITE
|
||||
void NotifyOnExternalFileIngested(
|
||||
ColumnFamilyData* cfd, const ExternalSstFileIngestionJob& ingestion_job);
|
||||
|
||||
Status FlushForGetLiveFiles();
|
||||
#endif // !ROCKSDB_LITE
|
||||
|
||||
void NewThreadStatusCfInfo(ColumnFamilyData* cfd) const;
|
||||
@@ -1753,7 +1760,7 @@ class DBImpl : public DB {
|
||||
// specified value, this flush request is considered to have completed its
|
||||
// work of flushing this column family. After completing the work for all
|
||||
// column families in this request, this flush is considered complete.
|
||||
typedef std::vector<std::pair<ColumnFamilyData*, uint64_t>> FlushRequest;
|
||||
using FlushRequest = std::vector<std::pair<ColumnFamilyData*, uint64_t>>;
|
||||
|
||||
void GenerateFlushRequest(const autovector<ColumnFamilyData*>& cfds,
|
||||
FlushRequest* req);
|
||||
@@ -2015,7 +2022,7 @@ class DBImpl : public DB {
|
||||
// accessed from the same write_thread_ without any locks. With
|
||||
// two_write_queues writes, where it can be updated in different threads,
|
||||
// read and writes are protected by log_write_mutex_ instead. This is to avoid
|
||||
// expesnive mutex_ lock during WAL write, which update log_empty_.
|
||||
// expensive mutex_ lock during WAL write, which update log_empty_.
|
||||
bool log_empty_;
|
||||
|
||||
ColumnFamilyHandleImpl* persist_stats_cf_handle_;
|
||||
@@ -2283,6 +2290,10 @@ class DBImpl : public DB {
|
||||
|
||||
// Flag to check whether Close() has been called on this DB
|
||||
bool closed_;
|
||||
// save the closing status, for re-calling the close()
|
||||
Status closing_status_;
|
||||
// mutex for DB::Close()
|
||||
InstrumentedMutex closing_mutex_;
|
||||
|
||||
// Conditional variable to coordinate installation of atomic flush results.
|
||||
// With atomic flush, each bg thread installs the result of flushing multiple
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#include "db/error_handler.h"
|
||||
#include "db/event_helpers.h"
|
||||
#include "file/sst_file_manager_impl.h"
|
||||
#include "logging/logging.h"
|
||||
#include "monitoring/iostats_context_imp.h"
|
||||
#include "monitoring/perf_context_imp.h"
|
||||
#include "monitoring/thread_status_updater.h"
|
||||
@@ -558,7 +559,15 @@ Status DBImpl::AtomicFlushMemTablesToOutputFiles(
|
||||
}
|
||||
|
||||
if (s.ok()) {
|
||||
auto wait_to_install_func = [&]() {
|
||||
const auto wait_to_install_func =
|
||||
[&]() -> std::pair<Status, bool /*continue to wait*/> {
|
||||
if (!versions_->io_status().ok()) {
|
||||
// Something went wrong elsewhere, we cannot count on waiting for our
|
||||
// turn to write/sync to MANIFEST or CURRENT. Just return.
|
||||
return std::make_pair(versions_->io_status(), false);
|
||||
} else if (shutting_down_.load(std::memory_order_acquire)) {
|
||||
return std::make_pair(Status::ShutdownInProgress(), false);
|
||||
}
|
||||
bool ready = true;
|
||||
for (size_t i = 0; i != cfds.size(); ++i) {
|
||||
const auto& mems = jobs[i]->GetMemTables();
|
||||
@@ -582,18 +591,40 @@ Status DBImpl::AtomicFlushMemTablesToOutputFiles(
|
||||
break;
|
||||
}
|
||||
}
|
||||
return ready;
|
||||
return std::make_pair(Status::OK(), !ready);
|
||||
};
|
||||
|
||||
bool resuming_from_bg_err = error_handler_.IsDBStopped();
|
||||
while ((!error_handler_.IsDBStopped() ||
|
||||
error_handler_.GetRecoveryError().ok()) &&
|
||||
!wait_to_install_func()) {
|
||||
while ((!resuming_from_bg_err || error_handler_.GetRecoveryError().ok())) {
|
||||
std::pair<Status, bool> res = wait_to_install_func();
|
||||
|
||||
TEST_SYNC_POINT_CALLBACK(
|
||||
"DBImpl::AtomicFlushMemTablesToOutputFiles:WaitToCommit", &res);
|
||||
|
||||
if (!res.first.ok()) {
|
||||
s = res.first;
|
||||
break;
|
||||
} else if (!res.second) {
|
||||
break;
|
||||
}
|
||||
atomic_flush_install_cv_.Wait();
|
||||
|
||||
resuming_from_bg_err = error_handler_.IsDBStopped();
|
||||
}
|
||||
|
||||
s = resuming_from_bg_err ? error_handler_.GetRecoveryError()
|
||||
: error_handler_.GetBGError();
|
||||
if (!resuming_from_bg_err) {
|
||||
// If not resuming from bg err, then we determine future action based on
|
||||
// whether we hit background error.
|
||||
if (s.ok()) {
|
||||
s = error_handler_.GetBGError();
|
||||
}
|
||||
} else if (s.ok()) {
|
||||
// If resuming from bg err, we still rely on wait_to_install_func()'s
|
||||
// result to determine future action. If wait_to_install_func() returns
|
||||
// non-ok already, then we should not proceed to flush result
|
||||
// installation.
|
||||
s = error_handler_.GetRecoveryError();
|
||||
}
|
||||
}
|
||||
|
||||
if (s.ok()) {
|
||||
@@ -1353,10 +1384,15 @@ Status DBImpl::CompactFilesImpl(
|
||||
|
||||
if (output_file_names != nullptr) {
|
||||
for (const auto& newf : c->edit()->GetNewFiles()) {
|
||||
(*output_file_names)
|
||||
.push_back(TableFileName(c->immutable_options()->cf_paths,
|
||||
newf.second.fd.GetNumber(),
|
||||
newf.second.fd.GetPathId()));
|
||||
output_file_names->push_back(TableFileName(
|
||||
c->immutable_options()->cf_paths, newf.second.fd.GetNumber(),
|
||||
newf.second.fd.GetPathId()));
|
||||
}
|
||||
|
||||
for (const auto& blob_file : c->edit()->GetBlobFileAdditions()) {
|
||||
output_file_names->push_back(
|
||||
BlobFileName(c->immutable_options()->cf_paths.front().path,
|
||||
blob_file.GetBlobFileNumber()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1709,8 +1745,11 @@ Status DBImpl::RunManualCompaction(
|
||||
|
||||
// When a manual compaction arrives, temporarily disable scheduling of
|
||||
// non-manual compactions and wait until the number of scheduled compaction
|
||||
// jobs drops to zero. This is needed to ensure that this manual compaction
|
||||
// can compact any range of keys/files.
|
||||
// jobs drops to zero. This used to be needed to ensure that this manual
|
||||
// compaction can compact any range of keys/files. Now it is optional
|
||||
// (see `CompactRangeOptions::exclusive_manual_compaction`). The use case for
|
||||
// `exclusive_manual_compaction=true` (the default) is unclear beyond not
|
||||
// trusting the new code.
|
||||
//
|
||||
// HasPendingManualCompaction() is true when at least one thread is inside
|
||||
// RunManualCompaction(), i.e. during that time no other compaction will
|
||||
@@ -1724,8 +1763,20 @@ Status DBImpl::RunManualCompaction(
|
||||
AddManualCompaction(&manual);
|
||||
TEST_SYNC_POINT_CALLBACK("DBImpl::RunManualCompaction:NotScheduled", &mutex_);
|
||||
if (exclusive) {
|
||||
// Limitation: there's no way to wake up the below loop when user sets
|
||||
// `*manual.canceled`. So `CompactRangeOptions::exclusive_manual_compaction`
|
||||
// and `CompactRangeOptions::canceled` might not work well together.
|
||||
while (bg_bottom_compaction_scheduled_ > 0 ||
|
||||
bg_compaction_scheduled_ > 0) {
|
||||
if (manual_compaction_paused_ > 0 ||
|
||||
(manual.canceled != nullptr && *manual.canceled == true)) {
|
||||
// Pretend the error came from compaction so the below cleanup/error
|
||||
// handling code can process it.
|
||||
manual.done = true;
|
||||
manual.status =
|
||||
Status::Incomplete(Status::SubCode::kManualCompactionPaused);
|
||||
break;
|
||||
}
|
||||
TEST_SYNC_POINT("DBImpl::RunManualCompaction:WaitScheduled");
|
||||
ROCKS_LOG_INFO(
|
||||
immutable_db_options_.info_log,
|
||||
@@ -2218,6 +2269,10 @@ Status DBImpl::EnableAutoCompaction(
|
||||
void DBImpl::DisableManualCompaction() {
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
manual_compaction_paused_.fetch_add(1, std::memory_order_release);
|
||||
|
||||
// Wake up manual compactions waiting to start.
|
||||
bg_cv_.SignalAll();
|
||||
|
||||
// Wait for any pending manual compactions to finish (typically through
|
||||
// failing with `Status::Incomplete`) prior to returning. This way we are
|
||||
// guaranteed no pending manual compaction will commit while manual
|
||||
@@ -2628,7 +2683,7 @@ void DBImpl::BackgroundCallFlush(Env::Priority thread_pri) {
|
||||
bool made_progress = false;
|
||||
JobContext job_context(next_job_id_.fetch_add(1), true);
|
||||
|
||||
TEST_SYNC_POINT("DBImpl::BackgroundCallFlush:start");
|
||||
TEST_SYNC_POINT_CALLBACK("DBImpl::BackgroundCallFlush:start", nullptr);
|
||||
|
||||
LogBuffer log_buffer(InfoLogLevel::INFO_LEVEL,
|
||||
immutable_db_options_.info_log.get());
|
||||
@@ -3461,6 +3516,30 @@ void DBImpl::BuildCompactionJobInfo(
|
||||
compaction_job_info->output_file_infos.push_back(CompactionFileInfo{
|
||||
newf.first, file_number, meta.oldest_blob_file_number});
|
||||
}
|
||||
compaction_job_info->blob_compression_type =
|
||||
c->mutable_cf_options()->blob_compression_type;
|
||||
|
||||
// Update BlobFilesInfo.
|
||||
for (const auto& blob_file : c->edit()->GetBlobFileAdditions()) {
|
||||
BlobFileAdditionInfo blob_file_addition_info(
|
||||
BlobFileName(c->immutable_options()->cf_paths.front().path,
|
||||
blob_file.GetBlobFileNumber()) /*blob_file_path*/,
|
||||
blob_file.GetBlobFileNumber(), blob_file.GetTotalBlobCount(),
|
||||
blob_file.GetTotalBlobBytes());
|
||||
compaction_job_info->blob_file_addition_infos.emplace_back(
|
||||
std::move(blob_file_addition_info));
|
||||
}
|
||||
|
||||
// Update BlobFilesGarbageInfo.
|
||||
for (const auto& blob_file : c->edit()->GetBlobFileGarbages()) {
|
||||
BlobFileGarbageInfo blob_file_garbage_info(
|
||||
BlobFileName(c->immutable_options()->cf_paths.front().path,
|
||||
blob_file.GetBlobFileNumber()) /*blob_file_path*/,
|
||||
blob_file.GetBlobFileNumber(), blob_file.GetGarbageBlobCount(),
|
||||
blob_file.GetGarbageBlobBytes());
|
||||
compaction_job_info->blob_file_garbage_infos.emplace_back(
|
||||
std::move(blob_file_garbage_info));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#include "db/db_impl/db_impl.h"
|
||||
#include "db/job_context.h"
|
||||
#include "db/version_set.h"
|
||||
#include "logging/logging.h"
|
||||
#include "rocksdb/status.h"
|
||||
#include "util/cast_util.h"
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
#include "file/file_util.h"
|
||||
#include "file/filename.h"
|
||||
#include "file/sst_file_manager_impl.h"
|
||||
#include "logging/logging.h"
|
||||
#include "port/port.h"
|
||||
#include "util/autovector.h"
|
||||
|
||||
@@ -318,7 +319,7 @@ bool CompareCandidateFile(const JobContext::CandidateFileInfo& first,
|
||||
return (first.file_path > second.file_path);
|
||||
}
|
||||
}
|
||||
}; // namespace
|
||||
} // namespace
|
||||
|
||||
// Delete obsolete files and log status and information of file deletion
|
||||
void DBImpl::DeleteObsoleteFileImpl(int job_id, const std::string& fname,
|
||||
@@ -329,9 +330,11 @@ void DBImpl::DeleteObsoleteFileImpl(int job_id, const std::string& fname,
|
||||
|
||||
Status file_deletion_status;
|
||||
if (type == kTableFile || type == kBlobFile || type == kWalFile) {
|
||||
file_deletion_status =
|
||||
DeleteDBFile(&immutable_db_options_, fname, path_to_sync,
|
||||
/*force_bg=*/false, /*force_fg=*/!wal_in_db_path_);
|
||||
// Rate limit WAL deletion only if its in the DB dir
|
||||
file_deletion_status = DeleteDBFile(
|
||||
&immutable_db_options_, fname, path_to_sync,
|
||||
/*force_bg=*/false,
|
||||
/*force_fg=*/(type == kWalFile) ? !wal_in_db_path_ : false);
|
||||
} else {
|
||||
file_deletion_status = env_->DeleteFile(fname);
|
||||
}
|
||||
@@ -360,6 +363,11 @@ void DBImpl::DeleteObsoleteFileImpl(int job_id, const std::string& fname,
|
||||
&event_logger_, job_id, number, fname, file_deletion_status, GetName(),
|
||||
immutable_db_options_.listeners);
|
||||
}
|
||||
if (type == kBlobFile) {
|
||||
EventHelpers::LogAndNotifyBlobFileDeletion(
|
||||
&event_logger_, immutable_db_options_.listeners, job_id, number, fname,
|
||||
file_deletion_status, GetName());
|
||||
}
|
||||
}
|
||||
|
||||
// Diffs the files listed in filenames and those that do not
|
||||
|
||||
@@ -13,9 +13,11 @@
|
||||
#include "db/error_handler.h"
|
||||
#include "db/periodic_work_scheduler.h"
|
||||
#include "env/composite_env_wrapper.h"
|
||||
#include "file/filename.h"
|
||||
#include "file/read_write_util.h"
|
||||
#include "file/sst_file_manager_impl.h"
|
||||
#include "file/writable_file_writer.h"
|
||||
#include "logging/logging.h"
|
||||
#include "monitoring/persistent_stats_history.h"
|
||||
#include "options/options_helper.h"
|
||||
#include "rocksdb/table.h"
|
||||
@@ -676,6 +678,12 @@ Status DBImpl::Recover(
|
||||
}
|
||||
}
|
||||
versions_->options_file_number_ = options_file_number;
|
||||
uint64_t options_file_size = 0;
|
||||
if (options_file_number > 0) {
|
||||
s = env_->GetFileSize(OptionsFileName(GetName(), options_file_number),
|
||||
&options_file_size);
|
||||
}
|
||||
versions_->options_file_size_ = options_file_size;
|
||||
}
|
||||
}
|
||||
return s;
|
||||
@@ -1424,8 +1432,9 @@ Status DBImpl::WriteLevel0TableForRecovery(int job_id, ColumnFamilyData* cfd,
|
||||
std::move(range_del_iters), &meta, &blob_file_additions,
|
||||
snapshot_seqs, earliest_write_conflict_snapshot, snapshot_checker,
|
||||
paranoid_file_checks, cfd->internal_stats(), &io_s, io_tracer_,
|
||||
&event_logger_, job_id, Env::IO_HIGH, nullptr /* table_properties */,
|
||||
write_hint, nullptr /*full_history_ts_low*/, &blob_callback_);
|
||||
BlobFileCreationReason::kRecovery, &event_logger_, job_id,
|
||||
Env::IO_HIGH, nullptr /* table_properties */, write_hint,
|
||||
nullptr /*full_history_ts_low*/, &blob_callback_);
|
||||
LogFlush(immutable_db_options_.info_log);
|
||||
ROCKS_LOG_DEBUG(immutable_db_options_.info_log,
|
||||
"[%s] [WriteLevel0TableForRecovery]"
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include "db/db_impl/db_impl.h"
|
||||
#include "db/db_iter.h"
|
||||
#include "db/merge_context.h"
|
||||
#include "logging/logging.h"
|
||||
#include "monitoring/perf_context_imp.h"
|
||||
#include "util/cast_util.h"
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include "db/arena_wrapped_db_iter.h"
|
||||
#include "db/merge_context.h"
|
||||
#include "logging/auto_roll_logger.h"
|
||||
#include "logging/logging.h"
|
||||
#include "monitoring/perf_context_imp.h"
|
||||
#include "rocksdb/configurable.h"
|
||||
#include "util/cast_util.h"
|
||||
@@ -415,7 +416,7 @@ Iterator* DBImplSecondary::NewIterator(const ReadOptions& read_options,
|
||||
return NewErrorIterator(
|
||||
Status::NotSupported("snapshot not supported in secondary mode"));
|
||||
} else {
|
||||
auto snapshot = versions_->LastSequence();
|
||||
SequenceNumber snapshot(kMaxSequenceNumber);
|
||||
result = NewIteratorImpl(read_options, cfd, snapshot, read_callback);
|
||||
}
|
||||
return result;
|
||||
@@ -423,14 +424,19 @@ Iterator* DBImplSecondary::NewIterator(const ReadOptions& read_options,
|
||||
|
||||
ArenaWrappedDBIter* DBImplSecondary::NewIteratorImpl(
|
||||
const ReadOptions& read_options, ColumnFamilyData* cfd,
|
||||
SequenceNumber snapshot, ReadCallback* read_callback) {
|
||||
SequenceNumber snapshot, ReadCallback* read_callback,
|
||||
bool expose_blob_index, bool allow_refresh) {
|
||||
assert(nullptr != cfd);
|
||||
SuperVersion* super_version = cfd->GetReferencedSuperVersion(this);
|
||||
assert(snapshot == kMaxSequenceNumber);
|
||||
snapshot = versions_->LastSequence();
|
||||
assert(snapshot != kMaxSequenceNumber);
|
||||
auto db_iter = NewArenaWrappedDbIterator(
|
||||
env_, read_options, *cfd->ioptions(), super_version->mutable_cf_options,
|
||||
super_version->current, snapshot,
|
||||
super_version->mutable_cf_options.max_sequential_skip_in_iterations,
|
||||
super_version->version_number, read_callback);
|
||||
super_version->version_number, read_callback, this, cfd,
|
||||
expose_blob_index, read_options.snapshot ? false : allow_refresh);
|
||||
auto internal_iter = NewInternalIterator(
|
||||
db_iter->GetReadOptions(), cfd, super_version, db_iter->GetArena(),
|
||||
db_iter->GetRangeDelAggregator(), snapshot,
|
||||
|
||||
@@ -9,7 +9,9 @@
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "db/db_impl/db_impl.h"
|
||||
#include "logging/logging.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
@@ -97,7 +99,9 @@ class DBImplSecondary : public DBImpl {
|
||||
ArenaWrappedDBIter* NewIteratorImpl(const ReadOptions& read_options,
|
||||
ColumnFamilyData* cfd,
|
||||
SequenceNumber snapshot,
|
||||
ReadCallback* read_callback);
|
||||
ReadCallback* read_callback,
|
||||
bool expose_blob_index = false,
|
||||
bool allow_refresh = true);
|
||||
|
||||
Status NewIterators(const ReadOptions& options,
|
||||
const std::vector<ColumnFamilyHandle*>& column_families,
|
||||
|
||||
+73
-39
@@ -11,6 +11,7 @@
|
||||
#include "db/db_impl/db_impl.h"
|
||||
#include "db/error_handler.h"
|
||||
#include "db/event_helpers.h"
|
||||
#include "logging/logging.h"
|
||||
#include "monitoring/perf_context_imp.h"
|
||||
#include "options/options_helper.h"
|
||||
#include "test_util/sync_point.h"
|
||||
@@ -160,8 +161,7 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options,
|
||||
RecordTick(stats_, WRITE_WITH_WAL);
|
||||
}
|
||||
|
||||
StopWatch write_sw(immutable_db_options_.clock, immutable_db_options_.stats,
|
||||
DB_WRITE);
|
||||
StopWatch write_sw(immutable_db_options_.clock, stats_, DB_WRITE);
|
||||
|
||||
write_thread_.JoinBatchGroup(&w);
|
||||
if (w.state == WriteThread::STATE_PARALLEL_MEMTABLE_WRITER) {
|
||||
@@ -471,8 +471,7 @@ Status DBImpl::PipelinedWriteImpl(const WriteOptions& write_options,
|
||||
uint64_t* log_used, uint64_t log_ref,
|
||||
bool disable_memtable, uint64_t* seq_used) {
|
||||
PERF_TIMER_GUARD(write_pre_and_post_process_time);
|
||||
StopWatch write_sw(immutable_db_options_.clock, immutable_db_options_.stats,
|
||||
DB_WRITE);
|
||||
StopWatch write_sw(immutable_db_options_.clock, stats_, DB_WRITE);
|
||||
|
||||
WriteContext write_context;
|
||||
|
||||
@@ -628,8 +627,7 @@ Status DBImpl::UnorderedWriteMemtable(const WriteOptions& write_options,
|
||||
SequenceNumber seq,
|
||||
const size_t sub_batch_cnt) {
|
||||
PERF_TIMER_GUARD(write_pre_and_post_process_time);
|
||||
StopWatch write_sw(immutable_db_options_.clock, immutable_db_options_.stats,
|
||||
DB_WRITE);
|
||||
StopWatch write_sw(immutable_db_options_.clock, stats_, DB_WRITE);
|
||||
|
||||
WriteThread::Writer w(write_options, my_batch, callback, log_ref,
|
||||
false /*disable_memtable*/);
|
||||
@@ -684,8 +682,7 @@ Status DBImpl::WriteImplWALOnly(
|
||||
WriteThread::Writer w(write_options, my_batch, callback, log_ref,
|
||||
disable_memtable, sub_batch_cnt, pre_release_callback);
|
||||
RecordTick(stats_, WRITE_WITH_WAL);
|
||||
StopWatch write_sw(immutable_db_options_.clock, immutable_db_options_.stats,
|
||||
DB_WRITE);
|
||||
StopWatch write_sw(immutable_db_options_.clock, stats_, DB_WRITE);
|
||||
|
||||
write_thread->JoinBatchGroup(&w);
|
||||
assert(w.state != WriteThread::STATE_PARALLEL_MEMTABLE_WRITER);
|
||||
@@ -1124,6 +1121,18 @@ IOStatus DBImpl::WriteToWAL(const WriteThread::WriteGroup& write_group,
|
||||
// writer thread, so no one will push to logs_,
|
||||
// - as long as other threads don't modify it, it's safe to read
|
||||
// from std::deque from multiple threads concurrently.
|
||||
//
|
||||
// Sync operation should work with locked log_write_mutex_, because:
|
||||
// when DBOptions.manual_wal_flush_ is set,
|
||||
// FlushWAL function will be invoked by another thread.
|
||||
// if without locked log_write_mutex_, the log file may get data
|
||||
// corruption
|
||||
|
||||
const bool needs_locking = manual_wal_flush_ && !two_write_queues_;
|
||||
if (UNLIKELY(needs_locking)) {
|
||||
log_write_mutex_.Lock();
|
||||
}
|
||||
|
||||
for (auto& log : logs_) {
|
||||
io_s = log.writer->file()->Sync(immutable_db_options_.use_fsync);
|
||||
if (!io_s.ok()) {
|
||||
@@ -1131,6 +1140,10 @@ IOStatus DBImpl::WriteToWAL(const WriteThread::WriteGroup& write_group,
|
||||
}
|
||||
}
|
||||
|
||||
if (UNLIKELY(needs_locking)) {
|
||||
log_write_mutex_.Unlock();
|
||||
}
|
||||
|
||||
if (io_s.ok() && need_log_dir_sync) {
|
||||
// We only sync WAL directory the first time WAL syncing is
|
||||
// requested, so that in case users never turn on WAL sync,
|
||||
@@ -1743,8 +1756,6 @@ void DBImpl::NotifyOnMemTableSealed(ColumnFamilyData* /*cfd*/,
|
||||
// two_write_queues_ is true (This is to simplify the reasoning.)
|
||||
Status DBImpl::SwitchMemtable(ColumnFamilyData* cfd, WriteContext* context) {
|
||||
mutex_.AssertHeld();
|
||||
WriteThread::Writer nonmem_w;
|
||||
std::unique_ptr<WritableFile> lfile;
|
||||
log::Writer* new_log = nullptr;
|
||||
MemTable* new_mem = nullptr;
|
||||
IOStatus io_s;
|
||||
@@ -1848,17 +1859,9 @@ Status DBImpl::SwitchMemtable(ColumnFamilyData* cfd, WriteContext* context) {
|
||||
if (!s.ok()) {
|
||||
// how do we fail if we're not creating new log?
|
||||
assert(creating_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;
|
||||
}
|
||||
delete new_mem;
|
||||
delete new_log;
|
||||
context->superversion_context.new_superversion.reset();
|
||||
// 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
|
||||
if (!io_s.ok()) {
|
||||
@@ -1998,13 +2001,18 @@ Status DB::Put(const WriteOptions& opt, ColumnFamilyHandle* column_family,
|
||||
size_t ts_sz = ts->size();
|
||||
assert(column_family->GetComparator());
|
||||
assert(ts_sz == column_family->GetComparator()->timestamp_size());
|
||||
WriteBatch batch(key.size() + ts_sz + value.size() + 24, /*max_bytes=*/0,
|
||||
ts_sz);
|
||||
Status s = batch.Put(column_family, key, value);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
WriteBatch batch;
|
||||
Status s;
|
||||
if (key.data() + key.size() == ts->data()) {
|
||||
Slice key_with_ts = Slice(key.data(), key.size() + ts_sz);
|
||||
s = batch.Put(column_family, key_with_ts, value);
|
||||
} else {
|
||||
std::array<Slice, 2> key_with_ts_slices{{key, *ts}};
|
||||
SliceParts key_with_ts(key_with_ts_slices.data(), 2);
|
||||
std::array<Slice, 1> value_slices{{value}};
|
||||
SliceParts values(value_slices.data(), 1);
|
||||
s = batch.Put(column_family, key_with_ts, values);
|
||||
}
|
||||
s = batch.AssignTimestamp(*ts);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
@@ -2023,17 +2031,19 @@ Status DB::Delete(const WriteOptions& opt, ColumnFamilyHandle* column_family,
|
||||
}
|
||||
const Slice* ts = opt.timestamp;
|
||||
assert(ts != nullptr);
|
||||
const size_t ts_sz = ts->size();
|
||||
constexpr size_t kKeyAndValueLenSize = 11;
|
||||
constexpr size_t kWriteBatchOverhead =
|
||||
WriteBatchInternal::kHeader + sizeof(ValueType) + kKeyAndValueLenSize;
|
||||
WriteBatch batch(key.size() + ts_sz + kWriteBatchOverhead, /*max_bytes=*/0,
|
||||
ts_sz);
|
||||
Status s = batch.Delete(column_family, key);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
size_t ts_sz = ts->size();
|
||||
assert(column_family->GetComparator());
|
||||
assert(ts_sz == column_family->GetComparator()->timestamp_size());
|
||||
WriteBatch batch;
|
||||
Status s;
|
||||
if (key.data() + key.size() == ts->data()) {
|
||||
Slice key_with_ts = Slice(key.data(), key.size() + ts_sz);
|
||||
s = batch.Delete(column_family, key_with_ts);
|
||||
} else {
|
||||
std::array<Slice, 2> key_with_ts_slices{{key, *ts}};
|
||||
SliceParts key_with_ts(key_with_ts_slices.data(), 2);
|
||||
s = batch.Delete(column_family, key_with_ts);
|
||||
}
|
||||
s = batch.AssignTimestamp(*ts);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
@@ -2042,12 +2052,36 @@ Status DB::Delete(const WriteOptions& opt, ColumnFamilyHandle* column_family,
|
||||
|
||||
Status DB::SingleDelete(const WriteOptions& opt,
|
||||
ColumnFamilyHandle* column_family, const Slice& key) {
|
||||
Status s;
|
||||
if (opt.timestamp == nullptr) {
|
||||
WriteBatch batch;
|
||||
s = batch.SingleDelete(column_family, key);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
s = Write(opt, &batch);
|
||||
return s;
|
||||
}
|
||||
|
||||
const Slice* ts = opt.timestamp;
|
||||
assert(ts != nullptr);
|
||||
size_t ts_sz = ts->size();
|
||||
assert(column_family->GetComparator());
|
||||
assert(ts_sz == column_family->GetComparator()->timestamp_size());
|
||||
WriteBatch batch;
|
||||
Status s = batch.SingleDelete(column_family, key);
|
||||
if (key.data() + key.size() == ts->data()) {
|
||||
Slice key_with_ts = Slice(key.data(), key.size() + ts_sz);
|
||||
s = batch.SingleDelete(column_family, key_with_ts);
|
||||
} else {
|
||||
std::array<Slice, 2> key_with_ts_slices{{key, *ts}};
|
||||
SliceParts key_with_ts(key_with_ts_slices.data(), 2);
|
||||
s = batch.SingleDelete(column_family, key_with_ts);
|
||||
}
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
return Write(opt, &batch);
|
||||
s = Write(opt, &batch);
|
||||
return s;
|
||||
}
|
||||
|
||||
Status DB::DeleteRange(const WriteOptions& opt,
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
|
||||
#include "db/db_test_util.h"
|
||||
#include "port/stack_trace.h"
|
||||
#include "test_util/testutil.h"
|
||||
#include "util/random.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
@@ -269,7 +270,7 @@ TEST_F(DBIOFailureTest, FlushSstRangeSyncError) {
|
||||
options.writable_file_max_buffer_size = 128 * 1024;
|
||||
options.bytes_per_sync = 128 * 1024;
|
||||
options.level0_file_num_compaction_trigger = 4;
|
||||
options.memtable_factory.reset(new SpecialSkipListFactory(10));
|
||||
options.memtable_factory.reset(test::NewSpecialSkipListFactory(10));
|
||||
BlockBasedTableOptions table_options;
|
||||
table_options.filter_policy.reset(NewBloomFilterPolicy(10));
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
@@ -396,7 +397,7 @@ TEST_F(DBIOFailureTest, FlushSstCloseError) {
|
||||
options.error_if_exists = false;
|
||||
options.paranoid_checks = true;
|
||||
options.level0_file_num_compaction_trigger = 4;
|
||||
options.memtable_factory.reset(new SpecialSkipListFactory(2));
|
||||
options.memtable_factory.reset(test::NewSpecialSkipListFactory(2));
|
||||
|
||||
DestroyAndReopen(options);
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
@@ -492,7 +493,7 @@ TEST_F(DBIOFailureTest, FlushSstSyncError) {
|
||||
options.paranoid_checks = true;
|
||||
options.use_fsync = false;
|
||||
options.level0_file_num_compaction_trigger = 4;
|
||||
options.memtable_factory.reset(new SpecialSkipListFactory(2));
|
||||
options.memtable_factory.reset(test::NewSpecialSkipListFactory(2));
|
||||
|
||||
DestroyAndReopen(options);
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
#include <string>
|
||||
|
||||
#include "db/db_impl/db_impl.h"
|
||||
#include "db/dbformat.h"
|
||||
#include "db/range_del_aggregator.h"
|
||||
#include "memory/arena.h"
|
||||
#include "options/cf_options.h"
|
||||
|
||||
+9
-7
@@ -2588,10 +2588,11 @@ TEST_F(DBIteratorTest, DBIteratorTestDifferentialSnapshots) {
|
||||
std::string values[4] = {"1c", "2c", "3c", "4b"};
|
||||
int i = 0;
|
||||
for (db_iter->SeekToFirst(); db_iter->Valid(); db_iter->Next()) {
|
||||
FullKey fkey;
|
||||
ParseFullKey(db_iter->key(), &fkey);
|
||||
ParsedInternalKey fkey;
|
||||
ASSERT_OK(
|
||||
ParseInternalKey(db_iter->key(), &fkey, true /* log_err_key */));
|
||||
ASSERT_EQ(user_keys[i], fkey.user_key.ToString());
|
||||
ASSERT_EQ(EntryType::kEntryPut, fkey.type);
|
||||
ASSERT_EQ(kTypeValue, fkey.type);
|
||||
ASSERT_EQ(seqnums[i], fkey.sequence);
|
||||
ASSERT_EQ(values[i], db_iter->value().ToString());
|
||||
i++;
|
||||
@@ -2620,14 +2621,15 @@ TEST_F(DBIteratorTest, DBIteratorTestDifferentialSnapshots) {
|
||||
nullptr /* read_callback */));
|
||||
// Expecting InternalKeys in [5,8] range with correct type
|
||||
int seqnums[4] = {5,8,11,13};
|
||||
EntryType key_types[4] = {EntryType::kEntryDelete,EntryType::kEntryDelete,
|
||||
EntryType::kEntryDelete,EntryType::kEntryPut};
|
||||
ValueType key_types[4] = {kTypeDeletion, kTypeDeletion, kTypeDeletion,
|
||||
kTypeValue};
|
||||
std::string user_keys[4] = {"1","2","3","4"};
|
||||
std::string values[4] = {"", "", "", "4b"};
|
||||
int i = 0;
|
||||
for (db_iter->SeekToFirst(); db_iter->Valid(); db_iter->Next()) {
|
||||
FullKey fkey;
|
||||
ParseFullKey(db_iter->key(), &fkey);
|
||||
ParsedInternalKey fkey;
|
||||
ASSERT_OK(
|
||||
ParseInternalKey(db_iter->key(), &fkey, true /* log_err_key */));
|
||||
ASSERT_EQ(user_keys[i], fkey.user_key.ToString());
|
||||
ASSERT_EQ(key_types[i], fkey.type);
|
||||
ASSERT_EQ(seqnums[i], fkey.sequence);
|
||||
|
||||
@@ -34,10 +34,9 @@ class DbKvChecksumTest
|
||||
corrupt_byte_addend_ = std::get<1>(GetParam());
|
||||
}
|
||||
|
||||
std::pair<WriteBatch, Status> GetWriteBatch(size_t ts_sz,
|
||||
ColumnFamilyHandle* cf_handle) {
|
||||
std::pair<WriteBatch, Status> GetWriteBatch(ColumnFamilyHandle* cf_handle) {
|
||||
Status s;
|
||||
WriteBatch wb(0 /* reserved_bytes */, 0 /* max_bytes */, ts_sz,
|
||||
WriteBatch wb(0 /* reserved_bytes */, 0 /* max_bytes */,
|
||||
8 /* protection_bytes_per_entry */);
|
||||
switch (op_type_) {
|
||||
case WriteBatchOpType::kPut:
|
||||
@@ -151,8 +150,7 @@ TEST_P(DbKvChecksumTest, MemTableAddCorrupted) {
|
||||
Reopen(options);
|
||||
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
auto batch_and_status =
|
||||
GetWriteBatch(0 /* ts_sz */, nullptr /* cf_handle */);
|
||||
auto batch_and_status = GetWriteBatch(nullptr /* cf_handle */);
|
||||
ASSERT_OK(batch_and_status.second);
|
||||
ASSERT_TRUE(
|
||||
db_->Write(WriteOptions(), &batch_and_status.first).IsCorruption());
|
||||
@@ -183,7 +181,7 @@ TEST_P(DbKvChecksumTest, MemTableAddWithColumnFamilyCorrupted) {
|
||||
ReopenWithColumnFamilies({kDefaultColumnFamilyName, "pikachu"}, options);
|
||||
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
auto batch_and_status = GetWriteBatch(0 /* ts_sz */, handles_[1]);
|
||||
auto batch_and_status = GetWriteBatch(handles_[1]);
|
||||
ASSERT_OK(batch_and_status.second);
|
||||
ASSERT_TRUE(
|
||||
db_->Write(WriteOptions(), &batch_and_status.first).IsCorruption());
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#include "rocksdb/perf_context.h"
|
||||
#include "rocksdb/perf_level.h"
|
||||
#include "rocksdb/table.h"
|
||||
#include "test_util/mock_time_env.h"
|
||||
#include "util/random.h"
|
||||
#include "util/string_util.h"
|
||||
|
||||
@@ -28,6 +29,25 @@ class DBPropertiesTest : public DBTestBase {
|
||||
public:
|
||||
DBPropertiesTest()
|
||||
: DBTestBase("db_properties_test", /*env_do_fsync=*/false) {}
|
||||
|
||||
void AssertDbStats(const std::map<std::string, std::string>& db_stats,
|
||||
double expected_uptime, int expected_user_bytes_written,
|
||||
int expected_wal_bytes_written,
|
||||
int expected_user_writes_by_self,
|
||||
int expected_user_writes_with_wal) {
|
||||
ASSERT_EQ(std::to_string(expected_uptime), db_stats.at("db.uptime"));
|
||||
ASSERT_EQ(std::to_string(expected_wal_bytes_written),
|
||||
db_stats.at("db.wal_bytes_written"));
|
||||
ASSERT_EQ("0", db_stats.at("db.wal_syncs"));
|
||||
ASSERT_EQ(std::to_string(expected_user_bytes_written),
|
||||
db_stats.at("db.user_bytes_written"));
|
||||
ASSERT_EQ("0", db_stats.at("db.user_writes_by_other"));
|
||||
ASSERT_EQ(std::to_string(expected_user_writes_by_self),
|
||||
db_stats.at("db.user_writes_by_self"));
|
||||
ASSERT_EQ(std::to_string(expected_user_writes_with_wal),
|
||||
db_stats.at("db.user_writes_with_wal"));
|
||||
ASSERT_EQ("0", db_stats.at("db.user_write_stall_micros"));
|
||||
}
|
||||
};
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
@@ -1895,7 +1915,80 @@ TEST_F(DBPropertiesTest, BlockCacheProperties) {
|
||||
ASSERT_EQ(0, value);
|
||||
}
|
||||
|
||||
TEST_F(DBPropertiesTest, GetMapPropertyDbStats) {
|
||||
auto mock_clock = std::make_shared<MockSystemClock>(env_->GetSystemClock());
|
||||
CompositeEnvWrapper env(env_, mock_clock);
|
||||
|
||||
Options opts = CurrentOptions();
|
||||
opts.env = &env;
|
||||
Reopen(opts);
|
||||
|
||||
{
|
||||
std::map<std::string, std::string> db_stats;
|
||||
ASSERT_TRUE(db_->GetMapProperty(DB::Properties::kDBStats, &db_stats));
|
||||
AssertDbStats(db_stats, 0.0 /* expected_uptime */,
|
||||
0 /* expected_user_bytes_written */,
|
||||
0 /* expected_wal_bytes_written */,
|
||||
0 /* expected_user_writes_by_self */,
|
||||
0 /* expected_user_writes_with_wal */);
|
||||
}
|
||||
|
||||
{
|
||||
mock_clock->SleepForMicroseconds(1500000);
|
||||
|
||||
std::map<std::string, std::string> db_stats;
|
||||
ASSERT_TRUE(db_->GetMapProperty(DB::Properties::kDBStats, &db_stats));
|
||||
AssertDbStats(db_stats, 1.5 /* expected_uptime */,
|
||||
0 /* expected_user_bytes_written */,
|
||||
0 /* expected_wal_bytes_written */,
|
||||
0 /* expected_user_writes_by_self */,
|
||||
0 /* expected_user_writes_with_wal */);
|
||||
}
|
||||
|
||||
int expected_user_bytes_written = 0;
|
||||
{
|
||||
// Write with WAL disabled.
|
||||
WriteOptions write_opts;
|
||||
write_opts.disableWAL = true;
|
||||
|
||||
WriteBatch batch;
|
||||
ASSERT_OK(batch.Put("key", "val"));
|
||||
expected_user_bytes_written += static_cast<int>(batch.GetDataSize());
|
||||
|
||||
ASSERT_OK(db_->Write(write_opts, &batch));
|
||||
|
||||
std::map<std::string, std::string> db_stats;
|
||||
ASSERT_TRUE(db_->GetMapProperty(DB::Properties::kDBStats, &db_stats));
|
||||
AssertDbStats(db_stats, 1.5 /* expected_uptime */,
|
||||
expected_user_bytes_written,
|
||||
0 /* expected_wal_bytes_written */,
|
||||
1 /* expected_user_writes_by_self */,
|
||||
0 /* expected_user_writes_with_wal */);
|
||||
}
|
||||
|
||||
int expected_wal_bytes_written = 0;
|
||||
{
|
||||
// Write with WAL enabled.
|
||||
WriteBatch batch;
|
||||
ASSERT_OK(batch.Delete("key"));
|
||||
expected_user_bytes_written += static_cast<int>(batch.GetDataSize());
|
||||
expected_wal_bytes_written += static_cast<int>(batch.GetDataSize());
|
||||
|
||||
ASSERT_OK(db_->Write(WriteOptions(), &batch));
|
||||
|
||||
std::map<std::string, std::string> db_stats;
|
||||
ASSERT_TRUE(db_->GetMapProperty(DB::Properties::kDBStats, &db_stats));
|
||||
AssertDbStats(db_stats, 1.5 /* expected_uptime */,
|
||||
expected_user_bytes_written, expected_wal_bytes_written,
|
||||
2 /* expected_user_writes_by_self */,
|
||||
1 /* expected_user_writes_with_wal */);
|
||||
}
|
||||
|
||||
Close();
|
||||
}
|
||||
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
+40
-19
@@ -118,7 +118,7 @@ TEST_F(DBRangeDelTest, CompactionOutputFilesExactlyFilled) {
|
||||
Options options = CurrentOptions();
|
||||
options.disable_auto_compactions = true;
|
||||
options.level0_file_num_compaction_trigger = kNumFiles;
|
||||
options.memtable_factory.reset(new SpecialSkipListFactory(kNumPerFile));
|
||||
options.memtable_factory.reset(test::NewSpecialSkipListFactory(kNumPerFile));
|
||||
options.num_levels = 2;
|
||||
options.target_file_size_base = kFileBytes;
|
||||
BlockBasedTableOptions table_options;
|
||||
@@ -166,20 +166,39 @@ TEST_F(DBRangeDelTest, MaxCompactionBytesCutsOutputFiles) {
|
||||
opts.disable_auto_compactions = true;
|
||||
opts.level0_file_num_compaction_trigger = kNumFiles;
|
||||
opts.max_compaction_bytes = kNumPerFile * kBytesPerVal;
|
||||
opts.memtable_factory.reset(new SpecialSkipListFactory(kNumPerFile));
|
||||
opts.memtable_factory.reset(test::NewSpecialSkipListFactory(kNumPerFile));
|
||||
// Want max_compaction_bytes to trigger the end of compaction output file, not
|
||||
// target_file_size_base, so make the latter much bigger
|
||||
opts.target_file_size_base = 100 * opts.max_compaction_bytes;
|
||||
// opts.target_file_size_base = 100 * opts.max_compaction_bytes;
|
||||
opts.target_file_size_base = 1;
|
||||
DestroyAndReopen(opts);
|
||||
|
||||
// snapshot protects range tombstone from dropping due to becoming obsolete.
|
||||
const Snapshot* snapshot = db_->GetSnapshot();
|
||||
|
||||
Random rnd(301);
|
||||
|
||||
ASSERT_OK(Put(GetNumericStr(0), rnd.RandomString(kBytesPerVal)));
|
||||
ASSERT_OK(
|
||||
Put(GetNumericStr(kNumPerFile - 1), rnd.RandomString(kBytesPerVal)));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(Put(GetNumericStr(kNumPerFile), rnd.RandomString(kBytesPerVal)));
|
||||
ASSERT_OK(
|
||||
Put(GetNumericStr(kNumPerFile * 2 - 1), rnd.RandomString(kBytesPerVal)));
|
||||
ASSERT_OK(Flush());
|
||||
MoveFilesToLevel(2);
|
||||
ASSERT_EQ(0, NumTableFilesAtLevel(0));
|
||||
ASSERT_EQ(NumTableFilesAtLevel(2), 2);
|
||||
|
||||
ASSERT_OK(db_->SetOptions(
|
||||
db_->DefaultColumnFamily(),
|
||||
{{"target_file_size_base", ToString(100 * opts.max_compaction_bytes)}}));
|
||||
|
||||
// It spans the whole key-range, thus will be included in all output files
|
||||
ASSERT_OK(db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(),
|
||||
GetNumericStr(0),
|
||||
GetNumericStr(kNumFiles * kNumPerFile - 1)));
|
||||
Random rnd(301);
|
||||
|
||||
for (int i = 0; i < kNumFiles; ++i) {
|
||||
std::vector<std::string> values;
|
||||
// Write 1MB (256 values, each 4K)
|
||||
@@ -193,11 +212,11 @@ TEST_F(DBRangeDelTest, MaxCompactionBytesCutsOutputFiles) {
|
||||
ASSERT_EQ(i + 1, NumTableFilesAtLevel(0));
|
||||
}
|
||||
|
||||
ASSERT_OK(dbfull()->TEST_CompactRange(0, nullptr, nullptr, nullptr,
|
||||
true /* disallow_trivial_move */));
|
||||
ASSERT_OK(dbfull()->TEST_CompactRange(0, nullptr, nullptr,
|
||||
/*column_family=*/nullptr,
|
||||
/*disallow_trivial_move=*/true));
|
||||
ASSERT_EQ(0, NumTableFilesAtLevel(0));
|
||||
ASSERT_GE(NumTableFilesAtLevel(1), 2);
|
||||
|
||||
std::vector<std::vector<FileMetaData>> files;
|
||||
dbfull()->TEST_GetFilesMetaData(db_->DefaultColumnFamily(), &files);
|
||||
|
||||
@@ -319,7 +338,7 @@ TEST_F(DBRangeDelTest, CompactionRemovesCoveredKeys) {
|
||||
Options opts = CurrentOptions();
|
||||
opts.comparator = test::Uint64Comparator();
|
||||
opts.disable_auto_compactions = true;
|
||||
opts.memtable_factory.reset(new SpecialSkipListFactory(kNumPerFile));
|
||||
opts.memtable_factory.reset(test::NewSpecialSkipListFactory(kNumPerFile));
|
||||
opts.num_levels = 2;
|
||||
opts.statistics = CreateDBStatistics();
|
||||
DestroyAndReopen(opts);
|
||||
@@ -377,7 +396,7 @@ TEST_F(DBRangeDelTest, ValidLevelSubcompactionBoundaries) {
|
||||
options.level0_file_num_compaction_trigger = kNumFiles;
|
||||
options.max_bytes_for_level_base = 2 * kFileBytes;
|
||||
options.max_subcompactions = 4;
|
||||
options.memtable_factory.reset(new SpecialSkipListFactory(kNumPerFile));
|
||||
options.memtable_factory.reset(test::NewSpecialSkipListFactory(kNumPerFile));
|
||||
options.num_levels = 3;
|
||||
options.target_file_size_base = kFileBytes;
|
||||
options.target_file_size_multiplier = 1;
|
||||
@@ -436,7 +455,7 @@ TEST_F(DBRangeDelTest, ValidUniversalSubcompactionBoundaries) {
|
||||
options.compaction_style = kCompactionStyleUniversal;
|
||||
options.level0_file_num_compaction_trigger = kFilesPerLevel;
|
||||
options.max_subcompactions = 4;
|
||||
options.memtable_factory.reset(new SpecialSkipListFactory(kNumPerFile));
|
||||
options.memtable_factory.reset(test::NewSpecialSkipListFactory(kNumPerFile));
|
||||
options.num_levels = kNumLevels;
|
||||
options.target_file_size_base = kNumPerFile << 10;
|
||||
options.target_file_size_multiplier = 1;
|
||||
@@ -489,7 +508,7 @@ TEST_F(DBRangeDelTest, CompactionRemovesCoveredMergeOperands) {
|
||||
const int kNumPerFile = 3, kNumFiles = 3;
|
||||
Options opts = CurrentOptions();
|
||||
opts.disable_auto_compactions = true;
|
||||
opts.memtable_factory.reset(new SpecialSkipListFactory(2 * kNumPerFile));
|
||||
opts.memtable_factory.reset(test::NewSpecialSkipListFactory(2 * kNumPerFile));
|
||||
opts.merge_operator = MergeOperators::CreateUInt64AddOperator();
|
||||
opts.num_levels = 2;
|
||||
Reopen(opts);
|
||||
@@ -613,7 +632,7 @@ TEST_F(DBRangeDelTest, TableEvictedDuringScan) {
|
||||
opts.comparator = test::Uint64Comparator();
|
||||
opts.level0_file_num_compaction_trigger = 4;
|
||||
opts.level0_stop_writes_trigger = 4;
|
||||
opts.memtable_factory.reset(new SpecialSkipListFactory(1));
|
||||
opts.memtable_factory.reset(test::NewSpecialSkipListFactory(1));
|
||||
opts.num_levels = 2;
|
||||
BlockBasedTableOptions bbto;
|
||||
bbto.cache_index_and_filter_blocks = true;
|
||||
@@ -694,7 +713,7 @@ TEST_F(DBRangeDelTest, GetCoveredKeyFromImmutableMemtable) {
|
||||
// memtable can hold. It switches the active memtable to immutable (flush is
|
||||
// prevented by the above options) upon inserting an element that would
|
||||
// overflow the memtable.
|
||||
opts.memtable_factory.reset(new SpecialSkipListFactory(1));
|
||||
opts.memtable_factory.reset(test::NewSpecialSkipListFactory(1));
|
||||
DestroyAndReopen(opts);
|
||||
|
||||
ASSERT_OK(db_->Put(WriteOptions(), "key", "val"));
|
||||
@@ -759,7 +778,7 @@ TEST_F(DBRangeDelTest, GetIgnoresRangeDeletions) {
|
||||
Options opts = CurrentOptions();
|
||||
opts.max_write_buffer_number = 4;
|
||||
opts.min_write_buffer_number_to_merge = 3;
|
||||
opts.memtable_factory.reset(new SpecialSkipListFactory(1));
|
||||
opts.memtable_factory.reset(test::NewSpecialSkipListFactory(1));
|
||||
Reopen(opts);
|
||||
|
||||
ASSERT_OK(db_->Put(WriteOptions(), "sst_key", "val"));
|
||||
@@ -788,7 +807,7 @@ TEST_F(DBRangeDelTest, IteratorRemovesCoveredKeys) {
|
||||
const int kNum = 200, kRangeBegin = 50, kRangeEnd = 150, kNumPerFile = 25;
|
||||
Options opts = CurrentOptions();
|
||||
opts.comparator = test::Uint64Comparator();
|
||||
opts.memtable_factory.reset(new SpecialSkipListFactory(kNumPerFile));
|
||||
opts.memtable_factory.reset(test::NewSpecialSkipListFactory(kNumPerFile));
|
||||
DestroyAndReopen(opts);
|
||||
|
||||
// Write half of the keys before the tombstone and half after the tombstone.
|
||||
@@ -823,7 +842,7 @@ TEST_F(DBRangeDelTest, IteratorOverUserSnapshot) {
|
||||
const int kNum = 200, kRangeBegin = 50, kRangeEnd = 150, kNumPerFile = 25;
|
||||
Options opts = CurrentOptions();
|
||||
opts.comparator = test::Uint64Comparator();
|
||||
opts.memtable_factory.reset(new SpecialSkipListFactory(kNumPerFile));
|
||||
opts.memtable_factory.reset(test::NewSpecialSkipListFactory(kNumPerFile));
|
||||
DestroyAndReopen(opts);
|
||||
|
||||
const Snapshot* snapshot = nullptr;
|
||||
@@ -857,7 +876,7 @@ TEST_F(DBRangeDelTest, IteratorIgnoresRangeDeletions) {
|
||||
Options opts = CurrentOptions();
|
||||
opts.max_write_buffer_number = 4;
|
||||
opts.min_write_buffer_number_to_merge = 3;
|
||||
opts.memtable_factory.reset(new SpecialSkipListFactory(1));
|
||||
opts.memtable_factory.reset(test::NewSpecialSkipListFactory(1));
|
||||
Reopen(opts);
|
||||
|
||||
ASSERT_OK(db_->Put(WriteOptions(), "sst_key", "val"));
|
||||
@@ -1005,7 +1024,7 @@ TEST_F(DBRangeDelTest, CompactionTreatsSplitInputLevelDeletionAtomically) {
|
||||
options.compression = kNoCompression;
|
||||
options.level0_file_num_compaction_trigger = kNumFilesPerLevel;
|
||||
options.memtable_factory.reset(
|
||||
new SpecialSkipListFactory(2 /* num_entries_flush */));
|
||||
test::NewSpecialSkipListFactory(2 /* num_entries_flush */));
|
||||
options.target_file_size_base = kValueBytes;
|
||||
// i == 0: CompactFiles
|
||||
// i == 1: CompactRange
|
||||
@@ -1073,7 +1092,7 @@ TEST_F(DBRangeDelTest, RangeTombstoneEndKeyAsSstableUpperBound) {
|
||||
options.compression = kNoCompression;
|
||||
options.level0_file_num_compaction_trigger = kNumFilesPerLevel;
|
||||
options.memtable_factory.reset(
|
||||
new SpecialSkipListFactory(2 /* num_entries_flush */));
|
||||
test::NewSpecialSkipListFactory(2 /* num_entries_flush */));
|
||||
options.target_file_size_base = kValueBytes;
|
||||
options.disable_auto_compactions = true;
|
||||
|
||||
@@ -1628,6 +1647,7 @@ TEST_F(DBRangeDelTest, OverlappedTombstones) {
|
||||
const int kNumPerFile = 4, kNumFiles = 2;
|
||||
Options options = CurrentOptions();
|
||||
options.disable_auto_compactions = true;
|
||||
options.target_file_size_base = 9 * 1024;
|
||||
options.max_compaction_bytes = 9 * 1024;
|
||||
DestroyAndReopen(options);
|
||||
Random rnd(301);
|
||||
@@ -1667,6 +1687,7 @@ TEST_F(DBRangeDelTest, OverlappedKeys) {
|
||||
const int kNumPerFile = 4, kNumFiles = 2;
|
||||
Options options = CurrentOptions();
|
||||
options.disable_auto_compactions = true;
|
||||
options.target_file_size_base = 9 * 1024;
|
||||
options.max_compaction_bytes = 9 * 1024;
|
||||
DestroyAndReopen(options);
|
||||
Random rnd(301);
|
||||
|
||||
+82
-3
@@ -12,6 +12,7 @@
|
||||
#include "port/stack_trace.h"
|
||||
#include "rocksdb/utilities/transaction_db.h"
|
||||
#include "test_util/sync_point.h"
|
||||
#include "test_util/testutil.h"
|
||||
#include "utilities/fault_injection_env.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
@@ -560,6 +561,84 @@ TEST_F(DBSecondaryTest, OpenAsSecondaryWALTailing) {
|
||||
verify_db_func("new_foo_value_1", "new_bar_value");
|
||||
}
|
||||
|
||||
TEST_F(DBSecondaryTest, SecondaryTailingBug_ISSUE_8467) {
|
||||
Options options;
|
||||
options.env = env_;
|
||||
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);
|
||||
|
||||
const auto verify_db = [&](const std::string& foo_val,
|
||||
const std::string& bar_val) {
|
||||
std::string value;
|
||||
ReadOptions ropts;
|
||||
Status s = db_secondary_->Get(ropts, "foo", &value);
|
||||
ASSERT_OK(s);
|
||||
ASSERT_EQ(foo_val, value);
|
||||
|
||||
s = db_secondary_->Get(ropts, "bar", &value);
|
||||
ASSERT_OK(s);
|
||||
ASSERT_EQ(bar_val, value);
|
||||
};
|
||||
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
ASSERT_OK(db_secondary_->TryCatchUpWithPrimary());
|
||||
verify_db("foo_value2", "bar_value2");
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBSecondaryTest, RefreshIterator) {
|
||||
Options options;
|
||||
options.env = env_;
|
||||
Reopen(options);
|
||||
|
||||
Options options1;
|
||||
options1.env = env_;
|
||||
options1.max_open_files = -1;
|
||||
OpenSecondary(options1);
|
||||
|
||||
std::unique_ptr<Iterator> it(db_secondary_->NewIterator(ReadOptions()));
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
ASSERT_OK(Put("foo", "foo_value" + std::to_string(i)));
|
||||
|
||||
ASSERT_OK(db_secondary_->TryCatchUpWithPrimary());
|
||||
if (0 == i) {
|
||||
it->Seek("foo");
|
||||
ASSERT_FALSE(it->Valid());
|
||||
ASSERT_OK(it->status());
|
||||
|
||||
ASSERT_OK(it->Refresh());
|
||||
|
||||
it->Seek("foo");
|
||||
ASSERT_OK(it->status());
|
||||
ASSERT_TRUE(it->Valid());
|
||||
ASSERT_EQ("foo", it->key());
|
||||
ASSERT_EQ("foo_value0", it->value());
|
||||
} else {
|
||||
it->Seek("foo");
|
||||
ASSERT_TRUE(it->Valid());
|
||||
ASSERT_EQ("foo", it->key());
|
||||
ASSERT_EQ("foo_value" + std::to_string(i - 1), it->value());
|
||||
ASSERT_OK(it->status());
|
||||
|
||||
ASSERT_OK(it->Refresh());
|
||||
|
||||
it->Seek("foo");
|
||||
ASSERT_OK(it->status());
|
||||
ASSERT_TRUE(it->Valid());
|
||||
ASSERT_EQ("foo", it->key());
|
||||
ASSERT_EQ("foo_value" + std::to_string(i), it->value());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBSecondaryTest, OpenWithNonExistColumnFamily) {
|
||||
Options options;
|
||||
options.env = env_;
|
||||
@@ -859,7 +938,7 @@ TEST_F(DBSecondaryTest, DISABLED_SwitchWAL) {
|
||||
options.max_write_buffer_number = 4;
|
||||
options.min_write_buffer_number_to_merge = 2;
|
||||
options.memtable_factory.reset(
|
||||
new SpecialSkipListFactory(kNumKeysPerMemtable));
|
||||
test::NewSpecialSkipListFactory(kNumKeysPerMemtable));
|
||||
Reopen(options);
|
||||
|
||||
Options options1;
|
||||
@@ -914,7 +993,7 @@ TEST_F(DBSecondaryTest, DISABLED_SwitchWALMultiColumnFamilies) {
|
||||
options.max_write_buffer_number = 4;
|
||||
options.min_write_buffer_number_to_merge = 2;
|
||||
options.memtable_factory.reset(
|
||||
new SpecialSkipListFactory(kNumKeysPerMemtable));
|
||||
test::NewSpecialSkipListFactory(kNumKeysPerMemtable));
|
||||
CreateAndReopenWithCF({kCFName1}, options);
|
||||
|
||||
Options options1;
|
||||
@@ -978,7 +1057,7 @@ TEST_F(DBSecondaryTest, CatchUpAfterFlush) {
|
||||
options.max_write_buffer_number = 4;
|
||||
options.min_write_buffer_number_to_merge = 2;
|
||||
options.memtable_factory.reset(
|
||||
new SpecialSkipListFactory(kNumKeysPerMemtable));
|
||||
test::NewSpecialSkipListFactory(kNumKeysPerMemtable));
|
||||
Reopen(options);
|
||||
|
||||
Options options1;
|
||||
|
||||
+30
-3
@@ -602,7 +602,14 @@ TEST_F(DBSSTTest, DBWithSstFileManagerForBlobFilesWithGC) {
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
}
|
||||
|
||||
TEST_F(DBSSTTest, RateLimitedDelete) {
|
||||
class DBSSTTestRateLimit : public DBSSTTest,
|
||||
public ::testing::WithParamInterface<bool> {
|
||||
public:
|
||||
DBSSTTestRateLimit() : DBSSTTest() {}
|
||||
~DBSSTTestRateLimit() override {}
|
||||
};
|
||||
|
||||
TEST_P(DBSSTTestRateLimit, RateLimitedDelete) {
|
||||
Destroy(last_options_);
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency({
|
||||
{"DBSSTTest::RateLimitedDelete:1",
|
||||
@@ -640,11 +647,15 @@ TEST_F(DBSSTTest, RateLimitedDelete) {
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
bool different_wal_dir = GetParam();
|
||||
Options options = CurrentOptions();
|
||||
SetTimeElapseOnlySleepOnReopen(&options);
|
||||
options.disable_auto_compactions = true;
|
||||
options.env = env_;
|
||||
options.statistics = CreateDBStatistics();
|
||||
if (different_wal_dir) {
|
||||
options.wal_dir = alternative_wal_dir_;
|
||||
}
|
||||
|
||||
int64_t rate_bytes_per_sec = 1024 * 10; // 10 Kbs / Sec
|
||||
Status s;
|
||||
@@ -656,7 +667,9 @@ TEST_F(DBSSTTest, RateLimitedDelete) {
|
||||
sfm->delete_scheduler()->SetMaxTrashDBRatio(1.1);
|
||||
|
||||
WriteOptions wo;
|
||||
wo.disableWAL = true;
|
||||
if (!different_wal_dir) {
|
||||
wo.disableWAL = true;
|
||||
}
|
||||
Reopen(options);
|
||||
// Create 4 files in L0
|
||||
for (char v = 'a'; v <= 'd'; v++) {
|
||||
@@ -701,6 +714,9 @@ TEST_F(DBSSTTest, RateLimitedDelete) {
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(RateLimitedDelete, DBSSTTestRateLimit,
|
||||
::testing::Bool());
|
||||
|
||||
TEST_F(DBSSTTest, RateLimitedWALDelete) {
|
||||
Destroy(last_options_);
|
||||
|
||||
@@ -809,9 +825,11 @@ TEST_P(DBWALTestWithParam, WALTrashCleanupOnOpen) {
|
||||
|
||||
// Create 4 files in L0
|
||||
for (char v = 'a'; v <= 'd'; v++) {
|
||||
if (v == 'd') {
|
||||
if (v == 'c') {
|
||||
// Maximize the change that the last log file will be preserved in trash
|
||||
// before restarting the DB.
|
||||
// We have to set this on the 2nd to last file for it to delay deletion
|
||||
// on the last file. (Quirk of DeleteScheduler::BackgroundEmptyTrash())
|
||||
options.sst_file_manager->SetDeleteRateBytesPerSecond(1);
|
||||
}
|
||||
ASSERT_OK(Put("Key2", DummyString(1024, v)));
|
||||
@@ -1663,8 +1681,17 @@ TEST_F(DBSSTTest, DBWithSFMForBlobFilesAtomicFlush) {
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
#ifdef ROCKSDB_UNITTESTS_WITH_CUSTOM_OBJECTS_FROM_STATIC_LIBS
|
||||
extern "C" {
|
||||
void RegisterCustomObjects(int argc, char** argv);
|
||||
}
|
||||
#else
|
||||
void RegisterCustomObjects(int /*argc*/, char** /*argv*/) {}
|
||||
#endif // ROCKSDB_UNITTESTS_WITH_CUSTOM_OBJECTS_FROM_STATIC_LIBS
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
ROCKSDB_NAMESPACE::port::InstallStackTraceHandler();
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
RegisterCustomObjects(argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
@@ -155,6 +155,55 @@ TEST_F(DBStatisticsTest, ExcludeTickers) {
|
||||
ASSERT_GT(options.statistics->getTickerCount(BYTES_READ), 0);
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
|
||||
TEST_F(DBStatisticsTest, VerifyChecksumReadStat) {
|
||||
Options options = CurrentOptions();
|
||||
options.file_checksum_gen_factory = GetFileChecksumGenCrc32cFactory();
|
||||
options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics();
|
||||
Reopen(options);
|
||||
|
||||
// Expected to be populated regardless of `PerfLevel` in user thread
|
||||
SetPerfLevel(kDisable);
|
||||
|
||||
{
|
||||
// Scenario 0: only WAL data. Not verified so require ticker to be zero.
|
||||
ASSERT_OK(Put("foo", "value"));
|
||||
ASSERT_OK(db_->VerifyFileChecksums(ReadOptions()));
|
||||
ASSERT_OK(db_->VerifyChecksum());
|
||||
ASSERT_EQ(0,
|
||||
options.statistics->getTickerCount(VERIFY_CHECKSUM_READ_BYTES));
|
||||
}
|
||||
|
||||
// Create one SST.
|
||||
ASSERT_OK(Flush());
|
||||
std::unordered_map<std::string, uint64_t> table_files;
|
||||
uint64_t table_files_size = 0;
|
||||
GetAllDataFiles(kTableFile, &table_files, &table_files_size);
|
||||
|
||||
{
|
||||
// Scenario 1: Table verified in `VerifyFileChecksums()`. This should read
|
||||
// the whole file so we require the ticker stat exactly matches the file
|
||||
// size.
|
||||
ASSERT_OK(options.statistics->Reset());
|
||||
ASSERT_OK(db_->VerifyFileChecksums(ReadOptions()));
|
||||
ASSERT_EQ(table_files_size,
|
||||
options.statistics->getTickerCount(VERIFY_CHECKSUM_READ_BYTES));
|
||||
}
|
||||
|
||||
{
|
||||
// Scenario 2: Table verified in `VerifyChecksum()`. This opens a
|
||||
// `TableReader` to verify each block. It can involve duplicate reads of the
|
||||
// same data so we set a lower-bound only.
|
||||
ASSERT_OK(options.statistics->Reset());
|
||||
ASSERT_OK(db_->VerifyChecksum());
|
||||
ASSERT_GE(options.statistics->getTickerCount(VERIFY_CHECKSUM_READ_BYTES),
|
||||
table_files_size);
|
||||
}
|
||||
}
|
||||
|
||||
#endif // !ROCKSDB_LITE
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
@@ -45,6 +45,8 @@ void VerifyTableProperties(DB* db, uint64_t expected_entries_size) {
|
||||
|
||||
ASSERT_EQ(props.size(), unique_entries.size());
|
||||
ASSERT_EQ(expected_entries_size, sum);
|
||||
|
||||
VerifySstUniqueIds(props);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
@@ -94,6 +96,37 @@ TEST_F(DBTablePropertiesTest, GetPropertiesOfAllTablesTest) {
|
||||
VerifyTableProperties(db_, 10 + 11 + 12 + 13);
|
||||
}
|
||||
|
||||
TEST_F(DBTablePropertiesTest, CreateOnDeletionCollectorFactory) {
|
||||
ConfigOptions options;
|
||||
options.ignore_unsupported_options = false;
|
||||
|
||||
std::shared_ptr<TablePropertiesCollectorFactory> factory;
|
||||
std::string id = CompactOnDeletionCollectorFactory::kClassName();
|
||||
ASSERT_OK(
|
||||
TablePropertiesCollectorFactory::CreateFromString(options, id, &factory));
|
||||
auto del_factory = factory->CheckedCast<CompactOnDeletionCollectorFactory>();
|
||||
ASSERT_NE(del_factory, nullptr);
|
||||
ASSERT_EQ(0U, del_factory->GetWindowSize());
|
||||
ASSERT_EQ(0U, del_factory->GetDeletionTrigger());
|
||||
ASSERT_EQ(0.0, del_factory->GetDeletionRatio());
|
||||
ASSERT_OK(TablePropertiesCollectorFactory::CreateFromString(
|
||||
options, "window_size=100; deletion_trigger=90; id=" + id, &factory));
|
||||
del_factory = factory->CheckedCast<CompactOnDeletionCollectorFactory>();
|
||||
ASSERT_NE(del_factory, nullptr);
|
||||
ASSERT_EQ(100U, del_factory->GetWindowSize());
|
||||
ASSERT_EQ(90U, del_factory->GetDeletionTrigger());
|
||||
ASSERT_EQ(0.0, del_factory->GetDeletionRatio());
|
||||
ASSERT_OK(TablePropertiesCollectorFactory::CreateFromString(
|
||||
options,
|
||||
"window_size=100; deletion_trigger=90; deletion_ratio=0.5; id=" + id,
|
||||
&factory));
|
||||
del_factory = factory->CheckedCast<CompactOnDeletionCollectorFactory>();
|
||||
ASSERT_NE(del_factory, nullptr);
|
||||
ASSERT_EQ(100U, del_factory->GetWindowSize());
|
||||
ASSERT_EQ(90U, del_factory->GetDeletionTrigger());
|
||||
ASSERT_EQ(0.5, del_factory->GetDeletionRatio());
|
||||
}
|
||||
|
||||
TablePropertiesCollection
|
||||
DBTablePropertiesTest::TestGetPropertiesOfTablesInRange(
|
||||
std::vector<Range> ranges, std::size_t* num_properties,
|
||||
|
||||
+145
-27
@@ -11,11 +11,13 @@
|
||||
// in Release build.
|
||||
// which is a pity, it is a good test
|
||||
#include <fcntl.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <set>
|
||||
#include <thread>
|
||||
#include <unordered_set>
|
||||
#include <utility>
|
||||
|
||||
#ifndef OS_WIN
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
@@ -34,7 +36,6 @@
|
||||
#include "db/write_batch_internal.h"
|
||||
#include "env/mock_env.h"
|
||||
#include "file/filename.h"
|
||||
#include "memtable/hash_linklist_rep.h"
|
||||
#include "monitoring/thread_status_util.h"
|
||||
#include "port/port.h"
|
||||
#include "port/stack_trace.h"
|
||||
@@ -53,6 +54,7 @@
|
||||
#include "rocksdb/table.h"
|
||||
#include "rocksdb/table_properties.h"
|
||||
#include "rocksdb/thread_status.h"
|
||||
#include "rocksdb/types.h"
|
||||
#include "rocksdb/utilities/checkpoint.h"
|
||||
#include "rocksdb/utilities/optimistic_transaction_db.h"
|
||||
#include "rocksdb/utilities/write_batch_with_index.h"
|
||||
@@ -96,7 +98,7 @@ class DBTestWithParam
|
||||
};
|
||||
|
||||
TEST_F(DBTest, MockEnvTest) {
|
||||
std::unique_ptr<MockEnv> env{new MockEnv(Env::Default())};
|
||||
std::unique_ptr<MockEnv> env{MockEnv::Create(Env::Default())};
|
||||
Options options;
|
||||
options.create_if_missing = true;
|
||||
options.env = env.get();
|
||||
@@ -2075,6 +2077,13 @@ TEST_F(DBTest, OverlapInLevel0) {
|
||||
Flush(1);
|
||||
ASSERT_EQ("2,1,1", FilesPerLevel(1));
|
||||
|
||||
// BEGIN addition to existing test
|
||||
// Take this opportunity to verify SST unique ids (including Plain table)
|
||||
TablePropertiesCollection tbc;
|
||||
ASSERT_OK(db_->GetPropertiesOfAllTables(handles_[1], &tbc));
|
||||
VerifySstUniqueIds(tbc);
|
||||
// END addition to existing test
|
||||
|
||||
// Compact away the placeholder files we created initially
|
||||
dbfull()->TEST_CompactRange(1, nullptr, nullptr, handles_[1]);
|
||||
dbfull()->TEST_CompactRange(2, nullptr, nullptr, handles_[1]);
|
||||
@@ -2301,8 +2310,8 @@ TEST_F(DBTest, SnapshotFiles) {
|
||||
uint64_t manifest_number = 0;
|
||||
uint64_t manifest_size = 0;
|
||||
std::vector<std::string> files;
|
||||
dbfull()->DisableFileDeletions();
|
||||
dbfull()->GetLiveFiles(files, &manifest_size);
|
||||
ASSERT_OK(dbfull()->DisableFileDeletions());
|
||||
ASSERT_OK(dbfull()->GetLiveFiles(files, &manifest_size));
|
||||
|
||||
// CURRENT, MANIFEST, OPTIONS, *.sst files (one for each CF)
|
||||
ASSERT_EQ(files.size(), 5U);
|
||||
@@ -2331,18 +2340,17 @@ TEST_F(DBTest, SnapshotFiles) {
|
||||
// latest manifest file
|
||||
if (ParseFileName(files[i].substr(1), &number, &type)) {
|
||||
if (type == kDescriptorFile) {
|
||||
if (number > manifest_number) {
|
||||
manifest_number = number;
|
||||
ASSERT_GE(size, manifest_size);
|
||||
size = manifest_size; // copy only valid MANIFEST data
|
||||
}
|
||||
ASSERT_EQ(manifest_number, 0);
|
||||
manifest_number = number;
|
||||
ASSERT_GE(size, manifest_size);
|
||||
size = manifest_size; // copy only valid MANIFEST data
|
||||
}
|
||||
}
|
||||
CopyFile(src, dest, size);
|
||||
}
|
||||
|
||||
// release file snapshot
|
||||
dbfull()->DisableFileDeletions();
|
||||
ASSERT_OK(dbfull()->EnableFileDeletions(/*force*/ false));
|
||||
// overwrite one key, this key should not appear in the snapshot
|
||||
std::vector<std::string> extras;
|
||||
for (unsigned int i = 0; i < 1; i++) {
|
||||
@@ -2379,8 +2387,8 @@ TEST_F(DBTest, SnapshotFiles) {
|
||||
uint64_t new_manifest_number = 0;
|
||||
uint64_t new_manifest_size = 0;
|
||||
std::vector<std::string> newfiles;
|
||||
dbfull()->DisableFileDeletions();
|
||||
dbfull()->GetLiveFiles(newfiles, &new_manifest_size);
|
||||
ASSERT_OK(dbfull()->DisableFileDeletions());
|
||||
ASSERT_OK(dbfull()->GetLiveFiles(newfiles, &new_manifest_size));
|
||||
|
||||
// find the new manifest file. assert that this manifest file is
|
||||
// the same one as in the previous snapshot. But its size should be
|
||||
@@ -2392,20 +2400,41 @@ TEST_F(DBTest, SnapshotFiles) {
|
||||
// latest manifest file
|
||||
if (ParseFileName(newfiles[i].substr(1), &number, &type)) {
|
||||
if (type == kDescriptorFile) {
|
||||
if (number > new_manifest_number) {
|
||||
uint64_t size;
|
||||
new_manifest_number = number;
|
||||
ASSERT_OK(env_->GetFileSize(src, &size));
|
||||
ASSERT_GE(size, new_manifest_size);
|
||||
}
|
||||
ASSERT_EQ(new_manifest_number, 0);
|
||||
uint64_t size;
|
||||
new_manifest_number = number;
|
||||
ASSERT_OK(env_->GetFileSize(src, &size));
|
||||
ASSERT_GE(size, new_manifest_size);
|
||||
}
|
||||
}
|
||||
}
|
||||
ASSERT_EQ(manifest_number, new_manifest_number);
|
||||
ASSERT_GT(new_manifest_size, manifest_size);
|
||||
|
||||
// release file snapshot
|
||||
dbfull()->DisableFileDeletions();
|
||||
// Also test GetLiveFilesStorageInfo
|
||||
std::vector<LiveFileStorageInfo> new_infos;
|
||||
ASSERT_OK(dbfull()->GetLiveFilesStorageInfo(LiveFilesStorageInfoOptions(),
|
||||
&new_infos));
|
||||
|
||||
// Close DB (while deletions disabled)
|
||||
Close();
|
||||
|
||||
// Validate
|
||||
for (auto& info : new_infos) {
|
||||
std::string path = info.directory + "/" + info.relative_filename;
|
||||
uint64_t size;
|
||||
ASSERT_OK(env_->GetFileSize(path, &size));
|
||||
if (info.trim_to_size) {
|
||||
ASSERT_LE(info.size, size);
|
||||
} else if (!info.replacement_contents.empty()) {
|
||||
ASSERT_EQ(info.size, info.replacement_contents.size());
|
||||
} else {
|
||||
ASSERT_EQ(info.size, size);
|
||||
}
|
||||
if (info.file_type == kDescriptorFile) {
|
||||
ASSERT_EQ(info.file_number, manifest_number);
|
||||
}
|
||||
}
|
||||
} while (ChangeCompactOptions());
|
||||
}
|
||||
|
||||
@@ -2812,7 +2841,7 @@ TEST_F(DBTest, GroupCommitTest) {
|
||||
#endif // TRAVIS
|
||||
|
||||
namespace {
|
||||
typedef std::map<std::string, std::string> KVMap;
|
||||
using KVMap = std::map<std::string, std::string>;
|
||||
}
|
||||
|
||||
class ModelDB : public DB {
|
||||
@@ -3120,6 +3149,12 @@ class ModelDB : public DB {
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status GetLiveFilesStorageInfo(
|
||||
const LiveFilesStorageInfoOptions& /*opts*/,
|
||||
std::vector<LiveFileStorageInfo>* /*files*/) override {
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status GetSortedWalFiles(VectorLogPtr& /*files*/) override {
|
||||
return Status::OK();
|
||||
}
|
||||
@@ -3938,6 +3973,56 @@ TEST_F(DBTest, DISABLED_RateLimitingTest) {
|
||||
ASSERT_LT(ratio, 0.6);
|
||||
}
|
||||
|
||||
// This is a mocked customed rate limiter without implementing optional APIs
|
||||
// (e.g, RateLimiter::GetTotalPendingRequests())
|
||||
class MockedRateLimiterWithNoOptionalAPIImpl : public RateLimiter {
|
||||
public:
|
||||
MockedRateLimiterWithNoOptionalAPIImpl() {}
|
||||
|
||||
~MockedRateLimiterWithNoOptionalAPIImpl() override {}
|
||||
|
||||
void SetBytesPerSecond(int64_t bytes_per_second) override {
|
||||
(void)bytes_per_second;
|
||||
}
|
||||
|
||||
using RateLimiter::Request;
|
||||
void Request(const int64_t bytes, const Env::IOPriority pri,
|
||||
Statistics* stats) override {
|
||||
(void)bytes;
|
||||
(void)pri;
|
||||
(void)stats;
|
||||
}
|
||||
|
||||
int64_t GetSingleBurstBytes() const override { return 200; }
|
||||
|
||||
int64_t GetTotalBytesThrough(
|
||||
const Env::IOPriority pri = Env::IO_TOTAL) const override {
|
||||
(void)pri;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int64_t GetTotalRequests(
|
||||
const Env::IOPriority pri = Env::IO_TOTAL) const override {
|
||||
(void)pri;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int64_t GetBytesPerSecond() const override { return 0; }
|
||||
};
|
||||
|
||||
// To test that customed rate limiter not implementing optional APIs (e.g,
|
||||
// RateLimiter::GetTotalPendingRequests()) works fine with RocksDB basic
|
||||
// operations (e.g, Put, Get, Flush)
|
||||
TEST_F(DBTest, CustomedRateLimiterWithNoOptionalAPIImplTest) {
|
||||
Options options = CurrentOptions();
|
||||
options.rate_limiter.reset(new MockedRateLimiterWithNoOptionalAPIImpl());
|
||||
DestroyAndReopen(options);
|
||||
ASSERT_OK(Put("abc", "def"));
|
||||
ASSERT_EQ(Get("abc"), "def");
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_EQ(Get("abc"), "def");
|
||||
}
|
||||
|
||||
TEST_F(DBTest, TableOptionsSanitizeTest) {
|
||||
Options options = CurrentOptions();
|
||||
options.create_if_missing = true;
|
||||
@@ -4099,6 +4184,39 @@ TEST_F(DBTest, ConcurrentFlushWAL) {
|
||||
}
|
||||
}
|
||||
|
||||
// This test failure will be caught with a probability
|
||||
TEST_F(DBTest, ManualFlushWalAndWriteRace) {
|
||||
Options options;
|
||||
options.env = env_;
|
||||
options.manual_wal_flush = true;
|
||||
options.create_if_missing = true;
|
||||
|
||||
DestroyAndReopen(options);
|
||||
|
||||
WriteOptions wopts;
|
||||
wopts.sync = true;
|
||||
|
||||
port::Thread writeThread([&]() {
|
||||
for (int i = 0; i < 100; i++) {
|
||||
auto istr = ToString(i);
|
||||
dbfull()->Put(wopts, "key_" + istr, "value_" + istr);
|
||||
}
|
||||
});
|
||||
port::Thread flushThread([&]() {
|
||||
for (int i = 0; i < 100; i++) {
|
||||
ASSERT_OK(dbfull()->FlushWAL(false));
|
||||
}
|
||||
});
|
||||
|
||||
writeThread.join();
|
||||
flushThread.join();
|
||||
ASSERT_OK(dbfull()->Put(wopts, "foo1", "value1"));
|
||||
ASSERT_OK(dbfull()->Put(wopts, "foo2", "value2"));
|
||||
Reopen(options);
|
||||
ASSERT_EQ("value1", Get("foo1"));
|
||||
ASSERT_EQ("value2", Get("foo2"));
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
TEST_F(DBTest, DynamicMemtableOptions) {
|
||||
const uint64_t k64KB = 1 << 16;
|
||||
@@ -4813,7 +4931,7 @@ TEST_F(DBTest, DynamicLevelCompressionPerLevel2) {
|
||||
options.level0_stop_writes_trigger = 2;
|
||||
options.soft_pending_compaction_bytes_limit = 1024 * 1024;
|
||||
options.target_file_size_base = 20;
|
||||
|
||||
options.env = env_;
|
||||
options.level_compaction_dynamic_level_bytes = true;
|
||||
options.max_bytes_for_level_base = 200;
|
||||
options.max_bytes_for_level_multiplier = 8;
|
||||
@@ -5732,8 +5850,8 @@ TEST_F(DBTest, DISABLED_SuggestCompactRangeTest) {
|
||||
};
|
||||
|
||||
Options options = CurrentOptions();
|
||||
options.memtable_factory.reset(
|
||||
new SpecialSkipListFactory(DBTestBase::kNumKeysByGenerateNewRandomFile));
|
||||
options.memtable_factory.reset(test::NewSpecialSkipListFactory(
|
||||
DBTestBase::kNumKeysByGenerateNewRandomFile));
|
||||
options.compaction_style = kCompactionStyleLevel;
|
||||
options.compaction_filter_factory.reset(
|
||||
new CompactionFilterFactoryGetContext());
|
||||
@@ -6138,7 +6256,7 @@ TEST_F(DBTest, DelayedWriteRate) {
|
||||
options.level0_stop_writes_trigger = 999999;
|
||||
options.delayed_write_rate = 20000000; // Start with 200MB/s
|
||||
options.memtable_factory.reset(
|
||||
new SpecialSkipListFactory(kEntriesPerMemTable));
|
||||
test::NewSpecialSkipListFactory(kEntriesPerMemTable));
|
||||
|
||||
SetTimeElapseOnlySleepOnReopen(&options);
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
@@ -6201,7 +6319,7 @@ TEST_F(DBTest, HardLimit) {
|
||||
options.max_bytes_for_level_base = 10000000000u;
|
||||
options.max_background_compactions = 1;
|
||||
options.memtable_factory.reset(
|
||||
new SpecialSkipListFactory(KNumKeysByGenerateNewFile - 1));
|
||||
test::NewSpecialSkipListFactory(KNumKeysByGenerateNewFile - 1));
|
||||
|
||||
env_->SetBackgroundThreads(1, Env::LOW);
|
||||
test::SleepingBackgroundTask sleeping_task_low;
|
||||
@@ -6450,7 +6568,7 @@ TEST_F(DBTest, LastWriteBufferDelay) {
|
||||
options.disable_auto_compactions = true;
|
||||
int kNumKeysPerMemtable = 3;
|
||||
options.memtable_factory.reset(
|
||||
new SpecialSkipListFactory(kNumKeysPerMemtable));
|
||||
test::NewSpecialSkipListFactory(kNumKeysPerMemtable));
|
||||
|
||||
Reopen(options);
|
||||
test::SleepingBackgroundTask sleeping_task;
|
||||
|
||||
+59
-21
@@ -17,11 +17,13 @@
|
||||
#include "options/options_helper.h"
|
||||
#include "port/port.h"
|
||||
#include "port/stack_trace.h"
|
||||
#include "rocksdb/iostats_context.h"
|
||||
#include "rocksdb/persistent_cache.h"
|
||||
#include "rocksdb/trace_record.h"
|
||||
#include "rocksdb/trace_record_result.h"
|
||||
#include "rocksdb/utilities/replayer.h"
|
||||
#include "rocksdb/wal_filter.h"
|
||||
#include "test_util/testutil.h"
|
||||
#include "util/random.h"
|
||||
#include "utilities/fault_injection_env.h"
|
||||
|
||||
@@ -35,11 +37,10 @@ class DBTest2 : public DBTestBase {
|
||||
#ifndef ROCKSDB_LITE
|
||||
uint64_t GetSstSizeHelper(Temperature temperature) {
|
||||
std::string prop;
|
||||
bool s =
|
||||
EXPECT_TRUE(
|
||||
dbfull()->GetProperty(DB::Properties::kLiveSstFilesSizeAtTemperature +
|
||||
ToString(static_cast<uint8_t>(temperature)),
|
||||
&prop);
|
||||
assert(s);
|
||||
&prop));
|
||||
return static_cast<uint64_t>(std::atoi(prop.c_str()));
|
||||
}
|
||||
#endif // ROCKSDB_LITE
|
||||
@@ -1264,7 +1265,7 @@ TEST_F(DBTest2, PresetCompressionDict) {
|
||||
options.disable_auto_compactions = true;
|
||||
options.level0_file_num_compaction_trigger = kNumL0Files;
|
||||
options.memtable_factory.reset(
|
||||
new SpecialSkipListFactory(kL0FileBytes / kBlockSizeBytes));
|
||||
test::NewSpecialSkipListFactory(kL0FileBytes / kBlockSizeBytes));
|
||||
options.num_levels = 2;
|
||||
options.target_file_size_base = kL0FileBytes;
|
||||
options.target_file_size_multiplier = 2;
|
||||
@@ -1484,7 +1485,7 @@ TEST_P(PresetCompressionDictTest, Flush) {
|
||||
options.compression_opts.max_dict_bytes = kDictLen;
|
||||
options.compression_opts.max_dict_buffer_bytes = kBlockLen;
|
||||
}
|
||||
options.memtable_factory.reset(new SpecialSkipListFactory(kKeysPerFile));
|
||||
options.memtable_factory.reset(test::NewSpecialSkipListFactory(kKeysPerFile));
|
||||
options.statistics = CreateDBStatistics();
|
||||
BlockBasedTableOptions bbto;
|
||||
bbto.block_size = kBlockLen;
|
||||
@@ -1684,9 +1685,9 @@ class CompactionCompressionListener : public EventListener {
|
||||
int bottommost_level = 0;
|
||||
for (int level = 0; level < db->NumberLevels(); level++) {
|
||||
std::string files_at_level;
|
||||
ASSERT_TRUE(
|
||||
db->GetProperty("rocksdb.num-files-at-level" + NumberToString(level),
|
||||
&files_at_level));
|
||||
ASSERT_TRUE(db->GetProperty(
|
||||
"rocksdb.num-files-at-level" + ROCKSDB_NAMESPACE::ToString(level),
|
||||
&files_at_level));
|
||||
if (files_at_level != "0") {
|
||||
bottommost_level = level;
|
||||
}
|
||||
@@ -2319,8 +2320,8 @@ INSTANTIATE_TEST_CASE_P(PinL0IndexAndFilterBlocksTest,
|
||||
#ifndef ROCKSDB_LITE
|
||||
TEST_F(DBTest2, MaxCompactionBytesTest) {
|
||||
Options options = CurrentOptions();
|
||||
options.memtable_factory.reset(
|
||||
new SpecialSkipListFactory(DBTestBase::kNumKeysByGenerateNewRandomFile));
|
||||
options.memtable_factory.reset(test::NewSpecialSkipListFactory(
|
||||
DBTestBase::kNumKeysByGenerateNewRandomFile));
|
||||
options.compaction_style = kCompactionStyleLevel;
|
||||
options.write_buffer_size = 200 << 10;
|
||||
options.arena_block_size = 4 << 10;
|
||||
@@ -3669,14 +3670,15 @@ TEST_F(DBTest2, IterRaceFlush1) {
|
||||
TEST_SYNC_POINT("DBTest2::IterRaceFlush:2");
|
||||
});
|
||||
|
||||
// iterator is created after the first Put(), so it should see either
|
||||
// "v1" or "v2".
|
||||
// iterator is created after the first Put(), and its snapshot sequence is
|
||||
// assigned after second Put(), so it must see v2.
|
||||
{
|
||||
std::unique_ptr<Iterator> it(db_->NewIterator(ReadOptions()));
|
||||
it->Seek("foo");
|
||||
ASSERT_TRUE(it->Valid());
|
||||
ASSERT_OK(it->status());
|
||||
ASSERT_EQ("foo", it->key().ToString());
|
||||
ASSERT_EQ("v2", it->value().ToString());
|
||||
}
|
||||
|
||||
t1.join();
|
||||
@@ -3699,14 +3701,15 @@ TEST_F(DBTest2, IterRaceFlush2) {
|
||||
TEST_SYNC_POINT("DBTest2::IterRaceFlush2:2");
|
||||
});
|
||||
|
||||
// iterator is created after the first Put(), so it should see either
|
||||
// "v1" or "v2".
|
||||
// iterator is created after the first Put(), and its snapshot sequence is
|
||||
// assigned before second Put(), thus it must see v1.
|
||||
{
|
||||
std::unique_ptr<Iterator> it(db_->NewIterator(ReadOptions()));
|
||||
it->Seek("foo");
|
||||
ASSERT_TRUE(it->Valid());
|
||||
ASSERT_OK(it->status());
|
||||
ASSERT_EQ("foo", it->key().ToString());
|
||||
ASSERT_EQ("v1", it->value().ToString());
|
||||
}
|
||||
|
||||
t1.join();
|
||||
@@ -3729,8 +3732,8 @@ TEST_F(DBTest2, IterRefreshRaceFlush) {
|
||||
TEST_SYNC_POINT("DBTest2::IterRefreshRaceFlush:2");
|
||||
});
|
||||
|
||||
// iterator is created after the first Put(), so it should see either
|
||||
// "v1" or "v2".
|
||||
// iterator is refreshed after the first Put(), and its sequence number is
|
||||
// assigned after second Put(), thus it must see v2.
|
||||
{
|
||||
std::unique_ptr<Iterator> it(db_->NewIterator(ReadOptions()));
|
||||
ASSERT_OK(it->status());
|
||||
@@ -3739,6 +3742,7 @@ TEST_F(DBTest2, IterRefreshRaceFlush) {
|
||||
ASSERT_TRUE(it->Valid());
|
||||
ASSERT_OK(it->status());
|
||||
ASSERT_EQ("foo", it->key().ToString());
|
||||
ASSERT_EQ("v2", it->value().ToString());
|
||||
}
|
||||
|
||||
t1.join();
|
||||
@@ -3945,7 +3949,8 @@ TEST_F(DBTest2, RateLimitedCompactionReads) {
|
||||
Options options = CurrentOptions();
|
||||
options.compression = kNoCompression;
|
||||
options.level0_file_num_compaction_trigger = kNumL0Files;
|
||||
options.memtable_factory.reset(new SpecialSkipListFactory(kNumKeysPerFile));
|
||||
options.memtable_factory.reset(
|
||||
test::NewSpecialSkipListFactory(kNumKeysPerFile));
|
||||
options.new_table_reader_for_compaction_inputs = true;
|
||||
// takes roughly one second, split into 100 x 10ms intervals. Each interval
|
||||
// permits 5.12KB, which is smaller than the block size, so this test
|
||||
@@ -5532,8 +5537,6 @@ TEST_F(DBTest2, TestCompactFiles) {
|
||||
}
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
// TODO: figure out why this test fails in appveyor
|
||||
#ifndef OS_WIN
|
||||
TEST_F(DBTest2, MultiDBParallelOpenTest) {
|
||||
const int kNumDbs = 2;
|
||||
Options options = CurrentOptions();
|
||||
@@ -5564,7 +5567,6 @@ TEST_F(DBTest2, MultiDBParallelOpenTest) {
|
||||
}
|
||||
|
||||
// Verify non-empty DBs can be recovered in parallel
|
||||
dbs.clear();
|
||||
open_threads.clear();
|
||||
for (int i = 0; i < kNumDbs; ++i) {
|
||||
open_threads.emplace_back(
|
||||
@@ -5581,11 +5583,11 @@ TEST_F(DBTest2, MultiDBParallelOpenTest) {
|
||||
ASSERT_OK(DestroyDB(dbnames[i], options));
|
||||
}
|
||||
}
|
||||
#endif // OS_WIN
|
||||
|
||||
namespace {
|
||||
class DummyOldStats : public Statistics {
|
||||
public:
|
||||
const char* Name() const override { return "DummyOldStats"; }
|
||||
uint64_t getTickerCount(uint32_t /*ticker_type*/) const override { return 0; }
|
||||
void recordTick(uint32_t /* ticker_type */, uint64_t /* count */) override {
|
||||
num_rt++;
|
||||
@@ -6516,6 +6518,9 @@ TEST_F(DBTest2, BottommostTemperature) {
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
|
||||
get_iostats_context()->Reset();
|
||||
IOStatsContext* iostats = get_iostats_context();
|
||||
|
||||
ColumnFamilyMetaData metadata;
|
||||
db_->GetColumnFamilyMetaData(&metadata);
|
||||
ASSERT_EQ(1, metadata.file_count);
|
||||
@@ -6524,11 +6529,31 @@ TEST_F(DBTest2, BottommostTemperature) {
|
||||
ASSERT_EQ(size, 0);
|
||||
size = GetSstSizeHelper(Temperature::kWarm);
|
||||
ASSERT_GT(size, 0);
|
||||
ASSERT_EQ(iostats->file_io_stats_by_temperature.hot_file_read_count, 0);
|
||||
ASSERT_EQ(iostats->file_io_stats_by_temperature.warm_file_read_count, 0);
|
||||
ASSERT_EQ(iostats->file_io_stats_by_temperature.hot_file_read_count, 0);
|
||||
|
||||
ASSERT_EQ("bar", Get("foo"));
|
||||
|
||||
ASSERT_EQ(iostats->file_io_stats_by_temperature.hot_file_read_count, 0);
|
||||
ASSERT_EQ(iostats->file_io_stats_by_temperature.warm_file_read_count, 1);
|
||||
ASSERT_EQ(iostats->file_io_stats_by_temperature.hot_file_read_count, 0);
|
||||
ASSERT_EQ(iostats->file_io_stats_by_temperature.hot_file_bytes_read, 0);
|
||||
ASSERT_GT(iostats->file_io_stats_by_temperature.warm_file_bytes_read, 0);
|
||||
ASSERT_EQ(iostats->file_io_stats_by_temperature.cold_file_bytes_read, 0);
|
||||
|
||||
// non-bottommost file still has unknown temperature
|
||||
ASSERT_OK(Put("foo", "bar"));
|
||||
ASSERT_OK(Put("bar", "bar"));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_EQ("bar", Get("bar"));
|
||||
ASSERT_EQ(iostats->file_io_stats_by_temperature.hot_file_read_count, 0);
|
||||
ASSERT_EQ(iostats->file_io_stats_by_temperature.warm_file_read_count, 1);
|
||||
ASSERT_EQ(iostats->file_io_stats_by_temperature.hot_file_read_count, 0);
|
||||
ASSERT_EQ(iostats->file_io_stats_by_temperature.hot_file_bytes_read, 0);
|
||||
ASSERT_GT(iostats->file_io_stats_by_temperature.warm_file_bytes_read, 0);
|
||||
ASSERT_EQ(iostats->file_io_stats_by_temperature.cold_file_bytes_read, 0);
|
||||
|
||||
db_->GetColumnFamilyMetaData(&metadata);
|
||||
ASSERT_EQ(2, metadata.file_count);
|
||||
ASSERT_EQ(Temperature::kUnknown, metadata.levels[0].files[0].temperature);
|
||||
@@ -6577,6 +6602,8 @@ TEST_F(DBTest2, BottommostTemperatureUniversal) {
|
||||
ASSERT_EQ(size, 0);
|
||||
size = GetSstSizeHelper(Temperature::kHot);
|
||||
ASSERT_EQ(size, 0);
|
||||
get_iostats_context()->Reset();
|
||||
IOStatsContext* iostats = get_iostats_context();
|
||||
|
||||
for (int i = 0; i < kTriggerNum; i++) {
|
||||
ASSERT_OK(Put("foo", "bar"));
|
||||
@@ -6594,6 +6621,17 @@ TEST_F(DBTest2, BottommostTemperatureUniversal) {
|
||||
ASSERT_GT(size, 0);
|
||||
size = GetSstSizeHelper(Temperature::kWarm);
|
||||
ASSERT_EQ(size, 0);
|
||||
ASSERT_EQ(iostats->file_io_stats_by_temperature.hot_file_read_count, 0);
|
||||
ASSERT_EQ(iostats->file_io_stats_by_temperature.warm_file_read_count, 0);
|
||||
ASSERT_EQ(iostats->file_io_stats_by_temperature.hot_file_read_count, 0);
|
||||
ASSERT_EQ("bar", Get("foo"));
|
||||
|
||||
ASSERT_EQ(iostats->file_io_stats_by_temperature.hot_file_read_count, 0);
|
||||
ASSERT_EQ(iostats->file_io_stats_by_temperature.warm_file_read_count, 0);
|
||||
ASSERT_EQ(iostats->file_io_stats_by_temperature.hot_file_read_count, 0);
|
||||
ASSERT_EQ(iostats->file_io_stats_by_temperature.hot_file_bytes_read, 0);
|
||||
ASSERT_EQ(iostats->file_io_stats_by_temperature.warm_file_bytes_read, 0);
|
||||
ASSERT_EQ(iostats->file_io_stats_by_temperature.cold_file_bytes_read, 0);
|
||||
|
||||
ASSERT_OK(Put("foo", "bar"));
|
||||
ASSERT_OK(Put("bar", "bar"));
|
||||
|
||||
+19
-10
@@ -13,6 +13,7 @@
|
||||
#include "env/mock_env.h"
|
||||
#include "rocksdb/convenience.h"
|
||||
#include "rocksdb/env_encryption.h"
|
||||
#include "rocksdb/unique_id.h"
|
||||
#include "rocksdb/utilities/object_registry.h"
|
||||
#include "util/random.h"
|
||||
|
||||
@@ -63,7 +64,7 @@ DBTestBase::DBTestBase(const std::string path, bool env_do_fsync)
|
||||
EXPECT_OK(test::CreateEnvFromSystem(config_options, &base_env, &env_guard_));
|
||||
EXPECT_NE(nullptr, base_env);
|
||||
if (getenv("MEM_ENV")) {
|
||||
mem_env_ = new MockEnv(base_env);
|
||||
mem_env_ = MockEnv::Create(base_env, base_env->GetSystemClock());
|
||||
}
|
||||
#ifndef ROCKSDB_LITE
|
||||
if (getenv("ENCRYPTED_ENV")) {
|
||||
@@ -1070,12 +1071,12 @@ int DBTestBase::NumTableFilesAtLevel(int level, int cf) {
|
||||
std::string property;
|
||||
if (cf == 0) {
|
||||
// default cfd
|
||||
EXPECT_TRUE(db_->GetProperty(
|
||||
"rocksdb.num-files-at-level" + NumberToString(level), &property));
|
||||
EXPECT_TRUE(db_->GetProperty("rocksdb.num-files-at-level" + ToString(level),
|
||||
&property));
|
||||
} else {
|
||||
EXPECT_TRUE(db_->GetProperty(
|
||||
handles_[cf], "rocksdb.num-files-at-level" + NumberToString(level),
|
||||
&property));
|
||||
EXPECT_TRUE(db_->GetProperty(handles_[cf],
|
||||
"rocksdb.num-files-at-level" + ToString(level),
|
||||
&property));
|
||||
}
|
||||
return atoi(property.c_str());
|
||||
}
|
||||
@@ -1085,12 +1086,10 @@ double DBTestBase::CompressionRatioAtLevel(int level, int cf) {
|
||||
if (cf == 0) {
|
||||
// default cfd
|
||||
EXPECT_TRUE(db_->GetProperty(
|
||||
"rocksdb.compression-ratio-at-level" + NumberToString(level),
|
||||
&property));
|
||||
"rocksdb.compression-ratio-at-level" + ToString(level), &property));
|
||||
} else {
|
||||
EXPECT_TRUE(db_->GetProperty(
|
||||
handles_[cf],
|
||||
"rocksdb.compression-ratio-at-level" + NumberToString(level),
|
||||
handles_[cf], "rocksdb.compression-ratio-at-level" + ToString(level),
|
||||
&property));
|
||||
}
|
||||
return std::stod(property);
|
||||
@@ -1656,4 +1655,14 @@ uint64_t DBTestBase::GetNumberOfSstFilesForColumnFamily(
|
||||
}
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
void VerifySstUniqueIds(const TablePropertiesCollection& props) {
|
||||
ASSERT_FALSE(props.empty()); // suspicious test if empty
|
||||
std::unordered_set<std::string> seen;
|
||||
for (auto& pair : props) {
|
||||
std::string id;
|
||||
ASSERT_OK(GetUniqueIdFromTableProperties(*pair.second, &id));
|
||||
ASSERT_TRUE(seen.insert(id).second);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
+4
-90
@@ -22,9 +22,7 @@
|
||||
#include <vector>
|
||||
|
||||
#include "db/db_impl/db_impl.h"
|
||||
#include "db/dbformat.h"
|
||||
#include "file/filename.h"
|
||||
#include "memtable/hash_linklist_rep.h"
|
||||
#include "rocksdb/cache.h"
|
||||
#include "rocksdb/compaction_filter.h"
|
||||
#include "rocksdb/convenience.h"
|
||||
@@ -112,94 +110,6 @@ struct OptionsOverride {
|
||||
|
||||
enum SkipPolicy { kSkipNone = 0, kSkipNoSnapshot = 1, kSkipNoPrefix = 2 };
|
||||
|
||||
// A hacky skip list mem table that triggers flush after number of entries.
|
||||
class SpecialMemTableRep : public MemTableRep {
|
||||
public:
|
||||
explicit SpecialMemTableRep(Allocator* allocator, MemTableRep* memtable,
|
||||
int num_entries_flush)
|
||||
: MemTableRep(allocator),
|
||||
memtable_(memtable),
|
||||
num_entries_flush_(num_entries_flush),
|
||||
num_entries_(0) {}
|
||||
|
||||
virtual KeyHandle Allocate(const size_t len, char** buf) override {
|
||||
return memtable_->Allocate(len, buf);
|
||||
}
|
||||
|
||||
// Insert key into the list.
|
||||
// REQUIRES: nothing that compares equal to key is currently in the list.
|
||||
virtual void Insert(KeyHandle handle) override {
|
||||
num_entries_++;
|
||||
memtable_->Insert(handle);
|
||||
}
|
||||
|
||||
void InsertConcurrently(KeyHandle handle) override {
|
||||
num_entries_++;
|
||||
memtable_->Insert(handle);
|
||||
}
|
||||
|
||||
// Returns true iff an entry that compares equal to key is in the list.
|
||||
virtual bool Contains(const char* key) const override {
|
||||
return memtable_->Contains(key);
|
||||
}
|
||||
|
||||
virtual size_t ApproximateMemoryUsage() override {
|
||||
// Return a high memory usage when number of entries exceeds the threshold
|
||||
// to trigger a flush.
|
||||
return (num_entries_ < num_entries_flush_) ? 0 : 1024 * 1024 * 1024;
|
||||
}
|
||||
|
||||
virtual void Get(const LookupKey& k, void* callback_args,
|
||||
bool (*callback_func)(void* arg,
|
||||
const char* entry)) override {
|
||||
memtable_->Get(k, callback_args, callback_func);
|
||||
}
|
||||
|
||||
uint64_t ApproximateNumEntries(const Slice& start_ikey,
|
||||
const Slice& end_ikey) override {
|
||||
return memtable_->ApproximateNumEntries(start_ikey, end_ikey);
|
||||
}
|
||||
|
||||
virtual MemTableRep::Iterator* GetIterator(Arena* arena = nullptr) override {
|
||||
return memtable_->GetIterator(arena);
|
||||
}
|
||||
|
||||
virtual ~SpecialMemTableRep() override {}
|
||||
|
||||
private:
|
||||
std::unique_ptr<MemTableRep> memtable_;
|
||||
int num_entries_flush_;
|
||||
int num_entries_;
|
||||
};
|
||||
|
||||
// The factory for the hacky skip list mem table that triggers flush after
|
||||
// number of entries exceeds a threshold.
|
||||
class SpecialSkipListFactory : public MemTableRepFactory {
|
||||
public:
|
||||
// After number of inserts exceeds `num_entries_flush` in a mem table, trigger
|
||||
// flush.
|
||||
explicit SpecialSkipListFactory(int num_entries_flush)
|
||||
: num_entries_flush_(num_entries_flush) {}
|
||||
|
||||
using MemTableRepFactory::CreateMemTableRep;
|
||||
virtual MemTableRep* CreateMemTableRep(
|
||||
const MemTableRep::KeyComparator& compare, Allocator* allocator,
|
||||
const SliceTransform* transform, Logger* /*logger*/) override {
|
||||
return new SpecialMemTableRep(
|
||||
allocator, factory_.CreateMemTableRep(compare, allocator, transform, 0),
|
||||
num_entries_flush_);
|
||||
}
|
||||
virtual const char* Name() const override { return "SkipListFactory"; }
|
||||
|
||||
bool IsInsertConcurrentlySupported() const override {
|
||||
return factory_.IsInsertConcurrentlySupported();
|
||||
}
|
||||
|
||||
private:
|
||||
SkipListFactory factory_;
|
||||
int num_entries_flush_;
|
||||
};
|
||||
|
||||
// Special Env used to delay background operations
|
||||
class SpecialEnv : public EnvWrapper {
|
||||
public:
|
||||
@@ -1285,4 +1195,8 @@ class DBTestBase : public testing::Test {
|
||||
bool time_elapse_only_sleep_on_reopen_ = false;
|
||||
};
|
||||
|
||||
// For verifying that all files generated by current version have SST
|
||||
// unique ids.
|
||||
void VerifySstUniqueIds(const TablePropertiesCollection& props);
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
@@ -12,6 +12,7 @@
|
||||
#if !defined(ROCKSDB_LITE)
|
||||
#include "rocksdb/utilities/table_properties_collectors.h"
|
||||
#include "test_util/sync_point.h"
|
||||
#include "test_util/testutil.h"
|
||||
#include "util/random.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
@@ -151,7 +152,7 @@ TEST_P(DBTestUniversalCompaction, OptimizeFiltersForHits) {
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(bbto));
|
||||
options.optimize_filters_for_hits = true;
|
||||
options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics();
|
||||
options.memtable_factory.reset(new SpecialSkipListFactory(3));
|
||||
options.memtable_factory.reset(test::NewSpecialSkipListFactory(3));
|
||||
|
||||
DestroyAndReopen(options);
|
||||
|
||||
@@ -736,6 +737,7 @@ TEST_P(DBTestUniversalCompactionParallel, UniversalCompactionParallel) {
|
||||
Options options = CurrentOptions();
|
||||
options.compaction_style = kCompactionStyleUniversal;
|
||||
options.num_levels = num_levels_;
|
||||
options.env = env_;
|
||||
options.write_buffer_size = 1 << 10; // 1KB
|
||||
options.level0_file_num_compaction_trigger = 3;
|
||||
options.max_background_compactions = 3;
|
||||
@@ -1230,7 +1232,7 @@ TEST_P(DBTestUniversalCompaction, UniversalCompactionFourPaths) {
|
||||
options.db_paths.emplace_back(dbname_ + "_3", 500 * 1024);
|
||||
options.db_paths.emplace_back(dbname_ + "_4", 1024 * 1024 * 1024);
|
||||
options.memtable_factory.reset(
|
||||
new SpecialSkipListFactory(KNumKeysByGenerateNewFile - 1));
|
||||
test::NewSpecialSkipListFactory(KNumKeysByGenerateNewFile - 1));
|
||||
options.compaction_style = kCompactionStyleUniversal;
|
||||
options.compaction_options_universal.size_ratio = 5;
|
||||
options.write_buffer_size = 111 << 10; // 114KB
|
||||
@@ -1334,7 +1336,7 @@ TEST_P(DBTestUniversalCompaction, UniversalCompactionCFPathUse) {
|
||||
options.db_paths.emplace_back(dbname_ + "_3", 500 * 1024);
|
||||
options.db_paths.emplace_back(dbname_ + "_4", 1024 * 1024 * 1024);
|
||||
options.memtable_factory.reset(
|
||||
new SpecialSkipListFactory(KNumKeysByGenerateNewFile - 1));
|
||||
test::NewSpecialSkipListFactory(KNumKeysByGenerateNewFile - 1));
|
||||
options.compaction_style = kCompactionStyleUniversal;
|
||||
options.compaction_options_universal.size_ratio = 10;
|
||||
options.write_buffer_size = 111 << 10; // 114KB
|
||||
@@ -1498,7 +1500,8 @@ TEST_P(DBTestUniversalCompaction, IncreaseUniversalCompactionNumLevels) {
|
||||
options.num_levels = 1;
|
||||
options.write_buffer_size = 200 << 10; // 200KB
|
||||
options.level0_file_num_compaction_trigger = 3;
|
||||
options.memtable_factory.reset(new SpecialSkipListFactory(KNumKeysPerFile));
|
||||
options.memtable_factory.reset(
|
||||
test::NewSpecialSkipListFactory(KNumKeysPerFile));
|
||||
options = CurrentOptions(options);
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
|
||||
@@ -1579,7 +1582,7 @@ TEST_P(DBTestUniversalCompaction, UniversalCompactionSecondPathRatio) {
|
||||
options.level0_file_num_compaction_trigger = 2;
|
||||
options.num_levels = 1;
|
||||
options.memtable_factory.reset(
|
||||
new SpecialSkipListFactory(KNumKeysByGenerateNewFile - 1));
|
||||
test::NewSpecialSkipListFactory(KNumKeysByGenerateNewFile - 1));
|
||||
|
||||
std::vector<std::string> filenames;
|
||||
if (env_->GetChildren(options.db_paths[1].path, &filenames).ok()) {
|
||||
@@ -1729,7 +1732,7 @@ TEST_P(DBTestUniversalCompaction, RecalculateScoreAfterPicking) {
|
||||
const int kNumFilesTrigger = 8;
|
||||
Options options = CurrentOptions();
|
||||
options.memtable_factory.reset(
|
||||
new SpecialSkipListFactory(KNumKeysByGenerateNewFile - 1));
|
||||
test::NewSpecialSkipListFactory(KNumKeysByGenerateNewFile - 1));
|
||||
options.compaction_options_universal.max_merge_width = kNumFilesTrigger / 2;
|
||||
options.compaction_options_universal.max_size_amplification_percent =
|
||||
static_cast<unsigned int>(-1);
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
#if !defined(ROCKSDB_LITE)
|
||||
#include "test_util/sync_point.h"
|
||||
#endif
|
||||
#include "test_util/testutil.h"
|
||||
#include "utilities/fault_injection_env.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
@@ -195,6 +196,69 @@ class DBBasicTestWithTimestamp : public DBBasicTestWithTimestampBase {
|
||||
: DBBasicTestWithTimestampBase("db_basic_test_with_timestamp") {}
|
||||
};
|
||||
|
||||
TEST_F(DBBasicTestWithTimestamp, MixedCfs) {
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
options.create_if_missing = true;
|
||||
options.avoid_flush_during_shutdown = true;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
Options options1 = CurrentOptions();
|
||||
options1.env = env_;
|
||||
const size_t kTimestampSize = Timestamp(0, 0).size();
|
||||
TestComparator test_cmp(kTimestampSize);
|
||||
options1.comparator = &test_cmp;
|
||||
ColumnFamilyHandle* handle = nullptr;
|
||||
Status s = db_->CreateColumnFamily(options1, "data", &handle);
|
||||
ASSERT_OK(s);
|
||||
|
||||
WriteBatch wb;
|
||||
ASSERT_OK(wb.Put("a", "value"));
|
||||
{
|
||||
std::string key("a");
|
||||
std::string ts(kTimestampSize, '\0');
|
||||
std::array<Slice, 2> key_with_ts_slices{{key, ts}};
|
||||
SliceParts key_with_ts(key_with_ts_slices.data(), 2);
|
||||
std::string value_str("value");
|
||||
Slice value_slice(value_str.data(), value_str.size());
|
||||
SliceParts value(&value_slice, 1);
|
||||
ASSERT_OK(wb.Put(handle, key_with_ts, value));
|
||||
}
|
||||
{
|
||||
std::string ts = Timestamp(1, 0);
|
||||
std::vector<Slice> ts_list({Slice(), ts});
|
||||
ASSERT_OK(wb.AssignTimestamps(ts_list));
|
||||
ASSERT_OK(db_->Write(WriteOptions(), &wb));
|
||||
}
|
||||
|
||||
const auto verify_db = [this](ColumnFamilyHandle* h) {
|
||||
ASSERT_EQ("value", Get("a"));
|
||||
std::string ts = Timestamp(1, 0);
|
||||
Slice read_ts_slice(ts);
|
||||
ReadOptions read_opts;
|
||||
read_opts.timestamp = &read_ts_slice;
|
||||
std::string value;
|
||||
ASSERT_OK(db_->Get(read_opts, h, "a", &value));
|
||||
ASSERT_EQ("value", value);
|
||||
};
|
||||
|
||||
verify_db(handle);
|
||||
|
||||
delete handle;
|
||||
Close();
|
||||
|
||||
std::vector<ColumnFamilyDescriptor> cf_descs;
|
||||
cf_descs.emplace_back(kDefaultColumnFamilyName, options);
|
||||
cf_descs.emplace_back("data", options1);
|
||||
options.create_if_missing = false;
|
||||
s = DB::Open(options, dbname_, cf_descs, &handles_, &db_);
|
||||
ASSERT_OK(s);
|
||||
|
||||
verify_db(handles_[1]);
|
||||
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(DBBasicTestWithTimestamp, CompactRangeWithSpecifiedRange) {
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
@@ -415,7 +479,8 @@ TEST_F(DBBasicTestWithTimestamp, SimpleIterate) {
|
||||
const size_t kTimestampSize = Timestamp(0, 0).size();
|
||||
TestComparator test_cmp(kTimestampSize);
|
||||
options.comparator = &test_cmp;
|
||||
options.memtable_factory.reset(new SpecialSkipListFactory(kNumKeysPerFile));
|
||||
options.memtable_factory.reset(
|
||||
test::NewSpecialSkipListFactory(kNumKeysPerFile));
|
||||
DestroyAndReopen(options);
|
||||
const std::vector<uint64_t> start_keys = {1, 0};
|
||||
const std::vector<std::string> write_timestamps = {Timestamp(1, 0),
|
||||
@@ -764,11 +829,15 @@ TEST_F(DBBasicTestWithTimestamp, ChangeIterationDirection) {
|
||||
const std::vector<std::tuple<std::string, std::string>> kvs = {
|
||||
std::make_tuple("aa", "value1"), std::make_tuple("ab", "value2")};
|
||||
for (const auto& ts : timestamps) {
|
||||
WriteBatch wb(0, 0, kTimestampSize);
|
||||
WriteBatch wb;
|
||||
for (const auto& kv : kvs) {
|
||||
const std::string& key = std::get<0>(kv);
|
||||
const std::string& value = std::get<1>(kv);
|
||||
ASSERT_OK(wb.Put(key, value));
|
||||
std::array<Slice, 2> key_with_ts_slices{{Slice(key), Slice(ts)}};
|
||||
SliceParts key_with_ts(key_with_ts_slices.data(), 2);
|
||||
std::array<Slice, 1> value_slices{{Slice(value)}};
|
||||
SliceParts values(value_slices.data(), 1);
|
||||
ASSERT_OK(wb.Put(key_with_ts, values));
|
||||
}
|
||||
|
||||
ASSERT_OK(wb.AssignTimestamp(ts));
|
||||
@@ -842,7 +911,8 @@ TEST_F(DBBasicTestWithTimestamp, SimpleForwardIterateLowerTsBound) {
|
||||
const size_t kTimestampSize = Timestamp(0, 0).size();
|
||||
TestComparator test_cmp(kTimestampSize);
|
||||
options.comparator = &test_cmp;
|
||||
options.memtable_factory.reset(new SpecialSkipListFactory(kNumKeysPerFile));
|
||||
options.memtable_factory.reset(
|
||||
test::NewSpecialSkipListFactory(kNumKeysPerFile));
|
||||
DestroyAndReopen(options);
|
||||
const std::vector<std::string> write_timestamps = {Timestamp(1, 0),
|
||||
Timestamp(3, 0)};
|
||||
@@ -913,13 +983,27 @@ TEST_F(DBBasicTestWithTimestamp, SimpleForwardIterateLowerTsBound) {
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(DBBasicTestWithTimestamp, ForwardIterateStartSeqnum) {
|
||||
class DBBasicDeletionTestWithTimestamp
|
||||
: public DBBasicTestWithTimestampBase,
|
||||
public testing::WithParamInterface<enum ValueType> {
|
||||
public:
|
||||
DBBasicDeletionTestWithTimestamp()
|
||||
: DBBasicTestWithTimestampBase("db_basic_deletion_test_with_timestamp") {}
|
||||
};
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(
|
||||
Timestamp, DBBasicDeletionTestWithTimestamp,
|
||||
::testing::Values(ValueType::kTypeSingleDeletion,
|
||||
ValueType::kTypeDeletionWithTimestamp));
|
||||
|
||||
TEST_P(DBBasicDeletionTestWithTimestamp, ForwardIterateStartSeqnum) {
|
||||
const int kNumKeysPerFile = 128;
|
||||
const uint64_t kMaxKey = 0xffffffffffffffff;
|
||||
const uint64_t kMinKey = kMaxKey - 1023;
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
options.create_if_missing = true;
|
||||
ValueType op_type = GetParam();
|
||||
// Need to disable compaction to bottommost level when sequence number will be
|
||||
// zeroed out, causing the verification of sequence number to fail in this
|
||||
// test.
|
||||
@@ -927,7 +1011,8 @@ TEST_F(DBBasicTestWithTimestamp, ForwardIterateStartSeqnum) {
|
||||
const size_t kTimestampSize = Timestamp(0, 0).size();
|
||||
TestComparator test_cmp(kTimestampSize);
|
||||
options.comparator = &test_cmp;
|
||||
options.memtable_factory.reset(new SpecialSkipListFactory(kNumKeysPerFile));
|
||||
options.memtable_factory.reset(
|
||||
test::NewSpecialSkipListFactory(kNumKeysPerFile));
|
||||
DestroyAndReopen(options);
|
||||
std::vector<SequenceNumber> start_seqs;
|
||||
|
||||
@@ -945,7 +1030,11 @@ TEST_F(DBBasicTestWithTimestamp, ForwardIterateStartSeqnum) {
|
||||
if (k % 2) {
|
||||
s = db_->Put(write_opts, Key1(k), "value" + std::to_string(i));
|
||||
} else {
|
||||
s = db_->Delete(write_opts, Key1(k));
|
||||
if (op_type == ValueType::kTypeDeletionWithTimestamp) {
|
||||
s = db_->Delete(write_opts, Key1(k));
|
||||
} else if (op_type == ValueType::kTypeSingleDeletion) {
|
||||
s = db_->SingleDelete(write_opts, Key1(k));
|
||||
}
|
||||
}
|
||||
ASSERT_OK(s);
|
||||
}
|
||||
@@ -967,8 +1056,7 @@ TEST_F(DBBasicTestWithTimestamp, ForwardIterateStartSeqnum) {
|
||||
uint64_t key = kMinKey;
|
||||
for (iter->Seek(Key1(kMinKey)); iter->Valid(); iter->Next()) {
|
||||
CheckIterEntry(
|
||||
iter.get(), Key1(key), expected_seq,
|
||||
(key % 2) ? kTypeValue : kTypeDeletionWithTimestamp,
|
||||
iter.get(), Key1(key), expected_seq, (key % 2) ? kTypeValue : op_type,
|
||||
(key % 2) ? "value" + std::to_string(i + 1) : std::string(),
|
||||
write_ts_list[i + 1]);
|
||||
++key;
|
||||
@@ -991,7 +1079,7 @@ TEST_F(DBBasicTestWithTimestamp, ForwardIterateStartSeqnum) {
|
||||
SequenceNumber expected_seq = start_seqs[i] + (kMaxKey - kMinKey) + 1;
|
||||
for (it->Seek(Key1(kMinKey)); it->Valid(); it->Next()) {
|
||||
CheckIterEntry(it.get(), Key1(key), expected_seq,
|
||||
(key % 2) ? kTypeValue : kTypeDeletionWithTimestamp,
|
||||
(key % 2) ? kTypeValue : op_type,
|
||||
"value" + std::to_string(i + 1), write_ts_list[i + 1]);
|
||||
++key;
|
||||
--expected_seq;
|
||||
@@ -1068,9 +1156,20 @@ TEST_F(DBBasicTestWithTimestamp, ReseekToNextUserKey) {
|
||||
}
|
||||
{
|
||||
std::string ts_str = Timestamp(static_cast<uint64_t>(kNumKeys + 1), 0);
|
||||
WriteBatch batch(0, 0, kTimestampSize);
|
||||
ASSERT_OK(batch.Put("a", "new_value"));
|
||||
ASSERT_OK(batch.Put("b", "new_value"));
|
||||
WriteBatch batch;
|
||||
const std::string dummy_ts(kTimestampSize, '\0');
|
||||
{
|
||||
std::array<Slice, 2> key_with_ts_slices{{"a", dummy_ts}};
|
||||
SliceParts key_with_ts(key_with_ts_slices.data(), 2);
|
||||
std::array<Slice, 1> value_slices{{"new_value"}};
|
||||
SliceParts values(value_slices.data(), 1);
|
||||
ASSERT_OK(batch.Put(key_with_ts, values));
|
||||
}
|
||||
{
|
||||
std::string key_with_ts("b");
|
||||
key_with_ts.append(dummy_ts);
|
||||
ASSERT_OK(batch.Put(key_with_ts, "new_value"));
|
||||
}
|
||||
s = batch.AssignTimestamp(ts_str);
|
||||
ASSERT_OK(s);
|
||||
s = db_->Write(write_opts, &batch);
|
||||
@@ -2235,7 +2334,8 @@ TEST_P(DBBasicTestWithTimestampCompressionSettings, PutAndGet) {
|
||||
Options options = CurrentOptions();
|
||||
options.create_if_missing = true;
|
||||
options.env = env_;
|
||||
options.memtable_factory.reset(new SpecialSkipListFactory(kNumKeysPerFile));
|
||||
options.memtable_factory.reset(
|
||||
test::NewSpecialSkipListFactory(kNumKeysPerFile));
|
||||
size_t ts_sz = Timestamp(0, 0).size();
|
||||
TestComparator test_cmp(ts_sz);
|
||||
options.comparator = &test_cmp;
|
||||
@@ -2313,7 +2413,8 @@ TEST_P(DBBasicTestWithTimestampCompressionSettings, PutDeleteGet) {
|
||||
TestComparator test_cmp(kTimestampSize);
|
||||
options.comparator = &test_cmp;
|
||||
const int kNumKeysPerFile = 1024;
|
||||
options.memtable_factory.reset(new SpecialSkipListFactory(kNumKeysPerFile));
|
||||
options.memtable_factory.reset(
|
||||
test::NewSpecialSkipListFactory(kNumKeysPerFile));
|
||||
BlockBasedTableOptions bbto;
|
||||
bbto.filter_policy = std::get<0>(GetParam());
|
||||
bbto.whole_key_filtering = true;
|
||||
@@ -2345,13 +2446,15 @@ TEST_P(DBBasicTestWithTimestampCompressionSettings, PutDeleteGet) {
|
||||
const size_t kNumL0Files =
|
||||
static_cast<size_t>(Options().level0_file_num_compaction_trigger);
|
||||
{
|
||||
// Generate enough L0 files with ts=1 to trigger compaction to L1
|
||||
// Half of the keys will go through Deletion and remaining half with
|
||||
// SingleDeletion. Generate enough L0 files with ts=1 to trigger compaction
|
||||
// to L1
|
||||
std::string ts_str = Timestamp(1, 0);
|
||||
Slice ts = ts_str;
|
||||
WriteOptions wopts;
|
||||
wopts.timestamp = &ts;
|
||||
for (size_t i = 0; i != kNumL0Files; ++i) {
|
||||
for (int j = 0; j != kNumKeysPerFile; ++j) {
|
||||
for (size_t i = 0; i < kNumL0Files; ++i) {
|
||||
for (int j = 0; j < kNumKeysPerFile; ++j) {
|
||||
ASSERT_OK(db_->Put(wopts, Key1(j), "value" + std::to_string(i)));
|
||||
}
|
||||
ASSERT_OK(db_->Flush(FlushOptions()));
|
||||
@@ -2361,11 +2464,15 @@ TEST_P(DBBasicTestWithTimestampCompressionSettings, PutDeleteGet) {
|
||||
ts_str = Timestamp(3, 0);
|
||||
ts = ts_str;
|
||||
wopts.timestamp = &ts;
|
||||
for (int i = 0; i != kNumKeysPerFile; ++i) {
|
||||
for (int i = 0; i < kNumKeysPerFile; ++i) {
|
||||
std::string key_str = Key1(i);
|
||||
Slice key(key_str);
|
||||
if ((i % 3) == 0) {
|
||||
ASSERT_OK(db_->Delete(wopts, key));
|
||||
if (i < kNumKeysPerFile / 2) {
|
||||
ASSERT_OK(db_->Delete(wopts, key));
|
||||
} else {
|
||||
ASSERT_OK(db_->SingleDelete(wopts, key));
|
||||
}
|
||||
} else {
|
||||
ASSERT_OK(db_->Put(wopts, key, "new_value"));
|
||||
}
|
||||
@@ -2379,7 +2486,11 @@ TEST_P(DBBasicTestWithTimestampCompressionSettings, PutDeleteGet) {
|
||||
std::string key_str = Key1(i);
|
||||
Slice key(key_str);
|
||||
if ((i % 3) == 1) {
|
||||
ASSERT_OK(db_->Delete(wopts, key));
|
||||
if (i < kNumKeysPerFile / 2) {
|
||||
ASSERT_OK(db_->Delete(wopts, key));
|
||||
} else {
|
||||
ASSERT_OK(db_->SingleDelete(wopts, key));
|
||||
}
|
||||
} else if ((i % 3) == 2) {
|
||||
ASSERT_OK(db_->Put(wopts, key, "new_value_2"));
|
||||
}
|
||||
@@ -2442,7 +2553,8 @@ TEST_P(DBBasicTestWithTimestampCompressionSettings, PutAndGetWithCompaction) {
|
||||
Options options = CurrentOptions();
|
||||
options.create_if_missing = true;
|
||||
options.env = env_;
|
||||
options.memtable_factory.reset(new SpecialSkipListFactory(kNumKeysPerFile));
|
||||
options.memtable_factory.reset(
|
||||
test::NewSpecialSkipListFactory(kNumKeysPerFile));
|
||||
|
||||
FlushedFileCollector* collector = new FlushedFileCollector();
|
||||
options.listeners.emplace_back(collector);
|
||||
@@ -2558,7 +2670,8 @@ TEST_F(DBBasicTestWithTimestamp, BatchWriteAndMultiGet) {
|
||||
Options options = CurrentOptions();
|
||||
options.create_if_missing = true;
|
||||
options.env = env_;
|
||||
options.memtable_factory.reset(new SpecialSkipListFactory(kNumKeysPerFile));
|
||||
options.memtable_factory.reset(
|
||||
test::NewSpecialSkipListFactory(kNumKeysPerFile));
|
||||
options.memtable_prefix_bloom_size_ratio = 0.1;
|
||||
options.memtable_whole_key_filtering = true;
|
||||
|
||||
@@ -2607,17 +2720,23 @@ TEST_F(DBBasicTestWithTimestamp, BatchWriteAndMultiGet) {
|
||||
}
|
||||
};
|
||||
|
||||
const std::string dummy_ts(ts_sz, '\0');
|
||||
for (size_t i = 0; i != kNumTimestamps; ++i) {
|
||||
write_ts_list.push_back(Timestamp(i * 2, 0));
|
||||
read_ts_list.push_back(Timestamp(1 + i * 2, 0));
|
||||
const Slice& write_ts = write_ts_list.back();
|
||||
for (int cf = 0; cf != static_cast<int>(num_cfs); ++cf) {
|
||||
WriteOptions wopts;
|
||||
WriteBatch batch(0, 0, ts_sz);
|
||||
WriteBatch batch;
|
||||
for (size_t j = 0; j != kNumKeysPerTimestamp; ++j) {
|
||||
ASSERT_OK(
|
||||
batch.Put(handles_[cf], Key1(j),
|
||||
"value_" + std::to_string(j) + "_" + std::to_string(i)));
|
||||
const std::string key = Key1(j);
|
||||
const std::string value =
|
||||
"value_" + std::to_string(j) + "_" + std::to_string(i);
|
||||
std::array<Slice, 2> key_with_ts_slices{{key, dummy_ts}};
|
||||
SliceParts key_with_ts(key_with_ts_slices.data(), 2);
|
||||
std::array<Slice, 1> value_slices{{value}};
|
||||
SliceParts values(value_slices.data(), 1);
|
||||
ASSERT_OK(batch.Put(handles_[cf], key_with_ts, values));
|
||||
}
|
||||
ASSERT_OK(batch.AssignTimestamp(write_ts));
|
||||
ASSERT_OK(db_->Write(wopts, &batch));
|
||||
@@ -2725,7 +2844,8 @@ TEST_P(DBBasicTestWithTimestampPrefixSeek, IterateWithPrefix) {
|
||||
TestComparator test_cmp(kTimestampSize);
|
||||
options.comparator = &test_cmp;
|
||||
options.prefix_extractor = std::get<0>(GetParam());
|
||||
options.memtable_factory.reset(new SpecialSkipListFactory(kNumKeysPerFile));
|
||||
options.memtable_factory.reset(
|
||||
test::NewSpecialSkipListFactory(kNumKeysPerFile));
|
||||
BlockBasedTableOptions bbto;
|
||||
bbto.filter_policy = std::get<1>(GetParam());
|
||||
bbto.index_type = std::get<3>(GetParam());
|
||||
@@ -2879,7 +2999,8 @@ TEST_P(DBBasicTestWithTsIterTombstones, IterWithDelete) {
|
||||
TestComparator test_cmp(kTimestampSize);
|
||||
options.comparator = &test_cmp;
|
||||
options.prefix_extractor = std::get<0>(GetParam());
|
||||
options.memtable_factory.reset(new SpecialSkipListFactory(kNumKeysPerFile));
|
||||
options.memtable_factory.reset(
|
||||
test::NewSpecialSkipListFactory(kNumKeysPerFile));
|
||||
BlockBasedTableOptions bbto;
|
||||
bbto.filter_policy = std::get<1>(GetParam());
|
||||
bbto.index_type = std::get<3>(GetParam());
|
||||
|
||||
Loaded 100 of 506 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user