mirror of
https://github.com/facebook/rocksdb.git
synced 2026-11-06 11:02:36 -05:00
Compare commits
202
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
764bfcd19b | ||
|
|
1fdb34fe99 | ||
|
|
5d2b9971e8 | ||
|
|
e8830cab4b | ||
|
|
73d59f235e | ||
|
|
91b2c6d1f4 | ||
|
|
f445eadba6 | ||
|
|
f438b98e55 | ||
|
|
fb4063749b | ||
|
|
05c9d53a40 | ||
|
|
832ab1424d | ||
|
|
516c7df05d | ||
|
|
bb3b2eb960 | ||
|
|
ae5305ee70 | ||
|
|
f37ea82191 | ||
|
|
c954445ac8 | ||
|
|
619f754816 | ||
|
|
f81fe96eea | ||
|
|
7d3eb006ff | ||
|
|
02d47c1ff4 | ||
|
|
0f17fb9622 | ||
|
|
9552660ecc | ||
|
|
016437d1cd | ||
|
|
b6b72687c6 | ||
|
|
a1f6142f38 | ||
|
|
1cf5deb8fd | ||
|
|
4e58b2ea3d | ||
|
|
d78b2893bc | ||
|
|
477107d6f9 | ||
|
|
be5cc4c7b8 | ||
|
|
ac6f435a9a | ||
|
|
d6f2ecf49c | ||
|
|
75ca13875c | ||
|
|
b1dad4cfcc | ||
|
|
d56070d875 | ||
|
|
dc813e4b85 | ||
|
|
17edc82a4b | ||
|
|
31d46993cc | ||
|
|
3c350a7cf0 | ||
|
|
04d373b260 | ||
|
|
72712f4e28 | ||
|
|
02dc074916 | ||
|
|
519f8b145f | ||
|
|
9b3cf908a6 | ||
|
|
a0ebec3804 | ||
|
|
886766c31d | ||
|
|
990b52e95b | ||
|
|
27221b0cc2 | ||
|
|
65ac72edd9 | ||
|
|
1626f6ab6b | ||
|
|
30c21df97c | ||
|
|
8c25204633 | ||
|
|
ac46790374 | ||
|
|
dfda91027b | ||
|
|
a27fce408e | ||
|
|
3db584059c | ||
|
|
879998b369 | ||
|
|
82e8e9e26b | ||
|
|
8959063c9c | ||
|
|
82057b0d8f | ||
|
|
2353c5c821 | ||
|
|
c94523ee56 | ||
|
|
902261519e | ||
|
|
0bd2ede10e | ||
|
|
9ea9007b50 | ||
|
|
ca92fc71a4 | ||
|
|
3ba3b153ef | ||
|
|
dbf44c314b | ||
|
|
c86a22ac43 | ||
|
|
3f5282268f | ||
|
|
faf529fd7c | ||
|
|
655ef7d77f | ||
|
|
ced618cf39 | ||
|
|
2c14662213 | ||
|
|
64324e329e | ||
|
|
90f5048207 | ||
|
|
1a88c43751 | ||
|
|
462ed70d64 | ||
|
|
1cf17ba53b | ||
|
|
3e801e5ed1 | ||
|
|
f46dd5cbeb | ||
|
|
d00e5de7fc | ||
|
|
927f274939 | ||
|
|
5f63a89b35 | ||
|
|
a876995ed4 | ||
|
|
42733637e1 | ||
|
|
38ad3c9f8a | ||
|
|
6ed7f146c3 | ||
|
|
a6d3de4e7a | ||
|
|
2f871bc85e | ||
|
|
198459ce17 | ||
|
|
4848bd0c4e | ||
|
|
8022500ecc | ||
|
|
7daae512d2 | ||
|
|
17f9a181d5 | ||
|
|
e7bb8e9b92 | ||
|
|
6c40806e51 | ||
|
|
ee234e83e3 | ||
|
|
cf7150ac2e | ||
|
|
ad789e4e0d | ||
|
|
da40d45267 | ||
|
|
bb5dcea98e | ||
|
|
f1f5ba085f | ||
|
|
b6280d01f9 | ||
|
|
640cfa7c33 | ||
|
|
4f12d49daf | ||
|
|
c9a0419413 | ||
|
|
9e2d5ab6bf | ||
|
|
6d37fdb365 | ||
|
|
7188bd34f3 | ||
|
|
d5612b43de | ||
|
|
dc064f302e | ||
|
|
d116a1725d | ||
|
|
90f744941d | ||
|
|
c7cf981a85 | ||
|
|
fa4de6e30f | ||
|
|
9d646a6311 | ||
|
|
889a0553c8 | ||
|
|
9c0c8f5ff6 | ||
|
|
aeed4f0749 | ||
|
|
19ec44fd39 | ||
|
|
8ae2bf5331 | ||
|
|
33ad9060d3 | ||
|
|
bf07e90cf2 | ||
|
|
d122025891 | ||
|
|
d916a1105a | ||
|
|
f3d91a0b57 | ||
|
|
999d955e4f | ||
|
|
1b1d264342 | ||
|
|
4ea56b1bd0 | ||
|
|
6d75319d95 | ||
|
|
76d77205da | ||
|
|
d511f35ea7 | ||
|
|
058026a885 | ||
|
|
b271f956c2 | ||
|
|
caf0f53a74 | ||
|
|
63f265c383 | ||
|
|
72e69497fc | ||
|
|
1b813a9b2e | ||
|
|
ab22cf349e | ||
|
|
de7f423a82 | ||
|
|
9e8ef45066 | ||
|
|
b15379dcea | ||
|
|
7a9a164276 | ||
|
|
eb8885a08a | ||
|
|
853a5cd3b2 | ||
|
|
bbf30330b4 | ||
|
|
d8d66c937e | ||
|
|
badfd70a3e | ||
|
|
c970358574 | ||
|
|
4cb7068c1e | ||
|
|
6175b4b294 | ||
|
|
140f256da2 | ||
|
|
ceb5fea1e3 | ||
|
|
a15354d04e | ||
|
|
1f802773bc | ||
|
|
22368965a0 | ||
|
|
fefdac1004 | ||
|
|
f9373e2d5c | ||
|
|
9dbf39399e | ||
|
|
892a156267 | ||
|
|
f8f6983f89 | ||
|
|
12b6cdeed3 | ||
|
|
ee7617167f | ||
|
|
a1a546a634 | ||
|
|
8abafb1feb | ||
|
|
54de56844d | ||
|
|
a11df583ec | ||
|
|
f5e46354d2 | ||
|
|
c33b32671e | ||
|
|
e0906eb785 | ||
|
|
134a52e144 | ||
|
|
bdc6abd0b4 | ||
|
|
fd45495cf5 | ||
|
|
2a81633da2 | ||
|
|
18f538038a | ||
|
|
8805ec2f49 | ||
|
|
ea212e5316 | ||
|
|
374c37da5b | ||
|
|
c6d2a7f821 | ||
|
|
4b0a43574a | ||
|
|
f95a5b2464 | ||
|
|
6811fb0658 | ||
|
|
a5e851e113 | ||
|
|
8425c8bd4d | ||
|
|
ab35505e21 | ||
|
|
2736752b33 | ||
|
|
b5613227a9 | ||
|
|
78ab11cd71 | ||
|
|
f3801528c1 | ||
|
|
1857576e03 | ||
|
|
80afa84903 | ||
|
|
4bb1e239b5 | ||
|
|
79f009f22e | ||
|
|
37e0fdc824 | ||
|
|
d538ebdff0 | ||
|
|
ddc07b40fc | ||
|
|
995fcf7573 | ||
|
|
5a59ce4149 | ||
|
|
fb768a4289 | ||
|
|
b55da012f6 | ||
|
|
991120fa10 |
No files matched your search
@@ -45,6 +45,8 @@ etags
|
||||
rocksdb_dump
|
||||
rocksdb_undump
|
||||
db_test2
|
||||
trace_analyzer
|
||||
trace_analyzer_test
|
||||
|
||||
java/out
|
||||
java/target
|
||||
|
||||
+22
-15
@@ -48,6 +48,7 @@ option(WITH_JEMALLOC "build with JeMalloc" OFF)
|
||||
option(WITH_SNAPPY "build with SNAPPY" OFF)
|
||||
option(WITH_LZ4 "build with lz4" OFF)
|
||||
option(WITH_ZLIB "build with zlib" OFF)
|
||||
option(WITH_ZSTD "build with zstd" OFF)
|
||||
if(MSVC)
|
||||
# Defaults currently different for GFLAGS.
|
||||
# We will address find_package work a little later
|
||||
@@ -108,7 +109,6 @@ else()
|
||||
list(APPEND THIRDPARTY_LIBS ${LZ4_LIBRARIES})
|
||||
endif()
|
||||
|
||||
option(WITH_ZSTD "build with zstd" OFF)
|
||||
if(WITH_ZSTD)
|
||||
find_package(zstd REQUIRED)
|
||||
add_definitions(-DZSTD)
|
||||
@@ -307,14 +307,6 @@ if(DISABLE_STALL_NOTIF)
|
||||
add_definitions(-DROCKSDB_DISABLE_STALL_NOTIFICATION)
|
||||
endif()
|
||||
|
||||
# Used to run CI build and tests so we can run faster
|
||||
set(OPTIMIZE_DEBUG_DEFAULT 0) # Debug build is unoptimized by default use -DOPTDBG=1 to optimize
|
||||
|
||||
if(DEFINED OPTDBG)
|
||||
set(OPTIMIZE_DEBUG ${OPTDBG})
|
||||
else()
|
||||
set(OPTIMIZE_DEBUG ${OPTIMIZE_DEBUG_DEFAULT})
|
||||
endif()
|
||||
|
||||
if(DEFINED USE_RTTI)
|
||||
if(USE_RTTI)
|
||||
@@ -342,12 +334,20 @@ else()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Used to run CI build and tests so we can run faster
|
||||
option(OPTDBG "Build optimized debug build with MSVC" OFF)
|
||||
option(WITH_RUNTIME_DEBUG "build with debug version of runtime library" ON)
|
||||
if(MSVC)
|
||||
if((${OPTIMIZE_DEBUG} EQUAL 1))
|
||||
if(OPTDBG)
|
||||
message(STATUS "Debug optimization is enabled")
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "/Oxt /${RUNTIME_LIBRARY}d")
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "/Oxt")
|
||||
else()
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /Od /RTC1 /Gm /${RUNTIME_LIBRARY}d")
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /Od /RTC1 /Gm")
|
||||
endif()
|
||||
if(WITH_RUNTIME_DEBUG)
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /${RUNTIME_LIBRARY}d")
|
||||
else()
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /${RUNTIME_LIBRARY}")
|
||||
endif()
|
||||
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /Oxt /Zp8 /Gm- /Gy /${RUNTIME_LIBRARY}")
|
||||
|
||||
@@ -494,7 +494,6 @@ set(SOURCES
|
||||
db/log_reader.cc
|
||||
db/log_writer.cc
|
||||
db/malloc_stats.cc
|
||||
db/managed_iterator.cc
|
||||
db/memtable.cc
|
||||
db/memtable_list.cc
|
||||
db/merge_helper.cc
|
||||
@@ -556,6 +555,8 @@ set(SOURCES
|
||||
table/cuckoo_table_builder.cc
|
||||
table/cuckoo_table_factory.cc
|
||||
table/cuckoo_table_reader.cc
|
||||
table/data_block_hash_index.cc
|
||||
table/data_block_footer.cc
|
||||
table/flush_block_policy.cc
|
||||
table/format.cc
|
||||
table/full_filter_block.cc
|
||||
@@ -579,6 +580,7 @@ set(SOURCES
|
||||
tools/ldb_cmd.cc
|
||||
tools/ldb_tool.cc
|
||||
tools/sst_dump_tool.cc
|
||||
tools/trace_analyzer_tool.cc
|
||||
util/arena.cc
|
||||
util/auto_roll_logger.cc
|
||||
util/bloom.cc
|
||||
@@ -603,25 +605,25 @@ set(SOURCES
|
||||
util/slice.cc
|
||||
util/sst_file_manager_impl.cc
|
||||
util/status.cc
|
||||
util/status_message.cc
|
||||
util/string_util.cc
|
||||
util/sync_point.cc
|
||||
util/sync_point_impl.cc
|
||||
util/testutil.cc
|
||||
util/thread_local.cc
|
||||
util/threadpool_imp.cc
|
||||
util/trace_replay.cc
|
||||
util/transaction_test_util.cc
|
||||
util/xxhash.cc
|
||||
utilities/backupable/backupable_db.cc
|
||||
utilities/blob_db/blob_compaction_filter.cc
|
||||
utilities/blob_db/blob_db.cc
|
||||
utilities/blob_db/blob_db_impl.cc
|
||||
utilities/blob_db/blob_db_impl_filesnapshot.cc
|
||||
utilities/blob_db/blob_dump_tool.cc
|
||||
utilities/blob_db/blob_file.cc
|
||||
utilities/blob_db/blob_log_reader.cc
|
||||
utilities/blob_db/blob_log_writer.cc
|
||||
utilities/blob_db/blob_log_format.cc
|
||||
utilities/blob_db/ttl_extractor.cc
|
||||
utilities/cassandra/cassandra_compaction_filter.cc
|
||||
utilities/cassandra/format.cc
|
||||
utilities/cassandra/merge_operator.cc
|
||||
@@ -658,6 +660,7 @@ set(SOURCES
|
||||
utilities/simulator_cache/sim_cache.cc
|
||||
utilities/spatialdb/spatial_db.cc
|
||||
utilities/table_properties_collectors/compact_on_deletion_collector.cc
|
||||
utilities/trace/file_trace_reader_writer.cc
|
||||
utilities/transactions/optimistic_transaction_db_impl.cc
|
||||
utilities/transactions/optimistic_transaction.cc
|
||||
utilities/transactions/pessimistic_transaction.cc
|
||||
@@ -921,12 +924,14 @@ if(WITH_TESTS)
|
||||
table/cleanable_test.cc
|
||||
table/cuckoo_table_builder_test.cc
|
||||
table/cuckoo_table_reader_test.cc
|
||||
table/data_block_hash_index_test.cc
|
||||
table/full_filter_block_test.cc
|
||||
table/merger_test.cc
|
||||
table/table_test.cc
|
||||
tools/ldb_cmd_test.cc
|
||||
tools/reduce_levels_test.cc
|
||||
tools/sst_dump_test.cc
|
||||
tools/trace_analyzer_test.cc
|
||||
util/arena_test.cc
|
||||
util/auto_roll_logger_test.cc
|
||||
util/autovector_test.cc
|
||||
@@ -941,6 +946,7 @@ if(WITH_TESTS)
|
||||
util/hash_test.cc
|
||||
util/heap_test.cc
|
||||
util/rate_limiter_test.cc
|
||||
util/repeatable_thread_test.cc
|
||||
util/slice_transform_test.cc
|
||||
util/timer_queue_test.cc
|
||||
util/thread_list_test.cc
|
||||
@@ -983,6 +989,7 @@ if(WITH_TESTS)
|
||||
set(BENCHMARKS
|
||||
cache/cache_bench.cc
|
||||
memtable/memtablerep_bench.cc
|
||||
db/range_del_aggregator_bench.cc
|
||||
tools/db_bench.cc
|
||||
table/table_reader_bench.cc
|
||||
utilities/column_aware_encoding_exp.cc
|
||||
|
||||
+72
-7
@@ -1,23 +1,87 @@
|
||||
# Rocksdb Change Log
|
||||
## Unreleased
|
||||
## 5.17.4 (12/05/2018)
|
||||
### Bug Fixes
|
||||
* Fix a deadlock caused by compaction and file ingestion waiting for each other in the event of write stalls.
|
||||
|
||||
# 5.17.2 (10/24/2018)
|
||||
### Bug Fixes
|
||||
* Fix the bug that WriteBatchWithIndex's SeekForPrev() doesn't see the entries with the same key.
|
||||
|
||||
# 5.17.1 (10/16/2018)
|
||||
### Bug Fixes
|
||||
* Fix slow flush/compaction when DB contains many snapshots. The problem became noticeable to us in DBs with 100,000+ snapshots, though it will affect others at different thresholds.
|
||||
* Properly set the stop key for a truncated manual CompactRange
|
||||
* Fix corner case where a write group leader blocked due to write stall blocks other writers in queue with WriteOptions::no_slowdown set.
|
||||
|
||||
### New Features
|
||||
* Introduced CacheAllocator, which lets the user specify custom allocator for memory in block cache.
|
||||
|
||||
## 5.17.0 (10/05/2018)
|
||||
### Public API Change
|
||||
* For users of `Statistics` objects created via `CreateDBStatistics()`, the format of the string returned by its `ToString()` method has changed.
|
||||
* With LRUCache, when high_pri_pool_ratio > 0, midpoint insertion strategy will be enabled to put low-pri items to the tail of low-pri list (the midpoint) when they first inserted into the cache. This is to make cache entries never get hit age out faster, improving cache efficiency when large background scan presents.
|
||||
* `OnTableFileCreated` will now be called for empty files generated during compaction. In that case, `TableFileCreationInfo::file_path` will be "(nil)" and `TableFileCreationInfo::file_size` will be zero.
|
||||
* Add `FlushOptions::allow_write_stall`, which controls whether Flush calls start working immediately, even if it causes user writes to stall, or will wait until flush can be performed without causing write stall (similar to `CompactRangeOptions::allow_write_stall`). Note that the default value is false, meaning we add delay to Flush calls until stalling can be avoided when possible. This is behavior change compared to previous RocksDB versions, where Flush calls didn't check if they might cause stall or not.
|
||||
* Application using PessimisticTransactionDB is expected to rollback/commit recovered transactions before starting new ones. This assumption is used to skip concurrency control during recovery.
|
||||
|
||||
### New Features
|
||||
* TransactionOptions::skip_concurrency_control allows pessimistic transactions to skip the overhead of concurrency control. Could be used for optimizing certain transactions or during recovery.
|
||||
|
||||
### Bug Fixes
|
||||
* Avoid creating empty SSTs and subsequently deleting them in certain cases during compaction.
|
||||
* Sync CURRENT file contents during checkpoint.
|
||||
|
||||
## 5.16.3 (10/1/2018)
|
||||
### Bug Fixes
|
||||
* Fix crash caused when `CompactFiles` run with `CompactionOptions::compression == CompressionType::kDisableCompressionOption`. Now that setting causes the compression type to be chosen according to the column family-wide compression options.
|
||||
|
||||
## 5.16.2 (9/21/2018)
|
||||
### Bug Fixes
|
||||
* Fix bug in partition filters with format_version=4.
|
||||
|
||||
## 5.16.1 (9/17/2018)
|
||||
### Bug Fixes
|
||||
* Remove trace_analyzer_tool from rocksdb_lib target in TARGETS file.
|
||||
* Fix RocksDB Java build and tests.
|
||||
* Remove sync point in Block destructor.
|
||||
|
||||
## 5.16.0 (8/21/2018)
|
||||
### Public API Change
|
||||
* The merge operands are passed to `MergeOperator::ShouldMerge` in the reversed order relative to how they were merged (passed to FullMerge or FullMergeV2) for performance reasons
|
||||
* GetAllKeyVersions() to take an extra argument of `max_num_ikeys`.
|
||||
* Using ZSTD dictionary trainer (i.e., setting `CompressionOptions::zstd_max_train_bytes` to a nonzero value) now requires ZSTD version 1.1.3 or later.
|
||||
|
||||
### New Features
|
||||
* Changes the format of index blocks by delta encoding the index values, which are the block handles. This saves the encoding of BlockHandle::offset of the non-head index entries in each restart interval. The feature is backward compatible but not forward compatible. It is disabled by default unless format_version 4 or above is used.
|
||||
* Add a new tool: trace_analyzer. Trace_analyzer analyzes the trace file generated by using trace_replay API. It can convert the binary format trace file to a human readable txt file, output the statistics of the analyzed query types such as access statistics and size statistics, combining the dumped whole key space file to analyze, support query correlation analyzing, and etc. Current supported query types are: Get, Put, Delete, SingleDelete, DeleteRange, Merge, Iterator (Seek, SeekForPrev only).
|
||||
* Add hash index support to data blocks, which helps reducing the cpu utilization of point-lookup operations. This feature is backward compatible with the data block created without the hash index. It is disabled by default unless BlockBasedTableOptions::data_block_index_type is set to data_block_index_type = kDataBlockBinaryAndHash.
|
||||
|
||||
### Bug Fixes
|
||||
* Fix a bug in misreporting the estimated partition index size in properties block.
|
||||
|
||||
## 5.15.0 (7/17/2018)
|
||||
### Public API Change
|
||||
* Remove managed iterator. ReadOptions.managed is not effective anymore.
|
||||
* For bottommost_compression, a compatible CompressionOptions is added via `bottommost_compression_opts`. To keep backward compatible, a new boolean `enabled` is added to CompressionOptions. For compression_opts, it will be always used no matter what value of `enabled` is. For bottommost_compression_opts, it will only be used when user set `enabled=true`, otherwise, compression_opts will be used for bottommost_compression as default.
|
||||
* With LRUCache, when high_pri_pool_ratio > 0, midpoint insertion strategy will be enabled to put low-pri items to the tail of low-pri list (the midpoint) when they first inserted into the cache. This is to make cache entries never get hit age out faster, improving cache efficiency when large background scan presents.
|
||||
* For users of `Statistics` objects created via `CreateDBStatistics()`, the format of the string returned by its `ToString()` method has changed.
|
||||
* The "rocksdb.num.entries" table property no longer counts range deletion tombstones as entries.
|
||||
|
||||
### New Features
|
||||
* Changes the format of index blocks by storing the key in their raw form rather than converting them to InternalKey. This saves 8 bytes per index key. The feature is backward compatbile but not forward compatible. It is disabled by default unless format_version 3 or above is used.
|
||||
* Changes the format of index blocks by storing the key in their raw form rather than converting them to InternalKey. This saves 8 bytes per index key. The feature is backward compatible but not forward compatible. It is disabled by default unless format_version 3 or above is used.
|
||||
* Avoid memcpy when reading mmap files with OpenReadOnly and max_open_files==-1.
|
||||
* Support dynamically changing `ColumnFamilyOptions::ttl` via `SetOptions()`.
|
||||
* Add a new table property, "rocksdb.num.range-deletions", which counts the number of range deletion tombstones in the table.
|
||||
* Improve the performance of iterators doing long range scans by using readahead, when using direct IO.
|
||||
* pin_top_level_index_and_filter (default true) in BlockBasedTableOptions can be used in combination with cache_index_and_filter_blocks to prefetch and pin the top-level index of partitioned index and filter blocks in cache. It has no impact when cache_index_and_filter_blocks is false.
|
||||
* Avoid memcpy when reading mmap files with OpenReadOnly and max_open_files==-1
|
||||
* Write properties meta-block at the end of block-based table to save read-ahead IO.
|
||||
|
||||
### Bug Fixes
|
||||
* fix deadlock with enable_pipelined_write=true and max_successive_merges > 0
|
||||
* Fix deadlock with enable_pipelined_write=true and max_successive_merges > 0
|
||||
* Check conflict at output level in CompactFiles.
|
||||
* Fix corruption in non-iterator reads when mmap is used for file reads
|
||||
* Fix bug with prefix search in partition filters where a shared prefix would be ignored from the later partitions. The bug could report an eixstent key as missing. The bug could be triggered if prefix_extractor is set and partition filters is enabled.
|
||||
* Change default value of `bytes_max_delete_chunk` to 0 in NewSstFileManager() as it doesn't work well with checkpoints.
|
||||
* Fix a bug caused by not copying the block trailer with compressed SST file, direct IO, prefetcher and no compressed block cache.
|
||||
* Fix write can stuck indefinitely if enable_pipelined_write=true. The issue exists since pipelined write was introduced in 5.5.0.
|
||||
|
||||
## 5.14.0 (5/16/2018)
|
||||
### Public API Change
|
||||
@@ -127,7 +191,8 @@
|
||||
* `BackupableDBOptions::max_valid_backups_to_open == 0` now means no backups will be opened during BackupEngine initialization. Previously this condition disabled limiting backups opened.
|
||||
* `DBOptions::preserve_deletes` is a new option that allows one to specify that DB should not drop tombstones for regular deletes if they have sequence number larger than what was set by the new API call `DB::SetPreserveDeletesSequenceNumber(SequenceNumber seqnum)`. Disabled by default.
|
||||
* API call `DB::SetPreserveDeletesSequenceNumber(SequenceNumber seqnum)` was added, users who wish to preserve deletes are expected to periodically call this function to advance the cutoff seqnum (all deletes made before this seqnum can be dropped by DB). It's user responsibility to figure out how to advance the seqnum in the way so the tombstones are kept for the desired period of time, yet are eventually processed in time and don't eat up too much space.
|
||||
* `ReadOptions::iter_start_seqnum` was added; if set to something > 0 user will see 2 changes in iterators behavior 1) only keys written with sequence larger than this parameter would be returned and 2) the `Slice` returned by iter->key() now points to the memory that keep User-oriented representation of the internal key, rather than user key. New struct `FullKey` was added to represent internal keys, along with a new helper function `ParseFullKey(const Slice& internal_key, FullKey* result);`.
|
||||
* `ReadOptions::iter_start_seqnum` was added;
|
||||
if set to something > 0 user will see 2 changes in iterators behavior 1) only keys written with sequence larger than this parameter would be returned and 2) the `Slice` returned by iter->key() now points to the memory that keep User-oriented representation of the internal key, rather than user key. New struct `FullKey` was added to represent internal keys, along with a new helper function `ParseFullKey(const Slice& internal_key, FullKey* result);`.
|
||||
* Deprecate trash_dir param in NewSstFileManager, right now we will rename deleted files to <name>.trash instead of moving them to trash directory
|
||||
* Allow setting a custom trash/DB size ratio limit in the SstFileManager, after which files that are to be scheduled for deletion are deleted immediately, regardless of any delete ratelimit.
|
||||
* Return an error on write if write_options.sync = true and write_options.disableWAL = true to warn user of inconsistent options. Previously we will not write to WAL and not respecting the sync options in this case.
|
||||
|
||||
@@ -382,6 +382,8 @@ VALGRIND_OPTS = --error-exitcode=$(VALGRIND_ERROR) --leak-check=full
|
||||
|
||||
BENCHTOOLOBJECTS = $(BENCH_LIB_SOURCES:.cc=.o) $(LIBOBJECTS) $(TESTUTIL)
|
||||
|
||||
ANALYZETOOLOBJECTS = $(ANALYZER_LIB_SOURCES:.cc=.o)
|
||||
|
||||
EXPOBJECTS = $(EXP_LIB_SOURCES:.cc=.o) $(LIBOBJECTS) $(TESTUTIL)
|
||||
|
||||
TESTS = \
|
||||
@@ -437,6 +439,7 @@ TESTS = \
|
||||
table_properties_collector_test \
|
||||
arena_test \
|
||||
block_test \
|
||||
data_block_hash_index_test \
|
||||
cache_test \
|
||||
corruption_test \
|
||||
slice_transform_test \
|
||||
@@ -529,6 +532,8 @@ TESTS = \
|
||||
write_prepared_transaction_test \
|
||||
write_unprepared_transaction_test \
|
||||
db_universal_compaction_test \
|
||||
trace_analyzer_test \
|
||||
repeatable_thread_test \
|
||||
|
||||
PARALLEL_TEST = \
|
||||
backupable_db_test \
|
||||
@@ -572,12 +577,13 @@ TOOLS = \
|
||||
rocksdb_dump \
|
||||
rocksdb_undump \
|
||||
blob_dump \
|
||||
trace_analyzer \
|
||||
|
||||
TEST_LIBS = \
|
||||
librocksdb_env_basic_test.a
|
||||
|
||||
# TODO: add back forward_iterator_bench, after making it build in all environemnts.
|
||||
BENCHMARKS = db_bench table_reader_bench cache_bench memtablerep_bench column_aware_encoding_exp persistent_cache_bench
|
||||
BENCHMARKS = db_bench table_reader_bench cache_bench memtablerep_bench column_aware_encoding_exp persistent_cache_bench range_del_aggregator_bench
|
||||
|
||||
# if user didn't config LIBNAME, set the default
|
||||
ifeq ($(LIBNAME),)
|
||||
@@ -665,7 +671,7 @@ $(SHARED4): $(shared_all_libobjects)
|
||||
endif # PLATFORM_SHARED_EXT
|
||||
|
||||
.PHONY: blackbox_crash_test check clean coverage crash_test ldb_tests package \
|
||||
release tags valgrind_check whitebox_crash_test format static_lib shared_lib all \
|
||||
release tags tags0 valgrind_check whitebox_crash_test format static_lib shared_lib all \
|
||||
dbg rocksdbjavastatic rocksdbjava install install-static install-shared uninstall \
|
||||
analyze tools tools_lib
|
||||
|
||||
@@ -997,8 +1003,10 @@ unity.a: unity.o
|
||||
$(AM_V_AR)rm -f $@
|
||||
$(AM_V_at)$(AR) $(ARFLAGS) $@ unity.o
|
||||
|
||||
|
||||
TOOLLIBOBJECTS = $(TOOL_LIB_SOURCES:.cc=.o)
|
||||
# try compiling db_test with unity
|
||||
unity_test: db/db_test.o db/db_test_util.o $(TESTHARNESS) unity.a
|
||||
unity_test: db/db_test.o db/db_test_util.o $(TESTHARNESS) $(TOOLLIBOBJECTS) unity.a
|
||||
$(AM_LINK)
|
||||
./unity_test
|
||||
|
||||
@@ -1018,6 +1026,13 @@ tags:
|
||||
cscope -b `$(FIND) . -name '*.cc'` `$(FIND) . -name '*.h'` `$(FIND) . -name '*.c'`
|
||||
ctags -e -R -o etags *
|
||||
|
||||
tags0:
|
||||
ctags -R .
|
||||
cscope -b `$(FIND) . -name '*.cc' -and ! -name '*_test.cc'` \
|
||||
`$(FIND) . -name '*.c' -and ! -name '*_test.c'` \
|
||||
`$(FIND) . -name '*.h' -and ! -name '*_test.h'`
|
||||
ctags -e -R -o etags *
|
||||
|
||||
format:
|
||||
build_tools/format-diff.sh
|
||||
|
||||
@@ -1031,7 +1046,7 @@ $(LIBRARY): $(LIBOBJECTS)
|
||||
$(AM_V_AR)rm -f $@
|
||||
$(AM_V_at)$(AR) $(ARFLAGS) $@ $(LIBOBJECTS)
|
||||
|
||||
$(TOOLS_LIBRARY): $(BENCH_LIB_SOURCES:.cc=.o) $(TOOL_LIB_SOURCES:.cc=.o) $(LIB_SOURCES:.cc=.o) $(TESTUTIL)
|
||||
$(TOOLS_LIBRARY): $(BENCH_LIB_SOURCES:.cc=.o) $(TOOL_LIB_SOURCES:.cc=.o) $(LIB_SOURCES:.cc=.o) $(TESTUTIL) $(ANALYZER_LIB_SOURCES:.cc=.o)
|
||||
$(AM_V_AR)rm -f $@
|
||||
$(AM_V_at)$(AR) $(ARFLAGS) $@ $^
|
||||
|
||||
@@ -1042,6 +1057,9 @@ librocksdb_env_basic_test.a: env/env_basic_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
db_bench: tools/db_bench.o $(BENCHTOOLOBJECTS)
|
||||
$(AM_LINK)
|
||||
|
||||
trace_analyzer: tools/trace_analyzer.o $(ANALYZETOOLOBJECTS) $(LIBOBJECTS)
|
||||
$(AM_LINK)
|
||||
|
||||
cache_bench: cache/cache_bench.o $(LIBOBJECTS) $(TESTUTIL)
|
||||
$(AM_LINK)
|
||||
|
||||
@@ -1350,6 +1368,9 @@ table_test: table/table_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
block_test: table/block_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
data_block_hash_index_test: table/data_block_hash_index_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
inlineskiplist_test: memtable/inlineskiplist_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
@@ -1446,6 +1467,9 @@ options_util_test: utilities/options/options_util_test.o $(LIBOBJECTS) $(TESTHAR
|
||||
db_bench_tool_test: tools/db_bench_tool_test.o $(BENCHTOOLOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
trace_analyzer_test: tools/trace_analyzer_test.o $(LIBOBJECTS) $(ANALYZETOOLOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
event_logger_test: util/event_logger_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
@@ -1527,9 +1551,15 @@ lua_test: utilities/lua/rocks_lua_test.o db/db_test_util.o $(LIBOBJECTS) $(TESTH
|
||||
range_del_aggregator_test: db/range_del_aggregator_test.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
range_del_aggregator_bench: db/range_del_aggregator_bench.o $(LIBOBJECTS) $(TESTUTIL)
|
||||
$(AM_LINK)
|
||||
|
||||
blob_db_test: utilities/blob_db/blob_db_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
repeatable_thread_test: util/repeatable_thread_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
#-------------------------------------------------
|
||||
# make install related stuff
|
||||
INSTALL_PATH ?= /usr/local
|
||||
@@ -1600,7 +1630,7 @@ ZLIB_SHA256 ?= c3e5e9fdd5004dcb542feda5ee4f0ff0744628baf8ed2dd5d66f8ca1197cb1a1
|
||||
ZLIB_DOWNLOAD_BASE ?= http://zlib.net
|
||||
BZIP2_VER ?= 1.0.6
|
||||
BZIP2_SHA256 ?= a2848f34fcd5d6cf47def00461fcb528a0484d8edef8208d6d2e2909dc61d9cd
|
||||
BZIP2_DOWNLOAD_BASE ?= http://www.bzip.org
|
||||
BZIP2_DOWNLOAD_BASE ?= https://web.archive.org/web/20180624184835/http://www.bzip.org
|
||||
SNAPPY_VER ?= 1.1.4
|
||||
SNAPPY_SHA256 ?= 134bfe122fd25599bb807bb8130e7ba6d9bdb851e0b16efcb83ac4f5d0b70057
|
||||
SNAPPY_DOWNLOAD_BASE ?= https://github.com/google/snappy/releases/download
|
||||
@@ -1760,20 +1790,26 @@ rocksdbjavastaticrelease: rocksdbjavastatic
|
||||
cd java/target;jar -uf $(ROCKSDB_JAR_ALL) librocksdbjni-*.so librocksdbjni-*.jnilib
|
||||
cd java/target/classes;jar -uf ../$(ROCKSDB_JAR_ALL) org/rocksdb/*.class org/rocksdb/util/*.class
|
||||
|
||||
rocksdbjavastaticreleasedocker: rocksdbjavastatic
|
||||
DOCKER_LINUX_X64_CONTAINER=`docker ps -aqf name=rocksdb_linux_x64-be`; \
|
||||
if [ -z "$$DOCKER_LINUX_X64_CONTAINER" ]; then \
|
||||
docker container create --attach stdin --attach stdout --attach stderr --volume `pwd`:/rocksdb-host --name rocksdb_linux_x64-be evolvedbinary/rocksjava:centos6_x64-be /rocksdb-host/java/crossbuild/docker-build-linux-centos.sh; \
|
||||
fi
|
||||
docker start -a rocksdb_linux_x64-be
|
||||
rocksdbjavastaticreleasedocker: rocksdbjavastatic rocksdbjavastaticdockerx86 rocksdbjavastaticdockerx86_64
|
||||
cd java;jar -cf target/$(ROCKSDB_JAR_ALL) HISTORY*.md
|
||||
cd java/target;jar -uf $(ROCKSDB_JAR_ALL) librocksdbjni-*.so librocksdbjni-*.jnilib
|
||||
cd java/target/classes;jar -uf ../$(ROCKSDB_JAR_ALL) org/rocksdb/*.class org/rocksdb/util/*.class
|
||||
|
||||
rocksdbjavastaticdockerx86:
|
||||
mkdir -p java/target
|
||||
DOCKER_LINUX_X86_CONTAINER=`docker ps -aqf name=rocksdb_linux_x86-be`; \
|
||||
if [ -z "$$DOCKER_LINUX_X86_CONTAINER" ]; then \
|
||||
docker container create --attach stdin --attach stdout --attach stderr --volume `pwd`:/rocksdb-host --name rocksdb_linux_x86-be evolvedbinary/rocksjava:centos6_x86-be /rocksdb-host/java/crossbuild/docker-build-linux-centos.sh; \
|
||||
fi
|
||||
docker start -a rocksdb_linux_x86-be
|
||||
cd java;jar -cf target/$(ROCKSDB_JAR_ALL) HISTORY*.md
|
||||
cd java/target;jar -uf $(ROCKSDB_JAR_ALL) librocksdbjni-*.so librocksdbjni-*.jnilib
|
||||
cd java/target/classes;jar -uf ../$(ROCKSDB_JAR_ALL) org/rocksdb/*.class org/rocksdb/util/*.class
|
||||
|
||||
rocksdbjavastaticdockerx86_64:
|
||||
mkdir -p java/target
|
||||
DOCKER_LINUX_X64_CONTAINER=`docker ps -aqf name=rocksdb_linux_x64-be`; \
|
||||
if [ -z "$$DOCKER_LINUX_X64_CONTAINER" ]; then \
|
||||
docker container create --attach stdin --attach stdout --attach stderr --volume `pwd`:/rocksdb-host --name rocksdb_linux_x64-be evolvedbinary/rocksjava:centos6_x64-be /rocksdb-host/java/crossbuild/docker-build-linux-centos.sh; \
|
||||
fi
|
||||
docker start -a rocksdb_linux_x64-be
|
||||
|
||||
rocksdbjavastaticdockerppc64le:
|
||||
mkdir -p java/target
|
||||
@@ -1829,7 +1865,7 @@ $(java_libobjects): jl/%.o: %.cc
|
||||
rocksdbjava: $(java_all_libobjects)
|
||||
$(AM_V_GEN)cd java;$(MAKE) javalib;
|
||||
$(AM_V_at)rm -f ./java/target/$(ROCKSDBJNILIB)
|
||||
$(AM_V_at)$(CXX) $(CXXFLAGS) -I./java/. $(JAVA_INCLUDE) -shared -fPIC -o ./java/target/$(ROCKSDBJNILIB) $(JNI_NATIVE_SOURCES) $(java_libobjects) $(JAVA_LDFLAGS) $(COVERAGEFLAGS)
|
||||
$(AM_V_at)$(CXX) $(CXXFLAGS) -I./java/. $(JAVA_INCLUDE) -shared -fPIC -o ./java/target/$(ROCKSDBJNILIB) $(JNI_NATIVE_SOURCES) $(java_all_libobjects) $(JAVA_LDFLAGS) $(COVERAGEFLAGS)
|
||||
$(AM_V_at)cd java;jar -cf target/$(ROCKSDB_JAR) HISTORY*.md
|
||||
$(AM_V_at)cd java/target;jar -uf $(ROCKSDB_JAR) $(ROCKSDBJNILIB)
|
||||
$(AM_V_at)cd java/target/classes;jar -uf ../$(ROCKSDB_JAR) org/rocksdb/*.class org/rocksdb/util/*.class
|
||||
@@ -1898,7 +1934,7 @@ endif
|
||||
# Source files dependencies detection
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
all_sources = $(LIB_SOURCES) $(MAIN_SOURCES) $(MOCK_LIB_SOURCES) $(TOOL_LIB_SOURCES) $(BENCH_LIB_SOURCES) $(TEST_LIB_SOURCES) $(EXP_LIB_SOURCES)
|
||||
all_sources = $(LIB_SOURCES) $(MAIN_SOURCES) $(MOCK_LIB_SOURCES) $(TOOL_LIB_SOURCES) $(BENCH_LIB_SOURCES) $(TEST_LIB_SOURCES) $(EXP_LIB_SOURCES) $(ANALYZER_LIB_SOURCES)
|
||||
DEPFILES = $(all_sources:.cc=.cc.d)
|
||||
|
||||
# Add proper dependency support so changing a .h file forces a .cc file to
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
load("@fbcode_macros//build_defs:auto_headers.bzl", "AutoHeaders")
|
||||
|
||||
REPO_PATH = package_name() + "/"
|
||||
|
||||
BUCK_BINS = "buck-out/gen/" + REPO_PATH
|
||||
@@ -106,7 +108,6 @@ cpp_library(
|
||||
"db/log_writer.cc",
|
||||
"db/logs_with_prep_tracker.cc",
|
||||
"db/malloc_stats.cc",
|
||||
"db/managed_iterator.cc",
|
||||
"db/memtable.cc",
|
||||
"db/memtable_list.cc",
|
||||
"db/merge_helper.cc",
|
||||
@@ -172,6 +173,8 @@ cpp_library(
|
||||
"table/cuckoo_table_builder.cc",
|
||||
"table/cuckoo_table_factory.cc",
|
||||
"table/cuckoo_table_reader.cc",
|
||||
"table/data_block_footer.cc",
|
||||
"table/data_block_hash_index.cc",
|
||||
"table/flush_block_policy.cc",
|
||||
"table/format.cc",
|
||||
"table/full_filter_block.cc",
|
||||
@@ -219,24 +222,24 @@ cpp_library(
|
||||
"util/slice.cc",
|
||||
"util/sst_file_manager_impl.cc",
|
||||
"util/status.cc",
|
||||
"util/status_message.cc",
|
||||
"util/string_util.cc",
|
||||
"util/sync_point.cc",
|
||||
"util/sync_point_impl.cc",
|
||||
"util/thread_local.cc",
|
||||
"util/threadpool_imp.cc",
|
||||
"util/trace_replay.cc",
|
||||
"util/transaction_test_util.cc",
|
||||
"util/xxhash.cc",
|
||||
"utilities/backupable/backupable_db.cc",
|
||||
"utilities/blob_db/blob_compaction_filter.cc",
|
||||
"utilities/blob_db/blob_db.cc",
|
||||
"utilities/blob_db/blob_db_impl.cc",
|
||||
"utilities/blob_db/blob_db_impl_filesnapshot.cc",
|
||||
"utilities/blob_db/blob_dump_tool.cc",
|
||||
"utilities/blob_db/blob_file.cc",
|
||||
"utilities/blob_db/blob_log_format.cc",
|
||||
"utilities/blob_db/blob_log_reader.cc",
|
||||
"utilities/blob_db/blob_log_writer.cc",
|
||||
"utilities/blob_db/ttl_extractor.cc",
|
||||
"utilities/cassandra/cassandra_compaction_filter.cc",
|
||||
"utilities/cassandra/format.cc",
|
||||
"utilities/cassandra/merge_operator.cc",
|
||||
@@ -271,6 +274,7 @@ cpp_library(
|
||||
"utilities/simulator_cache/sim_cache.cc",
|
||||
"utilities/spatialdb/spatial_db.cc",
|
||||
"utilities/table_properties_collectors/compact_on_deletion_collector.cc",
|
||||
"utilities/trace/file_trace_reader_writer.cc",
|
||||
"utilities/transactions/optimistic_transaction.cc",
|
||||
"utilities/transactions/optimistic_transaction_db_impl.cc",
|
||||
"utilities/transactions/pessimistic_transaction.cc",
|
||||
@@ -288,7 +292,7 @@ cpp_library(
|
||||
"utilities/write_batch_with_index/write_batch_with_index.cc",
|
||||
"utilities/write_batch_with_index/write_batch_with_index_internal.cc",
|
||||
],
|
||||
headers = AutoHeaders.RECURSIVE_GLOB,
|
||||
auto_headers = AutoHeaders.RECURSIVE_GLOB,
|
||||
arch_preprocessor_flags = rocksdb_arch_preprocessor_flags,
|
||||
compiler_flags = rocksdb_compiler_flags,
|
||||
preprocessor_flags = rocksdb_preprocessor_flags,
|
||||
@@ -301,6 +305,7 @@ cpp_library(
|
||||
srcs = [
|
||||
"db/db_test_util.cc",
|
||||
"table/mock_table.cc",
|
||||
"tools/trace_analyzer_tool.cc",
|
||||
"util/fault_injection_test_env.cc",
|
||||
"util/testharness.cc",
|
||||
"util/testutil.cc",
|
||||
@@ -309,7 +314,7 @@ cpp_library(
|
||||
"utilities/col_buf_encoder.cc",
|
||||
"utilities/column_aware_encoding_util.cc",
|
||||
],
|
||||
headers = AutoHeaders.RECURSIVE_GLOB,
|
||||
auto_headers = AutoHeaders.RECURSIVE_GLOB,
|
||||
arch_preprocessor_flags = rocksdb_arch_preprocessor_flags,
|
||||
compiler_flags = rocksdb_compiler_flags,
|
||||
preprocessor_flags = rocksdb_preprocessor_flags,
|
||||
@@ -321,9 +326,10 @@ cpp_library(
|
||||
name = "rocksdb_tools_lib",
|
||||
srcs = [
|
||||
"tools/db_bench_tool.cc",
|
||||
"tools/trace_analyzer_tool.cc",
|
||||
"util/testutil.cc",
|
||||
],
|
||||
headers = AutoHeaders.RECURSIVE_GLOB,
|
||||
auto_headers = AutoHeaders.RECURSIVE_GLOB,
|
||||
arch_preprocessor_flags = rocksdb_arch_preprocessor_flags,
|
||||
compiler_flags = rocksdb_compiler_flags,
|
||||
preprocessor_flags = rocksdb_preprocessor_flags,
|
||||
@@ -334,7 +340,7 @@ cpp_library(
|
||||
cpp_library(
|
||||
name = "env_basic_test_lib",
|
||||
srcs = ["env/env_basic_test.cc"],
|
||||
headers = AutoHeaders.RECURSIVE_GLOB,
|
||||
auto_headers = AutoHeaders.RECURSIVE_GLOB,
|
||||
arch_preprocessor_flags = rocksdb_arch_preprocessor_flags,
|
||||
compiler_flags = rocksdb_compiler_flags,
|
||||
preprocessor_flags = rocksdb_preprocessor_flags,
|
||||
@@ -499,6 +505,11 @@ ROCKS_TESTS = [
|
||||
"table/cuckoo_table_reader_test.cc",
|
||||
"serial",
|
||||
],
|
||||
[
|
||||
"data_block_hash_index_test",
|
||||
"table/data_block_hash_index_test.cc",
|
||||
"serial",
|
||||
],
|
||||
[
|
||||
"date_tiered_test",
|
||||
"utilities/date_tiered/date_tiered_test.cc",
|
||||
@@ -924,6 +935,11 @@ ROCKS_TESTS = [
|
||||
"db/repair_test.cc",
|
||||
"serial",
|
||||
],
|
||||
[
|
||||
"repeatable_thread_test",
|
||||
"util/repeatable_thread_test.cc",
|
||||
"serial",
|
||||
],
|
||||
[
|
||||
"sim_cache_test",
|
||||
"utilities/simulator_cache/sim_cache_test.cc",
|
||||
@@ -984,6 +1000,11 @@ ROCKS_TESTS = [
|
||||
"util/timer_queue_test.cc",
|
||||
"serial",
|
||||
],
|
||||
[
|
||||
"trace_analyzer_test",
|
||||
"tools/trace_analyzer_test.cc",
|
||||
"serial",
|
||||
],
|
||||
[
|
||||
"transaction_test",
|
||||
"utilities/transactions/transaction_test.cc",
|
||||
@@ -1066,20 +1087,19 @@ if not is_opt_mode:
|
||||
ttype = "gtest" if test_cfg[2] == "parallel" else "simple"
|
||||
test_bin = test_name + "_bin"
|
||||
|
||||
cpp_binary (
|
||||
name = test_bin,
|
||||
srcs = [test_cc],
|
||||
deps = [":rocksdb_test_lib"],
|
||||
preprocessor_flags = rocksdb_preprocessor_flags,
|
||||
arch_preprocessor_flags = rocksdb_arch_preprocessor_flags,
|
||||
compiler_flags = rocksdb_compiler_flags,
|
||||
external_deps = rocksdb_external_deps,
|
||||
cpp_binary(
|
||||
name = test_bin,
|
||||
srcs = [test_cc],
|
||||
arch_preprocessor_flags = rocksdb_arch_preprocessor_flags,
|
||||
compiler_flags = rocksdb_compiler_flags,
|
||||
preprocessor_flags = rocksdb_preprocessor_flags,
|
||||
deps = [":rocksdb_test_lib"],
|
||||
external_deps = rocksdb_external_deps,
|
||||
)
|
||||
|
||||
custom_unittest(
|
||||
name = test_name,
|
||||
type = ttype,
|
||||
deps = [":" + test_bin],
|
||||
command = [TEST_RUNNER, BUCK_BINS + test_bin]
|
||||
name = test_name,
|
||||
command = [TEST_RUNNER, BUCK_BINS + test_bin],
|
||||
type = ttype,
|
||||
deps = [":" + test_bin],
|
||||
)
|
||||
|
||||
@@ -12,6 +12,7 @@ At Facebook, we use RocksDB as storage engines in multiple data management servi
|
||||
6. LogDevice -- a distributed data store for logs [2]
|
||||
|
||||
[1] https://research.facebook.com/publications/realtime-data-processing-at-facebook/
|
||||
|
||||
[2] https://code.facebook.com/posts/357056558062811/logdevice-a-distributed-data-store-for-logs/
|
||||
|
||||
## LinkedIn
|
||||
@@ -88,3 +89,6 @@ LzLabs is using RocksDB as a storage engine in their multi-database distributed
|
||||
|
||||
## ProfaneDB
|
||||
[ProfaneDB](https://profanedb.gitlab.io/) is a database for Protocol Buffers, and uses RocksDB for storage. It is accessible via gRPC, and the schema is defined using directly `.proto` files.
|
||||
|
||||
## IOTA Foundation
|
||||
[IOTA Foundation](https://www.iota.org/) is using RocksDB in the [IOTA Reference Implementation (IRI)](https://github.com/iotaledger/iri) to store the local state of the Tangle. The Tangle is the first open-source distributed ledger powering the future of the Internet of Things.
|
||||
@@ -109,12 +109,14 @@ def generate_targets(repo_path):
|
||||
"rocksdb_test_lib",
|
||||
src_mk.get("MOCK_LIB_SOURCES", []) +
|
||||
src_mk.get("TEST_LIB_SOURCES", []) +
|
||||
src_mk.get("EXP_LIB_SOURCES", []),
|
||||
src_mk.get("EXP_LIB_SOURCES", []) +
|
||||
src_mk.get("ANALYZER_LIB_SOURCES", []),
|
||||
[":rocksdb_lib"])
|
||||
# rocksdb_tools_lib
|
||||
TARGETS.add_library(
|
||||
"rocksdb_tools_lib",
|
||||
src_mk.get("BENCH_LIB_SOURCES", []) +
|
||||
src_mk.get("ANALYZER_LIB_SOURCES", []) +
|
||||
["util/testutil.cc"],
|
||||
[":rocksdb_lib"])
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ def pretty_list(lst, indent=8):
|
||||
|
||||
if len(lst) == 1:
|
||||
return "\"%s\"" % lst[0]
|
||||
|
||||
|
||||
separator = "\",\n%s\"" % (" " * indent)
|
||||
res = separator.join(sorted(lst))
|
||||
res = "\n" + (" " * indent) + "\"" + res + "\",\n" + (" " * (indent - 4))
|
||||
@@ -31,13 +31,16 @@ class TARGETSBuilder:
|
||||
self.targets_file.close()
|
||||
|
||||
def add_library(self, name, srcs, deps=None, headers=None):
|
||||
headers_attr_prefix = ""
|
||||
if headers is None:
|
||||
headers_attr_prefix = "auto_"
|
||||
headers = "AutoHeaders.RECURSIVE_GLOB"
|
||||
self.targets_file.write(targets_cfg.library_template % (
|
||||
name,
|
||||
pretty_list(srcs),
|
||||
headers,
|
||||
pretty_list(deps)))
|
||||
self.targets_file.write(targets_cfg.library_template.format(
|
||||
name=name,
|
||||
srcs=pretty_list(srcs),
|
||||
headers_attr_prefix=headers_attr_prefix,
|
||||
headers=headers,
|
||||
deps=pretty_list(deps)))
|
||||
self.total_lib = self.total_lib + 1
|
||||
|
||||
def add_binary(self, name, srcs, deps=None):
|
||||
|
||||
+19
-18
@@ -2,7 +2,9 @@ from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
from __future__ import unicode_literals
|
||||
rocksdb_target_header = """REPO_PATH = package_name() + "/"
|
||||
rocksdb_target_header = """load("@fbcode_macros//build_defs:auto_headers.bzl", "AutoHeaders")
|
||||
|
||||
REPO_PATH = package_name() + "/"
|
||||
|
||||
BUCK_BINS = "buck-out/gen/" + REPO_PATH
|
||||
|
||||
@@ -73,13 +75,13 @@ if is_opt_mode:
|
||||
|
||||
library_template = """
|
||||
cpp_library(
|
||||
name = "%s",
|
||||
srcs = [%s],
|
||||
headers = %s,
|
||||
name = "{name}",
|
||||
srcs = [{srcs}],
|
||||
{headers_attr_prefix}headers = {headers},
|
||||
arch_preprocessor_flags = rocksdb_arch_preprocessor_flags,
|
||||
compiler_flags = rocksdb_compiler_flags,
|
||||
preprocessor_flags = rocksdb_preprocessor_flags,
|
||||
deps = [%s],
|
||||
deps = [{deps}],
|
||||
external_deps = rocksdb_external_deps,
|
||||
)
|
||||
"""
|
||||
@@ -118,21 +120,20 @@ if not is_opt_mode:
|
||||
ttype = "gtest" if test_cfg[2] == "parallel" else "simple"
|
||||
test_bin = test_name + "_bin"
|
||||
|
||||
cpp_binary (
|
||||
name = test_bin,
|
||||
srcs = [test_cc],
|
||||
deps = [":rocksdb_test_lib"],
|
||||
preprocessor_flags = rocksdb_preprocessor_flags,
|
||||
arch_preprocessor_flags = rocksdb_arch_preprocessor_flags,
|
||||
compiler_flags = rocksdb_compiler_flags,
|
||||
external_deps = rocksdb_external_deps,
|
||||
cpp_binary(
|
||||
name = test_bin,
|
||||
srcs = [test_cc],
|
||||
arch_preprocessor_flags = rocksdb_arch_preprocessor_flags,
|
||||
compiler_flags = rocksdb_compiler_flags,
|
||||
preprocessor_flags = rocksdb_preprocessor_flags,
|
||||
deps = [":rocksdb_test_lib"],
|
||||
external_deps = rocksdb_external_deps,
|
||||
)
|
||||
|
||||
custom_unittest(
|
||||
name = test_name,
|
||||
type = ttype,
|
||||
deps = [":" + test_bin],
|
||||
command = [TEST_RUNNER, BUCK_BINS + test_bin]
|
||||
name = test_name,
|
||||
command = [TEST_RUNNER, BUCK_BINS + test_bin],
|
||||
type = ttype,
|
||||
deps = [":" + test_bin],
|
||||
)
|
||||
|
||||
"""
|
||||
@@ -53,11 +53,13 @@ if [ -z "$ROCKSDB_NO_FBCODE" -a -d /mnt/gvfs/third-party ]; then
|
||||
FBCODE_BUILD="true"
|
||||
# If we're compiling with TSAN we need pic build
|
||||
PIC_BUILD=$COMPILE_WITH_TSAN
|
||||
if [ -z "$ROCKSDB_FBCODE_BUILD_WITH_481" ]; then
|
||||
source "$PWD/build_tools/fbcode_config.sh"
|
||||
else
|
||||
if [ -n "$ROCKSDB_FBCODE_BUILD_WITH_481" ]; then
|
||||
# we need this to build with MySQL. Don't use for other purposes.
|
||||
source "$PWD/build_tools/fbcode_config4.8.1.sh"
|
||||
elif [ -n "$ROCKSDB_FBCODE_BUILD_WITH_5xx" ]; then
|
||||
source "$PWD/build_tools/fbcode_config.sh"
|
||||
else
|
||||
source "$PWD/build_tools/fbcode_config_platform007.sh"
|
||||
fi
|
||||
fi
|
||||
|
||||
|
||||
+18
-19
@@ -1,19 +1,18 @@
|
||||
# shellcheck disable=SC2148
|
||||
GCC_BASE=/mnt/gvfs/third-party2/gcc/8219ec1bcedf8ad9da05e121e193364de2cc4f61/5.x/centos6-native/c447969
|
||||
CLANG_BASE=/mnt/gvfs/third-party2/llvm-fb/64d8d58e3d84f8bde7a029763d4f5baf39d0d5b9/stable/centos6-native/6aaf4de
|
||||
LIBGCC_BASE=/mnt/gvfs/third-party2/libgcc/ba9be983c81de7299b59fe71950c664a84dcb5f8/5.x/gcc-5-glibc-2.23/339d858
|
||||
GLIBC_BASE=/mnt/gvfs/third-party2/glibc/f20197cf3d4bd50339c9777aaa0b2ccadad9e2cb/2.23/gcc-5-glibc-2.23/ca1d1c0
|
||||
SNAPPY_BASE=/mnt/gvfs/third-party2/snappy/6427ce8c7496e4ab06c2da81543b94c0de8be3d0/1.1.3/gcc-5-glibc-2.23/9bc6787
|
||||
ZLIB_BASE=/mnt/gvfs/third-party2/zlib/8f1e8b867d26efef93eac2fabbdb2e1d512665d7/1.2.8/gcc-5-glibc-2.23/9bc6787
|
||||
BZIP2_BASE=/mnt/gvfs/third-party2/bzip2/70471c0571559fe0af7db6d7e8860b93a7eadfe1/1.0.6/gcc-5-glibc-2.23/9bc6787
|
||||
LZ4_BASE=/mnt/gvfs/third-party2/lz4/453c89d6f0e68cdf1c151c769197fabedad9cac8/r131/gcc-5-glibc-2.23/9bc6787
|
||||
ZSTD_BASE=/mnt/gvfs/third-party2/zstd/00a40fa5f8bd2cd0622f2e868552793aef37ccf4/1.3.0/gcc-5-glibc-2.23/03859b5
|
||||
GFLAGS_BASE=/mnt/gvfs/third-party2/gflags/47eef08f9acb77de982fbda6047c26d330739538/2.2.0/gcc-5-glibc-2.23/9bc6787
|
||||
JEMALLOC_BASE=/mnt/gvfs/third-party2/jemalloc/4414ddc78df8008b35cc4adac23590ad29148584/master/gcc-5-glibc-2.23/d506c82
|
||||
NUMA_BASE=/mnt/gvfs/third-party2/numa/9d7ae2693d05d62f9a579cb21e6b717cf257a75d/2.0.11/gcc-5-glibc-2.23/9bc6787
|
||||
LIBUNWIND_BASE=/mnt/gvfs/third-party2/libunwind/2b2dd58e3a52ccf2c1d827def59e5f740de0ad15/1.2/gcc-5-glibc-2.23/b443de1
|
||||
TBB_BASE=/mnt/gvfs/third-party2/tbb/379addf7ab2468a2b4293b47456cfcd1c9cb318d/4.3/gcc-5-glibc-2.23/9bc6787
|
||||
KERNEL_HEADERS_BASE=/mnt/gvfs/third-party2/kernel-headers/3f68f5fe65a85b7c2d3e66852268fbd1efdb3151/4.0.9-36_fbk5_2933_gd092e3f/gcc-5-glibc-2.23/da39a3e
|
||||
BINUTILS_BASE=/mnt/gvfs/third-party2/binutils/b9fab0aec99d9c36408e810b2677e91c12807afd/2.28/centos6-native/da39a3e
|
||||
VALGRIND_BASE=/mnt/gvfs/third-party2/valgrind/423431d61786b20bcc3bde8972901130cb29e6b3/3.11.0/gcc-5-glibc-2.23/9bc6787
|
||||
LUA_BASE=/mnt/gvfs/third-party2/lua/3b0bb3bd9a0f690a069c479fcc0f7424fc7456d2/5.2.3/gcc-5-glibc-2.23/65372bd
|
||||
GCC_BASE=/mnt/gvfs/third-party2/gcc/112ec378fec7002ad3e09afde022e656049f7191/5.x/centos7-native/c447969
|
||||
CLANG_BASE=/mnt/gvfs/third-party2/llvm-fb/04999bdb3ce81a11073535dcb00b5e13dc1cbaf5/stable/centos7-native/c9f9104
|
||||
LIBGCC_BASE=/mnt/gvfs/third-party2/libgcc/92b0c8e5c8eecc71eb042594ce1ab3413799b385/5.x/gcc-5-glibc-2.23/339d858
|
||||
GLIBC_BASE=/mnt/gvfs/third-party2/glibc/3d8698d5973ba94f41620a80a67e4457fdf01e90/2.23/gcc-5-glibc-2.23/ca1d1c0
|
||||
SNAPPY_BASE=/mnt/gvfs/third-party2/snappy/7f9bdaada18f59bc27ec2b0871eb8a6144343aef/1.1.3/gcc-5-glibc-2.23/9bc6787
|
||||
ZLIB_BASE=/mnt/gvfs/third-party2/zlib/22c2d65676fb7c23cfa797c4f6937f38b026f3cf/1.2.8/gcc-5-glibc-2.23/9bc6787
|
||||
BZIP2_BASE=/mnt/gvfs/third-party2/bzip2/dc49a21c5fceec6456a7a28a94dcd16690af1337/1.0.6/gcc-5-glibc-2.23/9bc6787
|
||||
LZ4_BASE=/mnt/gvfs/third-party2/lz4/907b498203d297947f3bb70b9466f47e100f1873/r131/gcc-5-glibc-2.23/9bc6787
|
||||
ZSTD_BASE=/mnt/gvfs/third-party2/zstd/af6628a46758f1a15484a1760cd7294164bc5ba1/1.3.5/gcc-5-glibc-2.23/03859b5
|
||||
GFLAGS_BASE=/mnt/gvfs/third-party2/gflags/0b9929d2588991c65a57168bf88aff2db87c5d48/2.2.0/gcc-5-glibc-2.23/9bc6787
|
||||
JEMALLOC_BASE=/mnt/gvfs/third-party2/jemalloc/b1a0e56c1e3e6929813a4331ade3a58ff083afbb/master/gcc-5-glibc-2.23/aa64d6b
|
||||
NUMA_BASE=/mnt/gvfs/third-party2/numa/9cbf2460284c669ed19c3ccb200a71f7dd7e53c7/2.0.11/gcc-5-glibc-2.23/9bc6787
|
||||
LIBUNWIND_BASE=/mnt/gvfs/third-party2/libunwind/bf3d7497fe4e6d007354f0adffa16ce3003f8338/1.3/gcc-5-glibc-2.23/b443de1
|
||||
TBB_BASE=/mnt/gvfs/third-party2/tbb/ff4e0b093534704d8abab678a4fd7f5ea7b094c7/2018_U5/gcc-5-glibc-2.23/9bc6787
|
||||
KERNEL_HEADERS_BASE=/mnt/gvfs/third-party2/kernel-headers/b5c4a61a5c483ba24722005ae07895971a2ac707/4.0.9-36_fbk5_2933_gd092e3f/gcc-5-glibc-2.23/da39a3e
|
||||
BINUTILS_BASE=/mnt/gvfs/third-party2/binutils/55031de95a2b46c82948743419a603b3d6aefe28/2.29.1/centos7-native/da39a3e
|
||||
VALGRIND_BASE=/mnt/gvfs/third-party2/valgrind/f3f697a28122e6bcd513273dd9c1ff23852fc59f/3.13.0/gcc-5-glibc-2.23/9bc6787
|
||||
LUA_BASE=/mnt/gvfs/third-party2/lua/f0cd714433206d5139df61659eb7b28b1dea6683/5.2.3/gcc-5-glibc-2.23/65372bd
|
||||
@@ -0,0 +1,18 @@
|
||||
GCC_BASE=/mnt/gvfs/third-party2/gcc/6e8e715624fd15256a7970073387793dfcf79b46/7.x/centos7-native/b2ef2b6
|
||||
CLANG_BASE=/mnt/gvfs/third-party2/llvm-fb/ef37e1faa1c29782abfac1ae65a291b9b7966f6d/stable/centos7-native/c9f9104
|
||||
LIBGCC_BASE=/mnt/gvfs/third-party2/libgcc/c67031f0f739ac61575a061518d6ef5038f99f90/7.x/platform007/5620abc
|
||||
GLIBC_BASE=/mnt/gvfs/third-party2/glibc/60d6f124a78798b73944f5ba87c2306ae3460153/2.26/platform007/f259413
|
||||
SNAPPY_BASE=/mnt/gvfs/third-party2/snappy/7f9bdaada18f59bc27ec2b0871eb8a6144343aef/1.1.3/platform007/ca4da3d
|
||||
ZLIB_BASE=/mnt/gvfs/third-party2/zlib/22c2d65676fb7c23cfa797c4f6937f38b026f3cf/1.2.8/platform007/ca4da3d
|
||||
BZIP2_BASE=/mnt/gvfs/third-party2/bzip2/dc49a21c5fceec6456a7a28a94dcd16690af1337/1.0.6/platform007/ca4da3d
|
||||
LZ4_BASE=/mnt/gvfs/third-party2/lz4/907b498203d297947f3bb70b9466f47e100f1873/r131/platform007/ca4da3d
|
||||
ZSTD_BASE=/mnt/gvfs/third-party2/zstd/3ee276cbacfad3074e3f07bf826ac47f06970f4e/1.3.5/platform007/15a3614
|
||||
GFLAGS_BASE=/mnt/gvfs/third-party2/gflags/0b9929d2588991c65a57168bf88aff2db87c5d48/2.2.0/platform007/ca4da3d
|
||||
JEMALLOC_BASE=/mnt/gvfs/third-party2/jemalloc/9c910d36d6235cc40e8ff559358f1833452300ca/master/platform007/5b0f53e
|
||||
NUMA_BASE=/mnt/gvfs/third-party2/numa/9cbf2460284c669ed19c3ccb200a71f7dd7e53c7/2.0.11/platform007/ca4da3d
|
||||
LIBUNWIND_BASE=/mnt/gvfs/third-party2/libunwind/bf3d7497fe4e6d007354f0adffa16ce3003f8338/1.3/platform007/6f3e0a9
|
||||
TBB_BASE=/mnt/gvfs/third-party2/tbb/ff4e0b093534704d8abab678a4fd7f5ea7b094c7/2018_U5/platform007/ca4da3d
|
||||
KERNEL_HEADERS_BASE=/mnt/gvfs/third-party2/kernel-headers/b5c4a61a5c483ba24722005ae07895971a2ac707/fb/platform007/da39a3e
|
||||
BINUTILS_BASE=/mnt/gvfs/third-party2/binutils/92ff90349e2f43ea0a8246d8b1cf17b6869013e3/2.29.1/centos7-native/da39a3e
|
||||
VALGRIND_BASE=/mnt/gvfs/third-party2/valgrind/f3f697a28122e6bcd513273dd9c1ff23852fc59f/3.13.0/platform007/ca4da3d
|
||||
LUA_BASE=/mnt/gvfs/third-party2/lua/f0cd714433206d5139df61659eb7b28b1dea6683/5.3.4/platform007/5007832
|
||||
@@ -64,8 +64,12 @@ class MatchErrorParser(ErrorParserBase):
|
||||
|
||||
class CompilerErrorParser(MatchErrorParser):
|
||||
def __init__(self):
|
||||
# format: '<filename>:<line #>:<column #>: error: <error msg>'
|
||||
super(CompilerErrorParser, self).__init__(r'\S+:\d+:\d+: error:')
|
||||
# format (compile error):
|
||||
# '<filename>:<line #>:<column #>: error: <error msg>'
|
||||
# format (link error):
|
||||
# '<filename>:<line #>: error: <error msg>'
|
||||
# The below regex catches both
|
||||
super(CompilerErrorParser, self).__init__(r'\S+:\d+: error:')
|
||||
|
||||
|
||||
class ScanBuildErrorParser(MatchErrorParser):
|
||||
|
||||
@@ -43,12 +43,16 @@ if test -z $PIC_BUILD; then
|
||||
LZ4_INCLUDE=" -I $LZ4_BASE/include/"
|
||||
LZ4_LIBS=" $LZ4_BASE/lib/liblz4.a"
|
||||
CFLAGS+=" -DLZ4"
|
||||
|
||||
ZSTD_INCLUDE=" -I $ZSTD_BASE/include/"
|
||||
ZSTD_LIBS=" $ZSTD_BASE/lib/libzstd.a"
|
||||
CFLAGS+=" -DZSTD"
|
||||
fi
|
||||
|
||||
ZSTD_INCLUDE=" -I $ZSTD_BASE/include/"
|
||||
if test -z $PIC_BUILD; then
|
||||
ZSTD_LIBS=" $ZSTD_BASE/lib/libzstd.a"
|
||||
else
|
||||
ZSTD_LIBS=" $ZSTD_BASE/lib/libzstd_pic.a"
|
||||
fi
|
||||
CFLAGS+=" -DZSTD"
|
||||
|
||||
# location of gflags headers and libraries
|
||||
GFLAGS_INCLUDE=" -I $GFLAGS_BASE/include/"
|
||||
if test -z $PIC_BUILD; then
|
||||
|
||||
@@ -0,0 +1,157 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# Set environment variables so that we can compile rocksdb using
|
||||
# fbcode settings. It uses the latest g++ and clang compilers and also
|
||||
# uses jemalloc
|
||||
# Environment variables that change the behavior of this script:
|
||||
# PIC_BUILD -- if true, it will only take pic versions of libraries from fbcode. libraries that don't have pic variant will not be included
|
||||
|
||||
|
||||
BASEDIR=`dirname $BASH_SOURCE`
|
||||
source "$BASEDIR/dependencies_platform007.sh"
|
||||
|
||||
CFLAGS=""
|
||||
|
||||
# libgcc
|
||||
LIBGCC_INCLUDE="$LIBGCC_BASE/include/c++/7.3.0"
|
||||
LIBGCC_LIBS=" -L $LIBGCC_BASE/lib"
|
||||
|
||||
# glibc
|
||||
GLIBC_INCLUDE="$GLIBC_BASE/include"
|
||||
GLIBC_LIBS=" -L $GLIBC_BASE/lib"
|
||||
|
||||
# snappy
|
||||
SNAPPY_INCLUDE=" -I $SNAPPY_BASE/include/"
|
||||
if test -z $PIC_BUILD; then
|
||||
SNAPPY_LIBS=" $SNAPPY_BASE/lib/libsnappy.a"
|
||||
else
|
||||
SNAPPY_LIBS=" $SNAPPY_BASE/lib/libsnappy_pic.a"
|
||||
fi
|
||||
CFLAGS+=" -DSNAPPY"
|
||||
|
||||
if test -z $PIC_BUILD; then
|
||||
# location of zlib headers and libraries
|
||||
ZLIB_INCLUDE=" -I $ZLIB_BASE/include/"
|
||||
ZLIB_LIBS=" $ZLIB_BASE/lib/libz.a"
|
||||
CFLAGS+=" -DZLIB"
|
||||
|
||||
# location of bzip headers and libraries
|
||||
BZIP_INCLUDE=" -I $BZIP2_BASE/include/"
|
||||
BZIP_LIBS=" $BZIP2_BASE/lib/libbz2.a"
|
||||
CFLAGS+=" -DBZIP2"
|
||||
|
||||
LZ4_INCLUDE=" -I $LZ4_BASE/include/"
|
||||
LZ4_LIBS=" $LZ4_BASE/lib/liblz4.a"
|
||||
CFLAGS+=" -DLZ4"
|
||||
fi
|
||||
|
||||
ZSTD_INCLUDE=" -I $ZSTD_BASE/include/"
|
||||
if test -z $PIC_BUILD; then
|
||||
ZSTD_LIBS=" $ZSTD_BASE/lib/libzstd.a"
|
||||
else
|
||||
ZSTD_LIBS=" $ZSTD_BASE/lib/libzstd_pic.a"
|
||||
fi
|
||||
CFLAGS+=" -DZSTD"
|
||||
|
||||
# location of gflags headers and libraries
|
||||
GFLAGS_INCLUDE=" -I $GFLAGS_BASE/include/"
|
||||
if test -z $PIC_BUILD; then
|
||||
GFLAGS_LIBS=" $GFLAGS_BASE/lib/libgflags.a"
|
||||
else
|
||||
GFLAGS_LIBS=" $GFLAGS_BASE/lib/libgflags_pic.a"
|
||||
fi
|
||||
CFLAGS+=" -DGFLAGS=gflags"
|
||||
|
||||
# location of jemalloc
|
||||
JEMALLOC_INCLUDE=" -I $JEMALLOC_BASE/include/"
|
||||
JEMALLOC_LIB=" $JEMALLOC_BASE/lib/libjemalloc.a"
|
||||
|
||||
if test -z $PIC_BUILD; then
|
||||
# location of numa
|
||||
NUMA_INCLUDE=" -I $NUMA_BASE/include/"
|
||||
NUMA_LIB=" $NUMA_BASE/lib/libnuma.a"
|
||||
CFLAGS+=" -DNUMA"
|
||||
|
||||
# location of libunwind
|
||||
LIBUNWIND="$LIBUNWIND_BASE/lib/libunwind.a"
|
||||
fi
|
||||
|
||||
# location of TBB
|
||||
TBB_INCLUDE=" -isystem $TBB_BASE/include/"
|
||||
if test -z $PIC_BUILD; then
|
||||
TBB_LIBS="$TBB_BASE/lib/libtbb.a"
|
||||
else
|
||||
TBB_LIBS="$TBB_BASE/lib/libtbb_pic.a"
|
||||
fi
|
||||
CFLAGS+=" -DTBB"
|
||||
|
||||
# use Intel SSE support for checksum calculations
|
||||
export USE_SSE=1
|
||||
export PORTABLE=1
|
||||
|
||||
BINUTILS="$BINUTILS_BASE/bin"
|
||||
AR="$BINUTILS/ar"
|
||||
|
||||
DEPS_INCLUDE="$SNAPPY_INCLUDE $ZLIB_INCLUDE $BZIP_INCLUDE $LZ4_INCLUDE $ZSTD_INCLUDE $GFLAGS_INCLUDE $NUMA_INCLUDE $TBB_INCLUDE"
|
||||
|
||||
STDLIBS="-L $GCC_BASE/lib64"
|
||||
|
||||
CLANG_BIN="$CLANG_BASE/bin"
|
||||
CLANG_LIB="$CLANG_BASE/lib"
|
||||
CLANG_SRC="$CLANG_BASE/../../src"
|
||||
|
||||
CLANG_ANALYZER="$CLANG_BIN/clang++"
|
||||
CLANG_SCAN_BUILD="$CLANG_SRC/llvm/tools/clang/tools/scan-build/bin/scan-build"
|
||||
|
||||
if [ -z "$USE_CLANG" ]; then
|
||||
# gcc
|
||||
CC="$GCC_BASE/bin/gcc"
|
||||
CXX="$GCC_BASE/bin/g++"
|
||||
|
||||
CFLAGS+=" -B$BINUTILS/gold"
|
||||
CFLAGS+=" -isystem $LIBGCC_INCLUDE"
|
||||
CFLAGS+=" -isystem $GLIBC_INCLUDE"
|
||||
JEMALLOC=1
|
||||
else
|
||||
# clang
|
||||
CLANG_INCLUDE="$CLANG_LIB/clang/stable/include"
|
||||
CC="$CLANG_BIN/clang"
|
||||
CXX="$CLANG_BIN/clang++"
|
||||
|
||||
KERNEL_HEADERS_INCLUDE="$KERNEL_HEADERS_BASE/include"
|
||||
|
||||
CFLAGS+=" -B$BINUTILS/gold -nostdinc -nostdlib"
|
||||
CFLAGS+=" -isystem $LIBGCC_BASE/include/c++/7.x "
|
||||
CFLAGS+=" -isystem $LIBGCC_BASE/include/c++/7.x/x86_64-facebook-linux "
|
||||
CFLAGS+=" -isystem $GLIBC_INCLUDE"
|
||||
CFLAGS+=" -isystem $LIBGCC_INCLUDE"
|
||||
CFLAGS+=" -isystem $CLANG_INCLUDE"
|
||||
CFLAGS+=" -isystem $KERNEL_HEADERS_INCLUDE/linux "
|
||||
CFLAGS+=" -isystem $KERNEL_HEADERS_INCLUDE "
|
||||
CFLAGS+=" -Wno-expansion-to-defined "
|
||||
CXXFLAGS="-nostdinc++"
|
||||
fi
|
||||
|
||||
CFLAGS+=" $DEPS_INCLUDE"
|
||||
CFLAGS+=" -DROCKSDB_PLATFORM_POSIX -DROCKSDB_LIB_IO_POSIX -DROCKSDB_FALLOCATE_PRESENT -DROCKSDB_MALLOC_USABLE_SIZE -DROCKSDB_RANGESYNC_PRESENT -DROCKSDB_SCHED_GETCPU_PRESENT -DROCKSDB_SUPPORT_THREAD_LOCAL -DHAVE_SSE42"
|
||||
CXXFLAGS+=" $CFLAGS"
|
||||
|
||||
EXEC_LDFLAGS=" $SNAPPY_LIBS $ZLIB_LIBS $BZIP_LIBS $LZ4_LIBS $ZSTD_LIBS $GFLAGS_LIBS $NUMA_LIB $TBB_LIBS"
|
||||
EXEC_LDFLAGS+=" -B$BINUTILS/gold"
|
||||
EXEC_LDFLAGS+=" -Wl,--dynamic-linker,/usr/local/fbcode/platform007/lib/ld.so"
|
||||
EXEC_LDFLAGS+=" $LIBUNWIND"
|
||||
EXEC_LDFLAGS+=" -Wl,-rpath=/usr/local/fbcode/platform007/lib"
|
||||
# required by libtbb
|
||||
EXEC_LDFLAGS+=" -ldl"
|
||||
|
||||
PLATFORM_LDFLAGS="$LIBGCC_LIBS $GLIBC_LIBS $STDLIBS -lgcc -lstdc++"
|
||||
|
||||
EXEC_LDFLAGS_SHARED="$SNAPPY_LIBS $ZLIB_LIBS $BZIP_LIBS $LZ4_LIBS $ZSTD_LIBS $GFLAGS_LIBS $TBB_LIBS"
|
||||
|
||||
VALGRIND_VER="$VALGRIND_BASE/bin/"
|
||||
|
||||
# lua not supported because it's on track for deprecation, I think
|
||||
LUA_PATH=
|
||||
LUA_LIB=
|
||||
|
||||
export CC CXX AR CFLAGS CXXFLAGS EXEC_LDFLAGS EXEC_LDFLAGS_SHARED VALGRIND_VER JEMALLOC_LIB JEMALLOC_INCLUDE CLANG_ANALYZER CLANG_SCAN_BUILD LUA_PATH LUA_LIB
|
||||
@@ -85,9 +85,12 @@ NON_SHM="TMPD=/tmp/rocksdb_test_tmp"
|
||||
GCC_481="ROCKSDB_FBCODE_BUILD_WITH_481=1"
|
||||
ASAN="COMPILE_WITH_ASAN=1"
|
||||
CLANG="USE_CLANG=1"
|
||||
LITE="OPT=\"-DROCKSDB_LITE -g\""
|
||||
TSAN="COMPILE_WITH_TSAN=1"
|
||||
# in gcc-5 there are known problems with TSAN like https://gcc.gnu.org/bugzilla/show_bug.cgi?id=71090.
|
||||
# using platform007 gives us gcc-8 or higher which has that bug fixed.
|
||||
TSAN="ROCKSDB_FBCODE_BUILD_WITH_PLATFORM007=1 COMPILE_WITH_TSAN=1"
|
||||
UBSAN="COMPILE_WITH_UBSAN=1"
|
||||
TSAN_CRASH='CRASH_TEST_EXT_ARGS="--compression_type=zstd --log2_keys_per_lock=22"'
|
||||
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"
|
||||
@@ -380,14 +383,14 @@ STRESS_CRASH_TEST_COMMANDS="[
|
||||
$CLEANUP_ENV,
|
||||
{
|
||||
'name':'Build and run RocksDB debug stress tests',
|
||||
'shell':'$SHM $DEBUG make J=1 db_stress || $CONTRUN_NAME=db_stress $TASK_CREATION_TOOL',
|
||||
'shell':'$SHM $DEBUG $NON_TSAN_CRASH make J=1 db_stress || $CONTRUN_NAME=db_stress $TASK_CREATION_TOOL',
|
||||
'user':'root',
|
||||
$PARSER
|
||||
},
|
||||
{
|
||||
'name':'Build and run RocksDB debug crash tests',
|
||||
'timeout': 86400,
|
||||
'shell':'$SHM $DEBUG make J=1 crash_test || $CONTRUN_NAME=crash_test $TASK_CREATION_TOOL',
|
||||
'shell':'$SHM $DEBUG $NON_TSAN_CRASH make J=1 crash_test || $CONTRUN_NAME=crash_test $TASK_CREATION_TOOL',
|
||||
'user':'root',
|
||||
$PARSER
|
||||
}
|
||||
@@ -452,7 +455,7 @@ ASAN_CRASH_TEST_COMMANDS="[
|
||||
{
|
||||
'name':'Build and run RocksDB debug asan_crash_test',
|
||||
'timeout': 86400,
|
||||
'shell':'$SHM $DEBUG make J=1 asan_crash_test || $CONTRUN_NAME=asan_crash_test $TASK_CREATION_TOOL',
|
||||
'shell':'$SHM $DEBUG $NON_TSAN_CRASH make J=1 asan_crash_test || $CONTRUN_NAME=asan_crash_test $TASK_CREATION_TOOL',
|
||||
'user':'root',
|
||||
$PARSER
|
||||
},
|
||||
@@ -494,7 +497,7 @@ UBSAN_CRASH_TEST_COMMANDS="[
|
||||
{
|
||||
'name':'Build and run RocksDB debug ubsan_crash_test',
|
||||
'timeout': 86400,
|
||||
'shell':'$SHM $DEBUG make J=1 ubsan_crash_test || $CONTRUN_NAME=ubsan_crash_test $TASK_CREATION_TOOL',
|
||||
'shell':'$SHM $DEBUG $NON_TSAN_CRASH make J=1 ubsan_crash_test || $CONTRUN_NAME=ubsan_crash_test $TASK_CREATION_TOOL',
|
||||
'user':'root',
|
||||
$PARSER
|
||||
},
|
||||
@@ -560,7 +563,7 @@ TSAN_CRASH_TEST_COMMANDS="[
|
||||
{
|
||||
'name':'Compile and run',
|
||||
'timeout': 86400,
|
||||
'shell':'set -o pipefail && $SHM $DEBUG $TSAN CRASH_TEST_KILL_ODD=1887 CRASH_TEST_EXT_ARGS=--log2_keys_per_lock=22 make J=1 crash_test || $CONTRUN_NAME=tsan_crash_test $TASK_CREATION_TOOL',
|
||||
'shell':'set -o pipefail && $SHM $DEBUG $TSAN $TSAN_CRASH CRASH_TEST_KILL_ODD=1887 make J=1 crash_test || $CONTRUN_NAME=tsan_crash_test $TASK_CREATION_TOOL',
|
||||
'user':'root',
|
||||
$PARSER
|
||||
},
|
||||
|
||||
@@ -53,6 +53,45 @@ function get_lib_base()
|
||||
log_variable $__res_var
|
||||
}
|
||||
|
||||
###########################################################
|
||||
# platform007 dependencies #
|
||||
###########################################################
|
||||
|
||||
OUTPUT="$BASEDIR/dependencies_platform007.sh"
|
||||
|
||||
rm -f "$OUTPUT"
|
||||
touch "$OUTPUT"
|
||||
|
||||
echo "Writing dependencies to $OUTPUT"
|
||||
|
||||
# Compilers locations
|
||||
GCC_BASE=`readlink -f $TP2_LATEST/gcc/7.x/centos7-native/*/`
|
||||
CLANG_BASE=`readlink -f $TP2_LATEST/llvm-fb/stable/centos7-native/*/`
|
||||
|
||||
log_variable GCC_BASE
|
||||
log_variable CLANG_BASE
|
||||
|
||||
# Libraries locations
|
||||
get_lib_base libgcc 7.x platform007
|
||||
get_lib_base glibc 2.26 platform007
|
||||
get_lib_base snappy LATEST platform007
|
||||
get_lib_base zlib LATEST platform007
|
||||
get_lib_base bzip2 LATEST platform007
|
||||
get_lib_base lz4 LATEST platform007
|
||||
get_lib_base zstd LATEST platform007
|
||||
get_lib_base gflags LATEST platform007
|
||||
get_lib_base jemalloc LATEST platform007
|
||||
get_lib_base numa LATEST platform007
|
||||
get_lib_base libunwind LATEST platform007
|
||||
get_lib_base tbb LATEST platform007
|
||||
|
||||
get_lib_base kernel-headers fb platform007
|
||||
get_lib_base binutils LATEST centos7-native
|
||||
get_lib_base valgrind LATEST platform007
|
||||
get_lib_base lua 5.3.4 platform007
|
||||
|
||||
git diff $OUTPUT
|
||||
|
||||
###########################################################
|
||||
# 5.x dependencies #
|
||||
###########################################################
|
||||
@@ -65,8 +104,8 @@ touch "$OUTPUT"
|
||||
echo "Writing dependencies to $OUTPUT"
|
||||
|
||||
# Compilers locations
|
||||
GCC_BASE=`readlink -f $TP2_LATEST/gcc/5.x/centos6-native/*/`
|
||||
CLANG_BASE=`readlink -f $TP2_LATEST/llvm-fb/stable/centos6-native/*/`
|
||||
GCC_BASE=`readlink -f $TP2_LATEST/gcc/5.x/centos7-native/*/`
|
||||
CLANG_BASE=`readlink -f $TP2_LATEST/llvm-fb/stable/centos7-native/*/`
|
||||
|
||||
log_variable GCC_BASE
|
||||
log_variable CLANG_BASE
|
||||
@@ -86,7 +125,7 @@ get_lib_base libunwind LATEST gcc-5-glibc-2.23
|
||||
get_lib_base tbb LATEST gcc-5-glibc-2.23
|
||||
|
||||
get_lib_base kernel-headers 4.0.9-36_fbk5_2933_gd092e3f gcc-5-glibc-2.23
|
||||
get_lib_base binutils LATEST centos6-native
|
||||
get_lib_base binutils LATEST centos7-native
|
||||
get_lib_base valgrind LATEST gcc-5-glibc-2.23
|
||||
get_lib_base lua 5.2.3 gcc-5-glibc-2.23
|
||||
|
||||
|
||||
+5
-7
@@ -121,8 +121,8 @@ Status BuildTable(
|
||||
file->SetIOPriority(io_priority);
|
||||
file->SetWriteLifeTimeHint(write_hint);
|
||||
|
||||
file_writer.reset(new WritableFileWriter(std::move(file), env_options,
|
||||
ioptions.statistics));
|
||||
file_writer.reset(new WritableFileWriter(
|
||||
std::move(file), fname, env_options, ioptions.statistics));
|
||||
builder = NewTableBuilder(
|
||||
ioptions, mutable_cf_options, internal_comparator,
|
||||
int_tbl_prop_collector_factories, column_family_id,
|
||||
@@ -230,11 +230,9 @@ Status BuildTable(
|
||||
}
|
||||
|
||||
// Output to event logger and fire events.
|
||||
if (!s.ok() || meta->fd.GetFileSize() > 0) {
|
||||
EventHelpers::LogAndNotifyTableFileCreationFinished(
|
||||
event_logger, ioptions.listeners, dbname, column_family_name, fname,
|
||||
job_id, meta->fd, tp, reason, s);
|
||||
}
|
||||
EventHelpers::LogAndNotifyTableFileCreationFinished(
|
||||
event_logger, ioptions.listeners, dbname, column_family_name, fname,
|
||||
job_id, meta->fd, tp, reason, s);
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
@@ -35,7 +35,6 @@ class VersionEdit;
|
||||
class TableBuilder;
|
||||
class WritableFileWriter;
|
||||
class InternalStats;
|
||||
class InternalIterator;
|
||||
|
||||
// @param column_family_name Name of the column family that is also identified
|
||||
// by column_family_id, or empty string if unknown. It must outlive the
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
#include "rocksdb/utilities/backupable_db.h"
|
||||
#include "rocksdb/utilities/checkpoint.h"
|
||||
#include "rocksdb/utilities/db_ttl.h"
|
||||
#include "rocksdb/utilities/memory_util.h"
|
||||
#include "rocksdb/utilities/optimistic_transaction_db.h"
|
||||
#include "rocksdb/utilities/transaction.h"
|
||||
#include "rocksdb/utilities/transaction_db.h"
|
||||
@@ -41,6 +42,10 @@
|
||||
#include "rocksdb/perf_context.h"
|
||||
#include "utilities/merge_operators.h"
|
||||
|
||||
#include <vector>
|
||||
#include <unordered_set>
|
||||
#include <map>
|
||||
|
||||
using rocksdb::BytewiseComparator;
|
||||
using rocksdb::Cache;
|
||||
using rocksdb::ColumnFamilyDescriptor;
|
||||
@@ -108,8 +113,12 @@ using rocksdb::TransactionLogIterator;
|
||||
using rocksdb::BatchResult;
|
||||
using rocksdb::PerfLevel;
|
||||
using rocksdb::PerfContext;
|
||||
using rocksdb::MemoryUtil;
|
||||
|
||||
using std::shared_ptr;
|
||||
using std::vector;
|
||||
using std::unordered_set;
|
||||
using std::map;
|
||||
|
||||
extern "C" {
|
||||
|
||||
@@ -2402,7 +2411,7 @@ void rocksdb_options_set_bytes_per_sync(
|
||||
|
||||
void rocksdb_options_set_writable_file_max_buffer_size(rocksdb_options_t* opt,
|
||||
uint64_t v) {
|
||||
opt->rep.writable_file_max_buffer_size = v;
|
||||
opt->rep.writable_file_max_buffer_size = static_cast<size_t>(v);
|
||||
}
|
||||
|
||||
void rocksdb_options_set_allow_concurrent_memtable_write(rocksdb_options_t* opt,
|
||||
@@ -2433,6 +2442,20 @@ void rocksdb_options_set_max_write_buffer_number_to_maintain(
|
||||
opt->rep.max_write_buffer_number_to_maintain = n;
|
||||
}
|
||||
|
||||
void rocksdb_options_set_enable_pipelined_write(rocksdb_options_t* opt,
|
||||
unsigned char v) {
|
||||
opt->rep.enable_pipelined_write = v;
|
||||
}
|
||||
|
||||
void rocksdb_options_set_max_subcompactions(rocksdb_options_t* opt,
|
||||
uint32_t n) {
|
||||
opt->rep.max_subcompactions = n;
|
||||
}
|
||||
|
||||
void rocksdb_options_set_max_background_jobs(rocksdb_options_t* opt, int n) {
|
||||
opt->rep.max_background_jobs = n;
|
||||
}
|
||||
|
||||
void rocksdb_options_set_max_background_compactions(rocksdb_options_t* opt, int n) {
|
||||
opt->rep.max_background_compactions = n;
|
||||
}
|
||||
@@ -4087,6 +4110,98 @@ const char* rocksdb_pinnableslice_value(const rocksdb_pinnableslice_t* v,
|
||||
*vlen = v->rep.size();
|
||||
return v->rep.data();
|
||||
}
|
||||
|
||||
// container to keep databases and caches in order to use rocksdb::MemoryUtil
|
||||
struct rocksdb_memory_consumers_t {
|
||||
std::vector<rocksdb_t*> dbs;
|
||||
std::unordered_set<rocksdb_cache_t*> caches;
|
||||
};
|
||||
|
||||
// initializes new container of memory consumers
|
||||
rocksdb_memory_consumers_t* rocksdb_memory_consumers_create() {
|
||||
return new rocksdb_memory_consumers_t;
|
||||
}
|
||||
|
||||
// adds datatabase to the container of memory consumers
|
||||
void rocksdb_memory_consumers_add_db(rocksdb_memory_consumers_t* consumers,
|
||||
rocksdb_t* db) {
|
||||
consumers->dbs.push_back(db);
|
||||
}
|
||||
|
||||
// adds cache to the container of memory consumers
|
||||
void rocksdb_memory_consumers_add_cache(rocksdb_memory_consumers_t* consumers,
|
||||
rocksdb_cache_t* cache) {
|
||||
consumers->caches.insert(cache);
|
||||
}
|
||||
|
||||
// deletes container with memory consumers
|
||||
void rocksdb_memory_consumers_destroy(rocksdb_memory_consumers_t* consumers) {
|
||||
delete consumers;
|
||||
}
|
||||
|
||||
// contains memory usage statistics provided by rocksdb::MemoryUtil
|
||||
struct rocksdb_memory_usage_t {
|
||||
uint64_t mem_table_total;
|
||||
uint64_t mem_table_unflushed;
|
||||
uint64_t mem_table_readers_total;
|
||||
uint64_t cache_total;
|
||||
};
|
||||
|
||||
// estimates amount of memory occupied by consumers (dbs and caches)
|
||||
rocksdb_memory_usage_t* rocksdb_approximate_memory_usage_create(
|
||||
rocksdb_memory_consumers_t* consumers, char** errptr) {
|
||||
|
||||
vector<DB*> dbs;
|
||||
for (auto db : consumers->dbs) {
|
||||
dbs.push_back(db->rep);
|
||||
}
|
||||
|
||||
unordered_set<const Cache*> cache_set;
|
||||
for (auto cache : consumers->caches) {
|
||||
cache_set.insert(const_cast<const Cache*>(cache->rep.get()));
|
||||
}
|
||||
|
||||
std::map<rocksdb::MemoryUtil::UsageType, uint64_t> usage_by_type;
|
||||
|
||||
auto status = MemoryUtil::GetApproximateMemoryUsageByType(dbs, cache_set,
|
||||
&usage_by_type);
|
||||
if (SaveError(errptr, status)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto result = new rocksdb_memory_usage_t;
|
||||
result->mem_table_total = usage_by_type[MemoryUtil::kMemTableTotal];
|
||||
result->mem_table_unflushed = usage_by_type[MemoryUtil::kMemTableUnFlushed];
|
||||
result->mem_table_readers_total = usage_by_type[MemoryUtil::kTableReadersTotal];
|
||||
result->cache_total = usage_by_type[MemoryUtil::kCacheTotal];
|
||||
return result;
|
||||
}
|
||||
|
||||
uint64_t rocksdb_approximate_memory_usage_get_mem_table_total(
|
||||
rocksdb_memory_usage_t* memory_usage) {
|
||||
return memory_usage->mem_table_total;
|
||||
}
|
||||
|
||||
uint64_t rocksdb_approximate_memory_usage_get_mem_table_unflushed(
|
||||
rocksdb_memory_usage_t* memory_usage) {
|
||||
return memory_usage->mem_table_unflushed;
|
||||
}
|
||||
|
||||
uint64_t rocksdb_approximate_memory_usage_get_mem_table_readers_total(
|
||||
rocksdb_memory_usage_t* memory_usage) {
|
||||
return memory_usage->mem_table_readers_total;
|
||||
}
|
||||
|
||||
uint64_t rocksdb_approximate_memory_usage_get_cache_total(
|
||||
rocksdb_memory_usage_t* memory_usage) {
|
||||
return memory_usage->cache_total;
|
||||
}
|
||||
|
||||
// deletes container with memory usage estimates
|
||||
void rocksdb_approximate_memory_usage_destroy(rocksdb_memory_usage_t* usage) {
|
||||
delete usage;
|
||||
}
|
||||
|
||||
} // end extern "C"
|
||||
|
||||
#endif // !ROCKSDB_LITE
|
||||
+56
-6
@@ -19,11 +19,8 @@
|
||||
|
||||
// Can not use port/port.h macros as this is a c file
|
||||
#ifdef OS_WIN
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#define snprintf _snprintf
|
||||
|
||||
// Ok for uniqueness
|
||||
int geteuid() {
|
||||
int result = 0;
|
||||
@@ -34,6 +31,11 @@ int geteuid() {
|
||||
return result;
|
||||
}
|
||||
|
||||
// VS < 2015
|
||||
#if defined(_MSC_VER) && (_MSC_VER < 1900)
|
||||
#define snprintf _snprintf
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
const char* phase = "";
|
||||
@@ -47,12 +49,19 @@ static void StartPhase(const char* name) {
|
||||
fprintf(stderr, "=== Test %s\n", name);
|
||||
phase = name;
|
||||
}
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(push)
|
||||
#pragma warning (disable: 4996) // getenv security warning
|
||||
#endif
|
||||
static const char* GetTempDir(void) {
|
||||
const char* ret = getenv("TEST_TMPDIR");
|
||||
if (ret == NULL || ret[0] == '\0')
|
||||
ret = "/tmp";
|
||||
return ret;
|
||||
}
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(pop)
|
||||
#endif
|
||||
|
||||
#define CheckNoError(err) \
|
||||
if ((err) != NULL) { \
|
||||
@@ -643,7 +652,7 @@ int main(int argc, char** argv) {
|
||||
rocksdb_sstfilewriter_t* writer =
|
||||
rocksdb_sstfilewriter_create(env_opt, io_options);
|
||||
|
||||
unlink(sstfilename);
|
||||
remove(sstfilename);
|
||||
rocksdb_sstfilewriter_open(writer, sstfilename, &err);
|
||||
CheckNoError(err);
|
||||
rocksdb_sstfilewriter_put(writer, "sstk1", 5, "v1", 2, &err);
|
||||
@@ -664,7 +673,7 @@ int main(int argc, char** argv) {
|
||||
CheckGet(db, roptions, "sstk2", "v2");
|
||||
CheckGet(db, roptions, "sstk3", "v3");
|
||||
|
||||
unlink(sstfilename);
|
||||
remove(sstfilename);
|
||||
rocksdb_sstfilewriter_open(writer, sstfilename, &err);
|
||||
CheckNoError(err);
|
||||
rocksdb_sstfilewriter_put(writer, "sstk2", 5, "v4", 2, &err);
|
||||
@@ -1334,6 +1343,47 @@ int main(int argc, char** argv) {
|
||||
rocksdb_destroy_db(options, dbname, &err);
|
||||
}
|
||||
|
||||
// Check memory usage stats
|
||||
StartPhase("approximate_memory_usage");
|
||||
{
|
||||
// Create database
|
||||
db = rocksdb_open(options, dbname, &err);
|
||||
CheckNoError(err);
|
||||
|
||||
rocksdb_memory_consumers_t* consumers;
|
||||
consumers = rocksdb_memory_consumers_create();
|
||||
rocksdb_memory_consumers_add_db(consumers, db);
|
||||
rocksdb_memory_consumers_add_cache(consumers, cache);
|
||||
|
||||
// take memory usage report before write-read operation
|
||||
rocksdb_memory_usage_t* mu1;
|
||||
mu1 = rocksdb_approximate_memory_usage_create(consumers, &err);
|
||||
CheckNoError(err);
|
||||
|
||||
// Put data (this should affect memtables)
|
||||
rocksdb_put(db, woptions, "memory", 6, "test", 4, &err);
|
||||
CheckNoError(err);
|
||||
CheckGet(db, roptions, "memory", "test");
|
||||
|
||||
// take memory usage report after write-read operation
|
||||
rocksdb_memory_usage_t* mu2;
|
||||
mu2 = rocksdb_approximate_memory_usage_create(consumers, &err);
|
||||
CheckNoError(err);
|
||||
|
||||
// amount of memory used within memtables should grow
|
||||
CheckCondition(rocksdb_approximate_memory_usage_get_mem_table_total(mu2) >=
|
||||
rocksdb_approximate_memory_usage_get_mem_table_total(mu1));
|
||||
CheckCondition(rocksdb_approximate_memory_usage_get_mem_table_unflushed(mu2) >=
|
||||
rocksdb_approximate_memory_usage_get_mem_table_unflushed(mu1));
|
||||
|
||||
rocksdb_memory_consumers_destroy(consumers);
|
||||
rocksdb_approximate_memory_usage_destroy(mu1);
|
||||
rocksdb_approximate_memory_usage_destroy(mu2);
|
||||
rocksdb_close(db);
|
||||
rocksdb_destroy_db(options, dbname, &err);
|
||||
CheckNoError(err);
|
||||
}
|
||||
|
||||
StartPhase("cuckoo_options");
|
||||
{
|
||||
rocksdb_cuckoo_table_options_t* cuckoo_options;
|
||||
@@ -1675,7 +1725,7 @@ int main(int argc, char** argv) {
|
||||
db = rocksdb_open(options, dbname, &err);
|
||||
CheckNoError(err);
|
||||
}
|
||||
|
||||
|
||||
StartPhase("cleanup");
|
||||
rocksdb_close(db);
|
||||
rocksdb_options_destroy(options);
|
||||
|
||||
@@ -308,6 +308,55 @@ TEST_F(CompactFilesTest, CompactionFilterWithGetSv) {
|
||||
delete db;
|
||||
}
|
||||
|
||||
TEST_F(CompactFilesTest, SentinelCompressionType) {
|
||||
if (!Zlib_Supported()) {
|
||||
fprintf(stderr, "zlib compression not supported, skip this test\n");
|
||||
return;
|
||||
}
|
||||
if (!Snappy_Supported()) {
|
||||
fprintf(stderr, "snappy compression not supported, skip this test\n");
|
||||
return;
|
||||
}
|
||||
// Check that passing `CompressionType::kDisableCompressionOption` to
|
||||
// `CompactFiles` causes it to use the column family compression options.
|
||||
for (auto compaction_style :
|
||||
{CompactionStyle::kCompactionStyleLevel,
|
||||
CompactionStyle::kCompactionStyleUniversal,
|
||||
CompactionStyle::kCompactionStyleNone}) {
|
||||
DestroyDB(db_name_, Options());
|
||||
Options options;
|
||||
options.compaction_style = compaction_style;
|
||||
// L0: Snappy, L1: ZSTD, L2: Snappy
|
||||
options.compression_per_level = {CompressionType::kSnappyCompression,
|
||||
CompressionType::kZlibCompression,
|
||||
CompressionType::kSnappyCompression};
|
||||
options.create_if_missing = true;
|
||||
FlushedFileCollector* collector = new FlushedFileCollector();
|
||||
options.listeners.emplace_back(collector);
|
||||
DB* db = nullptr;
|
||||
ASSERT_OK(DB::Open(options, db_name_, &db));
|
||||
|
||||
db->Put(WriteOptions(), "key", "val");
|
||||
db->Flush(FlushOptions());
|
||||
|
||||
auto l0_files = collector->GetFlushedFiles();
|
||||
ASSERT_EQ(1, l0_files.size());
|
||||
|
||||
// L0->L1 compaction, so output should be ZSTD-compressed
|
||||
CompactionOptions compaction_opts;
|
||||
compaction_opts.compression = CompressionType::kDisableCompressionOption;
|
||||
ASSERT_OK(db->CompactFiles(compaction_opts, l0_files, 1));
|
||||
|
||||
rocksdb::TablePropertiesCollection all_tables_props;
|
||||
ASSERT_OK(db->GetPropertiesOfAllTables(&all_tables_props));
|
||||
for (const auto& name_and_table_props : all_tables_props) {
|
||||
ASSERT_EQ(CompressionTypeToString(CompressionType::kZlibCompression),
|
||||
name_and_table_props.second->compression_name);
|
||||
}
|
||||
delete db;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
+5
-15
@@ -25,22 +25,12 @@ CompactedDBImpl::~CompactedDBImpl() {
|
||||
}
|
||||
|
||||
size_t CompactedDBImpl::FindFile(const Slice& key) {
|
||||
size_t left = 0;
|
||||
size_t right = files_.num_files - 1;
|
||||
while (left < right) {
|
||||
size_t mid = (left + right) >> 1;
|
||||
const FdWithKeyRange& f = files_.files[mid];
|
||||
if (user_comparator_->Compare(ExtractUserKey(f.largest_key), key) < 0) {
|
||||
// Key at "mid.largest" is < "target". Therefore all
|
||||
// files at or before "mid" are uninteresting.
|
||||
left = mid + 1;
|
||||
} else {
|
||||
// Key at "mid.largest" is >= "target". Therefore all files
|
||||
// after "mid" are uninteresting.
|
||||
right = mid;
|
||||
}
|
||||
}
|
||||
return right;
|
||||
auto cmp = [&](const FdWithKeyRange& f, const Slice& k) -> bool {
|
||||
return user_comparator_->Compare(ExtractUserKey(f.largest_key), k) < 0;
|
||||
};
|
||||
return static_cast<size_t>(std::lower_bound(files_.files,
|
||||
files_.files + right, key, cmp) - files_.files);
|
||||
}
|
||||
|
||||
Status CompactedDBImpl::Get(const ReadOptions& options, ColumnFamilyHandle*,
|
||||
|
||||
+47
-11
@@ -18,8 +18,8 @@ CompactionIterator::CompactionIterator(
|
||||
SequenceNumber earliest_write_conflict_snapshot,
|
||||
const SnapshotChecker* snapshot_checker, Env* env,
|
||||
bool report_detailed_time, bool expect_valid_internal_key,
|
||||
RangeDelAggregator* range_del_agg,
|
||||
const Compaction* compaction, const CompactionFilter* compaction_filter,
|
||||
RangeDelAggregator* range_del_agg, const Compaction* compaction,
|
||||
const CompactionFilter* compaction_filter,
|
||||
const std::atomic<bool>* shutting_down,
|
||||
const SequenceNumber preserve_deletes_seqnum)
|
||||
: CompactionIterator(
|
||||
@@ -77,6 +77,12 @@ CompactionIterator::CompactionIterator(
|
||||
earliest_snapshot_ = snapshots_->at(0);
|
||||
latest_snapshot_ = snapshots_->back();
|
||||
}
|
||||
#ifndef NDEBUG
|
||||
// findEarliestVisibleSnapshot assumes this ordering.
|
||||
for (size_t i = 1; i < snapshots_->size(); ++i) {
|
||||
assert(snapshots_->at(i - 1) <= snapshots_->at(i));
|
||||
}
|
||||
#endif
|
||||
if (compaction_filter_ != nullptr) {
|
||||
if (compaction_filter_->IgnoreSnapshots()) {
|
||||
ignore_snapshots_ = true;
|
||||
@@ -505,6 +511,31 @@ void CompactionIterator::NextFromInput() {
|
||||
++iter_stats_.num_optimized_del_drop_obsolete;
|
||||
}
|
||||
input_->Next();
|
||||
} else if ((ikey_.type == kTypeDeletion) && bottommost_level_ &&
|
||||
ikeyNotNeededForIncrementalSnapshot()) {
|
||||
// Handle the case where we have a delete key at the bottom most level
|
||||
// We can skip outputting the key iff there are no subsequent puts for this
|
||||
// key
|
||||
ParsedInternalKey next_ikey;
|
||||
input_->Next();
|
||||
// Skip over all versions of this key that happen to occur in the same snapshot
|
||||
// range as the delete
|
||||
while (input_->Valid() &&
|
||||
ParseInternalKey(input_->key(), &next_ikey) &&
|
||||
cmp_->Equal(ikey_.user_key, next_ikey.user_key) &&
|
||||
(prev_snapshot == 0 || next_ikey.sequence > prev_snapshot ||
|
||||
(snapshot_checker_ != nullptr &&
|
||||
UNLIKELY(!snapshot_checker_->IsInSnapshot(next_ikey.sequence,
|
||||
prev_snapshot))))) {
|
||||
input_->Next();
|
||||
}
|
||||
// If you find you still need to output a row with this key, we need to output the
|
||||
// delete too
|
||||
if (input_->Valid() && ParseInternalKey(input_->key(), &next_ikey) &&
|
||||
cmp_->Equal(ikey_.user_key, next_ikey.user_key)) {
|
||||
valid_ = true;
|
||||
at_next_ = true;
|
||||
}
|
||||
} else if (ikey_.type == kTypeMerge) {
|
||||
if (!merge_helper_->HasOperator()) {
|
||||
status_ = Status::InvalidArgument(
|
||||
@@ -603,18 +634,23 @@ void CompactionIterator::PrepareOutput() {
|
||||
inline SequenceNumber CompactionIterator::findEarliestVisibleSnapshot(
|
||||
SequenceNumber in, SequenceNumber* prev_snapshot) {
|
||||
assert(snapshots_->size());
|
||||
SequenceNumber prev = kMaxSequenceNumber;
|
||||
for (const auto cur : *snapshots_) {
|
||||
assert(prev == kMaxSequenceNumber || prev <= cur);
|
||||
if (cur >= in && (snapshot_checker_ == nullptr ||
|
||||
snapshot_checker_->IsInSnapshot(in, cur))) {
|
||||
*prev_snapshot = prev == kMaxSequenceNumber ? 0 : prev;
|
||||
auto snapshots_iter = std::lower_bound(
|
||||
snapshots_->begin(), snapshots_->end(), in);
|
||||
if (snapshots_iter == snapshots_->begin()) {
|
||||
*prev_snapshot = 0;
|
||||
} else {
|
||||
*prev_snapshot = *std::prev(snapshots_iter);
|
||||
assert(*prev_snapshot < in);
|
||||
}
|
||||
for (; snapshots_iter != snapshots_->end(); ++snapshots_iter) {
|
||||
auto cur = *snapshots_iter;
|
||||
assert(in <= cur);
|
||||
if (snapshot_checker_ == nullptr ||
|
||||
snapshot_checker_->IsInSnapshot(in, cur)) {
|
||||
return cur;
|
||||
}
|
||||
prev = cur;
|
||||
assert(prev < kMaxSequenceNumber);
|
||||
*prev_snapshot = cur;
|
||||
}
|
||||
*prev_snapshot = prev;
|
||||
return kMaxSequenceNumber;
|
||||
}
|
||||
|
||||
|
||||
@@ -247,9 +247,8 @@ class CompactionIteratorTest : public testing::TestWithParam<bool> {
|
||||
c_iter_.reset(new CompactionIterator(
|
||||
iter_.get(), cmp_, merge_helper_.get(), last_sequence, &snapshots_,
|
||||
earliest_write_conflict_snapshot, snapshot_checker_.get(),
|
||||
Env::Default(), false /* report_detailed_time */,
|
||||
false, range_del_agg_.get(), std::move(compaction), filter,
|
||||
&shutting_down_));
|
||||
Env::Default(), false /* report_detailed_time */, false,
|
||||
range_del_agg_.get(), std::move(compaction), filter, &shutting_down_));
|
||||
}
|
||||
|
||||
void AddSnapshot(SequenceNumber snapshot,
|
||||
@@ -672,8 +671,12 @@ TEST_P(CompactionIteratorTest, ZeroOutSequenceAtBottomLevel) {
|
||||
TEST_P(CompactionIteratorTest, RemoveDeletionAtBottomLevel) {
|
||||
AddSnapshot(1);
|
||||
RunTest({test::KeyStr("a", 1, kTypeDeletion),
|
||||
test::KeyStr("b", 2, kTypeDeletion)},
|
||||
{"", ""}, {test::KeyStr("b", 2, kTypeDeletion)}, {""},
|
||||
test::KeyStr("b", 3, kTypeDeletion),
|
||||
test::KeyStr("b", 1, kTypeValue)},
|
||||
{"", "", ""},
|
||||
{test::KeyStr("b", 3, kTypeDeletion),
|
||||
test::KeyStr("b", 0, kTypeValue)},
|
||||
{"", ""},
|
||||
kMaxSequenceNumber /*last_commited_seq*/, nullptr /*merge_operator*/,
|
||||
nullptr /*compaction_filter*/, true /*bottommost_level*/);
|
||||
}
|
||||
@@ -842,13 +845,26 @@ TEST_F(CompactionIteratorWithSnapshotCheckerTest,
|
||||
{test::KeyStr("a", 1, kTypeDeletion), test::KeyStr("b", 2, kTypeDeletion),
|
||||
test::KeyStr("c", 3, kTypeDeletion)},
|
||||
{"", "", ""},
|
||||
{test::KeyStr("b", 2, kTypeDeletion),
|
||||
test::KeyStr("c", 3, kTypeDeletion)},
|
||||
{},
|
||||
{"", ""}, kMaxSequenceNumber /*last_commited_seq*/,
|
||||
nullptr /*merge_operator*/, nullptr /*compaction_filter*/,
|
||||
true /*bottommost_level*/);
|
||||
}
|
||||
|
||||
TEST_F(CompactionIteratorWithSnapshotCheckerTest,
|
||||
NotRemoveDeletionIfValuePresentToEarlierSnapshot) {
|
||||
AddSnapshot(2,1);
|
||||
RunTest(
|
||||
{test::KeyStr("a", 4, kTypeDeletion), test::KeyStr("a", 1, kTypeValue),
|
||||
test::KeyStr("b", 3, kTypeValue)},
|
||||
{"", "", ""},
|
||||
{test::KeyStr("a", 4, kTypeDeletion), test::KeyStr("a", 0, kTypeValue),
|
||||
test::KeyStr("b", 3, kTypeValue)},
|
||||
{"", "", ""}, kMaxSequenceNumber /*last_commited_seq*/,
|
||||
nullptr /*merge_operator*/, nullptr /*compaction_filter*/,
|
||||
true /*bottommost_level*/);
|
||||
}
|
||||
|
||||
TEST_F(CompactionIteratorWithSnapshotCheckerTest,
|
||||
NotRemoveSingleDeletionIfNotVisibleToEarliestSnapshot) {
|
||||
AddSnapshot(2, 1);
|
||||
|
||||
+12
-10
@@ -593,13 +593,11 @@ Status CompactionJob::Run() {
|
||||
thread.join();
|
||||
}
|
||||
|
||||
if (output_directory_) {
|
||||
output_directory_->Fsync();
|
||||
}
|
||||
|
||||
compaction_stats_.micros = env_->NowMicros() - start_micros;
|
||||
MeasureTime(stats_, COMPACTION_TIME, compaction_stats_.micros);
|
||||
|
||||
TEST_SYNC_POINT("CompactionJob::Run:BeforeVerify");
|
||||
|
||||
// Check if any thread encountered an error during execution
|
||||
Status status;
|
||||
for (const auto& state : compact_->sub_compact_states) {
|
||||
@@ -609,6 +607,10 @@ Status CompactionJob::Run() {
|
||||
}
|
||||
}
|
||||
|
||||
if (status.ok() && output_directory_) {
|
||||
status = output_directory_->Fsync();
|
||||
}
|
||||
|
||||
if (status.ok()) {
|
||||
thread_pool.clear();
|
||||
std::vector<const FileMetaData*> files_meta;
|
||||
@@ -1075,7 +1077,8 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
|
||||
}
|
||||
|
||||
if (status.ok() && sub_compact->builder == nullptr &&
|
||||
sub_compact->outputs.size() == 0) {
|
||||
sub_compact->outputs.size() == 0 &&
|
||||
!range_del_agg->IsEmpty()) {
|
||||
// handle subcompaction containing only range deletions
|
||||
status = OpenCompactionOutputFile(sub_compact);
|
||||
}
|
||||
@@ -1306,9 +1309,7 @@ Status CompactionJob::FinishCompactionOutputFile(
|
||||
// VersionEdit.
|
||||
assert(!sub_compact->outputs.empty());
|
||||
sub_compact->outputs.pop_back();
|
||||
sub_compact->builder.reset();
|
||||
sub_compact->current_output_file_size = 0;
|
||||
return s;
|
||||
meta = nullptr;
|
||||
}
|
||||
|
||||
if (s.ok() && (current_entries > 0 || tp.num_range_deletions > 0)) {
|
||||
@@ -1462,8 +1463,9 @@ Status CompactionJob::OpenCompactionOutputFile(
|
||||
writable_file->SetWriteLifeTimeHint(write_hint_);
|
||||
writable_file->SetPreallocationBlockSize(static_cast<size_t>(
|
||||
sub_compact->compaction->OutputFilePreallocationSize()));
|
||||
sub_compact->outfile.reset(new WritableFileWriter(
|
||||
std::move(writable_file), env_options_, db_options_.statistics.get()));
|
||||
sub_compact->outfile.reset(
|
||||
new WritableFileWriter(std::move(writable_file), fname, env_options_,
|
||||
db_options_.statistics.get()));
|
||||
|
||||
// If the Column family flag is to only optimize filters for hits,
|
||||
// we can skip creating filters if this is the bottommost_level where
|
||||
|
||||
@@ -79,7 +79,7 @@ class CompactionJobTest : public testing::Test {
|
||||
shutting_down_(false),
|
||||
preserve_deletes_seqnum_(0),
|
||||
mock_table_factory_(new mock::MockTableFactory()),
|
||||
error_handler_(db_options_, &mutex_) {
|
||||
error_handler_(nullptr, db_options_, &mutex_) {
|
||||
EXPECT_OK(env_->CreateDirIfMissing(dbname_));
|
||||
db_options_.db_paths.emplace_back(dbname_,
|
||||
std::numeric_limits<uint64_t>::max());
|
||||
@@ -205,7 +205,7 @@ class CompactionJobTest : public testing::Test {
|
||||
manifest, &file, env_->OptimizeForManifestWrite(env_options_));
|
||||
ASSERT_OK(s);
|
||||
unique_ptr<WritableFileWriter> file_writer(
|
||||
new WritableFileWriter(std::move(file), env_options_));
|
||||
new WritableFileWriter(std::move(file), manifest, env_options_));
|
||||
{
|
||||
log::Writer log(std::move(file_writer), 0, false);
|
||||
std::string record;
|
||||
|
||||
+35
-13
@@ -49,7 +49,7 @@ bool FindIntraL0Compaction(const std::vector<FileMetaData*>& level_files,
|
||||
// increasing.
|
||||
size_t new_compact_bytes_per_del_file = 0;
|
||||
for (span_len = 1; span_len < level_files.size(); ++span_len) {
|
||||
compact_bytes += level_files[span_len]->fd.file_size;
|
||||
compact_bytes += static_cast<size_t>(level_files[span_len]->fd.file_size);
|
||||
new_compact_bytes_per_del_file = compact_bytes / span_len;
|
||||
if (level_files[span_len]->being_compacted ||
|
||||
new_compact_bytes_per_del_file > compact_bytes_per_del_file) {
|
||||
@@ -219,7 +219,8 @@ void CompactionPicker::GetRange(const std::vector<CompactionInputFiles>& inputs,
|
||||
|
||||
bool CompactionPicker::ExpandInputsToCleanCut(const std::string& /*cf_name*/,
|
||||
VersionStorageInfo* vstorage,
|
||||
CompactionInputFiles* inputs) {
|
||||
CompactionInputFiles* inputs,
|
||||
InternalKey** next_smallest) {
|
||||
// This isn't good compaction
|
||||
assert(!inputs->empty());
|
||||
|
||||
@@ -242,7 +243,8 @@ bool CompactionPicker::ExpandInputsToCleanCut(const std::string& /*cf_name*/,
|
||||
GetRange(*inputs, &smallest, &largest);
|
||||
inputs->clear();
|
||||
vstorage->GetOverlappingInputs(level, &smallest, &largest, &inputs->files,
|
||||
hint_index, &hint_index);
|
||||
hint_index, &hint_index, true,
|
||||
next_smallest);
|
||||
} while (inputs->size() > old_size);
|
||||
|
||||
// we started off with inputs non-empty and the previous loop only grew
|
||||
@@ -315,13 +317,29 @@ Compaction* CompactionPicker::CompactFiles(
|
||||
// shouldn't have been released since.
|
||||
assert(!FilesRangeOverlapWithCompaction(input_files, output_level));
|
||||
|
||||
auto c =
|
||||
new Compaction(vstorage, ioptions_, mutable_cf_options, input_files,
|
||||
output_level, compact_options.output_file_size_limit,
|
||||
mutable_cf_options.max_compaction_bytes, output_path_id,
|
||||
compact_options.compression, ioptions_.compression_opts,
|
||||
compact_options.max_subcompactions,
|
||||
/* grandparents */ {}, true);
|
||||
CompressionType compression_type;
|
||||
if (compact_options.compression == kDisableCompressionOption) {
|
||||
int base_level;
|
||||
if (ioptions_.compaction_style == kCompactionStyleLevel) {
|
||||
base_level = vstorage->base_level();
|
||||
} else {
|
||||
base_level = 1;
|
||||
}
|
||||
compression_type =
|
||||
GetCompressionType(ioptions_, vstorage, mutable_cf_options,
|
||||
output_level, base_level);
|
||||
} else {
|
||||
// TODO(ajkr): `CompactionOptions` offers configurable `CompressionType`
|
||||
// without configurable `CompressionOptions`, which is inconsistent.
|
||||
compression_type = compact_options.compression;
|
||||
}
|
||||
auto c = new Compaction(
|
||||
vstorage, ioptions_, mutable_cf_options, input_files, output_level,
|
||||
compact_options.output_file_size_limit,
|
||||
mutable_cf_options.max_compaction_bytes, output_path_id, compression_type,
|
||||
GetCompressionOptions(ioptions_, vstorage, output_level),
|
||||
compact_options.max_subcompactions,
|
||||
/* grandparents */ {}, true);
|
||||
RegisterCompaction(c);
|
||||
return c;
|
||||
}
|
||||
@@ -633,7 +651,6 @@ Compaction* CompactionPicker::CompactRange(
|
||||
uint64_t s = inputs[i]->compensated_file_size;
|
||||
total += s;
|
||||
if (total >= limit) {
|
||||
**compaction_end = inputs[i + 1]->smallest;
|
||||
covering_the_whole_range = false;
|
||||
inputs.files.resize(i + 1);
|
||||
break;
|
||||
@@ -642,7 +659,10 @@ Compaction* CompactionPicker::CompactRange(
|
||||
}
|
||||
assert(output_path_id < static_cast<uint32_t>(ioptions_.cf_paths.size()));
|
||||
|
||||
if (ExpandInputsToCleanCut(cf_name, vstorage, &inputs) == false) {
|
||||
InternalKey key_storage;
|
||||
InternalKey* next_smallest = &key_storage;
|
||||
if (ExpandInputsToCleanCut(cf_name, vstorage, &inputs, &next_smallest) ==
|
||||
false) {
|
||||
// manual compaction is now multi-threaded, so it can
|
||||
// happen that ExpandWhileOverlapping fails
|
||||
// we handle it higher in RunManualCompaction
|
||||
@@ -650,8 +670,10 @@ Compaction* CompactionPicker::CompactRange(
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (covering_the_whole_range) {
|
||||
if (covering_the_whole_range || !next_smallest) {
|
||||
*compaction_end = nullptr;
|
||||
} else {
|
||||
**compaction_end = *next_smallest;
|
||||
}
|
||||
|
||||
CompactionInputFiles output_level_inputs;
|
||||
|
||||
@@ -151,7 +151,8 @@ class CompactionPicker {
|
||||
// Will return false if it is impossible to apply this compaction.
|
||||
bool ExpandInputsToCleanCut(const std::string& cf_name,
|
||||
VersionStorageInfo* vstorage,
|
||||
CompactionInputFiles* inputs);
|
||||
CompactionInputFiles* inputs,
|
||||
InternalKey** next_smallest = nullptr);
|
||||
|
||||
// Returns true if any one of the parent files are being compacted
|
||||
bool IsRangeInCompaction(VersionStorageInfo* vstorage,
|
||||
|
||||
@@ -90,8 +90,8 @@ class CompactionPickerTest : public testing::Test {
|
||||
f->fd = FileDescriptor(file_number, path_id, file_size);
|
||||
f->smallest = InternalKey(smallest, smallest_seq, kTypeValue);
|
||||
f->largest = InternalKey(largest, largest_seq, kTypeValue);
|
||||
f->smallest_seqno = smallest_seq;
|
||||
f->largest_seqno = largest_seq;
|
||||
f->fd.smallest_seqno = smallest_seq;
|
||||
f->fd.largest_seqno = largest_seq;
|
||||
f->compensated_file_size = file_size;
|
||||
f->refs = 0;
|
||||
vstorage_->AddFile(level, f);
|
||||
|
||||
@@ -97,17 +97,17 @@ void GetSmallestLargestSeqno(const std::vector<FileMetaData*>& files,
|
||||
SequenceNumber* largest_seqno) {
|
||||
bool is_first = true;
|
||||
for (FileMetaData* f : files) {
|
||||
assert(f->smallest_seqno <= f->largest_seqno);
|
||||
assert(f->fd.smallest_seqno <= f->fd.largest_seqno);
|
||||
if (is_first) {
|
||||
is_first = false;
|
||||
*smallest_seqno = f->smallest_seqno;
|
||||
*largest_seqno = f->largest_seqno;
|
||||
*smallest_seqno = f->fd.smallest_seqno;
|
||||
*largest_seqno = f->fd.largest_seqno;
|
||||
} else {
|
||||
if (f->smallest_seqno < *smallest_seqno) {
|
||||
*smallest_seqno = f->smallest_seqno;
|
||||
if (f->fd.smallest_seqno < *smallest_seqno) {
|
||||
*smallest_seqno = f->fd.smallest_seqno;
|
||||
}
|
||||
if (f->largest_seqno > *largest_seqno) {
|
||||
*largest_seqno = f->largest_seqno;
|
||||
if (f->fd.largest_seqno > *largest_seqno) {
|
||||
*largest_seqno = f->fd.largest_seqno;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -365,11 +365,11 @@ Compaction* UniversalCompactionPicker::PickCompaction(
|
||||
size_t level_index = 0U;
|
||||
if (c->start_level() == 0) {
|
||||
for (auto f : *c->inputs(0)) {
|
||||
assert(f->smallest_seqno <= f->largest_seqno);
|
||||
assert(f->fd.smallest_seqno <= f->fd.largest_seqno);
|
||||
if (is_first) {
|
||||
is_first = false;
|
||||
}
|
||||
prev_smallest_seqno = f->smallest_seqno;
|
||||
prev_smallest_seqno = f->fd.smallest_seqno;
|
||||
}
|
||||
level_index = 1U;
|
||||
}
|
||||
|
||||
@@ -22,11 +22,12 @@ class DBBloomFilterTest : public DBTestBase {
|
||||
|
||||
class DBBloomFilterTestWithParam
|
||||
: public DBTestBase,
|
||||
public testing::WithParamInterface<std::tuple<bool, bool>> {
|
||||
public testing::WithParamInterface<std::tuple<bool, bool, uint32_t>> {
|
||||
// public testing::WithParamInterface<bool> {
|
||||
protected:
|
||||
bool use_block_based_filter_;
|
||||
bool partition_filters_;
|
||||
uint32_t format_version_;
|
||||
|
||||
public:
|
||||
DBBloomFilterTestWithParam() : DBTestBase("/db_bloom_filter_tests") {}
|
||||
@@ -36,9 +37,12 @@ class DBBloomFilterTestWithParam
|
||||
void SetUp() override {
|
||||
use_block_based_filter_ = std::get<0>(GetParam());
|
||||
partition_filters_ = std::get<1>(GetParam());
|
||||
format_version_ = std::get<2>(GetParam());
|
||||
}
|
||||
};
|
||||
|
||||
class DBBloomFilterTestDefFormatVersion : public DBBloomFilterTestWithParam {};
|
||||
|
||||
class SliceTransformLimitedDomainGeneric : public SliceTransform {
|
||||
const char* Name() const override {
|
||||
return "SliceTransformLimitedDomainGeneric";
|
||||
@@ -62,7 +66,7 @@ class SliceTransformLimitedDomainGeneric : public SliceTransform {
|
||||
// KeyMayExist can lead to a few false positives, but not false negatives.
|
||||
// To make test deterministic, use a much larger number of bits per key-20 than
|
||||
// bits in the key, so that false positives are eliminated
|
||||
TEST_P(DBBloomFilterTestWithParam, KeyMayExist) {
|
||||
TEST_P(DBBloomFilterTestDefFormatVersion, KeyMayExist) {
|
||||
do {
|
||||
ReadOptions ropts;
|
||||
std::string value;
|
||||
@@ -401,6 +405,11 @@ TEST_P(DBBloomFilterTestWithParam, BloomFilter) {
|
||||
table_options.index_type =
|
||||
BlockBasedTableOptions::IndexType::kTwoLevelIndexSearch;
|
||||
}
|
||||
table_options.format_version = format_version_;
|
||||
if (format_version_ >= 4) {
|
||||
// value delta encoding challenged more with index interval > 1
|
||||
table_options.index_block_restart_interval = 8;
|
||||
}
|
||||
table_options.metadata_block_size = 32;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
|
||||
@@ -456,10 +465,26 @@ TEST_P(DBBloomFilterTestWithParam, BloomFilter) {
|
||||
} while (ChangeCompactOptions());
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(DBBloomFilterTestWithParam, DBBloomFilterTestWithParam,
|
||||
::testing::Values(std::make_tuple(true, false),
|
||||
std::make_tuple(false, true),
|
||||
std::make_tuple(false, false)));
|
||||
INSTANTIATE_TEST_CASE_P(
|
||||
FormatDef, DBBloomFilterTestDefFormatVersion,
|
||||
::testing::Values(std::make_tuple(true, false, test::kDefaultFormatVersion),
|
||||
std::make_tuple(false, true, test::kDefaultFormatVersion),
|
||||
std::make_tuple(false, false,
|
||||
test::kDefaultFormatVersion)));
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(
|
||||
FormatDef, DBBloomFilterTestWithParam,
|
||||
::testing::Values(std::make_tuple(true, false, test::kDefaultFormatVersion),
|
||||
std::make_tuple(false, true, test::kDefaultFormatVersion),
|
||||
std::make_tuple(false, false,
|
||||
test::kDefaultFormatVersion)));
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(
|
||||
FormatLatest, DBBloomFilterTestWithParam,
|
||||
::testing::Values(std::make_tuple(true, false, test::kLatestFormatVersion),
|
||||
std::make_tuple(false, true, test::kLatestFormatVersion),
|
||||
std::make_tuple(false, false,
|
||||
test::kLatestFormatVersion)));
|
||||
|
||||
TEST_F(DBBloomFilterTest, BloomFilterRate) {
|
||||
while (ChangeFilterOptions()) {
|
||||
|
||||
+232
-13
@@ -116,6 +116,21 @@ private:
|
||||
std::vector<std::atomic<int>> compaction_completed_;
|
||||
};
|
||||
|
||||
class SstStatsCollector : public EventListener {
|
||||
public:
|
||||
SstStatsCollector() : num_ssts_creation_started_(0) {}
|
||||
|
||||
void OnTableFileCreationStarted(
|
||||
const TableFileCreationBriefInfo& /* info */) override {
|
||||
++num_ssts_creation_started_;
|
||||
}
|
||||
|
||||
int num_ssts_creation_started() { return num_ssts_creation_started_; }
|
||||
|
||||
private:
|
||||
std::atomic<int> num_ssts_creation_started_;
|
||||
};
|
||||
|
||||
static const int kCDTValueSize = 1000;
|
||||
static const int kCDTKeysPerBuffer = 4;
|
||||
static const int kCDTNumLevels = 8;
|
||||
@@ -2462,6 +2477,7 @@ TEST_P(DBCompactionTestWithParam, ManualLevelCompactionOutputPathId) {
|
||||
|
||||
// Compaction range overlaps files
|
||||
Compact(1, "p1", "p9", 1);
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
ASSERT_EQ("0,1", FilesPerLevel(1));
|
||||
ASSERT_EQ(1, GetSstFileCount(options.db_paths[1].path));
|
||||
ASSERT_EQ(0, GetSstFileCount(options.db_paths[0].path));
|
||||
@@ -2477,6 +2493,7 @@ TEST_P(DBCompactionTestWithParam, ManualLevelCompactionOutputPathId) {
|
||||
|
||||
// Compact just the new range
|
||||
Compact(1, "b", "f", 1);
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
ASSERT_EQ("0,2", FilesPerLevel(1));
|
||||
ASSERT_EQ(2, GetSstFileCount(options.db_paths[1].path));
|
||||
ASSERT_EQ(0, GetSstFileCount(options.db_paths[0].path));
|
||||
@@ -2493,6 +2510,7 @@ TEST_P(DBCompactionTestWithParam, ManualLevelCompactionOutputPathId) {
|
||||
compact_options.target_path_id = 1;
|
||||
compact_options.exclusive_manual_compaction = exclusive_manual_compaction_;
|
||||
db_->CompactRange(compact_options, handles_[1], nullptr, nullptr);
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
|
||||
ASSERT_EQ("0,1", FilesPerLevel(1));
|
||||
ASSERT_EQ(1, GetSstFileCount(options.db_paths[1].path));
|
||||
@@ -3376,14 +3394,13 @@ TEST_F(DBCompactionTest, LevelCompactExpiredTtlFiles) {
|
||||
}
|
||||
Flush();
|
||||
}
|
||||
Flush();
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
MoveFilesToLevel(3);
|
||||
ASSERT_EQ("0,0,0,2", FilesPerLevel());
|
||||
|
||||
// Delete previously written keys.
|
||||
for (int i = 0; i < kNumLevelFiles; ++i) {
|
||||
for (int j = 0; j < kNumKeysPerFile; ++j) {
|
||||
// Overwrite previous keys with smaller, but predictable, values.
|
||||
ASSERT_OK(Delete(Key(i * kNumKeysPerFile + j)));
|
||||
}
|
||||
Flush();
|
||||
@@ -3396,7 +3413,7 @@ TEST_F(DBCompactionTest, LevelCompactExpiredTtlFiles) {
|
||||
env_->addon_time_.fetch_add(36 * 60 * 60); // 36 hours
|
||||
ASSERT_EQ("0,2,0,2", FilesPerLevel());
|
||||
|
||||
// Just do a siimple write + flush so that the Ttl expired files get
|
||||
// Just do a simple write + flush so that the Ttl expired files get
|
||||
// compacted.
|
||||
ASSERT_OK(Put("a", "1"));
|
||||
Flush();
|
||||
@@ -3410,6 +3427,57 @@ TEST_F(DBCompactionTest, LevelCompactExpiredTtlFiles) {
|
||||
// All non-L0 files are deleted, as they contained only deleted data.
|
||||
ASSERT_EQ("1", FilesPerLevel());
|
||||
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
||||
|
||||
// Test dynamically changing ttl.
|
||||
|
||||
env_->addon_time_.store(0);
|
||||
DestroyAndReopen(options);
|
||||
|
||||
for (int i = 0; i < kNumLevelFiles; ++i) {
|
||||
for (int j = 0; j < kNumKeysPerFile; ++j) {
|
||||
ASSERT_OK(
|
||||
Put(Key(i * kNumKeysPerFile + j), RandomString(&rnd, kValueSize)));
|
||||
}
|
||||
Flush();
|
||||
}
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
MoveFilesToLevel(3);
|
||||
ASSERT_EQ("0,0,0,2", FilesPerLevel());
|
||||
|
||||
// Delete previously written keys.
|
||||
for (int i = 0; i < kNumLevelFiles; ++i) {
|
||||
for (int j = 0; j < kNumKeysPerFile; ++j) {
|
||||
ASSERT_OK(Delete(Key(i * kNumKeysPerFile + j)));
|
||||
}
|
||||
Flush();
|
||||
}
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
ASSERT_EQ("2,0,0,2", FilesPerLevel());
|
||||
MoveFilesToLevel(1);
|
||||
ASSERT_EQ("0,2,0,2", FilesPerLevel());
|
||||
|
||||
// Move time forward by 12 hours, and make sure that compaction still doesn't
|
||||
// trigger as ttl is set to 24 hours.
|
||||
env_->addon_time_.fetch_add(12 * 60 * 60);
|
||||
ASSERT_OK(Put("a", "1"));
|
||||
Flush();
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
ASSERT_EQ("1,2,0,2", FilesPerLevel());
|
||||
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"LevelCompactionPicker::PickCompaction:Return", [&](void* arg) {
|
||||
Compaction* compaction = reinterpret_cast<Compaction*>(arg);
|
||||
ASSERT_TRUE(compaction->compaction_reason() == CompactionReason::kTtl);
|
||||
});
|
||||
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
// Dynamically change ttl to 10 hours.
|
||||
// This should trigger a ttl compaction, as 12 hours have already passed.
|
||||
ASSERT_OK(dbfull()->SetOptions({{"ttl", "36000"}}));
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
// All non-L0 files are deleted, as they contained only deleted data.
|
||||
ASSERT_EQ("1", FilesPerLevel());
|
||||
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
|
||||
TEST_F(DBCompactionTest, CompactRangeDelayedByL0FileCount) {
|
||||
@@ -3435,12 +3503,13 @@ TEST_F(DBCompactionTest, CompactRangeDelayedByL0FileCount) {
|
||||
// ensure the auto compaction doesn't finish until manual compaction has
|
||||
// had a chance to be delayed.
|
||||
rocksdb::SyncPoint::GetInstance()->LoadDependency(
|
||||
{{"DBImpl::CompactRange:StallWait", "CompactionJob::Run():End"}});
|
||||
{{"DBImpl::WaitUntilFlushWouldNotStallWrites:StallWait",
|
||||
"CompactionJob::Run():End"}});
|
||||
} else {
|
||||
// ensure the auto-compaction doesn't finish until manual compaction has
|
||||
// continued without delay.
|
||||
rocksdb::SyncPoint::GetInstance()->LoadDependency(
|
||||
{{"DBImpl::CompactRange:StallWaitDone", "CompactionJob::Run():End"}});
|
||||
{{"DBImpl::FlushMemTable:StallWaitDone", "CompactionJob::Run():End"}});
|
||||
}
|
||||
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
@@ -3488,12 +3557,13 @@ TEST_F(DBCompactionTest, CompactRangeDelayedByImmMemTableCount) {
|
||||
// ensure the flush doesn't finish until manual compaction has had a
|
||||
// chance to be delayed.
|
||||
rocksdb::SyncPoint::GetInstance()->LoadDependency(
|
||||
{{"DBImpl::CompactRange:StallWait", "FlushJob::WriteLevel0Table"}});
|
||||
{{"DBImpl::WaitUntilFlushWouldNotStallWrites:StallWait",
|
||||
"FlushJob::WriteLevel0Table"}});
|
||||
} else {
|
||||
// ensure the flush doesn't finish until manual compaction has continued
|
||||
// without delay.
|
||||
rocksdb::SyncPoint::GetInstance()->LoadDependency(
|
||||
{{"DBImpl::CompactRange:StallWaitDone",
|
||||
{{"DBImpl::FlushMemTable:StallWaitDone",
|
||||
"FlushJob::WriteLevel0Table"}});
|
||||
}
|
||||
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
||||
@@ -3503,6 +3573,7 @@ TEST_F(DBCompactionTest, CompactRangeDelayedByImmMemTableCount) {
|
||||
ASSERT_OK(Put(Key(0), RandomString(&rnd, 1024)));
|
||||
FlushOptions flush_opts;
|
||||
flush_opts.wait = false;
|
||||
flush_opts.allow_write_stall = true;
|
||||
dbfull()->Flush(flush_opts);
|
||||
}
|
||||
|
||||
@@ -3538,7 +3609,7 @@ TEST_F(DBCompactionTest, CompactRangeShutdownWhileDelayed) {
|
||||
// The auto-compaction waits until the manual compaction finishes to ensure
|
||||
// the signal comes from closing CF/DB, not from compaction making progress.
|
||||
rocksdb::SyncPoint::GetInstance()->LoadDependency(
|
||||
{{"DBImpl::CompactRange:StallWait",
|
||||
{{"DBImpl::WaitUntilFlushWouldNotStallWrites:StallWait",
|
||||
"DBCompactionTest::CompactRangeShutdownWhileDelayed:PreShutdown"},
|
||||
{"DBCompactionTest::CompactRangeShutdownWhileDelayed:PostManual",
|
||||
"CompactionJob::Run():End"}});
|
||||
@@ -3589,18 +3660,21 @@ TEST_F(DBCompactionTest, CompactRangeSkipFlushAfterDelay) {
|
||||
// began. So it unblocks CompactRange and precludes its flush. Throughout the
|
||||
// test, stall conditions are upheld via high L0 file count.
|
||||
rocksdb::SyncPoint::GetInstance()->LoadDependency(
|
||||
{{"DBImpl::CompactRange:StallWait",
|
||||
{{"DBImpl::WaitUntilFlushWouldNotStallWrites:StallWait",
|
||||
"DBCompactionTest::CompactRangeSkipFlushAfterDelay:PreFlush"},
|
||||
{"DBCompactionTest::CompactRangeSkipFlushAfterDelay:PostFlush",
|
||||
"DBImpl::CompactRange:StallWaitDone"},
|
||||
{"DBImpl::CompactRange:StallWaitDone", "CompactionJob::Run():End"}});
|
||||
"DBImpl::FlushMemTable:StallWaitDone"},
|
||||
{"DBImpl::FlushMemTable:StallWaitDone", "CompactionJob::Run():End"}});
|
||||
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
//used for the delayable flushes
|
||||
FlushOptions flush_opts;
|
||||
flush_opts.allow_write_stall = true;
|
||||
for (int i = 0; i < kNumL0FilesLimit - 1; ++i) {
|
||||
for (int j = 0; j < 2; ++j) {
|
||||
ASSERT_OK(Put(Key(j), RandomString(&rnd, 1024)));
|
||||
}
|
||||
Flush();
|
||||
dbfull()->Flush(flush_opts);
|
||||
}
|
||||
auto manual_compaction_thread = port::Thread([this]() {
|
||||
CompactRangeOptions cro;
|
||||
@@ -3610,7 +3684,7 @@ TEST_F(DBCompactionTest, CompactRangeSkipFlushAfterDelay) {
|
||||
|
||||
TEST_SYNC_POINT("DBCompactionTest::CompactRangeSkipFlushAfterDelay:PreFlush");
|
||||
Put(ToString(0), RandomString(&rnd, 1024));
|
||||
Flush();
|
||||
dbfull()->Flush(flush_opts);
|
||||
Put(ToString(0), RandomString(&rnd, 1024));
|
||||
TEST_SYNC_POINT("DBCompactionTest::CompactRangeSkipFlushAfterDelay:PostFlush");
|
||||
manual_compaction_thread.join();
|
||||
@@ -3766,6 +3840,30 @@ TEST_F(DBCompactionTest, CompactFilesOutputRangeConflict) {
|
||||
bg_thread.join();
|
||||
}
|
||||
|
||||
TEST_F(DBCompactionTest, CompactionHasEmptyOutput) {
|
||||
Options options = CurrentOptions();
|
||||
SstStatsCollector* collector = new SstStatsCollector();
|
||||
options.level0_file_num_compaction_trigger = 2;
|
||||
options.listeners.emplace_back(collector);
|
||||
Reopen(options);
|
||||
|
||||
// Make sure the L0 files overlap to prevent trivial move.
|
||||
ASSERT_OK(Put("a", "val"));
|
||||
ASSERT_OK(Put("b", "val"));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(Delete("a"));
|
||||
ASSERT_OK(Delete("b"));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
ASSERT_EQ(NumTableFilesAtLevel(0), 0);
|
||||
ASSERT_EQ(NumTableFilesAtLevel(1), 0);
|
||||
|
||||
// Expect one file creation to start for each flush, and zero for compaction
|
||||
// since no keys are written.
|
||||
ASSERT_EQ(2, collector->num_ssts_creation_started());
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(DBCompactionTestWithParam, DBCompactionTestWithParam,
|
||||
::testing::Values(std::make_tuple(1, true),
|
||||
std::make_tuple(1, false),
|
||||
@@ -3863,6 +3961,127 @@ INSTANTIATE_TEST_CASE_P(
|
||||
CompactionPri::kOldestSmallestSeqFirst,
|
||||
CompactionPri::kMinOverlappingRatio));
|
||||
|
||||
class NoopMergeOperator : public MergeOperator {
|
||||
public:
|
||||
NoopMergeOperator() {}
|
||||
|
||||
virtual bool FullMergeV2(const MergeOperationInput& /*merge_in*/,
|
||||
MergeOperationOutput* merge_out) const override {
|
||||
std::string val("bar");
|
||||
merge_out->new_value = val;
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual const char* Name() const override { return "Noop"; }
|
||||
};
|
||||
|
||||
TEST_F(DBCompactionTest, PartialManualCompaction) {
|
||||
Options opts = CurrentOptions();
|
||||
opts.num_levels = 3;
|
||||
opts.level0_file_num_compaction_trigger = 10;
|
||||
opts.compression = kNoCompression;
|
||||
opts.merge_operator.reset(new NoopMergeOperator());
|
||||
opts.target_file_size_base = 10240;
|
||||
DestroyAndReopen(opts);
|
||||
|
||||
Random rnd(301);
|
||||
for (auto i = 0; i < 8; ++i) {
|
||||
for (auto j = 0; j < 10; ++j) {
|
||||
Merge("foo", RandomString(&rnd, 1024));
|
||||
}
|
||||
Flush();
|
||||
}
|
||||
|
||||
MoveFilesToLevel(2);
|
||||
|
||||
std::string prop;
|
||||
EXPECT_TRUE(dbfull()->GetProperty(DB::Properties::kLiveSstFilesSize, &prop));
|
||||
uint64_t max_compaction_bytes = atoi(prop.c_str()) / 2;
|
||||
ASSERT_OK(dbfull()->SetOptions(
|
||||
{{"max_compaction_bytes", std::to_string(max_compaction_bytes)}}));
|
||||
|
||||
CompactRangeOptions cro;
|
||||
cro.bottommost_level_compaction = BottommostLevelCompaction::kForce;
|
||||
dbfull()->CompactRange(cro, nullptr, nullptr);
|
||||
}
|
||||
|
||||
// FixFileIngestionCompactionDeadlock tests and verifies that compaction and
|
||||
// file ingestion do not cause deadlock in the event of write stall triggered
|
||||
// by number of L0 files reaching level0_stop_writes_trigger.
|
||||
TEST_P(DBCompactionTestWithParam, FixFileIngestionCompactionDeadlock) {
|
||||
const int kNumKeysPerFile = 100;
|
||||
// Generate SST files.
|
||||
Options options = CurrentOptions();
|
||||
|
||||
// Generate an external SST file containing a single key, i.e. 99
|
||||
std::string sst_files_dir = dbname_ + "/sst_files/";
|
||||
test::DestroyDir(env_, sst_files_dir);
|
||||
ASSERT_OK(env_->CreateDir(sst_files_dir));
|
||||
SstFileWriter sst_writer(EnvOptions(), options);
|
||||
const std::string sst_file_path = sst_files_dir + "test.sst";
|
||||
ASSERT_OK(sst_writer.Open(sst_file_path));
|
||||
ASSERT_OK(sst_writer.Put(Key(kNumKeysPerFile - 1), "value"));
|
||||
ASSERT_OK(sst_writer.Finish());
|
||||
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
SyncPoint::GetInstance()->LoadDependency({
|
||||
{"DBImpl::IngestExternalFile:AfterIncIngestFileCounter",
|
||||
"BackgroundCallCompaction:0"},
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
options.write_buffer_size = 110 << 10; // 110KB
|
||||
options.level0_file_num_compaction_trigger =
|
||||
options.level0_stop_writes_trigger;
|
||||
options.max_subcompactions = max_subcompactions_;
|
||||
options.memtable_factory.reset(new SpecialSkipListFactory(kNumKeysPerFile));
|
||||
DestroyAndReopen(options);
|
||||
Random rnd(301);
|
||||
|
||||
// Generate level0_stop_writes_trigger L0 files to trigger write stop
|
||||
for (int i = 0; i != options.level0_file_num_compaction_trigger; ++i) {
|
||||
for (int j = 0; j != kNumKeysPerFile; ++j) {
|
||||
ASSERT_OK(Put(Key(j), RandomString(&rnd, 990)));
|
||||
}
|
||||
if (0 == i) {
|
||||
// When we reach here, the memtables have kNumKeysPerFile keys. Note that
|
||||
// flush is not yet triggered. We need to write an extra key so that the
|
||||
// write path will call PreprocessWrite and flush the previous key-value
|
||||
// pairs to e flushed. After that, there will be the newest key in the
|
||||
// memtable, and a bunch of L0 files. Since there is already one key in
|
||||
// the memtable, then for i = 1, 2, ..., we do not have to write this
|
||||
// extra key to trigger flush.
|
||||
ASSERT_OK(Put("", ""));
|
||||
}
|
||||
dbfull()->TEST_WaitForFlushMemTable();
|
||||
ASSERT_EQ(NumTableFilesAtLevel(0 /*level*/, 0 /*cf*/), i + 1);
|
||||
}
|
||||
// When we reach this point, there will be level0_stop_writes_trigger L0
|
||||
// files and one extra key (99) in memory, which overlaps with the external
|
||||
// SST file. Write stall triggers, and can be cleared only after compaction
|
||||
// reduces the number of L0 files.
|
||||
|
||||
// Compaction will also be triggered since we have reached the threshold for
|
||||
// auto compaction. Note that compaction may begin after the following file
|
||||
// ingestion thread and waits for ingestion to finish.
|
||||
|
||||
// Thread to ingest file with overlapping key range with the current
|
||||
// memtable. Consequently ingestion will trigger a flush. The flush MUST
|
||||
// proceed without waiting for the write stall condition to clear, otherwise
|
||||
// deadlock can happen.
|
||||
port::Thread ingestion_thr([&]() {
|
||||
IngestExternalFileOptions ifo;
|
||||
Status s = db_->IngestExternalFile({sst_file_path}, ifo);
|
||||
ASSERT_OK(s);
|
||||
});
|
||||
|
||||
// More write to trigger write stop
|
||||
ingestion_thr.join();
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
Close();
|
||||
}
|
||||
|
||||
#endif // !defined(ROCKSDB_LITE)
|
||||
} // namespace rocksdb
|
||||
|
||||
|
||||
@@ -35,6 +35,7 @@ TEST_F(DBFlushTest, FlushWhileWritingManifest) {
|
||||
Reopen(options);
|
||||
FlushOptions no_wait;
|
||||
no_wait.wait = false;
|
||||
no_wait.allow_write_stall=true;
|
||||
|
||||
SyncPoint::GetInstance()->LoadDependency(
|
||||
{{"VersionSet::LogAndApply:WriteManifest",
|
||||
@@ -55,6 +56,9 @@ TEST_F(DBFlushTest, FlushWhileWritingManifest) {
|
||||
#endif // ROCKSDB_LITE
|
||||
}
|
||||
|
||||
#ifndef TRAVIS
|
||||
// Disable this test temporarily on Travis as it fails intermittently.
|
||||
// Github issue: #4151
|
||||
TEST_F(DBFlushTest, SyncFail) {
|
||||
std::unique_ptr<FaultInjectionTestEnv> fault_injection_env(
|
||||
new FaultInjectionTestEnv(env_));
|
||||
@@ -92,6 +96,7 @@ TEST_F(DBFlushTest, SyncFail) {
|
||||
ASSERT_EQ(refs_before, cfd->current()->TEST_refs());
|
||||
Destroy(options);
|
||||
}
|
||||
#endif // TRAVIS
|
||||
|
||||
TEST_F(DBFlushTest, FlushInLowPriThreadPool) {
|
||||
// Verify setting an empty high-pri (flush) thread pool causes flushes to be
|
||||
|
||||
+218
-73
@@ -41,7 +41,6 @@
|
||||
#include "db/log_reader.h"
|
||||
#include "db/log_writer.h"
|
||||
#include "db/malloc_stats.h"
|
||||
#include "db/managed_iterator.h"
|
||||
#include "db/memtable.h"
|
||||
#include "db/memtable_list.h"
|
||||
#include "db/merge_context.h"
|
||||
@@ -216,9 +215,11 @@ DBImpl::DBImpl(const DBOptions& options, const std::string& dbname,
|
||||
// requires a custom gc for compaction, we use that to set use_custom_gc_
|
||||
// as well.
|
||||
use_custom_gc_(seq_per_batch),
|
||||
shutdown_initiated_(false),
|
||||
own_sfm_(options.sst_file_manager == nullptr),
|
||||
preserve_deletes_(options.preserve_deletes),
|
||||
closed_(false),
|
||||
error_handler_(immutable_db_options_, &mutex_) {
|
||||
error_handler_(this, immutable_db_options_, &mutex_) {
|
||||
// !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_);
|
||||
@@ -260,16 +261,62 @@ Status DBImpl::Resume() {
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status s = error_handler_.GetBGError();
|
||||
if (s.severity() > Status::Severity::kHardError) {
|
||||
if (error_handler_.IsRecoveryInProgress()) {
|
||||
// Don't allow a mix of manual and automatic recovery
|
||||
return Status::Busy();
|
||||
}
|
||||
|
||||
mutex_.Unlock();
|
||||
Status s = error_handler_.RecoverFromBGError(true);
|
||||
mutex_.Lock();
|
||||
return s;
|
||||
}
|
||||
|
||||
// This function implements the guts of recovery from a background error. It
|
||||
// is eventually called for both manual as well as automatic recovery. It does
|
||||
// the following -
|
||||
// 1. Wait for currently scheduled background flush/compaction to exit, in
|
||||
// order to inadvertently causing an error and thinking recovery failed
|
||||
// 2. Flush memtables if there's any data for all the CFs. This may result
|
||||
// another error, which will be saved by error_handler_ and reported later
|
||||
// as the recovery status
|
||||
// 3. Find and delete any obsolete files
|
||||
// 4. Schedule compactions if needed for all the CFs. This is needed as the
|
||||
// flush in the prior step might have been a no-op for some CFs, which
|
||||
// means a new super version wouldn't have been installed
|
||||
Status DBImpl::ResumeImpl() {
|
||||
mutex_.AssertHeld();
|
||||
WaitForBackgroundWork();
|
||||
|
||||
Status bg_error = error_handler_.GetBGError();
|
||||
Status s;
|
||||
if (shutdown_initiated_) {
|
||||
// Returning shutdown status to SFM during auto recovery will cause it
|
||||
// to abort the recovery and allow the shutdown to progress
|
||||
s = Status::ShutdownInProgress();
|
||||
}
|
||||
if (s.ok() && bg_error.severity() > Status::Severity::kHardError) {
|
||||
ROCKS_LOG_INFO(immutable_db_options_.info_log,
|
||||
"DB resume requested but failed due to Fatal/Unrecoverable error");
|
||||
return s;
|
||||
s = bg_error;
|
||||
}
|
||||
|
||||
// We cannot guarantee consistency of the WAL. So force flush Memtables of
|
||||
// all the column families
|
||||
if (s.ok()) {
|
||||
s = FlushAllCFs(FlushReason::kErrorRecovery);
|
||||
if (!s.ok()) {
|
||||
ROCKS_LOG_INFO(immutable_db_options_.info_log,
|
||||
"DB resume requested but failed due to Flush failure [%s]",
|
||||
s.ToString().c_str());
|
||||
}
|
||||
}
|
||||
|
||||
JobContext job_context(0);
|
||||
FindObsoleteFiles(&job_context, true);
|
||||
error_handler_.ClearBGError();
|
||||
if (s.ok()) {
|
||||
s = error_handler_.ClearBGError();
|
||||
}
|
||||
mutex_.Unlock();
|
||||
|
||||
job_context.manifest_file_number = 1;
|
||||
@@ -278,13 +325,36 @@ Status DBImpl::Resume() {
|
||||
}
|
||||
job_context.Clean();
|
||||
|
||||
ROCKS_LOG_INFO(immutable_db_options_.info_log, "Successfully resumed DB");
|
||||
if (s.ok()) {
|
||||
ROCKS_LOG_INFO(immutable_db_options_.info_log, "Successfully resumed DB");
|
||||
}
|
||||
mutex_.Lock();
|
||||
MaybeScheduleFlushOrCompaction();
|
||||
// Check for shutdown again before scheduling further compactions,
|
||||
// since we released and re-acquired the lock above
|
||||
if (shutdown_initiated_) {
|
||||
s = Status::ShutdownInProgress();
|
||||
}
|
||||
if (s.ok()) {
|
||||
for (auto cfd : *versions_->GetColumnFamilySet()) {
|
||||
SchedulePendingCompaction(cfd);
|
||||
}
|
||||
MaybeScheduleFlushOrCompaction();
|
||||
}
|
||||
|
||||
// Wake up any waiters - in this case, it could be the shutdown thread
|
||||
bg_cv_.SignalAll();
|
||||
|
||||
// No need to check BGError again. If something happened, event listener would be
|
||||
// notified and the operation causing it would have failed
|
||||
return Status::OK();
|
||||
return s;
|
||||
}
|
||||
|
||||
void DBImpl::WaitForBackgroundWork() {
|
||||
// Wait for background work to finish
|
||||
while (bg_bottom_compaction_scheduled_ || bg_compaction_scheduled_ ||
|
||||
bg_flush_scheduled_) {
|
||||
bg_cv_.Wait();
|
||||
}
|
||||
}
|
||||
|
||||
// Will lock the mutex_, will wait for completion if wait is true
|
||||
@@ -314,14 +384,20 @@ void DBImpl::CancelAllBackgroundWork(bool wait) {
|
||||
if (!wait) {
|
||||
return;
|
||||
}
|
||||
// Wait for background work to finish
|
||||
while (bg_bottom_compaction_scheduled_ || bg_compaction_scheduled_ ||
|
||||
bg_flush_scheduled_) {
|
||||
bg_cv_.Wait();
|
||||
}
|
||||
WaitForBackgroundWork();
|
||||
}
|
||||
|
||||
Status DBImpl::CloseHelper() {
|
||||
// Guarantee that there is no background error recovery in progress before
|
||||
// continuing with the shutdown
|
||||
mutex_.Lock();
|
||||
shutdown_initiated_ = true;
|
||||
error_handler_.CancelErrorRecovery();
|
||||
while (error_handler_.IsRecoveryInProgress()) {
|
||||
bg_cv_.Wait();
|
||||
}
|
||||
mutex_.Unlock();
|
||||
|
||||
// CancelAllBackgroundWork called with false means we just set the shutdown
|
||||
// marker. After this we do a variant of the waiting and unschedule work
|
||||
// (to consider: moving all the waiting into CancelAllBackgroundWork(true))
|
||||
@@ -339,7 +415,8 @@ Status DBImpl::CloseHelper() {
|
||||
// Wait for background work to finish
|
||||
while (bg_bottom_compaction_scheduled_ || bg_compaction_scheduled_ ||
|
||||
bg_flush_scheduled_ || bg_purge_scheduled_ ||
|
||||
pending_purge_obsolete_files_) {
|
||||
pending_purge_obsolete_files_ ||
|
||||
error_handler_.IsRecoveryInProgress()) {
|
||||
TEST_SYNC_POINT("DBImpl::~DBImpl:WaitJob");
|
||||
bg_cv_.Wait();
|
||||
}
|
||||
@@ -349,9 +426,12 @@ Status DBImpl::CloseHelper() {
|
||||
flush_scheduler_.Clear();
|
||||
|
||||
while (!flush_queue_.empty()) {
|
||||
auto cfd = PopFirstFromFlushQueue();
|
||||
if (cfd->Unref()) {
|
||||
delete cfd;
|
||||
const FlushRequest& flush_req = PopFirstFromFlushQueue();
|
||||
for (const auto& iter : flush_req) {
|
||||
ColumnFamilyData* cfd = iter.first;
|
||||
if (cfd->Unref()) {
|
||||
delete cfd;
|
||||
}
|
||||
}
|
||||
}
|
||||
while (!compaction_queue_.empty()) {
|
||||
@@ -441,6 +521,17 @@ Status DBImpl::CloseHelper() {
|
||||
ROCKS_LOG_INFO(immutable_db_options_.info_log, "Shutdown complete");
|
||||
LogFlush(immutable_db_options_.info_log);
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
// If the sst_file_manager was allocated by us during DB::Open(), ccall
|
||||
// Close() on it before closing the info_log. Otherwise, background thread
|
||||
// in SstFileManagerImpl might try to log something
|
||||
if (immutable_db_options_.sst_file_manager && own_sfm_) {
|
||||
auto sfm = static_cast<SstFileManagerImpl*>(
|
||||
immutable_db_options_.sst_file_manager.get());
|
||||
sfm->Close();
|
||||
}
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
if (immutable_db_options_.info_log && own_info_log_) {
|
||||
Status s = immutable_db_options_.info_log->Close();
|
||||
if (ret.ok()) {
|
||||
@@ -1048,7 +1139,7 @@ InternalIterator* DBImpl::NewInternalIterator(
|
||||
} else {
|
||||
CleanupSuperVersion(super_version);
|
||||
}
|
||||
return NewErrorInternalIterator(s, arena);
|
||||
return NewErrorInternalIterator<Slice>(s, arena);
|
||||
}
|
||||
|
||||
ColumnFamilyHandle* DBImpl::DefaultColumnFamily() const {
|
||||
@@ -1072,6 +1163,15 @@ Status DBImpl::GetImpl(const ReadOptions& read_options,
|
||||
auto cfh = reinterpret_cast<ColumnFamilyHandleImpl*>(column_family);
|
||||
auto cfd = cfh->cfd();
|
||||
|
||||
if (tracer_) {
|
||||
// TODO: This mutex should be removed later, to improve performance when
|
||||
// tracing is enabled.
|
||||
InstrumentedMutexLock lock(&trace_mutex_);
|
||||
if (tracer_) {
|
||||
tracer_->Get(column_family, key);
|
||||
}
|
||||
}
|
||||
|
||||
// Acquire SuperVersion
|
||||
SuperVersion* sv = GetAndRefSuperVersion(cfd);
|
||||
|
||||
@@ -1578,6 +1678,10 @@ bool DBImpl::KeyMayExist(const ReadOptions& read_options,
|
||||
|
||||
Iterator* DBImpl::NewIterator(const ReadOptions& read_options,
|
||||
ColumnFamilyHandle* column_family) {
|
||||
if (read_options.managed) {
|
||||
return NewErrorIterator(
|
||||
Status::NotSupported("Managed iterator is not supported anymore."));
|
||||
}
|
||||
Iterator* result = nullptr;
|
||||
if (read_options.read_tier == kPersistedTier) {
|
||||
return NewErrorIterator(Status::NotSupported(
|
||||
@@ -1595,22 +1699,7 @@ Iterator* DBImpl::NewIterator(const ReadOptions& read_options,
|
||||
auto cfh = reinterpret_cast<ColumnFamilyHandleImpl*>(column_family);
|
||||
auto cfd = cfh->cfd();
|
||||
ReadCallback* read_callback = nullptr; // No read callback provided.
|
||||
if (read_options.managed) {
|
||||
#ifdef ROCKSDB_LITE
|
||||
// not supported in lite version
|
||||
result = NewErrorIterator(Status::InvalidArgument(
|
||||
"Managed Iterators not supported in RocksDBLite."));
|
||||
#else
|
||||
if ((read_options.tailing) || (read_options.snapshot != nullptr) ||
|
||||
(is_snapshot_supported_)) {
|
||||
result = new ManagedIterator(this, read_options, cfd);
|
||||
} else {
|
||||
// Managed iter not supported
|
||||
result = NewErrorIterator(Status::InvalidArgument(
|
||||
"Managed Iterators not supported without snapshots."));
|
||||
}
|
||||
#endif
|
||||
} else if (read_options.tailing) {
|
||||
if (read_options.tailing) {
|
||||
#ifdef ROCKSDB_LITE
|
||||
// not supported in lite version
|
||||
result = nullptr;
|
||||
@@ -1621,8 +1710,8 @@ Iterator* DBImpl::NewIterator(const ReadOptions& read_options,
|
||||
result = NewDBIterator(
|
||||
env_, read_options, *cfd->ioptions(), sv->mutable_cf_options,
|
||||
cfd->user_comparator(), iter, kMaxSequenceNumber,
|
||||
sv->mutable_cf_options.max_sequential_skip_in_iterations,
|
||||
read_callback);
|
||||
sv->mutable_cf_options.max_sequential_skip_in_iterations, read_callback,
|
||||
this, cfd);
|
||||
#endif
|
||||
} else {
|
||||
// Note: no need to consider the special case of
|
||||
@@ -1689,9 +1778,8 @@ ArenaWrappedDBIter* DBImpl::NewIteratorImpl(const ReadOptions& read_options,
|
||||
ArenaWrappedDBIter* db_iter = NewArenaWrappedDbIterator(
|
||||
env_, read_options, *cfd->ioptions(), sv->mutable_cf_options, snapshot,
|
||||
sv->mutable_cf_options.max_sequential_skip_in_iterations,
|
||||
sv->version_number, read_callback,
|
||||
((read_options.snapshot != nullptr) ? nullptr : this), cfd, allow_blob,
|
||||
allow_refresh);
|
||||
sv->version_number, read_callback, this, cfd, allow_blob,
|
||||
((read_options.snapshot != nullptr) ? false : allow_refresh));
|
||||
|
||||
InternalIterator* internal_iter =
|
||||
NewInternalIterator(read_options, cfd, sv, db_iter->GetArena(),
|
||||
@@ -1705,6 +1793,9 @@ Status DBImpl::NewIterators(
|
||||
const ReadOptions& read_options,
|
||||
const std::vector<ColumnFamilyHandle*>& column_families,
|
||||
std::vector<Iterator*>* iterators) {
|
||||
if (read_options.managed) {
|
||||
return Status::NotSupported("Managed iterator is not supported anymore.");
|
||||
}
|
||||
if (read_options.read_tier == kPersistedTier) {
|
||||
return Status::NotSupported(
|
||||
"ReadTier::kPersistedData is not yet supported in iterators.");
|
||||
@@ -1712,23 +1803,7 @@ Status DBImpl::NewIterators(
|
||||
ReadCallback* read_callback = nullptr; // No read callback provided.
|
||||
iterators->clear();
|
||||
iterators->reserve(column_families.size());
|
||||
if (read_options.managed) {
|
||||
#ifdef ROCKSDB_LITE
|
||||
return Status::InvalidArgument(
|
||||
"Managed interator not supported in RocksDB lite");
|
||||
#else
|
||||
if ((!read_options.tailing) && (read_options.snapshot == nullptr) &&
|
||||
(!is_snapshot_supported_)) {
|
||||
return Status::InvalidArgument(
|
||||
"Managed interator not supported without snapshots");
|
||||
}
|
||||
for (auto cfh : column_families) {
|
||||
auto cfd = reinterpret_cast<ColumnFamilyHandleImpl*>(cfh)->cfd();
|
||||
auto iter = new ManagedIterator(this, read_options, cfd);
|
||||
iterators->push_back(iter);
|
||||
}
|
||||
#endif
|
||||
} else if (read_options.tailing) {
|
||||
if (read_options.tailing) {
|
||||
#ifdef ROCKSDB_LITE
|
||||
return Status::InvalidArgument(
|
||||
"Tailing interator not supported in RocksDB lite");
|
||||
@@ -1741,7 +1816,7 @@ Status DBImpl::NewIterators(
|
||||
env_, read_options, *cfd->ioptions(), sv->mutable_cf_options,
|
||||
cfd->user_comparator(), iter, kMaxSequenceNumber,
|
||||
sv->mutable_cf_options.max_sequential_skip_in_iterations,
|
||||
read_callback));
|
||||
read_callback, this, cfd));
|
||||
}
|
||||
#endif
|
||||
} else {
|
||||
@@ -2252,9 +2327,9 @@ Status DBImpl::DeleteFile(std::string name) {
|
||||
status = versions_->LogAndApply(cfd, *cfd->GetLatestMutableCFOptions(),
|
||||
&edit, &mutex_, directories_.GetDbDir());
|
||||
if (status.ok()) {
|
||||
InstallSuperVersionAndScheduleWork(cfd, &job_context.superversion_context,
|
||||
*cfd->GetLatestMutableCFOptions(),
|
||||
FlushReason::kDeleteFiles);
|
||||
InstallSuperVersionAndScheduleWork(
|
||||
cfd, &job_context.superversion_contexts[0],
|
||||
*cfd->GetLatestMutableCFOptions(), FlushReason::kDeleteFiles);
|
||||
}
|
||||
FindObsoleteFiles(&job_context, false);
|
||||
} // lock released here
|
||||
@@ -2337,9 +2412,9 @@ Status DBImpl::DeleteFilesInRanges(ColumnFamilyHandle* column_family,
|
||||
status = versions_->LogAndApply(cfd, *cfd->GetLatestMutableCFOptions(),
|
||||
&edit, &mutex_, directories_.GetDbDir());
|
||||
if (status.ok()) {
|
||||
InstallSuperVersionAndScheduleWork(cfd, &job_context.superversion_context,
|
||||
*cfd->GetLatestMutableCFOptions(),
|
||||
FlushReason::kDeleteFiles);
|
||||
InstallSuperVersionAndScheduleWork(
|
||||
cfd, &job_context.superversion_contexts[0],
|
||||
*cfd->GetLatestMutableCFOptions(), FlushReason::kDeleteFiles);
|
||||
}
|
||||
for (auto* deleted_file : deleted_files) {
|
||||
deleted_file->being_compacted = false;
|
||||
@@ -2427,7 +2502,7 @@ Status DBImpl::GetDbIdentity(std::string& identity) const {
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
char* buffer = reinterpret_cast<char*>(alloca(file_size));
|
||||
char* buffer = reinterpret_cast<char*>(alloca(static_cast<size_t>(file_size)));
|
||||
Slice id;
|
||||
s = id_file_reader->Read(static_cast<size_t>(file_size), &id, buffer);
|
||||
if (!s.ok()) {
|
||||
@@ -2904,6 +2979,10 @@ Status DBImpl::IngestExternalFile(
|
||||
ColumnFamilyHandle* column_family,
|
||||
const std::vector<std::string>& external_files,
|
||||
const IngestExternalFileOptions& ingestion_options) {
|
||||
if (external_files.empty()) {
|
||||
return Status::InvalidArgument("external_files is empty");
|
||||
}
|
||||
|
||||
Status status;
|
||||
auto cfh = reinterpret_cast<ColumnFamilyHandleImpl*>(column_family);
|
||||
auto cfd = cfh->cfd();
|
||||
@@ -2921,6 +3000,9 @@ Status DBImpl::IngestExternalFile(
|
||||
immutable_db_options_, env_options_,
|
||||
&snapshots_, ingestion_options);
|
||||
|
||||
SuperVersionContext dummy_sv_ctx(/* create_superversion */ true);
|
||||
VersionEdit dummy_edit;
|
||||
uint64_t next_file_number = 0;
|
||||
std::list<uint64_t>::iterator pending_output_elem;
|
||||
{
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
@@ -2931,12 +3013,33 @@ Status DBImpl::IngestExternalFile(
|
||||
|
||||
// Make sure that bg cleanup wont delete the files that we are ingesting
|
||||
pending_output_elem = CaptureCurrentFileNumberInPendingOutputs();
|
||||
|
||||
// If crash happen after a hard link established, Recover function may
|
||||
// reuse the file number that has already assigned to the internal file,
|
||||
// and this will overwrite the external file. To protect the external
|
||||
// file, we have to make sure the file number will never being reused.
|
||||
next_file_number = versions_->FetchAddFileNumber(external_files.size());
|
||||
auto cf_options = cfd->GetLatestMutableCFOptions();
|
||||
status = versions_->LogAndApply(cfd, *cf_options, &dummy_edit, &mutex_,
|
||||
directories_.GetDbDir());
|
||||
if (status.ok()) {
|
||||
InstallSuperVersionAndScheduleWork(cfd, &dummy_sv_ctx, *cf_options);
|
||||
}
|
||||
}
|
||||
dummy_sv_ctx.Clean();
|
||||
if (!status.ok()) {
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
ReleaseFileNumberFromPendingOutputs(pending_output_elem);
|
||||
return status;
|
||||
}
|
||||
|
||||
SuperVersion* super_version = cfd->GetReferencedSuperVersion(&mutex_);
|
||||
status = ingestion_job.Prepare(external_files, super_version);
|
||||
status =
|
||||
ingestion_job.Prepare(external_files, next_file_number, super_version);
|
||||
CleanupSuperVersion(super_version);
|
||||
if (!status.ok()) {
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
ReleaseFileNumberFromPendingOutputs(pending_output_elem);
|
||||
return status;
|
||||
}
|
||||
|
||||
@@ -2956,6 +3059,7 @@ Status DBImpl::IngestExternalFile(
|
||||
}
|
||||
|
||||
num_running_ingest_file_++;
|
||||
TEST_SYNC_POINT("DBImpl::IngestExternalFile:AfterIncIngestFileCounter");
|
||||
|
||||
// We cannot ingest a file into a dropped CF
|
||||
if (cfd->IsDropped()) {
|
||||
@@ -2971,7 +3075,9 @@ Status DBImpl::IngestExternalFile(
|
||||
&need_flush);
|
||||
if (status.ok() && need_flush) {
|
||||
mutex_.Unlock();
|
||||
status = FlushMemTable(cfd, FlushOptions(),
|
||||
FlushOptions flush_opts;
|
||||
flush_opts.allow_write_stall = true;
|
||||
status = FlushMemTable(cfd, flush_opts,
|
||||
FlushReason::kExternalFileIngestion,
|
||||
true /* writes_stopped */);
|
||||
mutex_.Lock();
|
||||
@@ -3030,8 +3136,6 @@ Status DBImpl::IngestExternalFile(
|
||||
|
||||
Status DBImpl::VerifyChecksum() {
|
||||
Status s;
|
||||
Options options;
|
||||
EnvOptions env_options;
|
||||
std::vector<ColumnFamilyData*> cfd_list;
|
||||
{
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
@@ -3049,13 +3153,19 @@ Status DBImpl::VerifyChecksum() {
|
||||
for (auto& sv : sv_list) {
|
||||
VersionStorageInfo* vstorage = sv->current->storage_info();
|
||||
ColumnFamilyData* cfd = sv->current->cfd();
|
||||
Options opts;
|
||||
{
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
opts = Options(BuildDBOptions(immutable_db_options_,
|
||||
mutable_db_options_), cfd->GetLatestCFOptions());
|
||||
}
|
||||
for (int i = 0; i < vstorage->num_non_empty_levels() && s.ok(); i++) {
|
||||
for (size_t j = 0; j < vstorage->LevelFilesBrief(i).num_files && s.ok();
|
||||
j++) {
|
||||
const auto& fd = vstorage->LevelFilesBrief(i).files[j].fd;
|
||||
std::string fname = TableFileName(cfd->ioptions()->cf_paths,
|
||||
fd.GetNumber(), fd.GetPathId());
|
||||
s = rocksdb::VerifySstFileChecksum(options, env_options, fname);
|
||||
s = rocksdb::VerifySstFileChecksum(opts, env_options_, fname);
|
||||
}
|
||||
}
|
||||
if (!s.ok()) {
|
||||
@@ -3079,7 +3189,6 @@ Status DBImpl::VerifyChecksum() {
|
||||
|
||||
void DBImpl::NotifyOnExternalFileIngested(
|
||||
ColumnFamilyData* cfd, const ExternalSstFileIngestionJob& ingestion_job) {
|
||||
#ifndef ROCKSDB_LITE
|
||||
if (immutable_db_options_.listeners.empty()) {
|
||||
return;
|
||||
}
|
||||
@@ -3095,8 +3204,6 @@ void DBImpl::NotifyOnExternalFileIngested(
|
||||
listener->OnExternalFileIngested(this, info);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
void DBImpl::WaitForIngestFile() {
|
||||
@@ -3106,5 +3213,43 @@ void DBImpl::WaitForIngestFile() {
|
||||
}
|
||||
}
|
||||
|
||||
Status DBImpl::StartTrace(const TraceOptions& /* options */,
|
||||
std::unique_ptr<TraceWriter>&& trace_writer) {
|
||||
InstrumentedMutexLock lock(&trace_mutex_);
|
||||
tracer_.reset(new Tracer(env_, std::move(trace_writer)));
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status DBImpl::EndTrace() {
|
||||
InstrumentedMutexLock lock(&trace_mutex_);
|
||||
Status s = tracer_->Close();
|
||||
tracer_.reset();
|
||||
return s;
|
||||
}
|
||||
|
||||
Status DBImpl::TraceIteratorSeek(const uint32_t& cf_id, const Slice& key) {
|
||||
Status s;
|
||||
if (tracer_) {
|
||||
InstrumentedMutexLock lock(&trace_mutex_);
|
||||
if (tracer_) {
|
||||
s = tracer_->IteratorSeek(cf_id, key);
|
||||
}
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
Status DBImpl::TraceIteratorSeekForPrev(const uint32_t& cf_id,
|
||||
const Slice& key) {
|
||||
Status s;
|
||||
if (tracer_) {
|
||||
InstrumentedMutexLock lock(&trace_mutex_);
|
||||
if (tracer_) {
|
||||
s = tracer_->IteratorSeekForPrev(cf_id, key);
|
||||
}
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
} // namespace rocksdb
|
||||
+101
-13
@@ -22,9 +22,9 @@
|
||||
#include "db/column_family.h"
|
||||
#include "db/compaction_job.h"
|
||||
#include "db/dbformat.h"
|
||||
#include "db/external_sst_file_ingestion_job.h"
|
||||
#include "db/error_handler.h"
|
||||
#include "db/event_helpers.h"
|
||||
#include "db/external_sst_file_ingestion_job.h"
|
||||
#include "db/flush_job.h"
|
||||
#include "db/flush_scheduler.h"
|
||||
#include "db/internal_stats.h"
|
||||
@@ -46,6 +46,7 @@
|
||||
#include "rocksdb/env.h"
|
||||
#include "rocksdb/memtablerep.h"
|
||||
#include "rocksdb/status.h"
|
||||
#include "rocksdb/trace_reader_writer.h"
|
||||
#include "rocksdb/transaction_log.h"
|
||||
#include "rocksdb/write_buffer_manager.h"
|
||||
#include "table/scoped_arena_iterator.h"
|
||||
@@ -54,6 +55,7 @@
|
||||
#include "util/hash.h"
|
||||
#include "util/stop_watch.h"
|
||||
#include "util/thread_local.h"
|
||||
#include "util/trace_replay.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
@@ -333,6 +335,15 @@ class DBImpl : public DB {
|
||||
|
||||
virtual Status VerifyChecksum() override;
|
||||
|
||||
using DB::StartTrace;
|
||||
virtual Status StartTrace(
|
||||
const TraceOptions& options,
|
||||
std::unique_ptr<TraceWriter>&& trace_writer) override;
|
||||
|
||||
using DB::EndTrace;
|
||||
virtual Status EndTrace() override;
|
||||
Status TraceIteratorSeek(const uint32_t& cf_id, const Slice& key);
|
||||
Status TraceIteratorSeekForPrev(const uint32_t& cf_id, const Slice& key);
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
// Similar to GetSnapshot(), but also lets the db know that this snapshot
|
||||
@@ -385,7 +396,7 @@ class DBImpl : public DB {
|
||||
Status TEST_SwitchMemtable(ColumnFamilyData* cfd = nullptr);
|
||||
|
||||
// Force current memtable contents to be flushed.
|
||||
Status TEST_FlushMemTable(bool wait = true,
|
||||
Status TEST_FlushMemTable(bool wait = true, bool allow_write_stall = false,
|
||||
ColumnFamilyHandle* cfh = nullptr);
|
||||
|
||||
// Wait for memtable compaction
|
||||
@@ -453,6 +464,7 @@ class DBImpl : public DB {
|
||||
|
||||
int TEST_BGCompactionsAllowed() const;
|
||||
int TEST_BGFlushesAllowed() const;
|
||||
size_t TEST_GetWalPreallocateBlockSize(uint64_t write_buffer_size) const;
|
||||
|
||||
#endif // NDEBUG
|
||||
|
||||
@@ -697,6 +709,8 @@ class DBImpl : public DB {
|
||||
Statistics* stats_;
|
||||
std::unordered_map<std::string, RecoveredTransaction*>
|
||||
recovered_transactions_;
|
||||
std::unique_ptr<Tracer> tracer_;
|
||||
InstrumentedMutex trace_mutex_;
|
||||
|
||||
// Except in DB::Open(), WriteOptionsFile can only be called when:
|
||||
// Persist options to options file.
|
||||
@@ -782,6 +796,7 @@ class DBImpl : public DB {
|
||||
|
||||
private:
|
||||
friend class DB;
|
||||
friend class ErrorHandler;
|
||||
friend class InternalStats;
|
||||
friend class PessimisticTransaction;
|
||||
friend class TransactionBaseImpl;
|
||||
@@ -790,18 +805,21 @@ class DBImpl : public DB {
|
||||
friend class WritePreparedTxnDB;
|
||||
friend class WriteBatchWithIndex;
|
||||
friend class WriteUnpreparedTxnDB;
|
||||
friend class WriteUnpreparedTxn;
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
friend class ForwardIterator;
|
||||
#endif
|
||||
friend struct SuperVersion;
|
||||
friend class CompactedDBImpl;
|
||||
friend class DBTest_ConcurrentFlushWAL_Test;
|
||||
friend class DBTest_MixedSlowdownOptionsStop_Test;
|
||||
#ifndef NDEBUG
|
||||
friend class DBTest2_ReadCallbackTest_Test;
|
||||
friend class WriteCallbackTest_WriteWithCallbackTest_Test;
|
||||
friend class XFTransactionWriteHandler;
|
||||
friend class DBBlobIndexTest;
|
||||
friend class WriteUnpreparedTransactionTest_RecoveryRollbackUnprepared_Test;
|
||||
friend class WriteUnpreparedTransactionTest_RecoveryTest_Test;
|
||||
#endif
|
||||
struct CompactionState;
|
||||
|
||||
@@ -830,6 +848,8 @@ class DBImpl : public DB {
|
||||
bool read_only = false, bool error_if_log_file_exist = false,
|
||||
bool error_if_data_exists_in_logs = false);
|
||||
|
||||
Status ResumeImpl();
|
||||
|
||||
void MaybeIgnoreError(Status* s) const;
|
||||
|
||||
const Status CreateArchivalDirectory();
|
||||
@@ -869,12 +889,41 @@ class DBImpl : public DB {
|
||||
Status SyncClosedLogs(JobContext* job_context);
|
||||
|
||||
// Flush the in-memory write buffer to storage. Switches to a new
|
||||
// log-file/memtable and writes a new descriptor iff successful.
|
||||
// log-file/memtable and writes a new descriptor iff successful. Then
|
||||
// installs a new super version for the column family.
|
||||
Status FlushMemTableToOutputFile(ColumnFamilyData* cfd,
|
||||
const MutableCFOptions& mutable_cf_options,
|
||||
bool* madeProgress, JobContext* job_context,
|
||||
SuperVersionContext* superversion_context,
|
||||
LogBuffer* log_buffer);
|
||||
|
||||
// Argument required by background flush thread.
|
||||
struct BGFlushArg {
|
||||
BGFlushArg()
|
||||
: cfd_(nullptr), memtable_id_(0), superversion_context_(nullptr) {}
|
||||
BGFlushArg(ColumnFamilyData* cfd, uint64_t memtable_id,
|
||||
SuperVersionContext* superversion_context)
|
||||
: cfd_(cfd),
|
||||
memtable_id_(memtable_id),
|
||||
superversion_context_(superversion_context) {}
|
||||
|
||||
// Column family to flush.
|
||||
ColumnFamilyData* cfd_;
|
||||
// Maximum ID of memtable to flush. In this column family, memtables with
|
||||
// IDs smaller than this value must be flushed before this flush completes.
|
||||
uint64_t memtable_id_;
|
||||
// Pointer to a SuperVersionContext object. After flush completes, RocksDB
|
||||
// installs a new superversion for the column family. This operation
|
||||
// requires a SuperVersionContext object (currently embedded in JobContext).
|
||||
SuperVersionContext* superversion_context_;
|
||||
};
|
||||
|
||||
// Flush the memtables of (multiple) column families to multiple files on
|
||||
// persistent storage.
|
||||
Status FlushMemTablesToOutputFiles(
|
||||
const autovector<BGFlushArg>& bg_flush_args, bool* made_progress,
|
||||
JobContext* job_context, LogBuffer* log_buffer);
|
||||
|
||||
// REQUIRES: log_numbers are sorted in ascending order
|
||||
Status RecoverLogFiles(const std::vector<uint64_t>& log_numbers,
|
||||
SequenceNumber* next_sequence, bool read_only);
|
||||
@@ -887,6 +936,12 @@ class DBImpl : public DB {
|
||||
Status WriteLevel0TableForRecovery(int job_id, ColumnFamilyData* cfd,
|
||||
MemTable* mem, VersionEdit* edit);
|
||||
|
||||
// Restore alive_log_files_ and total_log_size_ after recovery.
|
||||
// It needs to run only when there's no flush during recovery
|
||||
// (e.g. avoid_flush_during_recovery=true). May also trigger flush
|
||||
// in case total_log_size > max_total_wal_size.
|
||||
Status RestoreAliveLogFiles(const std::vector<uint64_t>& log_numbers);
|
||||
|
||||
// num_bytes: for slowdown case, delay time is calculated based on
|
||||
// `num_bytes` going through.
|
||||
Status DelayWrite(uint64_t num_bytes, const WriteOptions& write_options);
|
||||
@@ -896,19 +951,28 @@ class DBImpl : public DB {
|
||||
|
||||
Status ScheduleFlushes(WriteContext* context);
|
||||
|
||||
Status SwitchMemtable(ColumnFamilyData* cfd, WriteContext* context,
|
||||
FlushReason flush_reason = FlushReason::kOthers);
|
||||
Status SwitchMemtable(ColumnFamilyData* cfd, WriteContext* context);
|
||||
|
||||
// Force current memtable contents to be flushed.
|
||||
Status FlushMemTable(ColumnFamilyData* cfd, const FlushOptions& options,
|
||||
FlushReason flush_reason, bool writes_stopped = false);
|
||||
|
||||
// Wait until flushing this column family won't stall writes
|
||||
Status WaitUntilFlushWouldNotStallWrites(ColumnFamilyData* cfd,
|
||||
bool* flush_needed);
|
||||
|
||||
// Wait for memtable flushed.
|
||||
// If flush_memtable_id is non-null, wait until the memtable with the ID
|
||||
// gets flush. Otherwise, wait until the column family don't have any
|
||||
// memtable pending flush.
|
||||
Status WaitForFlushMemTable(ColumnFamilyData* cfd,
|
||||
const uint64_t* flush_memtable_id = nullptr);
|
||||
const uint64_t* flush_memtable_id = nullptr) {
|
||||
return WaitForFlushMemTables({cfd}, {flush_memtable_id});
|
||||
}
|
||||
// Wait for memtables to be flushed for multiple column families.
|
||||
Status WaitForFlushMemTables(
|
||||
const autovector<ColumnFamilyData*>& cfds,
|
||||
const autovector<const uint64_t*>& flush_memtable_ids);
|
||||
|
||||
// REQUIRES: mutex locked
|
||||
Status SwitchWAL(WriteContext* write_context);
|
||||
@@ -964,7 +1028,17 @@ class DBImpl : public DB {
|
||||
ColumnFamilyData* GetColumnFamilyDataByName(const std::string& cf_name);
|
||||
|
||||
void MaybeScheduleFlushOrCompaction();
|
||||
void SchedulePendingFlush(ColumnFamilyData* cfd, FlushReason flush_reason);
|
||||
|
||||
// A flush request specifies the column families to flush as well as the
|
||||
// largest memtable id to persist for each column family. Once all the
|
||||
// memtables whose IDs are smaller than or equal to this per-column-family
|
||||
// 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;
|
||||
|
||||
void SchedulePendingFlush(const FlushRequest& req, FlushReason flush_reason);
|
||||
|
||||
void SchedulePendingCompaction(ColumnFamilyData* cfd);
|
||||
void SchedulePendingPurge(std::string fname, std::string dir_to_sync,
|
||||
FileType type, uint64_t number, int job_id);
|
||||
@@ -983,9 +1057,10 @@ class DBImpl : public DB {
|
||||
LogBuffer* log_buffer,
|
||||
PrepickedCompaction* prepicked_compaction);
|
||||
Status BackgroundFlush(bool* madeProgress, JobContext* job_context,
|
||||
LogBuffer* log_buffer);
|
||||
LogBuffer* log_buffer, FlushReason* reason);
|
||||
|
||||
bool EnoughRoomForCompaction(const std::vector<CompactionInputFiles>& inputs,
|
||||
bool EnoughRoomForCompaction(ColumnFamilyData* cfd,
|
||||
const std::vector<CompactionInputFiles>& inputs,
|
||||
bool* sfm_bookkeeping, LogBuffer* log_buffer);
|
||||
|
||||
void PrintStatistics();
|
||||
@@ -1006,8 +1081,7 @@ class DBImpl : public DB {
|
||||
// helper functions for adding and removing from flush & compaction queues
|
||||
void AddToCompactionQueue(ColumnFamilyData* cfd);
|
||||
ColumnFamilyData* PopFirstFromCompactionQueue();
|
||||
void AddToFlushQueue(ColumnFamilyData* cfd, FlushReason flush_reason);
|
||||
ColumnFamilyData* PopFirstFromFlushQueue();
|
||||
FlushRequest PopFirstFromFlushQueue();
|
||||
|
||||
// helper function to call after some of the logs_ were synced
|
||||
void MarkLogsSynced(uint64_t up_to, bool synced_dir, const Status& status);
|
||||
@@ -1020,6 +1094,10 @@ class DBImpl : public DB {
|
||||
|
||||
Status CloseHelper();
|
||||
|
||||
Status FlushAllCFs(FlushReason flush_reason);
|
||||
|
||||
void WaitForBackgroundWork();
|
||||
|
||||
// table_cache_ provides its own synchronization
|
||||
std::shared_ptr<Cache> table_cache_;
|
||||
|
||||
@@ -1240,7 +1318,7 @@ class DBImpl : public DB {
|
||||
// in MaybeScheduleFlushOrCompaction()
|
||||
// invariant(column family present in flush_queue_ <==>
|
||||
// ColumnFamilyData::pending_flush_ == true)
|
||||
std::deque<ColumnFamilyData*> flush_queue_;
|
||||
std::deque<FlushRequest> flush_queue_;
|
||||
// invariant(column family present in compaction_queue_ <==>
|
||||
// ColumnFamilyData::pending_compaction_ == true)
|
||||
std::deque<ColumnFamilyData*> compaction_queue_;
|
||||
@@ -1464,6 +1542,16 @@ class DBImpl : public DB {
|
||||
// flush/compaction and if it is not provided vis SnapshotChecker, we should
|
||||
// disable gc to be safe.
|
||||
const bool use_custom_gc_;
|
||||
// Flag to indicate that the DB instance shutdown has been initiated. This
|
||||
// different from shutting_down_ atomic in that it is set at the beginning
|
||||
// of shutdown sequence, specifically in order to prevent any background
|
||||
// error recovery from going on in parallel. The latter, shutting_down_,
|
||||
// is set a little later during the shutdown after scheduling memtable
|
||||
// flushes
|
||||
bool shutdown_initiated_;
|
||||
// Flag to indicate whether sst_file_manager object was allocated in
|
||||
// DB::Open() or passed to us
|
||||
bool own_sfm_;
|
||||
|
||||
// Clients must periodically call SetPreserveDeletesSequenceNumber()
|
||||
// to advance this seqnum. Default value is 0 which means ALL deletes are
|
||||
|
||||
+348
-151
@@ -26,7 +26,7 @@
|
||||
namespace rocksdb {
|
||||
|
||||
bool DBImpl::EnoughRoomForCompaction(
|
||||
const std::vector<CompactionInputFiles>& inputs,
|
||||
ColumnFamilyData* cfd, const std::vector<CompactionInputFiles>& inputs,
|
||||
bool* sfm_reserved_compact_space, LogBuffer* log_buffer) {
|
||||
// Check if we have enough room to do the compaction
|
||||
bool enough_room = true;
|
||||
@@ -34,12 +34,17 @@ bool DBImpl::EnoughRoomForCompaction(
|
||||
auto sfm = static_cast<SstFileManagerImpl*>(
|
||||
immutable_db_options_.sst_file_manager.get());
|
||||
if (sfm) {
|
||||
enough_room = sfm->EnoughRoomForCompaction(inputs);
|
||||
// Pass the current bg_error_ to SFM so it can decide what checks to
|
||||
// perform. If this DB instance hasn't seen any error yet, the SFM can be
|
||||
// optimistic and not do disk space checks
|
||||
enough_room =
|
||||
sfm->EnoughRoomForCompaction(cfd, inputs, error_handler_.GetBGError());
|
||||
if (enough_room) {
|
||||
*sfm_reserved_compact_space = true;
|
||||
}
|
||||
}
|
||||
#else
|
||||
(void)cfd;
|
||||
(void)inputs;
|
||||
(void)sfm_reserved_compact_space;
|
||||
#endif // ROCKSDB_LITE
|
||||
@@ -104,7 +109,8 @@ Status DBImpl::SyncClosedLogs(JobContext* job_context) {
|
||||
|
||||
Status DBImpl::FlushMemTableToOutputFile(
|
||||
ColumnFamilyData* cfd, const MutableCFOptions& mutable_cf_options,
|
||||
bool* made_progress, JobContext* job_context, LogBuffer* log_buffer) {
|
||||
bool* made_progress, JobContext* job_context,
|
||||
SuperVersionContext* superversion_context, LogBuffer* log_buffer) {
|
||||
mutex_.AssertHeld();
|
||||
assert(cfd->imm()->NumNotFlushed() != 0);
|
||||
assert(cfd->imm()->IsFlushPending());
|
||||
@@ -160,7 +166,7 @@ Status DBImpl::FlushMemTableToOutputFile(
|
||||
}
|
||||
|
||||
if (s.ok()) {
|
||||
InstallSuperVersionAndScheduleWork(cfd, &job_context->superversion_context,
|
||||
InstallSuperVersionAndScheduleWork(cfd, superversion_context,
|
||||
mutable_cf_options);
|
||||
if (made_progress) {
|
||||
*made_progress = 1;
|
||||
@@ -200,6 +206,25 @@ Status DBImpl::FlushMemTableToOutputFile(
|
||||
return s;
|
||||
}
|
||||
|
||||
Status DBImpl::FlushMemTablesToOutputFiles(
|
||||
const autovector<BGFlushArg>& bg_flush_args, bool* made_progress,
|
||||
JobContext* job_context, LogBuffer* log_buffer) {
|
||||
Status s;
|
||||
for (auto& arg : bg_flush_args) {
|
||||
ColumnFamilyData* cfd = arg.cfd_;
|
||||
const MutableCFOptions& mutable_cf_options =
|
||||
*cfd->GetLatestMutableCFOptions();
|
||||
SuperVersionContext* superversion_context = arg.superversion_context_;
|
||||
s = FlushMemTableToOutputFile(cfd, mutable_cf_options, made_progress,
|
||||
job_context, superversion_context,
|
||||
log_buffer);
|
||||
if (!s.ok()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
void DBImpl::NotifyOnFlushBegin(ColumnFamilyData* cfd, FileMetaData* file_meta,
|
||||
const MutableCFOptions& mutable_cf_options,
|
||||
int job_id, TableProperties prop) {
|
||||
@@ -230,8 +255,8 @@ void DBImpl::NotifyOnFlushBegin(ColumnFamilyData* cfd, FileMetaData* file_meta,
|
||||
info.job_id = job_id;
|
||||
info.triggered_writes_slowdown = triggered_writes_slowdown;
|
||||
info.triggered_writes_stop = triggered_writes_stop;
|
||||
info.smallest_seqno = file_meta->smallest_seqno;
|
||||
info.largest_seqno = file_meta->largest_seqno;
|
||||
info.smallest_seqno = file_meta->fd.smallest_seqno;
|
||||
info.largest_seqno = file_meta->fd.largest_seqno;
|
||||
info.table_properties = prop;
|
||||
info.flush_reason = cfd->GetFlushReason();
|
||||
for (auto listener : immutable_db_options_.listeners) {
|
||||
@@ -281,8 +306,8 @@ void DBImpl::NotifyOnFlushCompleted(ColumnFamilyData* cfd,
|
||||
info.job_id = job_id;
|
||||
info.triggered_writes_slowdown = triggered_writes_slowdown;
|
||||
info.triggered_writes_stop = triggered_writes_stop;
|
||||
info.smallest_seqno = file_meta->smallest_seqno;
|
||||
info.largest_seqno = file_meta->largest_seqno;
|
||||
info.smallest_seqno = file_meta->fd.smallest_seqno;
|
||||
info.largest_seqno = file_meta->fd.largest_seqno;
|
||||
info.table_properties = prop;
|
||||
info.flush_reason = cfd->GetFlushReason();
|
||||
for (auto listener : immutable_db_options_.listeners) {
|
||||
@@ -324,60 +349,12 @@ Status DBImpl::CompactRange(const CompactRangeOptions& options,
|
||||
CleanupSuperVersion(super_version);
|
||||
}
|
||||
|
||||
if (!options.allow_write_stall && flush_needed) {
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
uint64_t orig_active_memtable_id = cfd->mem()->GetID();
|
||||
WriteStallCondition write_stall_condition = WriteStallCondition::kNormal;
|
||||
do {
|
||||
if (write_stall_condition != WriteStallCondition::kNormal) {
|
||||
TEST_SYNC_POINT("DBImpl::CompactRange:StallWait");
|
||||
ROCKS_LOG_INFO(immutable_db_options_.info_log,
|
||||
"[%s] CompactRange waiting on stall conditions to clear",
|
||||
cfd->GetName().c_str());
|
||||
bg_cv_.Wait();
|
||||
}
|
||||
if (cfd->IsDropped() || shutting_down_.load(std::memory_order_acquire)) {
|
||||
return Status::ShutdownInProgress();
|
||||
}
|
||||
|
||||
uint64_t earliest_memtable_id =
|
||||
std::min(cfd->mem()->GetID(), cfd->imm()->GetEarliestMemTableID());
|
||||
if (earliest_memtable_id > orig_active_memtable_id) {
|
||||
// We waited so long that the memtable we were originally waiting on was
|
||||
// flushed.
|
||||
flush_needed = false;
|
||||
break;
|
||||
}
|
||||
|
||||
const auto& mutable_cf_options = *cfd->GetLatestMutableCFOptions();
|
||||
const auto* vstorage = cfd->current()->storage_info();
|
||||
|
||||
// Skip stalling check if we're below auto-flush and auto-compaction
|
||||
// triggers. If it stalled in these conditions, that'd mean the stall
|
||||
// triggers are so low that stalling is needed for any background work. In
|
||||
// that case we shouldn't wait since background work won't be scheduled.
|
||||
if (cfd->imm()->NumNotFlushed() <
|
||||
cfd->ioptions()->min_write_buffer_number_to_merge &&
|
||||
vstorage->l0_delay_trigger_count() <
|
||||
mutable_cf_options.level0_file_num_compaction_trigger) {
|
||||
break;
|
||||
}
|
||||
|
||||
// check whether one extra immutable memtable or an extra L0 file would
|
||||
// cause write stalling mode to be entered. It could still enter stall
|
||||
// mode due to pending compaction bytes, but that's less common
|
||||
write_stall_condition =
|
||||
ColumnFamilyData::GetWriteStallConditionAndCause(
|
||||
cfd->imm()->NumNotFlushed() + 1,
|
||||
vstorage->l0_delay_trigger_count() + 1,
|
||||
vstorage->estimated_compaction_needed_bytes(), mutable_cf_options)
|
||||
.first;
|
||||
} while (write_stall_condition != WriteStallCondition::kNormal);
|
||||
}
|
||||
TEST_SYNC_POINT("DBImpl::CompactRange:StallWaitDone");
|
||||
Status s;
|
||||
if (flush_needed) {
|
||||
s = FlushMemTable(cfd, FlushOptions(), FlushReason::kManualCompaction);
|
||||
FlushOptions fo;
|
||||
fo.allow_write_stall = options.allow_write_stall;
|
||||
s = FlushMemTable(cfd, fo, FlushReason::kManualCompaction,
|
||||
false /* writes_stopped*/);
|
||||
if (!s.ok()) {
|
||||
LogFlush(immutable_db_options_.info_log);
|
||||
return s;
|
||||
@@ -612,7 +589,7 @@ Status DBImpl::CompactFilesImpl(
|
||||
bool sfm_reserved_compact_space = false;
|
||||
// First check if we have enough room to do the compaction
|
||||
bool enough_room = EnoughRoomForCompaction(
|
||||
input_files, &sfm_reserved_compact_space, log_buffer);
|
||||
cfd, input_files, &sfm_reserved_compact_space, log_buffer);
|
||||
|
||||
if (!enough_room) {
|
||||
// m's vars will get set properly at the end of this function,
|
||||
@@ -691,7 +668,7 @@ Status DBImpl::CompactFilesImpl(
|
||||
Status status = compaction_job.Install(*c->mutable_cf_options());
|
||||
if (status.ok()) {
|
||||
InstallSuperVersionAndScheduleWork(
|
||||
c->column_family_data(), &job_context->superversion_context,
|
||||
c->column_family_data(), &job_context->superversion_contexts[0],
|
||||
*c->mutable_cf_options(), FlushReason::kManualCompaction);
|
||||
}
|
||||
c->ReleaseCompactionFiles(s);
|
||||
@@ -885,7 +862,7 @@ Status DBImpl::ReFitLevel(ColumnFamilyData* cfd, int level, int target_level) {
|
||||
edit.DeleteFile(level, f->fd.GetNumber());
|
||||
edit.AddFile(to_level, f->fd.GetNumber(), f->fd.GetPathId(),
|
||||
f->fd.GetFileSize(), f->smallest, f->largest,
|
||||
f->smallest_seqno, f->largest_seqno,
|
||||
f->fd.smallest_seqno, f->fd.largest_seqno,
|
||||
f->marked_for_compaction);
|
||||
}
|
||||
ROCKS_LOG_DEBUG(immutable_db_options_.info_log,
|
||||
@@ -942,6 +919,68 @@ Status DBImpl::Flush(const FlushOptions& flush_options,
|
||||
return s;
|
||||
}
|
||||
|
||||
|
||||
Status DBImpl::FlushAllCFs(FlushReason flush_reason) {
|
||||
Status s;
|
||||
WriteContext context;
|
||||
WriteThread::Writer w;
|
||||
|
||||
mutex_.AssertHeld();
|
||||
write_thread_.EnterUnbatched(&w, &mutex_);
|
||||
|
||||
FlushRequest flush_req;
|
||||
for (auto cfd : *versions_->GetColumnFamilySet()) {
|
||||
if (cfd->imm()->NumNotFlushed() == 0 && cfd->mem()->IsEmpty() &&
|
||||
cached_recoverable_state_empty_.load()) {
|
||||
// Nothing to flush
|
||||
continue;
|
||||
}
|
||||
|
||||
// SwitchMemtable() will release and reacquire mutex during execution
|
||||
s = SwitchMemtable(cfd, &context);
|
||||
if (!s.ok()) {
|
||||
break;
|
||||
}
|
||||
|
||||
cfd->imm()->FlushRequested();
|
||||
|
||||
flush_req.emplace_back(cfd, cfd->imm()->GetLatestMemTableID());
|
||||
}
|
||||
|
||||
// schedule flush
|
||||
if (s.ok() && !flush_req.empty()) {
|
||||
SchedulePendingFlush(flush_req, flush_reason);
|
||||
MaybeScheduleFlushOrCompaction();
|
||||
}
|
||||
|
||||
write_thread_.ExitUnbatched(&w);
|
||||
|
||||
if (s.ok()) {
|
||||
for (auto& flush : flush_req) {
|
||||
auto cfd = flush.first;
|
||||
auto flush_memtable_id = flush.second;
|
||||
while (cfd->imm()->NumNotFlushed() > 0 &&
|
||||
cfd->imm()->GetEarliestMemTableID() <= flush_memtable_id) {
|
||||
if (!error_handler_.GetRecoveryError().ok()) {
|
||||
break;
|
||||
}
|
||||
if (cfd->IsDropped()) {
|
||||
// FlushJob cannot flush a dropped CF, if we did not break here
|
||||
// we will loop forever since cfd->imm()->NumNotFlushed() will never
|
||||
// drop to zero
|
||||
continue;
|
||||
}
|
||||
cfd->Ref();
|
||||
bg_cv_.Wait();
|
||||
cfd->Unref();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
flush_req.clear();
|
||||
return s;
|
||||
}
|
||||
|
||||
Status DBImpl::RunManualCompaction(ColumnFamilyData* cfd, int input_level,
|
||||
int output_level, uint32_t output_path_id,
|
||||
uint32_t max_subcompactions,
|
||||
@@ -1077,63 +1116,164 @@ Status DBImpl::FlushMemTable(ColumnFamilyData* cfd,
|
||||
FlushReason flush_reason, bool writes_stopped) {
|
||||
Status s;
|
||||
uint64_t flush_memtable_id = 0;
|
||||
if (!flush_options.allow_write_stall) {
|
||||
bool flush_needed = true;
|
||||
s = WaitUntilFlushWouldNotStallWrites(cfd, &flush_needed);
|
||||
TEST_SYNC_POINT("DBImpl::FlushMemTable:StallWaitDone");
|
||||
if (!s.ok() || !flush_needed) {
|
||||
return s;
|
||||
}
|
||||
}
|
||||
FlushRequest flush_req;
|
||||
{
|
||||
WriteContext context;
|
||||
InstrumentedMutexLock guard_lock(&mutex_);
|
||||
|
||||
if (cfd->imm()->NumNotFlushed() == 0 && cfd->mem()->IsEmpty() &&
|
||||
cached_recoverable_state_empty_.load()) {
|
||||
// Nothing to flush
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
WriteThread::Writer w;
|
||||
if (!writes_stopped) {
|
||||
write_thread_.EnterUnbatched(&w, &mutex_);
|
||||
}
|
||||
|
||||
// SwitchMemtable() will release and reacquire mutex during execution
|
||||
s = SwitchMemtable(cfd, &context);
|
||||
flush_memtable_id = cfd->imm()->GetLatestMemTableID();
|
||||
if (cfd->imm()->NumNotFlushed() != 0 || !cfd->mem()->IsEmpty() ||
|
||||
!cached_recoverable_state_empty_.load()) {
|
||||
s = SwitchMemtable(cfd, &context);
|
||||
flush_memtable_id = cfd->imm()->GetLatestMemTableID();
|
||||
flush_req.emplace_back(cfd, flush_memtable_id);
|
||||
}
|
||||
|
||||
if (s.ok() && !flush_req.empty()) {
|
||||
for (auto& elem : flush_req) {
|
||||
ColumnFamilyData* loop_cfd = elem.first;
|
||||
loop_cfd->imm()->FlushRequested();
|
||||
}
|
||||
SchedulePendingFlush(flush_req, flush_reason);
|
||||
MaybeScheduleFlushOrCompaction();
|
||||
}
|
||||
|
||||
if (!writes_stopped) {
|
||||
write_thread_.ExitUnbatched(&w);
|
||||
}
|
||||
|
||||
cfd->imm()->FlushRequested();
|
||||
|
||||
// schedule flush
|
||||
SchedulePendingFlush(cfd, flush_reason);
|
||||
MaybeScheduleFlushOrCompaction();
|
||||
}
|
||||
|
||||
if (s.ok() && flush_options.wait) {
|
||||
// Wait until the compaction completes
|
||||
s = WaitForFlushMemTable(cfd, &flush_memtable_id);
|
||||
autovector<ColumnFamilyData*> cfds;
|
||||
autovector<const uint64_t*> flush_memtable_ids;
|
||||
for (auto& iter : flush_req) {
|
||||
cfds.push_back(iter.first);
|
||||
flush_memtable_ids.push_back(&(iter.second));
|
||||
}
|
||||
s = WaitForFlushMemTables(cfds, flush_memtable_ids);
|
||||
}
|
||||
TEST_SYNC_POINT("FlushMemTableFinished");
|
||||
return s;
|
||||
}
|
||||
|
||||
Status DBImpl::WaitForFlushMemTable(ColumnFamilyData* cfd,
|
||||
const uint64_t* flush_memtable_id) {
|
||||
Status s;
|
||||
// Calling FlushMemTable(), whether from DB::Flush() or from Backup Engine, can
|
||||
// cause write stall, for example if one memtable is being flushed already.
|
||||
// This method tries to avoid write stall (similar to CompactRange() behavior)
|
||||
// it emulates how the SuperVersion / LSM would change if flush happens, checks
|
||||
// it against various constrains and delays flush if it'd cause write stall.
|
||||
// Called should check status and flush_needed to see if flush already happened.
|
||||
Status DBImpl::WaitUntilFlushWouldNotStallWrites(ColumnFamilyData* cfd,
|
||||
bool* flush_needed) {
|
||||
{
|
||||
*flush_needed = true;
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
uint64_t orig_active_memtable_id = cfd->mem()->GetID();
|
||||
WriteStallCondition write_stall_condition = WriteStallCondition::kNormal;
|
||||
do {
|
||||
if (write_stall_condition != WriteStallCondition::kNormal) {
|
||||
TEST_SYNC_POINT("DBImpl::WaitUntilFlushWouldNotStallWrites:StallWait");
|
||||
ROCKS_LOG_INFO(immutable_db_options_.info_log,
|
||||
"[%s] WaitUntilFlushWouldNotStallWrites"
|
||||
" waiting on stall conditions to clear",
|
||||
cfd->GetName().c_str());
|
||||
bg_cv_.Wait();
|
||||
}
|
||||
if (cfd->IsDropped() || shutting_down_.load(std::memory_order_acquire)) {
|
||||
return Status::ShutdownInProgress();
|
||||
}
|
||||
|
||||
uint64_t earliest_memtable_id =
|
||||
std::min(cfd->mem()->GetID(), cfd->imm()->GetEarliestMemTableID());
|
||||
if (earliest_memtable_id > orig_active_memtable_id) {
|
||||
// We waited so long that the memtable we were originally waiting on was
|
||||
// flushed.
|
||||
*flush_needed = false;
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
const auto& mutable_cf_options = *cfd->GetLatestMutableCFOptions();
|
||||
const auto* vstorage = cfd->current()->storage_info();
|
||||
|
||||
// Skip stalling check if we're below auto-flush and auto-compaction
|
||||
// triggers. If it stalled in these conditions, that'd mean the stall
|
||||
// triggers are so low that stalling is needed for any background work. In
|
||||
// that case we shouldn't wait since background work won't be scheduled.
|
||||
if (cfd->imm()->NumNotFlushed() <
|
||||
cfd->ioptions()->min_write_buffer_number_to_merge &&
|
||||
vstorage->l0_delay_trigger_count() <
|
||||
mutable_cf_options.level0_file_num_compaction_trigger) {
|
||||
break;
|
||||
}
|
||||
|
||||
// check whether one extra immutable memtable or an extra L0 file would
|
||||
// cause write stalling mode to be entered. It could still enter stall
|
||||
// mode due to pending compaction bytes, but that's less common
|
||||
write_stall_condition =
|
||||
ColumnFamilyData::GetWriteStallConditionAndCause(
|
||||
cfd->imm()->NumNotFlushed() + 1,
|
||||
vstorage->l0_delay_trigger_count() + 1,
|
||||
vstorage->estimated_compaction_needed_bytes(), mutable_cf_options)
|
||||
.first;
|
||||
} while (write_stall_condition != WriteStallCondition::kNormal);
|
||||
}
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
// Wait for memtables to be flushed for multiple column families.
|
||||
// let N = cfds.size()
|
||||
// for i in [0, N),
|
||||
// 1) if flush_memtable_ids[i] is not null, then the memtables with lower IDs
|
||||
// have to be flushed for THIS column family;
|
||||
// 2) if flush_memtable_ids[i] is null, then all memtables in THIS column
|
||||
// family have to be flushed.
|
||||
// Finish waiting when ALL column families finish flushing memtables.
|
||||
Status DBImpl::WaitForFlushMemTables(
|
||||
const autovector<ColumnFamilyData*>& cfds,
|
||||
const autovector<const uint64_t*>& flush_memtable_ids) {
|
||||
int num = static_cast<int>(cfds.size());
|
||||
// Wait until the compaction completes
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
while (cfd->imm()->NumNotFlushed() > 0 && !error_handler_.IsDBStopped() &&
|
||||
(flush_memtable_id == nullptr ||
|
||||
cfd->imm()->GetEarliestMemTableID() <= *flush_memtable_id)) {
|
||||
while (!error_handler_.IsDBStopped()) {
|
||||
if (shutting_down_.load(std::memory_order_acquire)) {
|
||||
return Status::ShutdownInProgress();
|
||||
}
|
||||
if (cfd->IsDropped()) {
|
||||
// FlushJob cannot flush a dropped CF, if we did not break here
|
||||
// we will loop forever since cfd->imm()->NumNotFlushed() will never
|
||||
// drop to zero
|
||||
// Number of column families that have been dropped.
|
||||
int num_dropped = 0;
|
||||
// Number of column families that have finished flush.
|
||||
int num_finished = 0;
|
||||
for (int i = 0; i < num; ++i) {
|
||||
if (cfds[i]->IsDropped()) {
|
||||
++num_dropped;
|
||||
} else if (cfds[i]->imm()->NumNotFlushed() == 0 ||
|
||||
(flush_memtable_ids[i] != nullptr &&
|
||||
cfds[i]->imm()->GetEarliestMemTableID() >
|
||||
*flush_memtable_ids[i])) {
|
||||
++num_finished;
|
||||
}
|
||||
}
|
||||
if (1 == num_dropped && 1 == num) {
|
||||
return Status::InvalidArgument("Cannot flush a dropped CF");
|
||||
}
|
||||
// Column families involved in this flush request have either been dropped
|
||||
// or finished flush. Then it's time to finish waiting.
|
||||
if (num_dropped + num_finished == num) {
|
||||
break;
|
||||
}
|
||||
bg_cv_.Wait();
|
||||
}
|
||||
Status s;
|
||||
if (error_handler_.IsDBStopped()) {
|
||||
s = error_handler_.GetBGError();
|
||||
}
|
||||
@@ -1163,6 +1303,12 @@ void DBImpl::MaybeScheduleFlushOrCompaction() {
|
||||
if (bg_work_paused_ > 0) {
|
||||
// we paused the background work
|
||||
return;
|
||||
} else if (error_handler_.IsBGWorkStopped() &&
|
||||
!error_handler_.IsRecoveryInProgress()) {
|
||||
// There has been a hard error and this call is not part of the recovery
|
||||
// sequence. Bail out here so we don't get into an endless loop of
|
||||
// scheduling BG work which will again call this function
|
||||
return;
|
||||
} else if (shutting_down_.load(std::memory_order_acquire)) {
|
||||
// DB is being deleted; no more background compactions
|
||||
return;
|
||||
@@ -1172,7 +1318,6 @@ void DBImpl::MaybeScheduleFlushOrCompaction() {
|
||||
env_->GetBackgroundThreads(Env::Priority::HIGH) == 0;
|
||||
while (!is_flush_pool_empty && unscheduled_flushes_ > 0 &&
|
||||
bg_flush_scheduled_ < bg_job_limits.max_flushes) {
|
||||
unscheduled_flushes_--;
|
||||
bg_flush_scheduled_++;
|
||||
env_->Schedule(&DBImpl::BGWorkFlush, this, Env::Priority::HIGH, this);
|
||||
}
|
||||
@@ -1183,7 +1328,6 @@ void DBImpl::MaybeScheduleFlushOrCompaction() {
|
||||
while (unscheduled_flushes_ > 0 &&
|
||||
bg_flush_scheduled_ + bg_compaction_scheduled_ <
|
||||
bg_job_limits.max_flushes) {
|
||||
unscheduled_flushes_--;
|
||||
bg_flush_scheduled_++;
|
||||
env_->Schedule(&DBImpl::BGWorkFlush, this, Env::Priority::LOW, this);
|
||||
}
|
||||
@@ -1192,6 +1336,12 @@ void DBImpl::MaybeScheduleFlushOrCompaction() {
|
||||
if (bg_compaction_paused_ > 0) {
|
||||
// we paused the background compaction
|
||||
return;
|
||||
} else if (error_handler_.IsBGWorkStopped()) {
|
||||
// Compaction is not part of the recovery sequence from a hard error. We
|
||||
// might get here because recovery might do a flush and install a new
|
||||
// super version, which will try to schedule pending compactions. Bail
|
||||
// out here and let the higher level recovery handle compactions
|
||||
return;
|
||||
}
|
||||
|
||||
if (HasExclusiveManualCompaction()) {
|
||||
@@ -1260,30 +1410,28 @@ ColumnFamilyData* DBImpl::PopFirstFromCompactionQueue() {
|
||||
return cfd;
|
||||
}
|
||||
|
||||
void DBImpl::AddToFlushQueue(ColumnFamilyData* cfd, FlushReason flush_reason) {
|
||||
assert(!cfd->queued_for_flush());
|
||||
cfd->Ref();
|
||||
flush_queue_.push_back(cfd);
|
||||
cfd->set_queued_for_flush(true);
|
||||
cfd->SetFlushReason(flush_reason);
|
||||
}
|
||||
|
||||
ColumnFamilyData* DBImpl::PopFirstFromFlushQueue() {
|
||||
DBImpl::FlushRequest DBImpl::PopFirstFromFlushQueue() {
|
||||
assert(!flush_queue_.empty());
|
||||
auto cfd = *flush_queue_.begin();
|
||||
FlushRequest flush_req = flush_queue_.front();
|
||||
assert(unscheduled_flushes_ >= static_cast<int>(flush_req.size()));
|
||||
unscheduled_flushes_ -= static_cast<int>(flush_req.size());
|
||||
flush_queue_.pop_front();
|
||||
assert(cfd->queued_for_flush());
|
||||
cfd->set_queued_for_flush(false);
|
||||
// TODO: need to unset flush reason?
|
||||
return cfd;
|
||||
return flush_req;
|
||||
}
|
||||
|
||||
void DBImpl::SchedulePendingFlush(ColumnFamilyData* cfd,
|
||||
void DBImpl::SchedulePendingFlush(const FlushRequest& flush_req,
|
||||
FlushReason flush_reason) {
|
||||
if (!cfd->queued_for_flush() && cfd->imm()->IsFlushPending()) {
|
||||
AddToFlushQueue(cfd, flush_reason);
|
||||
++unscheduled_flushes_;
|
||||
if (flush_req.empty()) {
|
||||
return;
|
||||
}
|
||||
for (auto& iter : flush_req) {
|
||||
ColumnFamilyData* cfd = iter.first;
|
||||
cfd->Ref();
|
||||
cfd->SetFlushReason(flush_reason);
|
||||
}
|
||||
unscheduled_flushes_ += static_cast<int>(flush_req.size());
|
||||
flush_queue_.push_back(flush_req);
|
||||
}
|
||||
|
||||
void DBImpl::SchedulePendingCompaction(ColumnFamilyData* cfd) {
|
||||
@@ -1351,15 +1499,18 @@ void DBImpl::UnscheduleCallback(void* arg) {
|
||||
}
|
||||
|
||||
Status DBImpl::BackgroundFlush(bool* made_progress, JobContext* job_context,
|
||||
LogBuffer* log_buffer) {
|
||||
LogBuffer* log_buffer, FlushReason* reason) {
|
||||
mutex_.AssertHeld();
|
||||
|
||||
Status status;
|
||||
*reason = FlushReason::kOthers;
|
||||
// If BG work is stopped due to an error, but a recovery is in progress,
|
||||
// that means this flush is part of the recovery. So allow it to go through
|
||||
if (!error_handler_.IsBGWorkStopped()) {
|
||||
if (shutting_down_.load(std::memory_order_acquire)) {
|
||||
status = Status::ShutdownInProgress();
|
||||
}
|
||||
} else {
|
||||
} else if (!error_handler_.IsRecoveryInProgress()) {
|
||||
status = error_handler_.GetBGError();
|
||||
}
|
||||
|
||||
@@ -1367,40 +1518,58 @@ Status DBImpl::BackgroundFlush(bool* made_progress, JobContext* job_context,
|
||||
return status;
|
||||
}
|
||||
|
||||
ColumnFamilyData* cfd = nullptr;
|
||||
autovector<BGFlushArg> bg_flush_args;
|
||||
std::vector<SuperVersionContext>& superversion_contexts =
|
||||
job_context->superversion_contexts;
|
||||
while (!flush_queue_.empty()) {
|
||||
// This cfd is already referenced
|
||||
auto first_cfd = PopFirstFromFlushQueue();
|
||||
const FlushRequest& flush_req = PopFirstFromFlushQueue();
|
||||
superversion_contexts.clear();
|
||||
superversion_contexts.reserve(flush_req.size());
|
||||
|
||||
if (first_cfd->IsDropped() || !first_cfd->imm()->IsFlushPending()) {
|
||||
// can't flush this CF, try next one
|
||||
if (first_cfd->Unref()) {
|
||||
delete first_cfd;
|
||||
for (const auto& iter : flush_req) {
|
||||
ColumnFamilyData* cfd = iter.first;
|
||||
if (cfd->IsDropped() || !cfd->imm()->IsFlushPending()) {
|
||||
// can't flush this CF, try next one
|
||||
if (cfd->Unref()) {
|
||||
delete cfd;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
continue;
|
||||
superversion_contexts.emplace_back(SuperVersionContext(true));
|
||||
bg_flush_args.emplace_back(cfd, iter.second,
|
||||
&(superversion_contexts.back()));
|
||||
}
|
||||
if (!bg_flush_args.empty()) {
|
||||
break;
|
||||
}
|
||||
|
||||
// found a flush!
|
||||
cfd = first_cfd;
|
||||
break;
|
||||
}
|
||||
|
||||
if (cfd != nullptr) {
|
||||
const MutableCFOptions mutable_cf_options =
|
||||
*cfd->GetLatestMutableCFOptions();
|
||||
if (!bg_flush_args.empty()) {
|
||||
auto bg_job_limits = GetBGJobLimits();
|
||||
ROCKS_LOG_BUFFER(
|
||||
log_buffer,
|
||||
"Calling FlushMemTableToOutputFile with column "
|
||||
"family [%s], flush slots available %d, compaction slots available %d, "
|
||||
"flush slots scheduled %d, compaction slots scheduled %d",
|
||||
cfd->GetName().c_str(), bg_job_limits.max_flushes,
|
||||
bg_job_limits.max_compactions, bg_flush_scheduled_,
|
||||
bg_compaction_scheduled_);
|
||||
status = FlushMemTableToOutputFile(cfd, mutable_cf_options, made_progress,
|
||||
job_context, log_buffer);
|
||||
if (cfd->Unref()) {
|
||||
delete cfd;
|
||||
for (const auto& arg : bg_flush_args) {
|
||||
ColumnFamilyData* cfd = arg.cfd_;
|
||||
ROCKS_LOG_BUFFER(
|
||||
log_buffer,
|
||||
"Calling FlushMemTableToOutputFile with column "
|
||||
"family [%s], flush slots available %d, compaction slots available "
|
||||
"%d, "
|
||||
"flush slots scheduled %d, compaction slots scheduled %d",
|
||||
cfd->GetName().c_str(), bg_job_limits.max_flushes,
|
||||
bg_job_limits.max_compactions, bg_flush_scheduled_,
|
||||
bg_compaction_scheduled_);
|
||||
}
|
||||
status = FlushMemTablesToOutputFiles(bg_flush_args, made_progress,
|
||||
job_context, log_buffer);
|
||||
// All the CFDs in the FlushReq must have the same flush reason, so just
|
||||
// grab the first one
|
||||
*reason = bg_flush_args[0].cfd_->GetFlushReason();
|
||||
for (auto& arg : bg_flush_args) {
|
||||
ColumnFamilyData* cfd = arg.cfd_;
|
||||
if (cfd->Unref()) {
|
||||
delete cfd;
|
||||
arg.cfd_ = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
return status;
|
||||
@@ -1421,9 +1590,12 @@ void DBImpl::BackgroundCallFlush() {
|
||||
|
||||
auto pending_outputs_inserted_elem =
|
||||
CaptureCurrentFileNumberInPendingOutputs();
|
||||
FlushReason reason;
|
||||
|
||||
Status s = BackgroundFlush(&made_progress, &job_context, &log_buffer);
|
||||
if (!s.ok() && !s.IsShutdownInProgress()) {
|
||||
Status s =
|
||||
BackgroundFlush(&made_progress, &job_context, &log_buffer, &reason);
|
||||
if (!s.ok() && !s.IsShutdownInProgress() &&
|
||||
reason != FlushReason::kErrorRecovery) {
|
||||
// Wait a little bit before retrying background flush in
|
||||
// case this is an environmental problem and we do not want to
|
||||
// chew up resources for failed flushes for the duration of
|
||||
@@ -1613,6 +1785,11 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
|
||||
}
|
||||
} else {
|
||||
status = error_handler_.GetBGError();
|
||||
// If we get here, it means a hard error happened after this compaction
|
||||
// was scheduled by MaybeScheduleFlushOrCompaction(), but before it got
|
||||
// a chance to execute. Since we didn't pop a cfd from the compaction
|
||||
// queue, increment unscheduled_compactions_
|
||||
unscheduled_compactions_++;
|
||||
}
|
||||
|
||||
if (!status.ok()) {
|
||||
@@ -1648,7 +1825,7 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
|
||||
} else {
|
||||
// First check if we have enough room to do the compaction
|
||||
bool enough_room = EnoughRoomForCompaction(
|
||||
*(c->inputs()), &sfm_reserved_compact_space, log_buffer);
|
||||
m->cfd, *(c->inputs()), &sfm_reserved_compact_space, log_buffer);
|
||||
|
||||
if (!enough_room) {
|
||||
// Then don't do the compaction
|
||||
@@ -1711,7 +1888,7 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
|
||||
|
||||
if (c != nullptr) {
|
||||
bool enough_room = EnoughRoomForCompaction(
|
||||
*(c->inputs()), &sfm_reserved_compact_space, log_buffer);
|
||||
cfd, *(c->inputs()), &sfm_reserved_compact_space, log_buffer);
|
||||
|
||||
if (!enough_room) {
|
||||
// Then don't do the compaction
|
||||
@@ -1775,7 +1952,7 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
|
||||
*c->mutable_cf_options(), c->edit(),
|
||||
&mutex_, directories_.GetDbDir());
|
||||
InstallSuperVersionAndScheduleWork(
|
||||
c->column_family_data(), &job_context->superversion_context,
|
||||
c->column_family_data(), &job_context->superversion_contexts[0],
|
||||
*c->mutable_cf_options(), FlushReason::kAutoCompaction);
|
||||
ROCKS_LOG_BUFFER(log_buffer, "[%s] Deleted %d files\n",
|
||||
c->column_family_data()->GetName().c_str(),
|
||||
@@ -1804,8 +1981,8 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
|
||||
c->edit()->DeleteFile(c->level(l), f->fd.GetNumber());
|
||||
c->edit()->AddFile(c->output_level(), f->fd.GetNumber(),
|
||||
f->fd.GetPathId(), f->fd.GetFileSize(), f->smallest,
|
||||
f->largest, f->smallest_seqno, f->largest_seqno,
|
||||
f->marked_for_compaction);
|
||||
f->largest, f->fd.smallest_seqno,
|
||||
f->fd.largest_seqno, f->marked_for_compaction);
|
||||
|
||||
ROCKS_LOG_BUFFER(
|
||||
log_buffer,
|
||||
@@ -1822,7 +1999,7 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
|
||||
&mutex_, directories_.GetDbDir());
|
||||
// Use latest MutableCFOptions
|
||||
InstallSuperVersionAndScheduleWork(
|
||||
c->column_family_data(), &job_context->superversion_context,
|
||||
c->column_family_data(), &job_context->superversion_contexts[0],
|
||||
*c->mutable_cf_options(), FlushReason::kAutoCompaction);
|
||||
|
||||
VersionStorageInfo::LevelSummaryStorage tmp;
|
||||
@@ -1899,7 +2076,7 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
|
||||
status = compaction_job.Install(*c->mutable_cf_options());
|
||||
if (status.ok()) {
|
||||
InstallSuperVersionAndScheduleWork(
|
||||
c->column_family_data(), &job_context->superversion_context,
|
||||
c->column_family_data(), &job_context->superversion_contexts[0],
|
||||
*c->mutable_cf_options(), FlushReason::kAutoCompaction);
|
||||
}
|
||||
*made_progress = true;
|
||||
@@ -1920,8 +2097,6 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
|
||||
NotifyOnCompactionCompleted(c->column_family_data(), c.get(), status,
|
||||
compaction_job_stats, job_context->job_id);
|
||||
}
|
||||
// this will unref its input_version and column_family_data
|
||||
c.reset();
|
||||
|
||||
if (status.ok() || status.IsCompactionTooLarge()) {
|
||||
// Done
|
||||
@@ -1931,7 +2106,26 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
|
||||
ROCKS_LOG_WARN(immutable_db_options_.info_log, "Compaction error: %s",
|
||||
status.ToString().c_str());
|
||||
error_handler_.SetBGError(status, BackgroundErrorReason::kCompaction);
|
||||
if (c != nullptr && !is_manual && !error_handler_.IsBGWorkStopped()) {
|
||||
// Put this cfd back in the compaction queue so we can retry after some
|
||||
// time
|
||||
auto cfd = c->column_family_data();
|
||||
assert(cfd != nullptr);
|
||||
// Since this compaction failed, we need to recompute the score so it
|
||||
// takes the original input files into account
|
||||
c->column_family_data()
|
||||
->current()
|
||||
->storage_info()
|
||||
->ComputeCompactionScore(*(c->immutable_cf_options()),
|
||||
*(c->mutable_cf_options()));
|
||||
if (!cfd->queued_for_compaction()) {
|
||||
AddToCompactionQueue(cfd);
|
||||
++unscheduled_compactions_;
|
||||
}
|
||||
}
|
||||
}
|
||||
// this will unref its input_version and column_family_data
|
||||
c.reset();
|
||||
|
||||
if (is_manual) {
|
||||
ManualCompactionState* m = manual_compaction;
|
||||
@@ -2080,7 +2274,10 @@ bool DBImpl::MCOverlap(ManualCompactionState* m, ManualCompactionState* m1) {
|
||||
|
||||
void DBImpl::InstallSuperVersionAndScheduleWork(
|
||||
ColumnFamilyData* cfd, SuperVersionContext* sv_context,
|
||||
const MutableCFOptions& mutable_cf_options, FlushReason flush_reason) {
|
||||
const MutableCFOptions& mutable_cf_options,
|
||||
FlushReason /* flush_reason */) {
|
||||
// TODO(yanqin) investigate if 'flush_reason' can be removed since it's not
|
||||
// used.
|
||||
mutex_.AssertHeld();
|
||||
|
||||
// Update max_total_in_memory_state_
|
||||
@@ -2091,14 +2288,14 @@ void DBImpl::InstallSuperVersionAndScheduleWork(
|
||||
old_sv->mutable_cf_options.max_write_buffer_number;
|
||||
}
|
||||
|
||||
if (sv_context->new_superversion == nullptr) {
|
||||
// this branch is unlikely to step in
|
||||
if (UNLIKELY(sv_context->new_superversion == nullptr)) {
|
||||
sv_context->NewSuperVersion();
|
||||
}
|
||||
cfd->InstallSuperVersion(sv_context, &mutex_, mutable_cf_options);
|
||||
|
||||
// Whenever we install new SuperVersion, we might need to issue new flushes or
|
||||
// compactions.
|
||||
SchedulePendingFlush(cfd, flush_reason);
|
||||
SchedulePendingCompaction(cfd);
|
||||
MaybeScheduleFlushOrCompaction();
|
||||
|
||||
|
||||
+10
-2
@@ -100,9 +100,11 @@ Status DBImpl::TEST_SwitchMemtable(ColumnFamilyData* cfd) {
|
||||
return SwitchMemtable(cfd, &write_context);
|
||||
}
|
||||
|
||||
Status DBImpl::TEST_FlushMemTable(bool wait, ColumnFamilyHandle* cfh) {
|
||||
Status DBImpl::TEST_FlushMemTable(bool wait, bool allow_write_stall,
|
||||
ColumnFamilyHandle* cfh) {
|
||||
FlushOptions fo;
|
||||
fo.wait = wait;
|
||||
fo.allow_write_stall = allow_write_stall;
|
||||
ColumnFamilyData* cfd;
|
||||
if (cfh == nullptr) {
|
||||
cfd = default_cf_handle_->cfd();
|
||||
@@ -135,7 +137,7 @@ Status DBImpl::TEST_WaitForCompact(bool wait_unscheduled) {
|
||||
while ((bg_bottom_compaction_scheduled_ || bg_compaction_scheduled_ ||
|
||||
bg_flush_scheduled_ ||
|
||||
(wait_unscheduled && unscheduled_compactions_)) &&
|
||||
!error_handler_.IsDBStopped()) {
|
||||
(error_handler_.GetBGError() == Status::OK())) {
|
||||
bg_cv_.Wait();
|
||||
}
|
||||
return error_handler_.GetBGError();
|
||||
@@ -235,5 +237,11 @@ SequenceNumber DBImpl::TEST_GetLastVisibleSequence() const {
|
||||
}
|
||||
}
|
||||
|
||||
size_t DBImpl::TEST_GetWalPreallocateBlockSize(
|
||||
uint64_t write_buffer_size) const {
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
return GetWalPreallocateBlockSize(write_buffer_size);
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
#endif // NDEBUG
|
||||
@@ -131,16 +131,16 @@ Status DBImpl::PromoteL0(ColumnFamilyHandle* column_family, int target_level) {
|
||||
edit.DeleteFile(0, f->fd.GetNumber());
|
||||
edit.AddFile(target_level, f->fd.GetNumber(), f->fd.GetPathId(),
|
||||
f->fd.GetFileSize(), f->smallest, f->largest,
|
||||
f->smallest_seqno, f->largest_seqno,
|
||||
f->fd.smallest_seqno, f->fd.largest_seqno,
|
||||
f->marked_for_compaction);
|
||||
}
|
||||
|
||||
status = versions_->LogAndApply(cfd, *cfd->GetLatestMutableCFOptions(),
|
||||
&edit, &mutex_, directories_.GetDbDir());
|
||||
if (status.ok()) {
|
||||
InstallSuperVersionAndScheduleWork(
|
||||
cfd, &job_context.superversion_context,
|
||||
*cfd->GetLatestMutableCFOptions());
|
||||
InstallSuperVersionAndScheduleWork(cfd,
|
||||
&job_context.superversion_contexts[0],
|
||||
*cfd->GetLatestMutableCFOptions());
|
||||
}
|
||||
} // lock released here
|
||||
LogFlush(immutable_db_options_.info_log);
|
||||
|
||||
+99
-33
@@ -134,8 +134,15 @@ DBOptions SanitizeOptions(const std::string& dbname, const DBOptions& src) {
|
||||
for (size_t i = 0; i < result.db_paths.size(); i++) {
|
||||
DeleteScheduler::CleanupDirectory(result.env, sfm, result.db_paths[i].path);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Create a default SstFileManager for purposes of tracking compaction size
|
||||
// and facilitating recovery from out of space errors.
|
||||
if (result.sst_file_manager.get() == nullptr) {
|
||||
std::shared_ptr<SstFileManager> sst_file_manager(
|
||||
NewSstFileManager(result.env, result.info_log));
|
||||
result.sst_file_manager = sst_file_manager;
|
||||
}
|
||||
#endif
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -232,7 +239,7 @@ Status DBImpl::NewDB() {
|
||||
file->SetPreallocationBlockSize(
|
||||
immutable_db_options_.manifest_preallocation_size);
|
||||
unique_ptr<WritableFileWriter> file_writer(
|
||||
new WritableFileWriter(std::move(file), env_options));
|
||||
new WritableFileWriter(std::move(file), manifest, env_options));
|
||||
log::Writer log(std::move(file_writer), 0, false);
|
||||
std::string record;
|
||||
new_db.EncodeTo(&record);
|
||||
@@ -361,7 +368,7 @@ Status DBImpl::Recover(
|
||||
s = env_->NewRandomAccessFile(IdentityFileName(dbname_), &idfile,
|
||||
customized_env);
|
||||
if (!s.ok()) {
|
||||
const char* error_msg = s.ToString().c_str();
|
||||
std::string error_str = s.ToString();
|
||||
// Check if unsupported Direct I/O is the root cause
|
||||
customized_env.use_direct_reads = false;
|
||||
s = env_->NewRandomAccessFile(IdentityFileName(dbname_), &idfile,
|
||||
@@ -371,7 +378,7 @@ Status DBImpl::Recover(
|
||||
"Direct I/O is not supported by the specified DB.");
|
||||
} else {
|
||||
return Status::InvalidArgument(
|
||||
"Found options incompatible with filesystem", error_msg);
|
||||
"Found options incompatible with filesystem", error_str.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -389,6 +396,16 @@ Status DBImpl::Recover(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Initial max_total_in_memory_state_ before recovery logs. Log recovery
|
||||
// may check this value to decide whether to flush.
|
||||
max_total_in_memory_state_ = 0;
|
||||
for (auto cfd : *versions_->GetColumnFamilySet()) {
|
||||
auto* mutable_cf_options = cfd->GetLatestMutableCFOptions();
|
||||
max_total_in_memory_state_ += mutable_cf_options->write_buffer_size *
|
||||
mutable_cf_options->max_write_buffer_number;
|
||||
}
|
||||
|
||||
if (s.ok()) {
|
||||
SequenceNumber next_sequence(kMaxSequenceNumber);
|
||||
default_cf_handle_ = new ColumnFamilyHandleImpl(
|
||||
@@ -461,14 +478,6 @@ Status DBImpl::Recover(
|
||||
}
|
||||
}
|
||||
|
||||
// Initial value
|
||||
max_total_in_memory_state_ = 0;
|
||||
for (auto cfd : *versions_->GetColumnFamilySet()) {
|
||||
auto* mutable_cf_options = cfd->GetLatestMutableCFOptions();
|
||||
max_total_in_memory_state_ += mutable_cf_options->write_buffer_size *
|
||||
mutable_cf_options->max_write_buffer_number;
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
@@ -598,8 +607,7 @@ Status DBImpl::RecoverLogFiles(const std::vector<uint64_t>& log_numbers,
|
||||
// to be skipped instead of propagating bad information (like overly
|
||||
// large sequence numbers).
|
||||
log::Reader reader(immutable_db_options_.info_log, std::move(file_reader),
|
||||
&reporter, true /*checksum*/, 0 /*initial_offset*/,
|
||||
log_number);
|
||||
&reporter, true /*checksum*/, log_number);
|
||||
|
||||
// Determine if we should tolerate incomplete records at the tail end of the
|
||||
// Read all the records and add to a memtable
|
||||
@@ -879,18 +887,8 @@ Status DBImpl::RecoverLogFiles(const std::vector<uint64_t>& log_numbers,
|
||||
}
|
||||
}
|
||||
|
||||
if (data_seen && !flushed) {
|
||||
// Mark these as alive so they'll be considered for deletion later by
|
||||
// FindObsoleteFiles()
|
||||
if (two_write_queues_) {
|
||||
log_write_mutex_.Lock();
|
||||
}
|
||||
for (auto log_number : log_numbers) {
|
||||
alive_log_files_.push_back(LogFileNumberSize(log_number));
|
||||
}
|
||||
if (two_write_queues_) {
|
||||
log_write_mutex_.Unlock();
|
||||
}
|
||||
if (status.ok() && data_seen && !flushed) {
|
||||
status = RestoreAliveLogFiles(log_numbers);
|
||||
}
|
||||
|
||||
event_logger_.Log() << "job" << job_id << "event"
|
||||
@@ -899,6 +897,60 @@ Status DBImpl::RecoverLogFiles(const std::vector<uint64_t>& log_numbers,
|
||||
return status;
|
||||
}
|
||||
|
||||
Status DBImpl::RestoreAliveLogFiles(const std::vector<uint64_t>& log_numbers) {
|
||||
if (log_numbers.empty()) {
|
||||
return Status::OK();
|
||||
}
|
||||
Status s;
|
||||
mutex_.AssertHeld();
|
||||
assert(immutable_db_options_.avoid_flush_during_recovery);
|
||||
if (two_write_queues_) {
|
||||
log_write_mutex_.Lock();
|
||||
}
|
||||
// Mark these as alive so they'll be considered for deletion later by
|
||||
// FindObsoleteFiles()
|
||||
total_log_size_ = 0;
|
||||
log_empty_ = false;
|
||||
for (auto log_number : log_numbers) {
|
||||
LogFileNumberSize log(log_number);
|
||||
std::string fname = LogFileName(immutable_db_options_.wal_dir, log_number);
|
||||
// This gets the appear size of the logs, not including preallocated space.
|
||||
s = env_->GetFileSize(fname, &log.size);
|
||||
if (!s.ok()) {
|
||||
break;
|
||||
}
|
||||
total_log_size_ += log.size;
|
||||
alive_log_files_.push_back(log);
|
||||
// We preallocate space for logs, but then after a crash and restart, those
|
||||
// preallocated space are not needed anymore. It is likely only the last
|
||||
// log has such preallocated space, so we only truncate for the last log.
|
||||
if (log_number == log_numbers.back()) {
|
||||
std::unique_ptr<WritableFile> last_log;
|
||||
Status truncate_status = env_->ReopenWritableFile(
|
||||
fname, &last_log,
|
||||
env_->OptimizeForLogWrite(
|
||||
env_options_,
|
||||
BuildDBOptions(immutable_db_options_, mutable_db_options_)));
|
||||
if (truncate_status.ok()) {
|
||||
truncate_status = last_log->Truncate(log.size);
|
||||
}
|
||||
if (truncate_status.ok()) {
|
||||
truncate_status = last_log->Close();
|
||||
}
|
||||
// Not a critical error if fail to truncate.
|
||||
if (!truncate_status.ok()) {
|
||||
ROCKS_LOG_WARN(immutable_db_options_.info_log,
|
||||
"Failed to truncate log #%" PRIu64 ": %s", log_number,
|
||||
truncate_status.ToString().c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
if (two_write_queues_) {
|
||||
log_write_mutex_.Unlock();
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
Status DBImpl::WriteLevel0TableForRecovery(int job_id, ColumnFamilyData* cfd,
|
||||
MemTable* mem, VersionEdit* edit) {
|
||||
mutex_.AssertHeld();
|
||||
@@ -969,7 +1021,7 @@ Status DBImpl::WriteLevel0TableForRecovery(int job_id, ColumnFamilyData* cfd,
|
||||
if (s.ok() && meta.fd.GetFileSize() > 0) {
|
||||
edit->AddFile(level, meta.fd.GetNumber(), meta.fd.GetPathId(),
|
||||
meta.fd.GetFileSize(), meta.smallest, meta.largest,
|
||||
meta.smallest_seqno, meta.largest_seqno,
|
||||
meta.fd.smallest_seqno, meta.fd.largest_seqno,
|
||||
meta.marked_for_compaction);
|
||||
}
|
||||
|
||||
@@ -1051,6 +1103,12 @@ Status DBImpl::Open(const DBOptions& db_options, const std::string& dbname,
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// For recovery from NoSpace() error, we can only handle
|
||||
// the case where the database is stored in a single path
|
||||
if (paths.size() <= 1) {
|
||||
impl->error_handler_.EnableAutoRecovery();
|
||||
}
|
||||
}
|
||||
|
||||
if (!s.ok()) {
|
||||
@@ -1075,10 +1133,10 @@ Status DBImpl::Open(const DBOptions& db_options, const std::string& dbname,
|
||||
impl->immutable_db_options_.env->OptimizeForLogWrite(
|
||||
soptions, BuildDBOptions(impl->immutable_db_options_,
|
||||
impl->mutable_db_options_));
|
||||
s = NewWritableFile(
|
||||
impl->immutable_db_options_.env,
|
||||
LogFileName(impl->immutable_db_options_.wal_dir, new_log_number),
|
||||
&lfile, opt_env_options);
|
||||
std::string log_fname =
|
||||
LogFileName(impl->immutable_db_options_.wal_dir, new_log_number);
|
||||
s = NewWritableFile(impl->immutable_db_options_.env, log_fname, &lfile,
|
||||
opt_env_options);
|
||||
if (s.ok()) {
|
||||
lfile->SetWriteLifeTimeHint(write_hint);
|
||||
lfile->SetPreallocationBlockSize(
|
||||
@@ -1086,8 +1144,8 @@ Status DBImpl::Open(const DBOptions& db_options, const std::string& dbname,
|
||||
{
|
||||
InstrumentedMutexLock wl(&impl->log_write_mutex_);
|
||||
impl->logfile_number_ = new_log_number;
|
||||
unique_ptr<WritableFileWriter> file_writer(
|
||||
new WritableFileWriter(std::move(lfile), opt_env_options));
|
||||
unique_ptr<WritableFileWriter> file_writer(new WritableFileWriter(
|
||||
std::move(lfile), log_fname, opt_env_options));
|
||||
impl->logs_.emplace_back(
|
||||
new_log_number,
|
||||
new log::Writer(
|
||||
@@ -1214,6 +1272,14 @@ Status DBImpl::Open(const DBOptions& db_options, const std::string& dbname,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Reserve some disk buffer space. This is a heuristic - when we run out
|
||||
// of disk space, this ensures that there is atleast write_buffer_size
|
||||
// amount of free space before we resume DB writes. In low disk space
|
||||
// conditions, we want to avoid a lot of small L0 files due to frequent
|
||||
// WAL write failures and resultant forced flushes
|
||||
sfm->ReserveDiskBuffer(max_write_buffer_size,
|
||||
impl->immutable_db_options_.db_paths[0].path);
|
||||
}
|
||||
#endif // !ROCKSDB_LITE
|
||||
|
||||
|
||||
@@ -31,22 +31,38 @@ Status DBImplReadOnly::Get(const ReadOptions& read_options,
|
||||
ColumnFamilyHandle* column_family, const Slice& key,
|
||||
PinnableSlice* pinnable_val) {
|
||||
assert(pinnable_val != nullptr);
|
||||
// TODO: stopwatch DB_GET needed?, perf timer needed?
|
||||
PERF_TIMER_GUARD(get_snapshot_time);
|
||||
Status s;
|
||||
SequenceNumber snapshot = versions_->LastSequence();
|
||||
auto cfh = reinterpret_cast<ColumnFamilyHandleImpl*>(column_family);
|
||||
auto cfd = cfh->cfd();
|
||||
if (tracer_) {
|
||||
InstrumentedMutexLock lock(&trace_mutex_);
|
||||
if (tracer_) {
|
||||
tracer_->Get(column_family, key);
|
||||
}
|
||||
}
|
||||
SuperVersion* super_version = cfd->GetSuperVersion();
|
||||
MergeContext merge_context;
|
||||
RangeDelAggregator range_del_agg(cfd->internal_comparator(), snapshot);
|
||||
LookupKey lkey(key, snapshot);
|
||||
PERF_TIMER_STOP(get_snapshot_time);
|
||||
if (super_version->mem->Get(lkey, pinnable_val->GetSelf(), &s, &merge_context,
|
||||
&range_del_agg, read_options)) {
|
||||
pinnable_val->PinSelf();
|
||||
RecordTick(stats_, MEMTABLE_HIT);
|
||||
} else {
|
||||
PERF_TIMER_GUARD(get_from_output_files_time);
|
||||
super_version->current->Get(read_options, lkey, pinnable_val, &s,
|
||||
&merge_context, &range_del_agg);
|
||||
RecordTick(stats_, MEMTABLE_MISS);
|
||||
}
|
||||
RecordTick(stats_, NUMBER_KEYS_READ);
|
||||
size_t size = pinnable_val->size();
|
||||
RecordTick(stats_, BYTES_READ, size);
|
||||
MeasureTime(stats_, BYTES_PER_READ, size);
|
||||
PERF_COUNTER_ADD(get_read_bytes, size);
|
||||
return s;
|
||||
}
|
||||
|
||||
|
||||
+94
-34
@@ -76,6 +76,12 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options,
|
||||
if (my_batch == nullptr) {
|
||||
return Status::Corruption("Batch is nullptr!");
|
||||
}
|
||||
if (tracer_) {
|
||||
InstrumentedMutexLock lock(&trace_mutex_);
|
||||
if (tracer_) {
|
||||
tracer_->Write(my_batch);
|
||||
}
|
||||
}
|
||||
if (write_options.sync && write_options.disableWAL) {
|
||||
return Status::InvalidArgument("Sync writes has to enable WAL.");
|
||||
}
|
||||
@@ -311,7 +317,8 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options,
|
||||
w.status = WriteBatchInternal::InsertInto(
|
||||
write_group, current_sequence, column_family_memtables_.get(),
|
||||
&flush_scheduler_, write_options.ignore_missing_column_families,
|
||||
0 /*recovery_log_number*/, this, parallel, seq_per_batch_);
|
||||
0 /*recovery_log_number*/, this, parallel, seq_per_batch_,
|
||||
batch_per_txn_);
|
||||
} else {
|
||||
SequenceNumber next_sequence = current_sequence;
|
||||
// Note: the logic for advancing seq here must be consistent with the
|
||||
@@ -346,7 +353,7 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options,
|
||||
&w, w.sequence, &column_family_memtables, &flush_scheduler_,
|
||||
write_options.ignore_missing_column_families, 0 /*log_number*/,
|
||||
this, true /*concurrent_memtable_writes*/, seq_per_batch_,
|
||||
w.batch_cnt);
|
||||
w.batch_cnt, batch_per_txn_);
|
||||
}
|
||||
}
|
||||
if (seq_used != nullptr) {
|
||||
@@ -508,7 +515,8 @@ Status DBImpl::PipelinedWriteImpl(const WriteOptions& write_options,
|
||||
memtable_write_group.status = WriteBatchInternal::InsertInto(
|
||||
memtable_write_group, w.sequence, column_family_memtables_.get(),
|
||||
&flush_scheduler_, write_options.ignore_missing_column_families,
|
||||
0 /*log_number*/, this, seq_per_batch_);
|
||||
0 /*log_number*/, this, false /*concurrent_memtable_writes*/,
|
||||
seq_per_batch_, batch_per_txn_);
|
||||
versions_->SetLastSequence(memtable_write_group.last_sequence);
|
||||
write_thread_.ExitAsMemTableWriter(&w, memtable_write_group);
|
||||
}
|
||||
@@ -565,7 +573,6 @@ Status DBImpl::WriteImplWALOnly(const WriteOptions& write_options,
|
||||
}
|
||||
// else we are the leader of the write batch group
|
||||
assert(w.state == WriteThread::STATE_GROUP_LEADER);
|
||||
WriteContext write_context;
|
||||
WriteThread::WriteGroup write_group;
|
||||
uint64_t last_sequence;
|
||||
nonmem_write_thread_.EnterAsBatchGroupLeader(&w, &write_group);
|
||||
@@ -703,6 +710,10 @@ Status DBImpl::PreprocessWrite(const WriteOptions& write_options,
|
||||
assert(write_context != nullptr && need_log_sync != nullptr);
|
||||
Status status;
|
||||
|
||||
if (error_handler_.IsDBStopped()) {
|
||||
status = error_handler_.GetBGError();
|
||||
}
|
||||
|
||||
PERF_TIMER_GUARD(write_scheduling_flushes_compactions_time);
|
||||
|
||||
assert(!single_column_family_mode_ ||
|
||||
@@ -721,10 +732,6 @@ Status DBImpl::PreprocessWrite(const WriteOptions& write_options,
|
||||
status = HandleWriteBufferFull(write_context);
|
||||
}
|
||||
|
||||
if (UNLIKELY(status.ok())) {
|
||||
status = error_handler_.GetBGError();
|
||||
}
|
||||
|
||||
if (UNLIKELY(status.ok() && !flush_scheduler_.Empty())) {
|
||||
status = ScheduleFlushes(write_context);
|
||||
}
|
||||
@@ -1057,6 +1064,7 @@ Status DBImpl::SwitchWAL(WriteContext* write_context) {
|
||||
oldest_alive_log, total_log_size_.load(), GetMaxTotalWalSize());
|
||||
// no need to refcount because drop is happening in write thread, so can't
|
||||
// happen while we're in the write thread
|
||||
FlushRequest flush_req;
|
||||
for (auto cfd : *versions_->GetColumnFamilySet()) {
|
||||
if (cfd->IsDropped()) {
|
||||
continue;
|
||||
@@ -1066,11 +1074,14 @@ Status DBImpl::SwitchWAL(WriteContext* write_context) {
|
||||
if (!status.ok()) {
|
||||
break;
|
||||
}
|
||||
flush_req.emplace_back(cfd, cfd->imm()->GetLatestMemTableID());
|
||||
cfd->imm()->FlushRequested();
|
||||
SchedulePendingFlush(cfd, FlushReason::kWriteBufferManager);
|
||||
}
|
||||
}
|
||||
MaybeScheduleFlushOrCompaction();
|
||||
if (status.ok()) {
|
||||
SchedulePendingFlush(flush_req, FlushReason::kWriteBufferManager);
|
||||
MaybeScheduleFlushOrCompaction();
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
@@ -1109,14 +1120,26 @@ Status DBImpl::HandleWriteBufferFull(WriteContext* write_context) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
autovector<ColumnFamilyData*> cfds;
|
||||
if (cfd_picked != nullptr) {
|
||||
status = SwitchMemtable(cfd_picked, write_context,
|
||||
FlushReason::kWriteBufferFull);
|
||||
if (status.ok()) {
|
||||
cfd_picked->imm()->FlushRequested();
|
||||
SchedulePendingFlush(cfd_picked, FlushReason::kWriteBufferFull);
|
||||
MaybeScheduleFlushOrCompaction();
|
||||
cfds.push_back(cfd_picked);
|
||||
}
|
||||
FlushRequest flush_req;
|
||||
for (const auto cfd : cfds) {
|
||||
cfd->Ref();
|
||||
status = SwitchMemtable(cfd, write_context);
|
||||
cfd->Unref();
|
||||
if (!status.ok()) {
|
||||
break;
|
||||
}
|
||||
uint64_t flush_memtable_id = cfd->imm()->GetLatestMemTableID();
|
||||
cfd->imm()->FlushRequested();
|
||||
flush_req.emplace_back(cfd, flush_memtable_id);
|
||||
}
|
||||
if (status.ok()) {
|
||||
SchedulePendingFlush(flush_req, FlushReason::kWriteBufferFull);
|
||||
MaybeScheduleFlushOrCompaction();
|
||||
}
|
||||
return status;
|
||||
}
|
||||
@@ -1139,10 +1162,14 @@ Status DBImpl::DelayWrite(uint64_t num_bytes,
|
||||
uint64_t delay = write_controller_.GetDelay(env_, num_bytes);
|
||||
if (delay > 0) {
|
||||
if (write_options.no_slowdown) {
|
||||
return Status::Incomplete();
|
||||
return Status::Incomplete("Write stall");
|
||||
}
|
||||
TEST_SYNC_POINT("DBImpl::DelayWrite:Sleep");
|
||||
|
||||
// Notify write_thread_ about the stall so it can setup a barrier and
|
||||
// fail any pending writers with no_slowdown
|
||||
write_thread_.BeginWriteStall();
|
||||
TEST_SYNC_POINT("DBImpl::DelayWrite:BeginWriteStallDone");
|
||||
mutex_.Unlock();
|
||||
// We will delay the write until we have slept for delay ms or
|
||||
// we don't need a delay anymore
|
||||
@@ -1159,15 +1186,25 @@ Status DBImpl::DelayWrite(uint64_t num_bytes,
|
||||
env_->SleepForMicroseconds(kDelayInterval);
|
||||
}
|
||||
mutex_.Lock();
|
||||
write_thread_.EndWriteStall();
|
||||
}
|
||||
|
||||
while (!error_handler_.IsDBStopped() && write_controller_.IsStopped()) {
|
||||
// Don't wait if there's a background error, even if its a soft error. We
|
||||
// might wait here indefinitely as the background compaction may never
|
||||
// finish successfully, resulting in the stall condition lasting
|
||||
// indefinitely
|
||||
while (error_handler_.GetBGError().ok() && write_controller_.IsStopped()) {
|
||||
if (write_options.no_slowdown) {
|
||||
return Status::Incomplete();
|
||||
return Status::Incomplete("Write stall");
|
||||
}
|
||||
delayed = true;
|
||||
|
||||
// Notify write_thread_ about the stall so it can setup a barrier and
|
||||
// fail any pending writers with no_slowdown
|
||||
write_thread_.BeginWriteStall();
|
||||
TEST_SYNC_POINT("DBImpl::DelayWrite:Wait");
|
||||
bg_cv_.Wait();
|
||||
write_thread_.EndWriteStall();
|
||||
}
|
||||
}
|
||||
assert(!delayed || !write_options.no_slowdown);
|
||||
@@ -1177,7 +1214,19 @@ Status DBImpl::DelayWrite(uint64_t num_bytes,
|
||||
RecordTick(stats_, STALL_MICROS, time_delayed);
|
||||
}
|
||||
|
||||
return error_handler_.GetBGError();
|
||||
// If DB is not in read-only mode and write_controller is not stopping
|
||||
// writes, we can ignore any background errors and allow the write to
|
||||
// proceed
|
||||
Status s;
|
||||
if (write_controller_.IsStopped()) {
|
||||
// If writes are still stopped, it means we bailed due to a background
|
||||
// error
|
||||
s = Status::Incomplete(error_handler_.GetBGError().ToString());
|
||||
}
|
||||
if (error_handler_.IsDBStopped()) {
|
||||
s = error_handler_.GetBGError();
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
Status DBImpl::ThrottleLowPriWritesIfNeeded(const WriteOptions& write_options,
|
||||
@@ -1212,16 +1261,28 @@ Status DBImpl::ThrottleLowPriWritesIfNeeded(const WriteOptions& write_options,
|
||||
|
||||
Status DBImpl::ScheduleFlushes(WriteContext* context) {
|
||||
ColumnFamilyData* cfd;
|
||||
FlushRequest flush_req;
|
||||
Status status;
|
||||
while ((cfd = flush_scheduler_.TakeNextColumnFamily()) != nullptr) {
|
||||
auto status = SwitchMemtable(cfd, context, FlushReason::kWriteBufferFull);
|
||||
status = SwitchMemtable(cfd, context);
|
||||
bool should_schedule = true;
|
||||
if (cfd->Unref()) {
|
||||
delete cfd;
|
||||
should_schedule = false;
|
||||
}
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
break;
|
||||
}
|
||||
if (should_schedule) {
|
||||
uint64_t flush_memtable_id = cfd->imm()->GetLatestMemTableID();
|
||||
flush_req.emplace_back(cfd, flush_memtable_id);
|
||||
}
|
||||
}
|
||||
return Status::OK();
|
||||
if (status.ok()) {
|
||||
SchedulePendingFlush(flush_req, FlushReason::kWriteBufferFull);
|
||||
MaybeScheduleFlushOrCompaction();
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
@@ -1242,8 +1303,7 @@ void DBImpl::NotifyOnMemTableSealed(ColumnFamilyData* /*cfd*/,
|
||||
|
||||
// REQUIRES: mutex_ is held
|
||||
// REQUIRES: this thread is currently at the front of the writer queue
|
||||
Status DBImpl::SwitchMemtable(ColumnFamilyData* cfd, WriteContext* context,
|
||||
FlushReason flush_reason) {
|
||||
Status DBImpl::SwitchMemtable(ColumnFamilyData* cfd, WriteContext* context) {
|
||||
mutex_.AssertHeld();
|
||||
WriteThread::Writer nonmem_w;
|
||||
if (two_write_queues_) {
|
||||
@@ -1312,6 +1372,8 @@ Status DBImpl::SwitchMemtable(ColumnFamilyData* cfd, WriteContext* context,
|
||||
auto write_hint = CalculateWALWriteHint();
|
||||
mutex_.Unlock();
|
||||
{
|
||||
std::string log_fname =
|
||||
LogFileName(immutable_db_options_.wal_dir, new_log_number);
|
||||
if (creating_new_log) {
|
||||
EnvOptions opt_env_opt =
|
||||
env_->OptimizeForLogWrite(env_options_, db_options);
|
||||
@@ -1319,14 +1381,12 @@ Status DBImpl::SwitchMemtable(ColumnFamilyData* cfd, WriteContext* context,
|
||||
ROCKS_LOG_INFO(immutable_db_options_.info_log,
|
||||
"reusing log %" PRIu64 " from recycle list\n",
|
||||
recycle_log_number);
|
||||
s = env_->ReuseWritableFile(
|
||||
LogFileName(immutable_db_options_.wal_dir, new_log_number),
|
||||
LogFileName(immutable_db_options_.wal_dir, recycle_log_number),
|
||||
&lfile, opt_env_opt);
|
||||
std::string old_log_fname =
|
||||
LogFileName(immutable_db_options_.wal_dir, recycle_log_number);
|
||||
s = env_->ReuseWritableFile(log_fname, old_log_fname, &lfile,
|
||||
opt_env_opt);
|
||||
} else {
|
||||
s = NewWritableFile(
|
||||
env_, LogFileName(immutable_db_options_.wal_dir, new_log_number),
|
||||
&lfile, opt_env_opt);
|
||||
s = NewWritableFile(env_, log_fname, &lfile, opt_env_opt);
|
||||
}
|
||||
if (s.ok()) {
|
||||
// Our final size should be less than write_buffer_size
|
||||
@@ -1337,7 +1397,7 @@ Status DBImpl::SwitchMemtable(ColumnFamilyData* cfd, WriteContext* context,
|
||||
lfile->SetPreallocationBlockSize(preallocate_block_size);
|
||||
lfile->SetWriteLifeTimeHint(write_hint);
|
||||
unique_ptr<WritableFileWriter> file_writer(
|
||||
new WritableFileWriter(std::move(lfile), opt_env_opt));
|
||||
new WritableFileWriter(std::move(lfile), log_fname, opt_env_opt));
|
||||
new_log = new log::Writer(
|
||||
std::move(file_writer), new_log_number,
|
||||
immutable_db_options_.recycle_log_file_num > 0, manual_wal_flush_);
|
||||
@@ -1415,7 +1475,7 @@ Status DBImpl::SwitchMemtable(ColumnFamilyData* cfd, WriteContext* context,
|
||||
new_mem->Ref();
|
||||
cfd->SetMemtable(new_mem);
|
||||
InstallSuperVersionAndScheduleWork(cfd, &context->superversion_context,
|
||||
mutable_cf_options, flush_reason);
|
||||
mutable_cf_options);
|
||||
if (two_write_queues_) {
|
||||
nonmem_write_thread_.ExitUnbatched(&nonmem_w);
|
||||
}
|
||||
|
||||
+34
-13
@@ -27,6 +27,7 @@
|
||||
#include "util/logging.h"
|
||||
#include "util/mutexlock.h"
|
||||
#include "util/string_util.h"
|
||||
#include "util/trace_replay.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
@@ -114,7 +115,8 @@ class DBIter final: public Iterator {
|
||||
const MutableCFOptions& mutable_cf_options, const Comparator* cmp,
|
||||
InternalIterator* iter, SequenceNumber s, bool arena_mode,
|
||||
uint64_t max_sequential_skip_in_iterations,
|
||||
ReadCallback* read_callback, bool allow_blob)
|
||||
ReadCallback* read_callback, DBImpl* db_impl, ColumnFamilyData* cfd,
|
||||
bool allow_blob)
|
||||
: arena_mode_(arena_mode),
|
||||
env_(_env),
|
||||
logger_(cf_options.info_log),
|
||||
@@ -135,6 +137,8 @@ class DBIter final: public Iterator {
|
||||
range_del_agg_(cf_options.internal_comparator, s,
|
||||
true /* collapse_deletions */),
|
||||
read_callback_(read_callback),
|
||||
db_impl_(db_impl),
|
||||
cfd_(cfd),
|
||||
allow_blob_(allow_blob),
|
||||
is_blob_(false),
|
||||
start_seqnum_(read_options.iter_start_seqnum) {
|
||||
@@ -344,6 +348,8 @@ class DBIter final: public Iterator {
|
||||
LocalStatistics local_stats_;
|
||||
PinnedIteratorsManager pinned_iters_mgr_;
|
||||
ReadCallback* read_callback_;
|
||||
DBImpl* db_impl_;
|
||||
ColumnFamilyData* cfd_;
|
||||
bool allow_blob_;
|
||||
bool is_blob_;
|
||||
// for diff snapshots we want the lower bound on the seqnum;
|
||||
@@ -1267,6 +1273,12 @@ void DBIter::Seek(const Slice& target) {
|
||||
saved_key_.Clear();
|
||||
saved_key_.SetInternalKey(target, seq);
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
if (db_impl_ != nullptr && cfd_ != nullptr) {
|
||||
db_impl_->TraceIteratorSeek(cfd_->GetID(), target);
|
||||
}
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
if (iterate_lower_bound_ != nullptr &&
|
||||
user_comparator_->Compare(saved_key_.GetUserKey(),
|
||||
*iterate_lower_bound_) < 0) {
|
||||
@@ -1331,6 +1343,12 @@ void DBIter::SeekForPrev(const Slice& target) {
|
||||
range_del_agg_.InvalidateRangeDelMapPositions();
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
if (db_impl_ != nullptr && cfd_ != nullptr) {
|
||||
db_impl_->TraceIteratorSeekForPrev(cfd_->GetID(), target);
|
||||
}
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
RecordTick(statistics_, NUMBER_DB_SEEK);
|
||||
if (iter_->Valid()) {
|
||||
if (prefix_extractor_ && prefix_same_as_start_) {
|
||||
@@ -1453,11 +1471,12 @@ Iterator* NewDBIterator(Env* env, const ReadOptions& read_options,
|
||||
InternalIterator* internal_iter,
|
||||
const SequenceNumber& sequence,
|
||||
uint64_t max_sequential_skip_in_iterations,
|
||||
ReadCallback* read_callback, bool allow_blob) {
|
||||
DBIter* db_iter =
|
||||
new DBIter(env, read_options, cf_options, mutable_cf_options,
|
||||
user_key_comparator, internal_iter, sequence, false,
|
||||
max_sequential_skip_in_iterations, read_callback, allow_blob);
|
||||
ReadCallback* read_callback, DBImpl* db_impl,
|
||||
ColumnFamilyData* cfd, bool allow_blob) {
|
||||
DBIter* db_iter = new DBIter(
|
||||
env, read_options, cf_options, mutable_cf_options, user_key_comparator,
|
||||
internal_iter, sequence, false, max_sequential_skip_in_iterations,
|
||||
read_callback, db_impl, cfd, allow_blob);
|
||||
return db_iter;
|
||||
}
|
||||
|
||||
@@ -1504,13 +1523,14 @@ void ArenaWrappedDBIter::Init(Env* env, const ReadOptions& read_options,
|
||||
const SequenceNumber& sequence,
|
||||
uint64_t max_sequential_skip_in_iteration,
|
||||
uint64_t version_number,
|
||||
ReadCallback* read_callback, bool allow_blob,
|
||||
ReadCallback* read_callback, DBImpl* db_impl,
|
||||
ColumnFamilyData* cfd, bool allow_blob,
|
||||
bool allow_refresh) {
|
||||
auto mem = arena_.AllocateAligned(sizeof(DBIter));
|
||||
db_iter_ = new (mem)
|
||||
DBIter(env, read_options, cf_options, mutable_cf_options,
|
||||
cf_options.user_comparator, nullptr, sequence, true,
|
||||
max_sequential_skip_in_iteration, read_callback, allow_blob);
|
||||
db_iter_ = new (mem) DBIter(env, read_options, cf_options, mutable_cf_options,
|
||||
cf_options.user_comparator, nullptr, sequence,
|
||||
true, max_sequential_skip_in_iteration,
|
||||
read_callback, db_impl, cfd, allow_blob);
|
||||
sv_number_ = version_number;
|
||||
allow_refresh_ = allow_refresh;
|
||||
}
|
||||
@@ -1534,7 +1554,8 @@ Status ArenaWrappedDBIter::Refresh() {
|
||||
SuperVersion* sv = cfd_->GetReferencedSuperVersion(db_impl_->mutex());
|
||||
Init(env, read_options_, *(cfd_->ioptions()), sv->mutable_cf_options,
|
||||
latest_seq, sv->mutable_cf_options.max_sequential_skip_in_iterations,
|
||||
cur_sv_number, read_callback_, allow_blob_, allow_refresh_);
|
||||
cur_sv_number, read_callback_, db_impl_, cfd_, allow_blob_,
|
||||
allow_refresh_);
|
||||
|
||||
InternalIterator* internal_iter = db_impl_->NewInternalIterator(
|
||||
read_options_, cfd_, sv, &arena_, db_iter_->GetRangeDelAggregator());
|
||||
@@ -1556,7 +1577,7 @@ ArenaWrappedDBIter* NewArenaWrappedDbIterator(
|
||||
ArenaWrappedDBIter* iter = new ArenaWrappedDBIter();
|
||||
iter->Init(env, read_options, cf_options, mutable_cf_options, sequence,
|
||||
max_sequential_skip_in_iterations, version_number, read_callback,
|
||||
allow_blob, allow_refresh);
|
||||
db_impl, cfd, allow_blob, allow_refresh);
|
||||
if (db_impl != nullptr && cfd != nullptr && allow_refresh) {
|
||||
iter->StoreRefreshInfo(read_options, db_impl, cfd, read_callback,
|
||||
allow_blob);
|
||||
|
||||
+10
-11
@@ -23,20 +23,18 @@ namespace rocksdb {
|
||||
|
||||
class Arena;
|
||||
class DBIter;
|
||||
class InternalIterator;
|
||||
|
||||
// Return a new iterator that converts internal keys (yielded by
|
||||
// "*internal_iter") that were live at the specified "sequence" number
|
||||
// into appropriate user keys.
|
||||
extern Iterator* NewDBIterator(Env* env, const ReadOptions& read_options,
|
||||
const ImmutableCFOptions& cf_options,
|
||||
const MutableCFOptions& mutable_cf_options,
|
||||
const Comparator* user_key_comparator,
|
||||
InternalIterator* internal_iter,
|
||||
const SequenceNumber& sequence,
|
||||
uint64_t max_sequential_skip_in_iterations,
|
||||
ReadCallback* read_callback,
|
||||
bool allow_blob = false);
|
||||
extern Iterator* NewDBIterator(
|
||||
Env* env, const ReadOptions& read_options,
|
||||
const ImmutableCFOptions& cf_options,
|
||||
const MutableCFOptions& mutable_cf_options,
|
||||
const Comparator* user_key_comparator, InternalIterator* internal_iter,
|
||||
const SequenceNumber& sequence, uint64_t max_sequential_skip_in_iterations,
|
||||
ReadCallback* read_callback, DBImpl* db_impl = nullptr,
|
||||
ColumnFamilyData* cfd = nullptr, bool allow_blob = false);
|
||||
|
||||
// A wrapper iterator which wraps DB Iterator and the arena, with which the DB
|
||||
// iterator is supposed be allocated. This class is used as an entry point of
|
||||
@@ -75,7 +73,8 @@ class ArenaWrappedDBIter : public Iterator {
|
||||
const MutableCFOptions& mutable_cf_options,
|
||||
const SequenceNumber& sequence,
|
||||
uint64_t max_sequential_skip_in_iterations, uint64_t version_number,
|
||||
ReadCallback* read_callback, bool allow_blob, bool allow_refresh);
|
||||
ReadCallback* read_callback, DBImpl* db_impl, ColumnFamilyData* cfd,
|
||||
bool allow_blob, bool allow_refresh);
|
||||
|
||||
void StoreRefreshInfo(const ReadOptions& read_options, DBImpl* db_impl,
|
||||
ColumnFamilyData* cfd, ReadCallback* read_callback,
|
||||
|
||||
+52
-67
@@ -184,73 +184,6 @@ TEST_P(DBIteratorTest, NonBlockingIteration) {
|
||||
kSkipMmapReads));
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
TEST_P(DBIteratorTest, ManagedNonBlockingIteration) {
|
||||
do {
|
||||
ReadOptions non_blocking_opts, regular_opts;
|
||||
Options options = CurrentOptions();
|
||||
options.statistics = rocksdb::CreateDBStatistics();
|
||||
non_blocking_opts.read_tier = kBlockCacheTier;
|
||||
non_blocking_opts.managed = true;
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
// write one kv to the database.
|
||||
ASSERT_OK(Put(1, "a", "b"));
|
||||
|
||||
// scan using non-blocking iterator. We should find it because
|
||||
// it is in memtable.
|
||||
Iterator* iter = NewIterator(non_blocking_opts, handles_[1]);
|
||||
int count = 0;
|
||||
for (iter->SeekToFirst(); iter->Valid(); iter->Next()) {
|
||||
ASSERT_OK(iter->status());
|
||||
count++;
|
||||
}
|
||||
ASSERT_EQ(count, 1);
|
||||
delete iter;
|
||||
|
||||
// flush memtable to storage. Now, the key should not be in the
|
||||
// memtable neither in the block cache.
|
||||
ASSERT_OK(Flush(1));
|
||||
|
||||
// verify that a non-blocking iterator does not find any
|
||||
// kvs. Neither does it do any IOs to storage.
|
||||
int64_t numopen = TestGetTickerCount(options, NO_FILE_OPENS);
|
||||
int64_t cache_added = TestGetTickerCount(options, BLOCK_CACHE_ADD);
|
||||
iter = NewIterator(non_blocking_opts, handles_[1]);
|
||||
count = 0;
|
||||
for (iter->SeekToFirst(); iter->Valid(); iter->Next()) {
|
||||
count++;
|
||||
}
|
||||
ASSERT_EQ(count, 0);
|
||||
ASSERT_TRUE(iter->status().IsIncomplete());
|
||||
ASSERT_EQ(numopen, TestGetTickerCount(options, NO_FILE_OPENS));
|
||||
ASSERT_EQ(cache_added, TestGetTickerCount(options, BLOCK_CACHE_ADD));
|
||||
delete iter;
|
||||
|
||||
// read in the specified block via a regular get
|
||||
ASSERT_EQ(Get(1, "a"), "b");
|
||||
|
||||
// verify that we can find it via a non-blocking scan
|
||||
numopen = TestGetTickerCount(options, NO_FILE_OPENS);
|
||||
cache_added = TestGetTickerCount(options, BLOCK_CACHE_ADD);
|
||||
iter = NewIterator(non_blocking_opts, handles_[1]);
|
||||
count = 0;
|
||||
for (iter->SeekToFirst(); iter->Valid(); iter->Next()) {
|
||||
ASSERT_OK(iter->status());
|
||||
count++;
|
||||
}
|
||||
ASSERT_EQ(count, 1);
|
||||
ASSERT_EQ(numopen, TestGetTickerCount(options, NO_FILE_OPENS));
|
||||
ASSERT_EQ(cache_added, TestGetTickerCount(options, BLOCK_CACHE_ADD));
|
||||
delete iter;
|
||||
|
||||
// This test verifies block cache behaviors, which is not used by plain
|
||||
// table format.
|
||||
// Exclude kHashCuckoo as it does not support iteration currently
|
||||
} while (ChangeOptions(kSkipPlainTable | kSkipNoSeekToLast | kSkipHashCuckoo |
|
||||
kSkipMmapReads));
|
||||
}
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
TEST_P(DBIteratorTest, IterSeekBeforePrev) {
|
||||
ASSERT_OK(Put("a", "b"));
|
||||
ASSERT_OK(Put("c", "d"));
|
||||
@@ -2160,6 +2093,34 @@ TEST_P(DBIteratorTest, Refresh) {
|
||||
iter.reset();
|
||||
}
|
||||
|
||||
TEST_P(DBIteratorTest, RefreshWithSnapshot) {
|
||||
ASSERT_OK(Put("x", "y"));
|
||||
const Snapshot* snapshot = db_->GetSnapshot();
|
||||
ReadOptions options;
|
||||
options.snapshot = snapshot;
|
||||
Iterator* iter = NewIterator(options);
|
||||
|
||||
iter->Seek(Slice("a"));
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ(iter->key().compare(Slice("x")), 0);
|
||||
iter->Next();
|
||||
ASSERT_FALSE(iter->Valid());
|
||||
|
||||
ASSERT_OK(Put("c", "d"));
|
||||
|
||||
iter->Seek(Slice("a"));
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ(iter->key().compare(Slice("x")), 0);
|
||||
iter->Next();
|
||||
ASSERT_FALSE(iter->Valid());
|
||||
|
||||
Status s;
|
||||
s = iter->Refresh();
|
||||
ASSERT_TRUE(s.IsNotSupported());
|
||||
db_->ReleaseSnapshot(snapshot);
|
||||
delete iter;
|
||||
}
|
||||
|
||||
TEST_P(DBIteratorTest, CreationFailure) {
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::NewInternalIterator:StatusCallback", [](void* arg) {
|
||||
@@ -2480,6 +2441,30 @@ TEST_P(DBIteratorTest, NonBlockingIterationBugRepro) {
|
||||
EXPECT_TRUE(iter->status().IsIncomplete());
|
||||
}
|
||||
|
||||
TEST_P(DBIteratorTest, SeekBackwardAfterOutOfUpperBound) {
|
||||
Put("a", "");
|
||||
Put("b", "");
|
||||
Flush();
|
||||
|
||||
ReadOptions ropt;
|
||||
Slice ub = "b";
|
||||
ropt.iterate_upper_bound = &ub;
|
||||
|
||||
std::unique_ptr<Iterator> it(dbfull()->NewIterator(ropt));
|
||||
it->SeekForPrev("a");
|
||||
ASSERT_TRUE(it->Valid());
|
||||
ASSERT_OK(it->status());
|
||||
ASSERT_EQ("a", it->key().ToString());
|
||||
it->Next();
|
||||
ASSERT_FALSE(it->Valid());
|
||||
ASSERT_OK(it->status());
|
||||
it->SeekForPrev("a");
|
||||
ASSERT_OK(it->status());
|
||||
|
||||
ASSERT_TRUE(it->Valid());
|
||||
ASSERT_EQ("a", it->key().ToString());
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(DBIteratorTestInstance, DBIteratorTest,
|
||||
testing::Values(true, false));
|
||||
|
||||
|
||||
+20
-19
@@ -180,17 +180,16 @@ void ParseTablePropertiesString(std::string tp_string, TableProperties* tp) {
|
||||
ResetTableProperties(tp);
|
||||
sscanf(tp_string.c_str(),
|
||||
"# data blocks %" SCNu64 " # entries %" SCNu64
|
||||
" # range deletions %" SCNu64
|
||||
" raw key size %" SCNu64
|
||||
" # range deletions %" SCNu64 " raw key size %" SCNu64
|
||||
" raw average key size %lf "
|
||||
" raw value size %" SCNu64
|
||||
" raw average value size %lf "
|
||||
" data block size %" SCNu64 " index block size (user-key? %" SCNu64
|
||||
") %" SCNu64 " filter block size %" SCNu64,
|
||||
", delta-value? %" SCNu64 ") %" SCNu64 " filter block size %" SCNu64,
|
||||
&tp->num_data_blocks, &tp->num_entries, &tp->num_range_deletions,
|
||||
&tp->raw_key_size, &dummy_double, &tp->raw_value_size, &dummy_double,
|
||||
&tp->data_size, &tp->index_key_is_user_key, &tp->index_size,
|
||||
&tp->filter_size);
|
||||
&tp->data_size, &tp->index_key_is_user_key,
|
||||
&tp->index_value_is_delta_encoded, &tp->index_size, &tp->filter_size);
|
||||
}
|
||||
|
||||
void VerifySimilar(uint64_t a, uint64_t b, double bias) {
|
||||
@@ -224,14 +223,11 @@ void VerifyTableProperties(const TableProperties& base_tp,
|
||||
ASSERT_EQ(base_tp.num_range_deletions, new_tp.num_range_deletions);
|
||||
}
|
||||
|
||||
void GetExpectedTableProperties(TableProperties* expected_tp,
|
||||
const int kKeySize, const int kValueSize,
|
||||
const int kKeysPerTable,
|
||||
const int kRangeDeletionsPerTable,
|
||||
const int kTableCount,
|
||||
const int kBloomBitsPerKey,
|
||||
const size_t kBlockSize,
|
||||
const bool index_key_is_user_key) {
|
||||
void GetExpectedTableProperties(
|
||||
TableProperties* expected_tp, const int kKeySize, const int kValueSize,
|
||||
const int kKeysPerTable, const int kRangeDeletionsPerTable,
|
||||
const int kTableCount, const int kBloomBitsPerKey, const size_t kBlockSize,
|
||||
const bool index_key_is_user_key, const bool value_delta_encoding) {
|
||||
const int kKeyCount = kTableCount * kKeysPerTable;
|
||||
const int kRangeDeletionCount = kTableCount * kRangeDeletionsPerTable;
|
||||
const int kAvgSuccessorSize = kKeySize / 5;
|
||||
@@ -248,7 +244,9 @@ void GetExpectedTableProperties(TableProperties* expected_tp,
|
||||
kTableCount * (kKeysPerTable * (kKeySize + 8 + kValueSize));
|
||||
expected_tp->index_size =
|
||||
expected_tp->num_data_blocks *
|
||||
(kAvgSuccessorSize + (index_key_is_user_key ? 0 : 8));
|
||||
(kAvgSuccessorSize + (index_key_is_user_key ? 0 : 8) -
|
||||
// discount 1 byte as value size is not encoded in value delta encoding
|
||||
(value_delta_encoding ? 1 : 0));
|
||||
expected_tp->filter_size =
|
||||
kTableCount * (kKeysPerTable * kBloomBitsPerKey / 8);
|
||||
}
|
||||
@@ -342,12 +340,14 @@ TEST_F(DBPropertiesTest, AggregatedTableProperties) {
|
||||
TableProperties output_tp;
|
||||
ParseTablePropertiesString(property, &output_tp);
|
||||
bool index_key_is_user_key = output_tp.index_key_is_user_key > 0;
|
||||
bool value_is_delta_encoded = output_tp.index_value_is_delta_encoded > 0;
|
||||
|
||||
TableProperties expected_tp;
|
||||
GetExpectedTableProperties(&expected_tp, kKeySize, kValueSize,
|
||||
kKeysPerTable, kRangeDeletionsPerTable,
|
||||
kTableCount, kBloomBitsPerKey,
|
||||
table_options.block_size, index_key_is_user_key);
|
||||
table_options.block_size, index_key_is_user_key,
|
||||
value_is_delta_encoded);
|
||||
|
||||
VerifyTableProperties(expected_tp, output_tp);
|
||||
}
|
||||
@@ -533,6 +533,7 @@ TEST_F(DBPropertiesTest, AggregatedTablePropertiesAtLevel) {
|
||||
db_->GetProperty(DB::Properties::kAggregatedTableProperties, &tp_string);
|
||||
ParseTablePropertiesString(tp_string, &tp);
|
||||
bool index_key_is_user_key = tp.index_key_is_user_key > 0;
|
||||
bool value_is_delta_encoded = tp.index_value_is_delta_encoded > 0;
|
||||
ASSERT_EQ(sum_tp.data_size, tp.data_size);
|
||||
ASSERT_EQ(sum_tp.index_size, tp.index_size);
|
||||
ASSERT_EQ(sum_tp.filter_size, tp.filter_size);
|
||||
@@ -542,10 +543,10 @@ TEST_F(DBPropertiesTest, AggregatedTablePropertiesAtLevel) {
|
||||
ASSERT_EQ(sum_tp.num_entries, tp.num_entries);
|
||||
ASSERT_EQ(sum_tp.num_range_deletions, tp.num_range_deletions);
|
||||
if (table > 3) {
|
||||
GetExpectedTableProperties(
|
||||
&expected_tp, kKeySize, kValueSize, kKeysPerTable,
|
||||
kRangeDeletionsPerTable, table, kBloomBitsPerKey,
|
||||
table_options.block_size, index_key_is_user_key);
|
||||
GetExpectedTableProperties(&expected_tp, kKeySize, kValueSize,
|
||||
kKeysPerTable, kRangeDeletionsPerTable, table,
|
||||
kBloomBitsPerKey, table_options.block_size,
|
||||
index_key_is_user_key, value_is_delta_encoded);
|
||||
// Gives larger bias here as index block size, filter block size,
|
||||
// and data block size become much harder to estimate in this test.
|
||||
VerifyTableProperties(expected_tp, tp, 0.5, 0.4, 0.4, 0.25);
|
||||
|
||||
@@ -191,7 +191,7 @@ TEST_F(DBRangeDelTest, SentinelsOmittedFromOutputFile) {
|
||||
|
||||
std::vector<std::vector<FileMetaData>> files;
|
||||
dbfull()->TEST_GetFilesMetaData(db_->DefaultColumnFamily(), &files);
|
||||
ASSERT_GT(files[0][0].smallest_seqno, 0);
|
||||
ASSERT_GT(files[0][0].fd.smallest_seqno, 0);
|
||||
|
||||
db_->ReleaseSnapshot(snapshot);
|
||||
}
|
||||
|
||||
+9
-24
@@ -436,7 +436,7 @@ TEST_F(DBSSTTest, OpenDBWithExistingTrash) {
|
||||
// deleted from first db_path were deleted using DeleteScheduler and
|
||||
// files in the second path were not.
|
||||
TEST_F(DBSSTTest, DeleteSchedulerMultipleDBPaths) {
|
||||
int bg_delete_file = 0;
|
||||
std::atomic<int> bg_delete_file(0);
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"DeleteScheduler::DeleteTrashFile:DeleteFile",
|
||||
[&](void* /*arg*/) { bg_delete_file++; });
|
||||
@@ -703,26 +703,19 @@ TEST_F(DBSSTTest, DBWithMaxSpaceAllowedRandomized) {
|
||||
// When bg_error_ is set we will verify that the DB size is greater
|
||||
// than the limit.
|
||||
|
||||
std::vector<int> max_space_limits_mbs = {1, 2, 4, 8, 10};
|
||||
decltype(max_space_limits_mbs)::value_type limit_mb_cb;
|
||||
bool bg_error_set = false;
|
||||
uint64_t total_sst_files_size = 0;
|
||||
std::vector<int> max_space_limits_mbs = {1, 10};
|
||||
std::atomic<bool> bg_error_set(false);
|
||||
|
||||
std::atomic<int> estimate_multiplier(1);
|
||||
int reached_max_space_on_flush = 0;
|
||||
int reached_max_space_on_compaction = 0;
|
||||
std::atomic<int> reached_max_space_on_flush(0);
|
||||
std::atomic<int> reached_max_space_on_compaction(0);
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::FlushMemTableToOutputFile:MaxAllowedSpaceReached",
|
||||
[&](void* arg) {
|
||||
Status* bg_error = static_cast<Status*>(arg);
|
||||
bg_error_set = true;
|
||||
GetAllSSTFiles(&total_sst_files_size);
|
||||
reached_max_space_on_flush++;
|
||||
// low limit for size calculated using sst files
|
||||
ASSERT_GE(total_sst_files_size, limit_mb_cb * 1024 * 1024);
|
||||
// clear error to ensure compaction callback is called
|
||||
*bg_error = Status::OK();
|
||||
estimate_multiplier++; // used in the main loop assert
|
||||
});
|
||||
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
@@ -735,15 +728,11 @@ TEST_F(DBSSTTest, DBWithMaxSpaceAllowedRandomized) {
|
||||
"CompactionJob::FinishCompactionOutputFile:MaxAllowedSpaceReached",
|
||||
[&](void* /*arg*/) {
|
||||
bg_error_set = true;
|
||||
GetAllSSTFiles(&total_sst_files_size);
|
||||
reached_max_space_on_compaction++;
|
||||
});
|
||||
|
||||
for (auto limit_mb : max_space_limits_mbs) {
|
||||
bg_error_set = false;
|
||||
total_sst_files_size = 0;
|
||||
estimate_multiplier = 1;
|
||||
limit_mb_cb = limit_mb;
|
||||
rocksdb::SyncPoint::GetInstance()->ClearTrace();
|
||||
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
||||
std::shared_ptr<SstFileManager> sst_file_manager(NewSstFileManager(env_));
|
||||
@@ -757,21 +746,17 @@ TEST_F(DBSSTTest, DBWithMaxSpaceAllowedRandomized) {
|
||||
|
||||
sfm->SetMaxAllowedSpaceUsage(limit_mb * 1024 * 1024);
|
||||
|
||||
int keys_written = 0;
|
||||
uint64_t estimated_db_size = 0;
|
||||
// It is easy to detect if the test is stuck in a loop. No need for
|
||||
// complex termination logic.
|
||||
while (true) {
|
||||
auto s = Put(RandomString(&rnd, 10), RandomString(&rnd, 50));
|
||||
if (!s.ok()) {
|
||||
break;
|
||||
}
|
||||
keys_written++;
|
||||
// Check the estimated db size vs the db limit just to make sure we
|
||||
// dont run into an infinite loop
|
||||
estimated_db_size = keys_written * 60; // ~60 bytes per key
|
||||
ASSERT_LT(estimated_db_size,
|
||||
estimate_multiplier * limit_mb * 1024 * 1024 * 2);
|
||||
}
|
||||
ASSERT_TRUE(bg_error_set);
|
||||
uint64_t total_sst_files_size = 0;
|
||||
GetAllSSTFiles(&total_sst_files_size);
|
||||
ASSERT_GE(total_sst_files_size, limit_mb * 1024 * 1024);
|
||||
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
|
||||
@@ -252,13 +252,14 @@ TEST_F(DBTablePropertiesTest, GetColumnFamilyNameProperty) {
|
||||
}
|
||||
|
||||
TEST_F(DBTablePropertiesTest, DeletionTriggeredCompactionMarking) {
|
||||
const int kNumKeys = 1000;
|
||||
const int kWindowSize = 100;
|
||||
const int kNumDelsTrigger = 90;
|
||||
int kNumKeys = 1000;
|
||||
int kWindowSize = 100;
|
||||
int kNumDelsTrigger = 90;
|
||||
std::shared_ptr<TablePropertiesCollectorFactory> compact_on_del =
|
||||
NewCompactOnDeletionCollectorFactory(kWindowSize, kNumDelsTrigger);
|
||||
|
||||
Options opts = CurrentOptions();
|
||||
opts.table_properties_collector_factories.emplace_back(
|
||||
NewCompactOnDeletionCollectorFactory(kWindowSize, kNumDelsTrigger));
|
||||
opts.table_properties_collector_factories.emplace_back(compact_on_del);
|
||||
Reopen(opts);
|
||||
|
||||
// add an L1 file to prevent tombstones from dropping due to obsolescence
|
||||
@@ -280,6 +281,48 @@ TEST_F(DBTablePropertiesTest, DeletionTriggeredCompactionMarking) {
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
ASSERT_EQ(0, NumTableFilesAtLevel(0));
|
||||
ASSERT_GT(NumTableFilesAtLevel(1), 0);
|
||||
|
||||
// Change the window size and deletion trigger and ensure new values take
|
||||
// effect
|
||||
kWindowSize = 50;
|
||||
kNumDelsTrigger = 40;
|
||||
static_cast<CompactOnDeletionCollectorFactory*>
|
||||
(compact_on_del.get())->SetWindowSize(kWindowSize);
|
||||
static_cast<CompactOnDeletionCollectorFactory*>
|
||||
(compact_on_del.get())->SetDeletionTrigger(kNumDelsTrigger);
|
||||
for (int i = 0; i < kNumKeys; ++i) {
|
||||
if (i >= kNumKeys - kWindowSize &&
|
||||
i < kNumKeys - kWindowSize + kNumDelsTrigger) {
|
||||
Delete(Key(i));
|
||||
} else {
|
||||
Put(Key(i), "val");
|
||||
}
|
||||
}
|
||||
Flush();
|
||||
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
ASSERT_EQ(0, NumTableFilesAtLevel(0));
|
||||
ASSERT_GT(NumTableFilesAtLevel(1), 0);
|
||||
|
||||
// Change the window size to disable delete triggered compaction
|
||||
kWindowSize = 0;
|
||||
static_cast<CompactOnDeletionCollectorFactory*>
|
||||
(compact_on_del.get())->SetWindowSize(kWindowSize);
|
||||
static_cast<CompactOnDeletionCollectorFactory*>
|
||||
(compact_on_del.get())->SetDeletionTrigger(kNumDelsTrigger);
|
||||
for (int i = 0; i < kNumKeys; ++i) {
|
||||
if (i >= kNumKeys - kWindowSize &&
|
||||
i < kNumKeys - kWindowSize + kNumDelsTrigger) {
|
||||
Delete(Key(i));
|
||||
} else {
|
||||
Put(Key(i), "val");
|
||||
}
|
||||
}
|
||||
Flush();
|
||||
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
ASSERT_EQ(1, NumTableFilesAtLevel(0));
|
||||
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
@@ -479,275 +479,6 @@ TEST_F(DBTestTailingIterator, TailingIteratorGap) {
|
||||
ASSERT_EQ("40", it->key().ToString());
|
||||
}
|
||||
|
||||
TEST_F(DBTestTailingIterator, ManagedTailingIteratorSingle) {
|
||||
ReadOptions read_options;
|
||||
read_options.tailing = true;
|
||||
read_options.managed = true;
|
||||
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options));
|
||||
iter->SeekToFirst();
|
||||
ASSERT_TRUE(!iter->Valid());
|
||||
|
||||
// add a record and check that iter can see it
|
||||
ASSERT_OK(db_->Put(WriteOptions(), "mirko", "fodor"));
|
||||
iter->SeekToFirst();
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ(iter->key().ToString(), "mirko");
|
||||
|
||||
iter->Next();
|
||||
ASSERT_TRUE(!iter->Valid());
|
||||
}
|
||||
|
||||
TEST_F(DBTestTailingIterator, ManagedTailingIteratorKeepAdding) {
|
||||
CreateAndReopenWithCF({"pikachu"}, CurrentOptions());
|
||||
ReadOptions read_options;
|
||||
read_options.tailing = true;
|
||||
read_options.managed = true;
|
||||
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options, handles_[1]));
|
||||
std::string value(1024, 'a');
|
||||
|
||||
const int num_records = 10000;
|
||||
for (int i = 0; i < num_records; ++i) {
|
||||
char buf[32];
|
||||
snprintf(buf, sizeof(buf), "%016d", i);
|
||||
|
||||
Slice key(buf, 16);
|
||||
ASSERT_OK(Put(1, key, value));
|
||||
|
||||
iter->Seek(key);
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ(iter->key().compare(key), 0);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBTestTailingIterator, ManagedTailingIteratorSeekToNext) {
|
||||
CreateAndReopenWithCF({"pikachu"}, CurrentOptions());
|
||||
ReadOptions read_options;
|
||||
read_options.tailing = true;
|
||||
read_options.managed = true;
|
||||
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options, handles_[1]));
|
||||
std::string value(1024, 'a');
|
||||
|
||||
const int num_records = 1000;
|
||||
for (int i = 1; i < num_records; ++i) {
|
||||
char buf1[32];
|
||||
char buf2[32];
|
||||
snprintf(buf1, sizeof(buf1), "00a0%016d", i * 5);
|
||||
|
||||
Slice key(buf1, 20);
|
||||
ASSERT_OK(Put(1, key, value));
|
||||
|
||||
if (i % 100 == 99) {
|
||||
ASSERT_OK(Flush(1));
|
||||
}
|
||||
|
||||
snprintf(buf2, sizeof(buf2), "00a0%016d", i * 5 - 2);
|
||||
Slice target(buf2, 20);
|
||||
iter->Seek(target);
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ(iter->key().compare(key), 0);
|
||||
}
|
||||
for (int i = 2 * num_records; i > 0; --i) {
|
||||
char buf1[32];
|
||||
char buf2[32];
|
||||
snprintf(buf1, sizeof(buf1), "00a0%016d", i * 5);
|
||||
|
||||
Slice key(buf1, 20);
|
||||
ASSERT_OK(Put(1, key, value));
|
||||
|
||||
if (i % 100 == 99) {
|
||||
ASSERT_OK(Flush(1));
|
||||
}
|
||||
|
||||
snprintf(buf2, sizeof(buf2), "00a0%016d", i * 5 - 2);
|
||||
Slice target(buf2, 20);
|
||||
iter->Seek(target);
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ(iter->key().compare(key), 0);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBTestTailingIterator, ManagedTailingIteratorDeletes) {
|
||||
CreateAndReopenWithCF({"pikachu"}, CurrentOptions());
|
||||
ReadOptions read_options;
|
||||
read_options.tailing = true;
|
||||
read_options.managed = true;
|
||||
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options, handles_[1]));
|
||||
|
||||
// write a single record, read it using the iterator, then delete it
|
||||
ASSERT_OK(Put(1, "0test", "test"));
|
||||
iter->SeekToFirst();
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ(iter->key().ToString(), "0test");
|
||||
ASSERT_OK(Delete(1, "0test"));
|
||||
|
||||
// write many more records
|
||||
const int num_records = 10000;
|
||||
std::string value(1024, 'A');
|
||||
|
||||
for (int i = 0; i < num_records; ++i) {
|
||||
char buf[32];
|
||||
snprintf(buf, sizeof(buf), "1%015d", i);
|
||||
|
||||
Slice key(buf, 16);
|
||||
ASSERT_OK(Put(1, key, value));
|
||||
}
|
||||
|
||||
// force a flush to make sure that no records are read from memtable
|
||||
ASSERT_OK(Flush(1));
|
||||
|
||||
// skip "0test"
|
||||
iter->Next();
|
||||
|
||||
// make sure we can read all new records using the existing iterator
|
||||
int count = 0;
|
||||
for (; iter->Valid(); iter->Next(), ++count) {
|
||||
}
|
||||
|
||||
ASSERT_EQ(count, num_records);
|
||||
}
|
||||
|
||||
TEST_F(DBTestTailingIterator, ManagedTailingIteratorPrefixSeek) {
|
||||
ReadOptions read_options;
|
||||
read_options.tailing = true;
|
||||
read_options.managed = true;
|
||||
|
||||
Options options = CurrentOptions();
|
||||
options.create_if_missing = true;
|
||||
options.disable_auto_compactions = true;
|
||||
options.prefix_extractor.reset(NewFixedPrefixTransform(2));
|
||||
options.memtable_factory.reset(NewHashSkipListRepFactory(16));
|
||||
options.allow_concurrent_memtable_write = false;
|
||||
DestroyAndReopen(options);
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options, handles_[1]));
|
||||
ASSERT_OK(Put(1, "0101", "test"));
|
||||
|
||||
ASSERT_OK(Flush(1));
|
||||
|
||||
ASSERT_OK(Put(1, "0202", "test"));
|
||||
|
||||
// Seek(0102) shouldn't find any records since 0202 has a different prefix
|
||||
iter->Seek("0102");
|
||||
ASSERT_TRUE(!iter->Valid());
|
||||
|
||||
iter->Seek("0202");
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ(iter->key().ToString(), "0202");
|
||||
|
||||
iter->Next();
|
||||
ASSERT_TRUE(!iter->Valid());
|
||||
}
|
||||
|
||||
TEST_F(DBTestTailingIterator, ManagedTailingIteratorIncomplete) {
|
||||
CreateAndReopenWithCF({"pikachu"}, CurrentOptions());
|
||||
ReadOptions read_options;
|
||||
read_options.tailing = true;
|
||||
read_options.managed = true;
|
||||
read_options.read_tier = kBlockCacheTier;
|
||||
|
||||
std::string key = "key";
|
||||
std::string value = "value";
|
||||
|
||||
ASSERT_OK(db_->Put(WriteOptions(), key, value));
|
||||
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options));
|
||||
iter->SeekToFirst();
|
||||
// we either see the entry or it's not in cache
|
||||
ASSERT_TRUE(iter->Valid() || iter->status().IsIncomplete());
|
||||
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
||||
iter->SeekToFirst();
|
||||
// should still be true after compaction
|
||||
ASSERT_TRUE(iter->Valid() || iter->status().IsIncomplete());
|
||||
}
|
||||
|
||||
TEST_F(DBTestTailingIterator, ManagedTailingIteratorSeekToSame) {
|
||||
Options options = CurrentOptions();
|
||||
options.compaction_style = kCompactionStyleUniversal;
|
||||
options.write_buffer_size = 1000;
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
|
||||
ReadOptions read_options;
|
||||
read_options.tailing = true;
|
||||
read_options.managed = true;
|
||||
|
||||
const int NROWS = 10000;
|
||||
// Write rows with keys 00000, 00002, 00004 etc.
|
||||
for (int i = 0; i < NROWS; ++i) {
|
||||
char buf[100];
|
||||
snprintf(buf, sizeof(buf), "%05d", 2 * i);
|
||||
std::string key(buf);
|
||||
std::string value("value");
|
||||
ASSERT_OK(db_->Put(WriteOptions(), key, value));
|
||||
}
|
||||
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options));
|
||||
// Seek to 00001. We expect to find 00002.
|
||||
std::string start_key = "00001";
|
||||
iter->Seek(start_key);
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
|
||||
std::string found = iter->key().ToString();
|
||||
ASSERT_EQ("00002", found);
|
||||
|
||||
// Now seek to the same key. The iterator should remain in the same
|
||||
// position.
|
||||
iter->Seek(found);
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ(found, iter->key().ToString());
|
||||
}
|
||||
|
||||
TEST_F(DBTestTailingIterator, ForwardIteratorVersionProperty) {
|
||||
Options options = CurrentOptions();
|
||||
options.write_buffer_size = 1000;
|
||||
|
||||
ReadOptions read_options;
|
||||
read_options.tailing = true;
|
||||
|
||||
Put("foo", "bar");
|
||||
|
||||
uint64_t v1, v2, v3, v4;
|
||||
{
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options));
|
||||
iter->Seek("foo");
|
||||
std::string prop_value;
|
||||
ASSERT_OK(iter->GetProperty("rocksdb.iterator.super-version-number",
|
||||
&prop_value));
|
||||
v1 = static_cast<uint64_t>(std::atoi(prop_value.c_str()));
|
||||
|
||||
Put("foo1", "bar1");
|
||||
Flush();
|
||||
|
||||
ASSERT_OK(iter->GetProperty("rocksdb.iterator.super-version-number",
|
||||
&prop_value));
|
||||
v2 = static_cast<uint64_t>(std::atoi(prop_value.c_str()));
|
||||
|
||||
iter->Seek("f");
|
||||
|
||||
ASSERT_OK(iter->GetProperty("rocksdb.iterator.super-version-number",
|
||||
&prop_value));
|
||||
v3 = static_cast<uint64_t>(std::atoi(prop_value.c_str()));
|
||||
|
||||
ASSERT_EQ(v1, v2);
|
||||
ASSERT_GT(v3, v2);
|
||||
}
|
||||
|
||||
{
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options));
|
||||
iter->Seek("foo");
|
||||
std::string prop_value;
|
||||
ASSERT_OK(iter->GetProperty("rocksdb.iterator.super-version-number",
|
||||
&prop_value));
|
||||
v4 = static_cast<uint64_t>(std::atoi(prop_value.c_str()));
|
||||
}
|
||||
ASSERT_EQ(v3, v4);
|
||||
}
|
||||
|
||||
TEST_F(DBTestTailingIterator, SeekWithUpperBoundBug) {
|
||||
ReadOptions read_options;
|
||||
read_options.tailing = true;
|
||||
|
||||
+202
-8
@@ -262,6 +262,196 @@ TEST_F(DBTest, SkipDelay) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBTest, MixedSlowdownOptions) {
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
options.write_buffer_size = 100000;
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
std::vector<port::Thread> threads;
|
||||
std::atomic<int> thread_num(0);
|
||||
|
||||
std::function<void()> write_slowdown_func = [&]() {
|
||||
int a = thread_num.fetch_add(1);
|
||||
std::string key = "foo" + std::to_string(a);
|
||||
WriteOptions wo;
|
||||
wo.no_slowdown = false;
|
||||
ASSERT_OK(dbfull()->Put(wo, key, "bar"));
|
||||
};
|
||||
std::function<void()> write_no_slowdown_func = [&]() {
|
||||
int a = thread_num.fetch_add(1);
|
||||
std::string key = "foo" + std::to_string(a);
|
||||
WriteOptions wo;
|
||||
wo.no_slowdown = true;
|
||||
ASSERT_NOK(dbfull()->Put(wo, key, "bar"));
|
||||
};
|
||||
// Use a small number to ensure a large delay that is still effective
|
||||
// when we do Put
|
||||
// TODO(myabandeh): this is time dependent and could potentially make
|
||||
// the test flaky
|
||||
auto token = dbfull()->TEST_write_controler().GetDelayToken(1);
|
||||
std::atomic<int> sleep_count(0);
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::DelayWrite:BeginWriteStallDone",
|
||||
[&](void* /*arg*/) {
|
||||
sleep_count.fetch_add(1);
|
||||
if (threads.empty()) {
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
threads.emplace_back(write_slowdown_func);
|
||||
}
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
threads.emplace_back(write_no_slowdown_func);
|
||||
}
|
||||
}
|
||||
});
|
||||
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
WriteOptions wo;
|
||||
wo.sync = false;
|
||||
wo.disableWAL = false;
|
||||
wo.no_slowdown = false;
|
||||
dbfull()->Put(wo, "foo", "bar");
|
||||
// We need the 2nd write to trigger delay. This is because delay is
|
||||
// estimated based on the last write size which is 0 for the first write.
|
||||
ASSERT_OK(dbfull()->Put(wo, "foo2", "bar2"));
|
||||
token.reset();
|
||||
|
||||
for (auto& t : threads) {
|
||||
t.join();
|
||||
}
|
||||
ASSERT_GE(sleep_count.load(), 1);
|
||||
|
||||
wo.no_slowdown = true;
|
||||
ASSERT_OK(dbfull()->Put(wo, "foo3", "bar"));
|
||||
}
|
||||
|
||||
TEST_F(DBTest, MixedSlowdownOptionsInQueue) {
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
options.write_buffer_size = 100000;
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
std::vector<port::Thread> threads;
|
||||
std::atomic<int> thread_num(0);
|
||||
|
||||
std::function<void()> write_no_slowdown_func = [&]() {
|
||||
int a = thread_num.fetch_add(1);
|
||||
std::string key = "foo" + std::to_string(a);
|
||||
WriteOptions wo;
|
||||
wo.no_slowdown = true;
|
||||
ASSERT_NOK(dbfull()->Put(wo, key, "bar"));
|
||||
};
|
||||
// Use a small number to ensure a large delay that is still effective
|
||||
// when we do Put
|
||||
// TODO(myabandeh): this is time dependent and could potentially make
|
||||
// the test flaky
|
||||
auto token = dbfull()->TEST_write_controler().GetDelayToken(1);
|
||||
std::atomic<int> sleep_count(0);
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::DelayWrite:Sleep",
|
||||
[&](void* /*arg*/) {
|
||||
sleep_count.fetch_add(1);
|
||||
if (threads.empty()) {
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
threads.emplace_back(write_no_slowdown_func);
|
||||
}
|
||||
// Sleep for 2s to allow the threads to insert themselves into the
|
||||
// write queue
|
||||
env_->SleepForMicroseconds(3000000ULL);
|
||||
}
|
||||
});
|
||||
std::atomic<int> wait_count(0);
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::DelayWrite:Wait",
|
||||
[&](void* /*arg*/) { wait_count.fetch_add(1); });
|
||||
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
WriteOptions wo;
|
||||
wo.sync = false;
|
||||
wo.disableWAL = false;
|
||||
wo.no_slowdown = false;
|
||||
dbfull()->Put(wo, "foo", "bar");
|
||||
// We need the 2nd write to trigger delay. This is because delay is
|
||||
// estimated based on the last write size which is 0 for the first write.
|
||||
ASSERT_OK(dbfull()->Put(wo, "foo2", "bar2"));
|
||||
token.reset();
|
||||
|
||||
for (auto& t : threads) {
|
||||
t.join();
|
||||
}
|
||||
ASSERT_EQ(sleep_count.load(), 1);
|
||||
ASSERT_GE(wait_count.load(), 0);
|
||||
}
|
||||
|
||||
TEST_F(DBTest, MixedSlowdownOptionsStop) {
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
options.write_buffer_size = 100000;
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
std::vector<port::Thread> threads;
|
||||
std::atomic<int> thread_num(0);
|
||||
|
||||
std::function<void()> write_slowdown_func = [&]() {
|
||||
int a = thread_num.fetch_add(1);
|
||||
std::string key = "foo" + std::to_string(a);
|
||||
WriteOptions wo;
|
||||
wo.no_slowdown = false;
|
||||
ASSERT_OK(dbfull()->Put(wo, key, "bar"));
|
||||
};
|
||||
std::function<void()> write_no_slowdown_func = [&]() {
|
||||
int a = thread_num.fetch_add(1);
|
||||
std::string key = "foo" + std::to_string(a);
|
||||
WriteOptions wo;
|
||||
wo.no_slowdown = true;
|
||||
ASSERT_NOK(dbfull()->Put(wo, key, "bar"));
|
||||
};
|
||||
std::function<void()> wakeup_writer = [&]() {
|
||||
dbfull()->mutex_.Lock();
|
||||
dbfull()->bg_cv_.SignalAll();
|
||||
dbfull()->mutex_.Unlock();
|
||||
};
|
||||
// Use a small number to ensure a large delay that is still effective
|
||||
// when we do Put
|
||||
// TODO(myabandeh): this is time dependent and could potentially make
|
||||
// the test flaky
|
||||
auto token = dbfull()->TEST_write_controler().GetStopToken();
|
||||
std::atomic<int> wait_count(0);
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::DelayWrite:Wait",
|
||||
[&](void* /*arg*/) {
|
||||
wait_count.fetch_add(1);
|
||||
if (threads.empty()) {
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
threads.emplace_back(write_slowdown_func);
|
||||
}
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
threads.emplace_back(write_no_slowdown_func);
|
||||
}
|
||||
// Sleep for 2s to allow the threads to insert themselves into the
|
||||
// write queue
|
||||
env_->SleepForMicroseconds(3000000ULL);
|
||||
}
|
||||
token.reset();
|
||||
threads.emplace_back(wakeup_writer);
|
||||
});
|
||||
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
WriteOptions wo;
|
||||
wo.sync = false;
|
||||
wo.disableWAL = false;
|
||||
wo.no_slowdown = false;
|
||||
dbfull()->Put(wo, "foo", "bar");
|
||||
// We need the 2nd write to trigger delay. This is because delay is
|
||||
// estimated based on the last write size which is 0 for the first write.
|
||||
ASSERT_OK(dbfull()->Put(wo, "foo2", "bar2"));
|
||||
token.reset();
|
||||
|
||||
for (auto& t : threads) {
|
||||
t.join();
|
||||
}
|
||||
ASSERT_GE(wait_count.load(), 1);
|
||||
|
||||
wo.no_slowdown = true;
|
||||
ASSERT_OK(dbfull()->Put(wo, "foo3", "bar"));
|
||||
}
|
||||
#ifndef ROCKSDB_LITE
|
||||
|
||||
TEST_F(DBTest, LevelLimitReopen) {
|
||||
@@ -2149,6 +2339,9 @@ static void GCThreadBody(void* arg) {
|
||||
|
||||
} // namespace
|
||||
|
||||
#ifndef TRAVIS
|
||||
// Disable this test temporarily on Travis as it fails intermittently.
|
||||
// Github issue: #4151
|
||||
TEST_F(DBTest, GroupCommitTest) {
|
||||
do {
|
||||
Options options = CurrentOptions();
|
||||
@@ -2195,6 +2388,7 @@ TEST_F(DBTest, GroupCommitTest) {
|
||||
ASSERT_GT(hist_data.average, 0.0);
|
||||
} while (ChangeOptions(kSkipNoSeekToLast));
|
||||
}
|
||||
#endif // TRAVIS
|
||||
|
||||
namespace {
|
||||
typedef std::map<std::string, std::string> KVMap;
|
||||
@@ -4327,7 +4521,7 @@ TEST_F(DBTest, DynamicCompactionOptions) {
|
||||
// Clean up memtable and L0. Block compaction threads. If continue to write
|
||||
// and flush memtables. We should see put stop after 8 memtable flushes
|
||||
// since level0_stop_writes_trigger = 8
|
||||
dbfull()->TEST_FlushMemTable(true);
|
||||
dbfull()->TEST_FlushMemTable(true, true);
|
||||
dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr);
|
||||
// Block compaction
|
||||
test::SleepingBackgroundTask sleeping_task_low;
|
||||
@@ -4340,7 +4534,7 @@ TEST_F(DBTest, DynamicCompactionOptions) {
|
||||
WriteOptions wo;
|
||||
while (count < 64) {
|
||||
ASSERT_OK(Put(Key(count), RandomString(&rnd, 1024), wo));
|
||||
dbfull()->TEST_FlushMemTable(true);
|
||||
dbfull()->TEST_FlushMemTable(true, true);
|
||||
count++;
|
||||
if (dbfull()->TEST_write_controler().IsStopped()) {
|
||||
sleeping_task_low.WakeUp();
|
||||
@@ -4368,7 +4562,7 @@ TEST_F(DBTest, DynamicCompactionOptions) {
|
||||
count = 0;
|
||||
while (count < 64) {
|
||||
ASSERT_OK(Put(Key(count), RandomString(&rnd, 1024), wo));
|
||||
dbfull()->TEST_FlushMemTable(true);
|
||||
dbfull()->TEST_FlushMemTable(true, true);
|
||||
count++;
|
||||
if (dbfull()->TEST_write_controler().IsStopped()) {
|
||||
sleeping_task_low.WakeUp();
|
||||
@@ -5508,7 +5702,7 @@ TEST_F(DBTest, SoftLimit) {
|
||||
for (int i = 0; i < 72; i++) {
|
||||
Put(Key(i), std::string(5000, 'x'));
|
||||
if (i % 10 == 0) {
|
||||
Flush();
|
||||
dbfull()->TEST_FlushMemTable(true, true);
|
||||
}
|
||||
}
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
@@ -5518,7 +5712,7 @@ TEST_F(DBTest, SoftLimit) {
|
||||
for (int i = 0; i < 72; i++) {
|
||||
Put(Key(i), std::string(5000, 'x'));
|
||||
if (i % 10 == 0) {
|
||||
Flush();
|
||||
dbfull()->TEST_FlushMemTable(true, true);
|
||||
}
|
||||
}
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
@@ -5537,7 +5731,7 @@ TEST_F(DBTest, SoftLimit) {
|
||||
Put(Key(i), std::string(5000, 'x'));
|
||||
Put(Key(100 - i), std::string(5000, 'x'));
|
||||
// Flush the file. File size is around 30KB.
|
||||
Flush();
|
||||
dbfull()->TEST_FlushMemTable(true, true);
|
||||
}
|
||||
ASSERT_TRUE(dbfull()->TEST_write_controler().NeedsDelay());
|
||||
ASSERT_TRUE(listener->CheckCondition(WriteStallCondition::kDelayed));
|
||||
@@ -5572,7 +5766,7 @@ TEST_F(DBTest, SoftLimit) {
|
||||
Put(Key(10 + i), std::string(5000, 'x'));
|
||||
Put(Key(90 - i), std::string(5000, 'x'));
|
||||
// Flush the file. File size is around 30KB.
|
||||
Flush();
|
||||
dbfull()->TEST_FlushMemTable(true, true);
|
||||
}
|
||||
|
||||
// Wake up sleep task to enable compaction to run and waits
|
||||
@@ -5593,7 +5787,7 @@ TEST_F(DBTest, SoftLimit) {
|
||||
Put(Key(20 + i), std::string(5000, 'x'));
|
||||
Put(Key(80 - i), std::string(5000, 'x'));
|
||||
// Flush the file. File size is around 30KB.
|
||||
Flush();
|
||||
dbfull()->TEST_FlushMemTable(true, true);
|
||||
}
|
||||
// Wake up sleep task to enable compaction to run and waits
|
||||
// for it to go to sleep state again to make sure one compaction
|
||||
|
||||
+291
-3
@@ -2321,9 +2321,9 @@ TEST_F(DBTest2, RateLimitedCompactionReads) {
|
||||
options.rate_limiter->GetTotalBytesThrough(Env::IO_LOW);
|
||||
// Include the explicit prefetch of the footer in direct I/O case.
|
||||
size_t direct_io_extra = use_direct_io ? 512 * 1024 : 0;
|
||||
ASSERT_GE(rate_limited_bytes,
|
||||
static_cast<size_t>(kNumKeysPerFile * kBytesPerKey * kNumL0Files +
|
||||
direct_io_extra));
|
||||
ASSERT_GE(
|
||||
rate_limited_bytes,
|
||||
static_cast<size_t>(kNumKeysPerFile * kBytesPerKey * kNumL0Files));
|
||||
ASSERT_LT(
|
||||
rate_limited_bytes,
|
||||
static_cast<size_t>(2 * kNumKeysPerFile * kBytesPerKey * kNumL0Files +
|
||||
@@ -2500,6 +2500,108 @@ TEST_F(DBTest2, LiveFilesOmitObsoleteFiles) {
|
||||
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
|
||||
TEST_F(DBTest2, TraceAndReplay) {
|
||||
Options options = CurrentOptions();
|
||||
options.merge_operator = MergeOperators::CreatePutOperator();
|
||||
ReadOptions ro;
|
||||
WriteOptions wo;
|
||||
TraceOptions trace_opts;
|
||||
EnvOptions env_opts;
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
Random rnd(301);
|
||||
Iterator* single_iter = nullptr;
|
||||
|
||||
std::string trace_filename = dbname_ + "/rocksdb.trace";
|
||||
std::unique_ptr<TraceWriter> trace_writer;
|
||||
ASSERT_OK(NewFileTraceWriter(env_, env_opts, trace_filename, &trace_writer));
|
||||
ASSERT_OK(db_->StartTrace(trace_opts, std::move(trace_writer)));
|
||||
|
||||
ASSERT_OK(Put(0, "a", "1"));
|
||||
ASSERT_OK(Merge(0, "b", "2"));
|
||||
ASSERT_OK(Delete(0, "c"));
|
||||
ASSERT_OK(SingleDelete(0, "d"));
|
||||
ASSERT_OK(db_->DeleteRange(wo, dbfull()->DefaultColumnFamily(), "e", "f"));
|
||||
|
||||
WriteBatch batch;
|
||||
ASSERT_OK(batch.Put("f", "11"));
|
||||
ASSERT_OK(batch.Merge("g", "12"));
|
||||
ASSERT_OK(batch.Delete("h"));
|
||||
ASSERT_OK(batch.SingleDelete("i"));
|
||||
ASSERT_OK(batch.DeleteRange("j", "k"));
|
||||
ASSERT_OK(db_->Write(wo, &batch));
|
||||
|
||||
single_iter = db_->NewIterator(ro);
|
||||
single_iter->Seek("f");
|
||||
single_iter->SeekForPrev("g");
|
||||
delete single_iter;
|
||||
|
||||
ASSERT_EQ("1", Get(0, "a"));
|
||||
ASSERT_EQ("12", Get(0, "g"));
|
||||
|
||||
ASSERT_OK(Put(1, "foo", "bar"));
|
||||
ASSERT_OK(Put(1, "rocksdb", "rocks"));
|
||||
ASSERT_EQ("NOT_FOUND", Get(1, "leveldb"));
|
||||
|
||||
ASSERT_OK(db_->EndTrace());
|
||||
// These should not get into the trace file as it is after EndTrace.
|
||||
Put("hello", "world");
|
||||
Merge("foo", "bar");
|
||||
|
||||
// Open another db, replay, and verify the data
|
||||
std::string value;
|
||||
std::string dbname2 = test::TmpDir(env_) + "/db_replay";
|
||||
ASSERT_OK(DestroyDB(dbname2, options));
|
||||
|
||||
// Using a different name than db2, to pacify infer's use-after-lifetime
|
||||
// warnings (http://fbinfer.com).
|
||||
DB* db2_init = nullptr;
|
||||
options.create_if_missing = true;
|
||||
ASSERT_OK(DB::Open(options, dbname2, &db2_init));
|
||||
ColumnFamilyHandle* cf;
|
||||
ASSERT_OK(
|
||||
db2_init->CreateColumnFamily(ColumnFamilyOptions(), "pikachu", &cf));
|
||||
delete cf;
|
||||
delete db2_init;
|
||||
|
||||
DB* db2 = nullptr;
|
||||
std::vector<ColumnFamilyDescriptor> column_families;
|
||||
ColumnFamilyOptions cf_options;
|
||||
cf_options.merge_operator = MergeOperators::CreatePutOperator();
|
||||
column_families.push_back(ColumnFamilyDescriptor("default", cf_options));
|
||||
column_families.push_back(
|
||||
ColumnFamilyDescriptor("pikachu", ColumnFamilyOptions()));
|
||||
std::vector<ColumnFamilyHandle*> handles;
|
||||
ASSERT_OK(DB::Open(DBOptions(), dbname2, column_families, &handles, &db2));
|
||||
|
||||
env_->SleepForMicroseconds(100);
|
||||
// Verify that the keys don't already exist
|
||||
ASSERT_TRUE(db2->Get(ro, handles[0], "a", &value).IsNotFound());
|
||||
ASSERT_TRUE(db2->Get(ro, handles[0], "g", &value).IsNotFound());
|
||||
|
||||
std::unique_ptr<TraceReader> trace_reader;
|
||||
ASSERT_OK(NewFileTraceReader(env_, env_opts, trace_filename, &trace_reader));
|
||||
Replayer replayer(db2, handles_, std::move(trace_reader));
|
||||
ASSERT_OK(replayer.Replay());
|
||||
|
||||
ASSERT_OK(db2->Get(ro, handles[0], "a", &value));
|
||||
ASSERT_EQ("1", value);
|
||||
ASSERT_OK(db2->Get(ro, handles[0], "g", &value));
|
||||
ASSERT_EQ("12", value);
|
||||
ASSERT_TRUE(db2->Get(ro, handles[0], "hello", &value).IsNotFound());
|
||||
ASSERT_TRUE(db2->Get(ro, handles[0], "world", &value).IsNotFound());
|
||||
|
||||
ASSERT_OK(db2->Get(ro, handles[1], "foo", &value));
|
||||
ASSERT_EQ("bar", value);
|
||||
ASSERT_OK(db2->Get(ro, handles[1], "rocksdb", &value));
|
||||
ASSERT_EQ("rocks", value);
|
||||
|
||||
for (auto handle : handles) {
|
||||
delete handle;
|
||||
}
|
||||
delete db2;
|
||||
ASSERT_OK(DestroyDB(dbname2, options));
|
||||
}
|
||||
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
TEST_F(DBTest2, PinnableSliceAndMmapReads) {
|
||||
@@ -2547,6 +2649,192 @@ TEST_F(DBTest2, PinnableSliceAndMmapReads) {
|
||||
#endif
|
||||
}
|
||||
|
||||
TEST_F(DBTest2, DISABLED_IteratorPinnedMemory) {
|
||||
Options options = CurrentOptions();
|
||||
options.create_if_missing = true;
|
||||
options.statistics = rocksdb::CreateDBStatistics();
|
||||
BlockBasedTableOptions bbto;
|
||||
bbto.no_block_cache = false;
|
||||
bbto.cache_index_and_filter_blocks = false;
|
||||
bbto.block_cache = NewLRUCache(100000);
|
||||
bbto.block_size = 400; // small block size
|
||||
options.table_factory.reset(new BlockBasedTableFactory(bbto));
|
||||
Reopen(options);
|
||||
|
||||
Random rnd(301);
|
||||
std::string v = RandomString(&rnd, 400);
|
||||
|
||||
// Since v is the size of a block, each key should take a block
|
||||
// of 400+ bytes.
|
||||
Put("1", v);
|
||||
Put("3", v);
|
||||
Put("5", v);
|
||||
Put("7", v);
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
ASSERT_EQ(0, bbto.block_cache->GetPinnedUsage());
|
||||
|
||||
// Verify that iterators don't pin more than one data block in block cache
|
||||
// at each time.
|
||||
{
|
||||
unique_ptr<Iterator> iter(db_->NewIterator(ReadOptions()));
|
||||
iter->SeekToFirst();
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
// Block cache should contain exactly one block.
|
||||
ASSERT_GT(bbto.block_cache->GetPinnedUsage(), 0);
|
||||
ASSERT_LT(bbto.block_cache->GetPinnedUsage(), 800);
|
||||
iter->Next();
|
||||
}
|
||||
ASSERT_FALSE(iter->Valid());
|
||||
|
||||
iter->Seek("4");
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
|
||||
ASSERT_GT(bbto.block_cache->GetPinnedUsage(), 0);
|
||||
ASSERT_LT(bbto.block_cache->GetPinnedUsage(), 800);
|
||||
|
||||
iter->Seek("3");
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
|
||||
ASSERT_GT(bbto.block_cache->GetPinnedUsage(), 0);
|
||||
ASSERT_LT(bbto.block_cache->GetPinnedUsage(), 800);
|
||||
}
|
||||
ASSERT_EQ(0, bbto.block_cache->GetPinnedUsage());
|
||||
|
||||
// Test compaction case
|
||||
Put("2", v);
|
||||
Put("5", v);
|
||||
Put("6", v);
|
||||
Put("8", v);
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
// Clear existing data in block cache
|
||||
bbto.block_cache->SetCapacity(0);
|
||||
bbto.block_cache->SetCapacity(100000);
|
||||
|
||||
// Verify compaction input iterators don't hold more than one data blocks at
|
||||
// one time.
|
||||
std::atomic<bool> finished(false);
|
||||
std::atomic<int> block_newed(0);
|
||||
std::atomic<int> block_destroyed(0);
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"Block::Block:0", [&](void* /*arg*/) {
|
||||
if (finished) {
|
||||
return;
|
||||
}
|
||||
// Two iterators. At most 2 outstanding blocks.
|
||||
EXPECT_GE(block_newed.load(), block_destroyed.load());
|
||||
EXPECT_LE(block_newed.load(), block_destroyed.load() + 1);
|
||||
block_newed.fetch_add(1);
|
||||
});
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"Block::~Block", [&](void* /*arg*/) {
|
||||
if (finished) {
|
||||
return;
|
||||
}
|
||||
// Two iterators. At most 2 outstanding blocks.
|
||||
EXPECT_GE(block_newed.load(), block_destroyed.load() + 1);
|
||||
EXPECT_LE(block_newed.load(), block_destroyed.load() + 2);
|
||||
block_destroyed.fetch_add(1);
|
||||
});
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"CompactionJob::Run:BeforeVerify",
|
||||
[&](void* /*arg*/) { finished = true; });
|
||||
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
||||
|
||||
// Two input files. Each of them has 4 data blocks.
|
||||
ASSERT_EQ(8, block_newed.load());
|
||||
ASSERT_EQ(8, block_destroyed.load());
|
||||
|
||||
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
|
||||
TEST_F(DBTest2, TestBBTTailPrefetch) {
|
||||
std::atomic<bool> called(false);
|
||||
size_t expected_lower_bound = 512 * 1024;
|
||||
size_t expected_higher_bound = 512 * 1024;
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"BlockBasedTable::Open::TailPrefetchLen", [&](void* arg) {
|
||||
size_t* prefetch_size = static_cast<size_t*>(arg);
|
||||
EXPECT_LE(expected_lower_bound, *prefetch_size);
|
||||
EXPECT_GE(expected_higher_bound, *prefetch_size);
|
||||
called = true;
|
||||
});
|
||||
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
Put("1", "1");
|
||||
Put("9", "1");
|
||||
Flush();
|
||||
|
||||
expected_lower_bound = 0;
|
||||
expected_higher_bound = 8 * 1024;
|
||||
|
||||
Put("1", "1");
|
||||
Put("9", "1");
|
||||
Flush();
|
||||
|
||||
Put("1", "1");
|
||||
Put("9", "1");
|
||||
Flush();
|
||||
|
||||
// Full compaction to make sure there is no L0 file after the open.
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
||||
|
||||
ASSERT_TRUE(called.load());
|
||||
called = false;
|
||||
|
||||
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
||||
rocksdb::SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
|
||||
std::atomic<bool> first_call(true);
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"BlockBasedTable::Open::TailPrefetchLen", [&](void* arg) {
|
||||
size_t* prefetch_size = static_cast<size_t*>(arg);
|
||||
if (first_call) {
|
||||
EXPECT_EQ(4 * 1024, *prefetch_size);
|
||||
first_call = false;
|
||||
} else {
|
||||
EXPECT_GE(4 * 1024, *prefetch_size);
|
||||
}
|
||||
called = true;
|
||||
});
|
||||
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
Options options = CurrentOptions();
|
||||
options.max_file_opening_threads = 1; // one thread
|
||||
BlockBasedTableOptions table_options;
|
||||
table_options.cache_index_and_filter_blocks = true;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
options.max_open_files = -1;
|
||||
Reopen(options);
|
||||
|
||||
Put("1", "1");
|
||||
Put("9", "1");
|
||||
Flush();
|
||||
|
||||
Put("1", "1");
|
||||
Put("9", "1");
|
||||
Flush();
|
||||
|
||||
ASSERT_TRUE(called.load());
|
||||
called = false;
|
||||
|
||||
// Parallel loading SST files
|
||||
options.max_file_opening_threads = 16;
|
||||
Reopen(options);
|
||||
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
||||
|
||||
ASSERT_TRUE(called.load());
|
||||
|
||||
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
||||
rocksdb::SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
+5
-4
@@ -449,15 +449,16 @@ Options DBTestBase::GetOptions(
|
||||
options.prefix_extractor.reset(NewNoopTransform());
|
||||
break;
|
||||
}
|
||||
case kBlockBasedTableWithPartitionedIndexFormat3: {
|
||||
table_options.format_version = 3;
|
||||
// Format 3 changes the binary index format. Since partitioned index is a
|
||||
case kBlockBasedTableWithPartitionedIndexFormat4: {
|
||||
table_options.format_version = 4;
|
||||
// Format 4 changes the binary index format. Since partitioned index is a
|
||||
// super-set of simple indexes, we are also using kTwoLevelIndexSearch to
|
||||
// test this format.
|
||||
table_options.index_type = BlockBasedTableOptions::kTwoLevelIndexSearch;
|
||||
// The top-level index in partition filters are also affected by format 3.
|
||||
// The top-level index in partition filters are also affected by format 4.
|
||||
table_options.filter_policy.reset(NewBloomFilterPolicy(10, false));
|
||||
table_options.partition_filters = true;
|
||||
table_options.index_block_restart_interval = 8;
|
||||
break;
|
||||
}
|
||||
case kBlockBasedTableWithIndexRestartInterval: {
|
||||
|
||||
+8
-4
@@ -109,8 +109,6 @@ struct OptionsOverride {
|
||||
// These will be used only if filter_policy is set
|
||||
bool partition_filters = false;
|
||||
uint64_t metadata_block_size = 1024;
|
||||
BlockBasedTableOptions::IndexType index_type =
|
||||
BlockBasedTableOptions::IndexType::kBinarySearch;
|
||||
|
||||
// Used as a bit mask of individual enums in which to skip an XF test point
|
||||
int skip_policy = 0;
|
||||
@@ -317,6 +315,9 @@ class SpecialEnv : public EnvWrapper {
|
||||
}
|
||||
}
|
||||
uint64_t GetFileSize() override { return base_->GetFileSize(); }
|
||||
Status Allocate(uint64_t offset, uint64_t len) override {
|
||||
return base_->Allocate(offset, len);
|
||||
}
|
||||
|
||||
private:
|
||||
SpecialEnv* env_;
|
||||
@@ -370,6 +371,9 @@ class SpecialEnv : public EnvWrapper {
|
||||
bool IsSyncThreadSafe() const override {
|
||||
return env_->is_wal_sync_thread_safe_.load();
|
||||
}
|
||||
Status Allocate(uint64_t offset, uint64_t len) override {
|
||||
return base_->Allocate(offset, len);
|
||||
}
|
||||
|
||||
private:
|
||||
SpecialEnv* env_;
|
||||
@@ -575,7 +579,7 @@ class SpecialEnv : public EnvWrapper {
|
||||
|
||||
std::atomic<bool> is_wal_sync_thread_safe_{true};
|
||||
|
||||
std::atomic<size_t> compaction_readahead_size_;
|
||||
std::atomic<size_t> compaction_readahead_size_{};
|
||||
};
|
||||
|
||||
class MockTimeEnv : public EnvWrapper {
|
||||
@@ -698,7 +702,7 @@ class DBTestBase : public testing::Test {
|
||||
kLevelSubcompactions,
|
||||
kBlockBasedTableWithIndexRestartInterval,
|
||||
kBlockBasedTableWithPartitionedIndex,
|
||||
kBlockBasedTableWithPartitionedIndexFormat3,
|
||||
kBlockBasedTableWithPartitionedIndexFormat4,
|
||||
kPartitionedFilterWithNewTableReaderForCompactions,
|
||||
kUniversalSubcompactions,
|
||||
// This must be the last line
|
||||
|
||||
@@ -1822,9 +1822,9 @@ TEST_P(DBTestUniversalCompaction, FinalSortedRunCompactFilesConflict) {
|
||||
"CompactFilesImpl:1"}});
|
||||
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
std::thread compact_files_thread([&]() {
|
||||
port::Thread compact_files_thread([&]() {
|
||||
ASSERT_OK(dbfull()->CompactFiles(CompactionOptions(), default_cfh,
|
||||
{first_sst_filename}, num_levels_ - 1));
|
||||
{first_sst_filename}, num_levels_ - 1));
|
||||
});
|
||||
|
||||
TEST_SYNC_POINT(
|
||||
|
||||
+103
-1
@@ -18,6 +18,15 @@ namespace rocksdb {
|
||||
class DBWALTest : public DBTestBase {
|
||||
public:
|
||||
DBWALTest() : DBTestBase("/db_wal_test") {}
|
||||
|
||||
#if defined(ROCKSDB_PLATFORM_POSIX)
|
||||
uint64_t GetAllocatedFileSize(std::string file_name) {
|
||||
struct stat sbuf;
|
||||
int err = stat(file_name.c_str(), &sbuf);
|
||||
assert(err == 0);
|
||||
return sbuf.st_blocks * 512;
|
||||
}
|
||||
#endif
|
||||
};
|
||||
|
||||
// A SpecialEnv enriched to give more insight about deleted files
|
||||
@@ -815,7 +824,7 @@ class RecoveryTestHelper {
|
||||
unique_ptr<WritableFile> file;
|
||||
ASSERT_OK(db_options.env->NewWritableFile(fname, &file, env_options));
|
||||
unique_ptr<WritableFileWriter> file_writer(
|
||||
new WritableFileWriter(std::move(file), env_options));
|
||||
new WritableFileWriter(std::move(file), fname, env_options));
|
||||
current_log_writer.reset(
|
||||
new log::Writer(std::move(file_writer), current_log_number,
|
||||
db_options.recycle_log_file_num > 0));
|
||||
@@ -1329,6 +1338,99 @@ TEST_F(DBWALTest, RecoverFromCorruptedWALWithoutFlush) {
|
||||
}
|
||||
}
|
||||
|
||||
// Tests that total log size is recovered if we set
|
||||
// avoid_flush_during_recovery=true.
|
||||
// Flush should trigger if max_total_wal_size is reached.
|
||||
TEST_F(DBWALTest, RestoreTotalLogSizeAfterRecoverWithoutFlush) {
|
||||
class TestFlushListener : public EventListener {
|
||||
public:
|
||||
std::atomic<int> count{0};
|
||||
|
||||
TestFlushListener() = default;
|
||||
|
||||
void OnFlushBegin(DB* /*db*/, const FlushJobInfo& flush_job_info) override {
|
||||
count++;
|
||||
assert(FlushReason::kWriteBufferManager == flush_job_info.flush_reason);
|
||||
}
|
||||
};
|
||||
std::shared_ptr<TestFlushListener> test_listener =
|
||||
std::make_shared<TestFlushListener>();
|
||||
|
||||
constexpr size_t kKB = 1024;
|
||||
constexpr size_t kMB = 1024 * 1024;
|
||||
Options options = CurrentOptions();
|
||||
options.avoid_flush_during_recovery = true;
|
||||
options.max_total_wal_size = 1 * kMB;
|
||||
options.listeners.push_back(test_listener);
|
||||
// Have to open DB in multi-CF mode to trigger flush when
|
||||
// max_total_wal_size is reached.
|
||||
CreateAndReopenWithCF({"one"}, options);
|
||||
// Write some keys and we will end up with one log file which is slightly
|
||||
// smaller than 1MB.
|
||||
std::string value_100k(100 * kKB, 'v');
|
||||
std::string value_300k(300 * kKB, 'v');
|
||||
ASSERT_OK(Put(0, "foo", "v1"));
|
||||
for (int i = 0; i < 9; i++) {
|
||||
ASSERT_OK(Put(1, "key" + ToString(i), value_100k));
|
||||
}
|
||||
// Get log files before reopen.
|
||||
VectorLogPtr log_files_before;
|
||||
ASSERT_OK(dbfull()->GetSortedWalFiles(log_files_before));
|
||||
ASSERT_EQ(1, log_files_before.size());
|
||||
uint64_t log_size_before = log_files_before[0]->SizeFileBytes();
|
||||
ASSERT_GT(log_size_before, 900 * kKB);
|
||||
ASSERT_LT(log_size_before, 1 * kMB);
|
||||
ReopenWithColumnFamilies({"default", "one"}, options);
|
||||
// Write one more value to make log larger than 1MB.
|
||||
ASSERT_OK(Put(1, "bar", value_300k));
|
||||
// Get log files again. A new log file will be opened.
|
||||
VectorLogPtr log_files_after_reopen;
|
||||
ASSERT_OK(dbfull()->GetSortedWalFiles(log_files_after_reopen));
|
||||
ASSERT_EQ(2, log_files_after_reopen.size());
|
||||
ASSERT_EQ(log_files_before[0]->LogNumber(),
|
||||
log_files_after_reopen[0]->LogNumber());
|
||||
ASSERT_GT(log_files_after_reopen[0]->SizeFileBytes() +
|
||||
log_files_after_reopen[1]->SizeFileBytes(),
|
||||
1 * kMB);
|
||||
// Write one more key to trigger flush.
|
||||
ASSERT_OK(Put(0, "foo", "v2"));
|
||||
dbfull()->TEST_WaitForFlushMemTable();
|
||||
// Flushed two column families.
|
||||
ASSERT_EQ(2, test_listener->count.load());
|
||||
}
|
||||
|
||||
#if defined(ROCKSDB_PLATFORM_POSIX)
|
||||
#if defined(ROCKSDB_FALLOCATE_PRESENT)
|
||||
// Tests that we will truncate the preallocated space of the last log from
|
||||
// previous.
|
||||
TEST_F(DBWALTest, TruncateLastLogAfterRecoverWithoutFlush) {
|
||||
constexpr size_t kKB = 1024;
|
||||
Options options = CurrentOptions();
|
||||
options.avoid_flush_during_recovery = true;
|
||||
DestroyAndReopen(options);
|
||||
size_t preallocated_size =
|
||||
dbfull()->TEST_GetWalPreallocateBlockSize(options.write_buffer_size);
|
||||
ASSERT_OK(Put("foo", "v1"));
|
||||
VectorLogPtr log_files_before;
|
||||
ASSERT_OK(dbfull()->GetSortedWalFiles(log_files_before));
|
||||
ASSERT_EQ(1, log_files_before.size());
|
||||
auto& file_before = log_files_before[0];
|
||||
ASSERT_LT(file_before->SizeFileBytes(), 1 * kKB);
|
||||
// The log file has preallocated space.
|
||||
ASSERT_GE(GetAllocatedFileSize(dbname_ + file_before->PathName()),
|
||||
preallocated_size);
|
||||
Reopen(options);
|
||||
VectorLogPtr log_files_after;
|
||||
ASSERT_OK(dbfull()->GetSortedWalFiles(log_files_after));
|
||||
ASSERT_EQ(1, log_files_after.size());
|
||||
ASSERT_LT(log_files_after[0]->SizeFileBytes(), 1 * kKB);
|
||||
// The preallocated space should be truncated.
|
||||
ASSERT_LT(GetAllocatedFileSize(dbname_ + file_before->PathName()),
|
||||
preallocated_size);
|
||||
}
|
||||
#endif // ROCKSDB_FALLOCATE_PRESENT
|
||||
#endif // ROCKSDB_PLATFORM_POSIX
|
||||
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
TEST_F(DBWALTest, WalTermTest) {
|
||||
|
||||
@@ -48,6 +48,8 @@ EntryType GetEntryType(ValueType value_type) {
|
||||
return kEntryMerge;
|
||||
case kTypeRangeDeletion:
|
||||
return kEntryRangeDeletion;
|
||||
case kTypeBlobIndex:
|
||||
return kEntryBlobIndex;
|
||||
default:
|
||||
return kEntryOther;
|
||||
}
|
||||
|
||||
@@ -346,6 +346,12 @@ class IterKey {
|
||||
|
||||
~IterKey() { ResetBuffer(); }
|
||||
|
||||
// The bool will be picked up by the next calls to SetKey
|
||||
void SetIsUserKey(bool is_user_key) { is_user_key_ = is_user_key; }
|
||||
|
||||
// Returns the key in whichever format that was provided to KeyIter
|
||||
Slice GetKey() const { return Slice(key_, key_size_); }
|
||||
|
||||
Slice GetInternalKey() const {
|
||||
assert(!IsUserKey());
|
||||
return Slice(key_, key_size_);
|
||||
@@ -395,6 +401,11 @@ class IterKey {
|
||||
key_size_ = total_size;
|
||||
}
|
||||
|
||||
Slice SetKey(const Slice& key, bool copy = true) {
|
||||
// is_user_key_ expected to be set already via SetIsUserKey
|
||||
return SetKeyImpl(key, copy);
|
||||
}
|
||||
|
||||
Slice SetUserKey(const Slice& key, bool copy = true) {
|
||||
is_user_key_ = true;
|
||||
return SetKeyImpl(key, copy);
|
||||
|
||||
+179
-4
@@ -4,7 +4,9 @@
|
||||
// (found in the LICENSE.Apache file in the root directory).
|
||||
//
|
||||
#include "db/error_handler.h"
|
||||
#include "db/db_impl.h"
|
||||
#include "db/event_helpers.h"
|
||||
#include "util/sst_file_manager_impl.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
@@ -33,7 +35,7 @@ std::map<std::tuple<BackgroundErrorReason, Status::Code, Status::SubCode, bool>,
|
||||
// Errors during BG flush
|
||||
{std::make_tuple(BackgroundErrorReason::kFlush, Status::Code::kIOError,
|
||||
Status::SubCode::kNoSpace, true),
|
||||
Status::Severity::kSoftError},
|
||||
Status::Severity::kHardError},
|
||||
{std::make_tuple(BackgroundErrorReason::kFlush, Status::Code::kIOError,
|
||||
Status::SubCode::kNoSpace, false),
|
||||
Status::Severity::kNoError},
|
||||
@@ -44,11 +46,11 @@ std::map<std::tuple<BackgroundErrorReason, Status::Code, Status::SubCode, bool>,
|
||||
{std::make_tuple(BackgroundErrorReason::kWriteCallback,
|
||||
Status::Code::kIOError, Status::SubCode::kNoSpace,
|
||||
true),
|
||||
Status::Severity::kFatalError},
|
||||
Status::Severity::kHardError},
|
||||
{std::make_tuple(BackgroundErrorReason::kWriteCallback,
|
||||
Status::Code::kIOError, Status::SubCode::kNoSpace,
|
||||
false),
|
||||
Status::Severity::kFatalError},
|
||||
Status::Severity::kHardError},
|
||||
};
|
||||
|
||||
std::map<std::tuple<BackgroundErrorReason, Status::Code, bool>, Status::Severity>
|
||||
@@ -118,6 +120,45 @@ std::map<std::tuple<BackgroundErrorReason, bool>, Status::Severity>
|
||||
Status::Severity::kFatalError},
|
||||
};
|
||||
|
||||
void ErrorHandler::CancelErrorRecovery() {
|
||||
#ifndef ROCKSDB_LITE
|
||||
db_mutex_->AssertHeld();
|
||||
|
||||
// We'll release the lock before calling sfm, so make sure no new
|
||||
// recovery gets scheduled at that point
|
||||
auto_recovery_ = false;
|
||||
SstFileManagerImpl* sfm = reinterpret_cast<SstFileManagerImpl*>(
|
||||
db_options_.sst_file_manager.get());
|
||||
if (sfm) {
|
||||
// This may or may not cancel a pending recovery
|
||||
db_mutex_->Unlock();
|
||||
bool cancelled = sfm->CancelErrorRecovery(this);
|
||||
db_mutex_->Lock();
|
||||
if (cancelled) {
|
||||
recovery_in_prog_ = false;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// This is the main function for looking at an error during a background
|
||||
// operation and deciding the severity, and error recovery strategy. The high
|
||||
// level algorithm is as follows -
|
||||
// 1. Classify the severity of the error based on the ErrorSeverityMap,
|
||||
// DefaultErrorSeverityMap and DefaultReasonMap defined earlier
|
||||
// 2. Call a Status code specific override function to adjust the severity
|
||||
// if needed. The reason for this is our ability to recover may depend on
|
||||
// the exact options enabled in DBOptions
|
||||
// 3. Determine if auto recovery is possible. A listener notification callback
|
||||
// is called, which can disable the auto recovery even if we decide its
|
||||
// feasible
|
||||
// 4. For Status::NoSpace() errors, rely on SstFileManagerImpl to control
|
||||
// the actual recovery. If no sst file manager is specified in DBOptions,
|
||||
// a default one is allocated during DB::Open(), so there will always be
|
||||
// one.
|
||||
// This can also get called as part of a recovery operation. In that case, we
|
||||
// also track the error seperately in recovery_error_ so we can tell in the
|
||||
// end whether recovery succeeded or not
|
||||
Status ErrorHandler::SetBGError(const Status& bg_err, BackgroundErrorReason reason) {
|
||||
db_mutex_->AssertHeld();
|
||||
|
||||
@@ -125,6 +166,12 @@ Status ErrorHandler::SetBGError(const Status& bg_err, BackgroundErrorReason reas
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
// Check if recovery is currently in progress. If it is, we will save this
|
||||
// error so we can check it at the end to see if recovery succeeded or not
|
||||
if (recovery_in_prog_ && recovery_error_.ok()) {
|
||||
recovery_error_ = bg_err;
|
||||
}
|
||||
|
||||
bool paranoid = db_options_.paranoid_checks;
|
||||
Status::Severity sev = Status::Severity::kFatalError;
|
||||
Status new_bg_err;
|
||||
@@ -156,15 +203,143 @@ Status ErrorHandler::SetBGError(const Status& bg_err, BackgroundErrorReason reas
|
||||
}
|
||||
|
||||
new_bg_err = Status(bg_err, sev);
|
||||
|
||||
bool auto_recovery = auto_recovery_;
|
||||
if (new_bg_err.severity() >= Status::Severity::kFatalError && auto_recovery) {
|
||||
auto_recovery = false;
|
||||
;
|
||||
}
|
||||
|
||||
// Allow some error specific overrides
|
||||
if (new_bg_err == Status::NoSpace()) {
|
||||
new_bg_err = OverrideNoSpaceError(new_bg_err, &auto_recovery);
|
||||
}
|
||||
|
||||
if (!new_bg_err.ok()) {
|
||||
Status s = new_bg_err;
|
||||
EventHelpers::NotifyOnBackgroundError(db_options_.listeners, reason, &s, db_mutex_);
|
||||
EventHelpers::NotifyOnBackgroundError(db_options_.listeners, reason, &s,
|
||||
db_mutex_, &auto_recovery);
|
||||
if (!s.ok() && (s.severity() > bg_error_.severity())) {
|
||||
bg_error_ = s;
|
||||
} else {
|
||||
// This error is less severe than previously encountered error. Don't
|
||||
// take any further action
|
||||
return bg_error_;
|
||||
}
|
||||
}
|
||||
|
||||
if (auto_recovery) {
|
||||
recovery_in_prog_ = true;
|
||||
|
||||
// Kick-off error specific recovery
|
||||
if (bg_error_ == Status::NoSpace()) {
|
||||
RecoverFromNoSpace();
|
||||
}
|
||||
}
|
||||
return bg_error_;
|
||||
}
|
||||
|
||||
Status ErrorHandler::OverrideNoSpaceError(Status bg_error,
|
||||
bool* auto_recovery) {
|
||||
#ifndef ROCKSDB_LITE
|
||||
if (bg_error.severity() >= Status::Severity::kFatalError) {
|
||||
return bg_error;
|
||||
}
|
||||
|
||||
if (db_options_.sst_file_manager.get() == nullptr) {
|
||||
// We rely on SFM to poll for enough disk space and recover
|
||||
*auto_recovery = false;
|
||||
return bg_error;
|
||||
}
|
||||
|
||||
if (db_options_.allow_2pc &&
|
||||
(bg_error.severity() <= Status::Severity::kSoftError)) {
|
||||
// Don't know how to recover, as the contents of the current WAL file may
|
||||
// be inconsistent, and it may be needed for 2PC. If 2PC is not enabled,
|
||||
// we can just flush the memtable and discard the log
|
||||
*auto_recovery = false;
|
||||
return Status(bg_error, Status::Severity::kFatalError);
|
||||
}
|
||||
|
||||
{
|
||||
uint64_t free_space;
|
||||
if (db_options_.env->GetFreeSpace(db_options_.db_paths[0].path,
|
||||
&free_space) == Status::NotSupported()) {
|
||||
*auto_recovery = false;
|
||||
}
|
||||
}
|
||||
|
||||
return bg_error;
|
||||
#else
|
||||
(void)auto_recovery;
|
||||
return Status(bg_error, Status::Severity::kFatalError);
|
||||
#endif
|
||||
}
|
||||
|
||||
void ErrorHandler::RecoverFromNoSpace() {
|
||||
#ifndef ROCKSDB_LITE
|
||||
SstFileManagerImpl* sfm =
|
||||
reinterpret_cast<SstFileManagerImpl*>(db_options_.sst_file_manager.get());
|
||||
|
||||
// Inform SFM of the error, so it can kick-off the recovery
|
||||
if (sfm) {
|
||||
sfm->StartErrorRecovery(this, bg_error_);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
Status ErrorHandler::ClearBGError() {
|
||||
#ifndef ROCKSDB_LITE
|
||||
db_mutex_->AssertHeld();
|
||||
|
||||
// Signal that recovery succeeded
|
||||
if (recovery_error_.ok()) {
|
||||
Status old_bg_error = bg_error_;
|
||||
bg_error_ = Status::OK();
|
||||
recovery_in_prog_ = false;
|
||||
EventHelpers::NotifyOnErrorRecoveryCompleted(db_options_.listeners,
|
||||
old_bg_error, db_mutex_);
|
||||
}
|
||||
return recovery_error_;
|
||||
#else
|
||||
return bg_error_;
|
||||
#endif
|
||||
}
|
||||
|
||||
Status ErrorHandler::RecoverFromBGError(bool is_manual) {
|
||||
#ifndef ROCKSDB_LITE
|
||||
InstrumentedMutexLock l(db_mutex_);
|
||||
if (is_manual) {
|
||||
// If its a manual recovery and there's a background recovery in progress
|
||||
// return busy status
|
||||
if (recovery_in_prog_) {
|
||||
return Status::Busy();
|
||||
}
|
||||
recovery_in_prog_ = true;
|
||||
}
|
||||
|
||||
if (bg_error_.severity() == Status::Severity::kSoftError) {
|
||||
// Simply clear the background error and return
|
||||
recovery_error_ = Status::OK();
|
||||
return ClearBGError();
|
||||
}
|
||||
|
||||
// Reset recovery_error_. We will use this to record any errors that happen
|
||||
// during the recovery process. While recovering, the only operations that
|
||||
// can generate background errors should be the flush operations
|
||||
recovery_error_ = Status::OK();
|
||||
Status s = db_->ResumeImpl();
|
||||
// For manual recover, shutdown, and fatal error cases, set
|
||||
// recovery_in_prog_ to false. For automatic background recovery, leave it
|
||||
// as is regardless of success or failure as it will be retried
|
||||
if (is_manual || s.IsShutdownInProgress() ||
|
||||
bg_error_.severity() >= Status::Severity::kFatalError) {
|
||||
recovery_in_prog_ = false;
|
||||
}
|
||||
return s;
|
||||
#else
|
||||
(void)is_manual;
|
||||
return bg_error_;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
+44
-21
@@ -11,42 +11,65 @@
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
class DBImpl;
|
||||
|
||||
class ErrorHandler {
|
||||
public:
|
||||
ErrorHandler(const ImmutableDBOptions& db_options,
|
||||
InstrumentedMutex* db_mutex)
|
||||
: db_options_(db_options),
|
||||
bg_error_(Status::OK()),
|
||||
db_mutex_(db_mutex)
|
||||
{}
|
||||
~ErrorHandler() {}
|
||||
ErrorHandler(DBImpl* db, const ImmutableDBOptions& db_options,
|
||||
InstrumentedMutex* db_mutex)
|
||||
: db_(db),
|
||||
db_options_(db_options),
|
||||
bg_error_(Status::OK()),
|
||||
recovery_error_(Status::OK()),
|
||||
db_mutex_(db_mutex),
|
||||
auto_recovery_(false),
|
||||
recovery_in_prog_(false) {}
|
||||
~ErrorHandler() {}
|
||||
|
||||
Status::Severity GetErrorSeverity(BackgroundErrorReason reason,
|
||||
Status::Code code, Status::SubCode subcode);
|
||||
void EnableAutoRecovery() { auto_recovery_ = true; }
|
||||
|
||||
Status SetBGError(const Status& bg_err, BackgroundErrorReason reason);
|
||||
Status::Severity GetErrorSeverity(BackgroundErrorReason reason,
|
||||
Status::Code code,
|
||||
Status::SubCode subcode);
|
||||
|
||||
Status GetBGError()
|
||||
{
|
||||
return bg_error_;
|
||||
}
|
||||
Status SetBGError(const Status& bg_err, BackgroundErrorReason reason);
|
||||
|
||||
void ClearBGError() {
|
||||
bg_error_ = Status::OK();
|
||||
}
|
||||
Status GetBGError() { return bg_error_; }
|
||||
|
||||
bool IsDBStopped() {
|
||||
return !bg_error_.ok();
|
||||
Status GetRecoveryError() { return recovery_error_; }
|
||||
|
||||
Status ClearBGError();
|
||||
|
||||
bool IsDBStopped() {
|
||||
return !bg_error_.ok() &&
|
||||
bg_error_.severity() >= Status::Severity::kHardError;
|
||||
}
|
||||
|
||||
bool IsBGWorkStopped() {
|
||||
return !bg_error_.ok();
|
||||
return !bg_error_.ok() &&
|
||||
(bg_error_.severity() >= Status::Severity::kHardError ||
|
||||
!auto_recovery_);
|
||||
}
|
||||
|
||||
private:
|
||||
bool IsRecoveryInProgress() { return recovery_in_prog_; }
|
||||
|
||||
Status RecoverFromBGError(bool is_manual = false);
|
||||
void CancelErrorRecovery();
|
||||
|
||||
private:
|
||||
DBImpl* db_;
|
||||
const ImmutableDBOptions& db_options_;
|
||||
Status bg_error_;
|
||||
// A seperate Status variable used to record any errors during the
|
||||
// recovery process from hard errors
|
||||
Status recovery_error_;
|
||||
InstrumentedMutex* db_mutex_;
|
||||
// A flag indicating whether automatic recovery from errors is enabled
|
||||
bool auto_recovery_;
|
||||
bool recovery_in_prog_;
|
||||
|
||||
Status OverrideNoSpaceError(Status bg_error, bool* auto_recovery);
|
||||
void RecoverFromNoSpace();
|
||||
};
|
||||
|
||||
}
|
||||
+556
-3
@@ -6,9 +6,12 @@
|
||||
// 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.
|
||||
#ifndef ROCKSDB_LITE
|
||||
|
||||
#include "db/db_test_util.h"
|
||||
#include "port/stack_trace.h"
|
||||
#include "rocksdb/perf_context.h"
|
||||
#include "rocksdb/sst_file_manager.h"
|
||||
#include "util/fault_injection_test_env.h"
|
||||
#if !defined(ROCKSDB_LITE)
|
||||
#include "util/sync_point.h"
|
||||
@@ -33,36 +36,139 @@ class DBErrorHandlingEnv : public EnvWrapper {
|
||||
bool trig_io_error;
|
||||
};
|
||||
|
||||
class ErrorHandlerListener : public EventListener {
|
||||
public:
|
||||
ErrorHandlerListener()
|
||||
: mutex_(),
|
||||
cv_(&mutex_),
|
||||
no_auto_recovery_(false),
|
||||
recovery_complete_(false),
|
||||
file_creation_started_(false),
|
||||
override_bg_error_(false),
|
||||
file_count_(0),
|
||||
fault_env_(nullptr) {}
|
||||
|
||||
void OnTableFileCreationStarted(
|
||||
const TableFileCreationBriefInfo& /*ti*/) override {
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
file_creation_started_ = true;
|
||||
if (file_count_ > 0) {
|
||||
if (--file_count_ == 0) {
|
||||
fault_env_->SetFilesystemActive(false, file_creation_error_);
|
||||
file_creation_error_ = Status::OK();
|
||||
}
|
||||
}
|
||||
cv_.SignalAll();
|
||||
}
|
||||
|
||||
void OnErrorRecoveryBegin(BackgroundErrorReason /*reason*/,
|
||||
Status /*bg_error*/,
|
||||
bool* auto_recovery) override {
|
||||
if (*auto_recovery && no_auto_recovery_) {
|
||||
*auto_recovery = false;
|
||||
}
|
||||
}
|
||||
|
||||
void OnErrorRecoveryCompleted(Status /*old_bg_error*/) override {
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
recovery_complete_ = true;
|
||||
cv_.SignalAll();
|
||||
}
|
||||
|
||||
bool WaitForRecovery(uint64_t /*abs_time_us*/) {
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
while (!recovery_complete_) {
|
||||
cv_.Wait(/*abs_time_us*/);
|
||||
}
|
||||
if (recovery_complete_) {
|
||||
recovery_complete_ = false;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void WaitForTableFileCreationStarted(uint64_t /*abs_time_us*/) {
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
while (!file_creation_started_) {
|
||||
cv_.Wait(/*abs_time_us*/);
|
||||
}
|
||||
file_creation_started_ = false;
|
||||
}
|
||||
|
||||
void OnBackgroundError(BackgroundErrorReason /*reason*/,
|
||||
Status* bg_error) override {
|
||||
if (override_bg_error_) {
|
||||
*bg_error = bg_error_;
|
||||
override_bg_error_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
void EnableAutoRecovery(bool enable = true) { no_auto_recovery_ = !enable; }
|
||||
|
||||
void OverrideBGError(Status bg_err) {
|
||||
bg_error_ = bg_err;
|
||||
override_bg_error_ = true;
|
||||
}
|
||||
|
||||
void InjectFileCreationError(FaultInjectionTestEnv* env, int file_count,
|
||||
Status s) {
|
||||
fault_env_ = env;
|
||||
file_count_ = file_count;
|
||||
file_creation_error_ = s;
|
||||
}
|
||||
|
||||
private:
|
||||
InstrumentedMutex mutex_;
|
||||
InstrumentedCondVar cv_;
|
||||
bool no_auto_recovery_;
|
||||
bool recovery_complete_;
|
||||
bool file_creation_started_;
|
||||
bool override_bg_error_;
|
||||
int file_count_;
|
||||
Status file_creation_error_;
|
||||
Status bg_error_;
|
||||
FaultInjectionTestEnv* fault_env_;
|
||||
};
|
||||
|
||||
TEST_F(DBErrorHandlingTest, FLushWriteError) {
|
||||
std::unique_ptr<FaultInjectionTestEnv> fault_env(
|
||||
new FaultInjectionTestEnv(Env::Default()));
|
||||
std::shared_ptr<ErrorHandlerListener> listener(new ErrorHandlerListener());
|
||||
Options options = GetDefaultOptions();
|
||||
options.create_if_missing = true;
|
||||
options.env = fault_env.get();
|
||||
options.listeners.emplace_back(listener);
|
||||
Status s;
|
||||
|
||||
listener->EnableAutoRecovery(false);
|
||||
DestroyAndReopen(options);
|
||||
|
||||
Put(Key(0), "va;");
|
||||
Put(Key(0), "val");
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"FlushJob::Start", [&](void *) {
|
||||
fault_env->SetFilesystemActive(false, Status::NoSpace("Out of space"));
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
s = Flush();
|
||||
ASSERT_EQ(s.severity(), rocksdb::Status::Severity::kSoftError);
|
||||
ASSERT_EQ(s.severity(), rocksdb::Status::Severity::kHardError);
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
fault_env->SetFilesystemActive(true);
|
||||
s = dbfull()->Resume();
|
||||
ASSERT_EQ(s, Status::OK());
|
||||
|
||||
Reopen(options);
|
||||
ASSERT_EQ("val", Get(Key(0)));
|
||||
Destroy(options);
|
||||
}
|
||||
|
||||
TEST_F(DBErrorHandlingTest, CompactionWriteError) {
|
||||
std::unique_ptr<FaultInjectionTestEnv> fault_env(
|
||||
new FaultInjectionTestEnv(Env::Default()));
|
||||
std::shared_ptr<ErrorHandlerListener> listener(new ErrorHandlerListener());
|
||||
Options options = GetDefaultOptions();
|
||||
options.create_if_missing = true;
|
||||
options.level0_file_num_compaction_trigger = 2;
|
||||
options.listeners.emplace_back(listener);
|
||||
options.env = fault_env.get();
|
||||
Status s;
|
||||
DestroyAndReopen(options);
|
||||
@@ -72,6 +178,10 @@ TEST_F(DBErrorHandlingTest, CompactionWriteError) {
|
||||
s = Flush();
|
||||
ASSERT_EQ(s, Status::OK());
|
||||
|
||||
listener->OverrideBGError(
|
||||
Status(Status::NoSpace(), Status::Severity::kHardError)
|
||||
);
|
||||
listener->EnableAutoRecovery(false);
|
||||
rocksdb::SyncPoint::GetInstance()->LoadDependency(
|
||||
{{"FlushMemTableFinished", "BackgroundCallCompaction:0"}});
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
@@ -85,7 +195,7 @@ TEST_F(DBErrorHandlingTest, CompactionWriteError) {
|
||||
ASSERT_EQ(s, Status::OK());
|
||||
|
||||
s = dbfull()->TEST_WaitForCompact();
|
||||
ASSERT_EQ(s.severity(), rocksdb::Status::Severity::kSoftError);
|
||||
ASSERT_EQ(s.severity(), rocksdb::Status::Severity::kHardError);
|
||||
|
||||
fault_env->SetFilesystemActive(true);
|
||||
s = dbfull()->Resume();
|
||||
@@ -129,6 +239,439 @@ TEST_F(DBErrorHandlingTest, CorruptionError) {
|
||||
Destroy(options);
|
||||
}
|
||||
|
||||
TEST_F(DBErrorHandlingTest, AutoRecoverFlushError) {
|
||||
std::unique_ptr<FaultInjectionTestEnv> fault_env(
|
||||
new FaultInjectionTestEnv(Env::Default()));
|
||||
std::shared_ptr<ErrorHandlerListener> listener(new ErrorHandlerListener());
|
||||
Options options = GetDefaultOptions();
|
||||
options.create_if_missing = true;
|
||||
options.env = fault_env.get();
|
||||
options.listeners.emplace_back(listener);
|
||||
Status s;
|
||||
|
||||
listener->EnableAutoRecovery();
|
||||
DestroyAndReopen(options);
|
||||
|
||||
Put(Key(0), "val");
|
||||
SyncPoint::GetInstance()->SetCallBack("FlushJob::Start", [&](void*) {
|
||||
fault_env->SetFilesystemActive(false, Status::NoSpace("Out of space"));
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
s = Flush();
|
||||
ASSERT_EQ(s.severity(), rocksdb::Status::Severity::kHardError);
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
fault_env->SetFilesystemActive(true);
|
||||
ASSERT_EQ(listener->WaitForRecovery(5000000), true);
|
||||
|
||||
s = Put(Key(1), "val");
|
||||
ASSERT_EQ(s, Status::OK());
|
||||
|
||||
Reopen(options);
|
||||
ASSERT_EQ("val", Get(Key(0)));
|
||||
ASSERT_EQ("val", Get(Key(1)));
|
||||
Destroy(options);
|
||||
}
|
||||
|
||||
TEST_F(DBErrorHandlingTest, FailRecoverFlushError) {
|
||||
std::unique_ptr<FaultInjectionTestEnv> fault_env(
|
||||
new FaultInjectionTestEnv(Env::Default()));
|
||||
std::shared_ptr<ErrorHandlerListener> listener(new ErrorHandlerListener());
|
||||
Options options = GetDefaultOptions();
|
||||
options.create_if_missing = true;
|
||||
options.env = fault_env.get();
|
||||
options.listeners.emplace_back(listener);
|
||||
Status s;
|
||||
|
||||
listener->EnableAutoRecovery();
|
||||
DestroyAndReopen(options);
|
||||
|
||||
Put(Key(0), "val");
|
||||
SyncPoint::GetInstance()->SetCallBack("FlushJob::Start", [&](void*) {
|
||||
fault_env->SetFilesystemActive(false, Status::NoSpace("Out of space"));
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
s = Flush();
|
||||
ASSERT_EQ(s.severity(), rocksdb::Status::Severity::kHardError);
|
||||
// We should be able to shutdown the database while auto recovery is going
|
||||
// on in the background
|
||||
Close();
|
||||
DestroyDB(dbname_, options);
|
||||
}
|
||||
|
||||
TEST_F(DBErrorHandlingTest, WALWriteError) {
|
||||
std::unique_ptr<FaultInjectionTestEnv> fault_env(
|
||||
new FaultInjectionTestEnv(Env::Default()));
|
||||
std::shared_ptr<ErrorHandlerListener> listener(new ErrorHandlerListener());
|
||||
Options options = GetDefaultOptions();
|
||||
options.create_if_missing = true;
|
||||
options.writable_file_max_buffer_size = 32768;
|
||||
options.env = fault_env.get();
|
||||
options.listeners.emplace_back(listener);
|
||||
Status s;
|
||||
Random rnd(301);
|
||||
|
||||
listener->EnableAutoRecovery();
|
||||
DestroyAndReopen(options);
|
||||
|
||||
{
|
||||
WriteBatch batch;
|
||||
|
||||
for (auto i = 0; i<100; ++i) {
|
||||
batch.Put(Key(i), RandomString(&rnd, 1024));
|
||||
}
|
||||
|
||||
WriteOptions wopts;
|
||||
wopts.sync = true;
|
||||
ASSERT_EQ(dbfull()->Write(wopts, &batch), Status::OK());
|
||||
};
|
||||
|
||||
{
|
||||
WriteBatch batch;
|
||||
int write_error = 0;
|
||||
|
||||
for (auto i = 100; i<199; ++i) {
|
||||
batch.Put(Key(i), RandomString(&rnd, 1024));
|
||||
}
|
||||
|
||||
SyncPoint::GetInstance()->SetCallBack("WritableFileWriter::Append:BeforePrepareWrite", [&](void*) {
|
||||
write_error++;
|
||||
if (write_error > 2) {
|
||||
fault_env->SetFilesystemActive(false, Status::NoSpace("Out of space"));
|
||||
}
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
WriteOptions wopts;
|
||||
wopts.sync = true;
|
||||
s = dbfull()->Write(wopts, &batch);
|
||||
ASSERT_EQ(s, s.NoSpace());
|
||||
}
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
fault_env->SetFilesystemActive(true);
|
||||
ASSERT_EQ(listener->WaitForRecovery(5000000), true);
|
||||
for (auto i=0; i<199; ++i) {
|
||||
if (i < 100) {
|
||||
ASSERT_NE(Get(Key(i)), "NOT_FOUND");
|
||||
} else {
|
||||
ASSERT_EQ(Get(Key(i)), "NOT_FOUND");
|
||||
}
|
||||
}
|
||||
Reopen(options);
|
||||
for (auto i=0; i<199; ++i) {
|
||||
if (i < 100) {
|
||||
ASSERT_NE(Get(Key(i)), "NOT_FOUND");
|
||||
} else {
|
||||
ASSERT_EQ(Get(Key(i)), "NOT_FOUND");
|
||||
}
|
||||
}
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(DBErrorHandlingTest, MultiCFWALWriteError) {
|
||||
std::unique_ptr<FaultInjectionTestEnv> fault_env(
|
||||
new FaultInjectionTestEnv(Env::Default()));
|
||||
std::shared_ptr<ErrorHandlerListener> listener(new ErrorHandlerListener());
|
||||
Options options = GetDefaultOptions();
|
||||
options.create_if_missing = true;
|
||||
options.writable_file_max_buffer_size = 32768;
|
||||
options.env = fault_env.get();
|
||||
options.listeners.emplace_back(listener);
|
||||
Status s;
|
||||
Random rnd(301);
|
||||
|
||||
listener->EnableAutoRecovery();
|
||||
CreateAndReopenWithCF({"one", "two", "three"}, options);
|
||||
|
||||
{
|
||||
WriteBatch batch;
|
||||
|
||||
for (auto i = 1; i < 4; ++i) {
|
||||
for (auto j = 0; j < 100; ++j) {
|
||||
batch.Put(handles_[i], Key(j), RandomString(&rnd, 1024));
|
||||
}
|
||||
}
|
||||
|
||||
WriteOptions wopts;
|
||||
wopts.sync = true;
|
||||
ASSERT_EQ(dbfull()->Write(wopts, &batch), Status::OK());
|
||||
};
|
||||
|
||||
{
|
||||
WriteBatch batch;
|
||||
int write_error = 0;
|
||||
|
||||
// Write to one CF
|
||||
for (auto i = 100; i < 199; ++i) {
|
||||
batch.Put(handles_[2], Key(i), RandomString(&rnd, 1024));
|
||||
}
|
||||
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"WritableFileWriter::Append:BeforePrepareWrite", [&](void*) {
|
||||
write_error++;
|
||||
if (write_error > 2) {
|
||||
fault_env->SetFilesystemActive(false,
|
||||
Status::NoSpace("Out of space"));
|
||||
}
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
WriteOptions wopts;
|
||||
wopts.sync = true;
|
||||
s = dbfull()->Write(wopts, &batch);
|
||||
ASSERT_EQ(s, s.NoSpace());
|
||||
}
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
fault_env->SetFilesystemActive(true);
|
||||
ASSERT_EQ(listener->WaitForRecovery(5000000), true);
|
||||
|
||||
for (auto i = 1; i < 4; ++i) {
|
||||
// Every CF should have been flushed
|
||||
ASSERT_EQ(NumTableFilesAtLevel(0, i), 1);
|
||||
}
|
||||
|
||||
for (auto i = 1; i < 4; ++i) {
|
||||
for (auto j = 0; j < 199; ++j) {
|
||||
if (j < 100) {
|
||||
ASSERT_NE(Get(i, Key(j)), "NOT_FOUND");
|
||||
} else {
|
||||
ASSERT_EQ(Get(i, Key(j)), "NOT_FOUND");
|
||||
}
|
||||
}
|
||||
}
|
||||
ReopenWithColumnFamilies({"default", "one", "two", "three"}, options);
|
||||
for (auto i = 1; i < 4; ++i) {
|
||||
for (auto j = 0; j < 199; ++j) {
|
||||
if (j < 100) {
|
||||
ASSERT_NE(Get(i, Key(j)), "NOT_FOUND");
|
||||
} else {
|
||||
ASSERT_EQ(Get(i, Key(j)), "NOT_FOUND");
|
||||
}
|
||||
}
|
||||
}
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(DBErrorHandlingTest, MultiDBCompactionError) {
|
||||
FaultInjectionTestEnv* def_env = new FaultInjectionTestEnv(Env::Default());
|
||||
std::vector<std::unique_ptr<FaultInjectionTestEnv>> fault_env;
|
||||
std::vector<Options> options;
|
||||
std::vector<std::shared_ptr<ErrorHandlerListener>> listener;
|
||||
std::vector<DB*> db;
|
||||
std::shared_ptr<SstFileManager> sfm(NewSstFileManager(def_env));
|
||||
int kNumDbInstances = 3;
|
||||
Random rnd(301);
|
||||
|
||||
for (auto i = 0; i < kNumDbInstances; ++i) {
|
||||
listener.emplace_back(new ErrorHandlerListener());
|
||||
options.emplace_back(GetDefaultOptions());
|
||||
fault_env.emplace_back(new FaultInjectionTestEnv(Env::Default()));
|
||||
options[i].create_if_missing = true;
|
||||
options[i].level0_file_num_compaction_trigger = 2;
|
||||
options[i].writable_file_max_buffer_size = 32768;
|
||||
options[i].env = fault_env[i].get();
|
||||
options[i].listeners.emplace_back(listener[i]);
|
||||
options[i].sst_file_manager = sfm;
|
||||
DB* dbptr;
|
||||
char buf[16];
|
||||
|
||||
listener[i]->EnableAutoRecovery();
|
||||
// Setup for returning error for the 3rd SST, which would be level 1
|
||||
listener[i]->InjectFileCreationError(fault_env[i].get(), 3,
|
||||
Status::NoSpace("Out of space"));
|
||||
snprintf(buf, sizeof(buf), "_%d", i);
|
||||
DestroyDB(dbname_ + std::string(buf), options[i]);
|
||||
ASSERT_EQ(DB::Open(options[i], dbname_ + std::string(buf), &dbptr),
|
||||
Status::OK());
|
||||
db.emplace_back(dbptr);
|
||||
}
|
||||
|
||||
for (auto i = 0; i < kNumDbInstances; ++i) {
|
||||
WriteBatch batch;
|
||||
|
||||
for (auto j = 0; j <= 100; ++j) {
|
||||
batch.Put(Key(j), RandomString(&rnd, 1024));
|
||||
}
|
||||
|
||||
WriteOptions wopts;
|
||||
wopts.sync = true;
|
||||
ASSERT_EQ(db[i]->Write(wopts, &batch), Status::OK());
|
||||
ASSERT_EQ(db[i]->Flush(FlushOptions()), Status::OK());
|
||||
}
|
||||
|
||||
def_env->SetFilesystemActive(false, Status::NoSpace("Out of space"));
|
||||
for (auto i = 0; i < kNumDbInstances; ++i) {
|
||||
WriteBatch batch;
|
||||
|
||||
// Write to one CF
|
||||
for (auto j = 100; j < 199; ++j) {
|
||||
batch.Put(Key(j), RandomString(&rnd, 1024));
|
||||
}
|
||||
|
||||
WriteOptions wopts;
|
||||
wopts.sync = true;
|
||||
ASSERT_EQ(db[i]->Write(wopts, &batch), Status::OK());
|
||||
ASSERT_EQ(db[i]->Flush(FlushOptions()), Status::OK());
|
||||
}
|
||||
|
||||
for (auto i = 0; i < kNumDbInstances; ++i) {
|
||||
Status s = static_cast<DBImpl*>(db[i])->TEST_WaitForCompact(true);
|
||||
ASSERT_EQ(s.severity(), Status::Severity::kSoftError);
|
||||
fault_env[i]->SetFilesystemActive(true);
|
||||
}
|
||||
|
||||
def_env->SetFilesystemActive(true);
|
||||
for (auto i = 0; i < kNumDbInstances; ++i) {
|
||||
std::string prop;
|
||||
ASSERT_EQ(listener[i]->WaitForRecovery(5000000), true);
|
||||
EXPECT_TRUE(db[i]->GetProperty(
|
||||
"rocksdb.num-files-at-level" + NumberToString(0), &prop));
|
||||
EXPECT_EQ(atoi(prop.c_str()), 0);
|
||||
EXPECT_TRUE(db[i]->GetProperty(
|
||||
"rocksdb.num-files-at-level" + NumberToString(1), &prop));
|
||||
EXPECT_EQ(atoi(prop.c_str()), 1);
|
||||
}
|
||||
|
||||
for (auto i = 0; i < kNumDbInstances; ++i) {
|
||||
char buf[16];
|
||||
snprintf(buf, sizeof(buf), "_%d", i);
|
||||
delete db[i];
|
||||
fault_env[i]->SetFilesystemActive(true);
|
||||
if (getenv("KEEP_DB")) {
|
||||
printf("DB is still at %s%s\n", dbname_.c_str(), buf);
|
||||
} else {
|
||||
Status s = DestroyDB(dbname_ + std::string(buf), options[i]);
|
||||
}
|
||||
}
|
||||
options.clear();
|
||||
sfm.reset();
|
||||
delete def_env;
|
||||
}
|
||||
|
||||
TEST_F(DBErrorHandlingTest, MultiDBVariousErrors) {
|
||||
FaultInjectionTestEnv* def_env = new FaultInjectionTestEnv(Env::Default());
|
||||
std::vector<std::unique_ptr<FaultInjectionTestEnv>> fault_env;
|
||||
std::vector<Options> options;
|
||||
std::vector<std::shared_ptr<ErrorHandlerListener>> listener;
|
||||
std::vector<DB*> db;
|
||||
std::shared_ptr<SstFileManager> sfm(NewSstFileManager(def_env));
|
||||
int kNumDbInstances = 3;
|
||||
Random rnd(301);
|
||||
|
||||
for (auto i = 0; i < kNumDbInstances; ++i) {
|
||||
listener.emplace_back(new ErrorHandlerListener());
|
||||
options.emplace_back(GetDefaultOptions());
|
||||
fault_env.emplace_back(new FaultInjectionTestEnv(Env::Default()));
|
||||
options[i].create_if_missing = true;
|
||||
options[i].level0_file_num_compaction_trigger = 2;
|
||||
options[i].writable_file_max_buffer_size = 32768;
|
||||
options[i].env = fault_env[i].get();
|
||||
options[i].listeners.emplace_back(listener[i]);
|
||||
options[i].sst_file_manager = sfm;
|
||||
DB* dbptr;
|
||||
char buf[16];
|
||||
|
||||
listener[i]->EnableAutoRecovery();
|
||||
switch (i) {
|
||||
case 0:
|
||||
// Setup for returning error for the 3rd SST, which would be level 1
|
||||
listener[i]->InjectFileCreationError(fault_env[i].get(), 3,
|
||||
Status::NoSpace("Out of space"));
|
||||
break;
|
||||
case 1:
|
||||
// Setup for returning error after the 1st SST, which would result
|
||||
// in a hard error
|
||||
listener[i]->InjectFileCreationError(fault_env[i].get(), 2,
|
||||
Status::NoSpace("Out of space"));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
snprintf(buf, sizeof(buf), "_%d", i);
|
||||
DestroyDB(dbname_ + std::string(buf), options[i]);
|
||||
ASSERT_EQ(DB::Open(options[i], dbname_ + std::string(buf), &dbptr),
|
||||
Status::OK());
|
||||
db.emplace_back(dbptr);
|
||||
}
|
||||
|
||||
for (auto i = 0; i < kNumDbInstances; ++i) {
|
||||
WriteBatch batch;
|
||||
|
||||
for (auto j = 0; j <= 100; ++j) {
|
||||
batch.Put(Key(j), RandomString(&rnd, 1024));
|
||||
}
|
||||
|
||||
WriteOptions wopts;
|
||||
wopts.sync = true;
|
||||
ASSERT_EQ(db[i]->Write(wopts, &batch), Status::OK());
|
||||
ASSERT_EQ(db[i]->Flush(FlushOptions()), Status::OK());
|
||||
}
|
||||
|
||||
def_env->SetFilesystemActive(false, Status::NoSpace("Out of space"));
|
||||
for (auto i = 0; i < kNumDbInstances; ++i) {
|
||||
WriteBatch batch;
|
||||
|
||||
// Write to one CF
|
||||
for (auto j = 100; j < 199; ++j) {
|
||||
batch.Put(Key(j), RandomString(&rnd, 1024));
|
||||
}
|
||||
|
||||
WriteOptions wopts;
|
||||
wopts.sync = true;
|
||||
ASSERT_EQ(db[i]->Write(wopts, &batch), Status::OK());
|
||||
if (i != 1) {
|
||||
ASSERT_EQ(db[i]->Flush(FlushOptions()), Status::OK());
|
||||
} else {
|
||||
ASSERT_EQ(db[i]->Flush(FlushOptions()), Status::NoSpace());
|
||||
}
|
||||
}
|
||||
|
||||
for (auto i = 0; i < kNumDbInstances; ++i) {
|
||||
Status s = static_cast<DBImpl*>(db[i])->TEST_WaitForCompact(true);
|
||||
switch (i) {
|
||||
case 0:
|
||||
ASSERT_EQ(s.severity(), Status::Severity::kSoftError);
|
||||
break;
|
||||
case 1:
|
||||
ASSERT_EQ(s.severity(), Status::Severity::kHardError);
|
||||
break;
|
||||
case 2:
|
||||
ASSERT_EQ(s, Status::OK());
|
||||
break;
|
||||
}
|
||||
fault_env[i]->SetFilesystemActive(true);
|
||||
}
|
||||
|
||||
def_env->SetFilesystemActive(true);
|
||||
for (auto i = 0; i < kNumDbInstances; ++i) {
|
||||
std::string prop;
|
||||
if (i < 2) {
|
||||
ASSERT_EQ(listener[i]->WaitForRecovery(5000000), true);
|
||||
}
|
||||
if (i == 1) {
|
||||
ASSERT_EQ(static_cast<DBImpl*>(db[i])->TEST_WaitForCompact(true),
|
||||
Status::OK());
|
||||
}
|
||||
EXPECT_TRUE(db[i]->GetProperty(
|
||||
"rocksdb.num-files-at-level" + NumberToString(0), &prop));
|
||||
EXPECT_EQ(atoi(prop.c_str()), 0);
|
||||
EXPECT_TRUE(db[i]->GetProperty(
|
||||
"rocksdb.num-files-at-level" + NumberToString(1), &prop));
|
||||
EXPECT_EQ(atoi(prop.c_str()), 1);
|
||||
}
|
||||
|
||||
for (auto i = 0; i < kNumDbInstances; ++i) {
|
||||
char buf[16];
|
||||
snprintf(buf, sizeof(buf), "_%d", i);
|
||||
fault_env[i]->SetFilesystemActive(true);
|
||||
delete db[i];
|
||||
if (getenv("KEEP_DB")) {
|
||||
printf("DB is still at %s%s\n", dbname_.c_str(), buf);
|
||||
} else {
|
||||
DestroyDB(dbname_ + std::string(buf), options[i]);
|
||||
}
|
||||
}
|
||||
options.clear();
|
||||
delete def_env;
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
@@ -136,3 +679,13 @@ int main(int argc, char** argv) {
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
|
||||
#else
|
||||
#include <stdio.h>
|
||||
|
||||
int main(int /*argc*/, char** /*argv*/) {
|
||||
fprintf(stderr, "SKIPPED as Cuckoo table is not supported in ROCKSDB_LITE\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif // ROCKSDB_LITE
|
||||
+27
-2
@@ -40,8 +40,8 @@ void EventHelpers::NotifyTableFileCreationStarted(
|
||||
|
||||
void EventHelpers::NotifyOnBackgroundError(
|
||||
const std::vector<std::shared_ptr<EventListener>>& listeners,
|
||||
BackgroundErrorReason reason, Status* bg_error,
|
||||
InstrumentedMutex* db_mutex) {
|
||||
BackgroundErrorReason reason, Status* bg_error, InstrumentedMutex* db_mutex,
|
||||
bool* auto_recovery) {
|
||||
#ifndef ROCKSDB_LITE
|
||||
if (listeners.size() == 0U) {
|
||||
return;
|
||||
@@ -51,6 +51,9 @@ void EventHelpers::NotifyOnBackgroundError(
|
||||
db_mutex->Unlock();
|
||||
for (auto& listener : listeners) {
|
||||
listener->OnBackgroundError(reason, bg_error);
|
||||
if (*auto_recovery) {
|
||||
listener->OnErrorRecoveryBegin(reason, *bg_error, auto_recovery);
|
||||
}
|
||||
}
|
||||
db_mutex->Lock();
|
||||
#else
|
||||
@@ -58,6 +61,7 @@ void EventHelpers::NotifyOnBackgroundError(
|
||||
(void)reason;
|
||||
(void)bg_error;
|
||||
(void)db_mutex;
|
||||
(void)auto_recovery;
|
||||
#endif // ROCKSDB_LITE
|
||||
}
|
||||
|
||||
@@ -167,4 +171,25 @@ void EventHelpers::LogAndNotifyTableFileDeletion(
|
||||
#endif // !ROCKSDB_LITE
|
||||
}
|
||||
|
||||
void EventHelpers::NotifyOnErrorRecoveryCompleted(
|
||||
const std::vector<std::shared_ptr<EventListener>>& listeners,
|
||||
Status old_bg_error, InstrumentedMutex* db_mutex) {
|
||||
#ifndef ROCKSDB_LITE
|
||||
if (listeners.size() == 0U) {
|
||||
return;
|
||||
}
|
||||
db_mutex->AssertHeld();
|
||||
// release lock while notifying events
|
||||
db_mutex->Unlock();
|
||||
for (auto& listener : listeners) {
|
||||
listener->OnErrorRecoveryCompleted(old_bg_error);
|
||||
}
|
||||
db_mutex->Lock();
|
||||
#else
|
||||
(void)listeners;
|
||||
(void)old_bg_error;
|
||||
(void)db_mutex;
|
||||
#endif // ROCKSDB_LITE
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
+4
-1
@@ -28,7 +28,7 @@ class EventHelpers {
|
||||
static void NotifyOnBackgroundError(
|
||||
const std::vector<std::shared_ptr<EventListener>>& listeners,
|
||||
BackgroundErrorReason reason, Status* bg_error,
|
||||
InstrumentedMutex* db_mutex);
|
||||
InstrumentedMutex* db_mutex, bool* auto_recovery);
|
||||
static void LogAndNotifyTableFileCreationFinished(
|
||||
EventLogger* event_logger,
|
||||
const std::vector<std::shared_ptr<EventListener>>& listeners,
|
||||
@@ -41,6 +41,9 @@ class EventHelpers {
|
||||
uint64_t file_number, const std::string& file_path,
|
||||
const Status& status, const std::string& db_name,
|
||||
const std::vector<std::shared_ptr<EventListener>>& listeners);
|
||||
static void NotifyOnErrorRecoveryCompleted(
|
||||
const std::vector<std::shared_ptr<EventListener>>& listeners,
|
||||
Status bg_error, InstrumentedMutex* db_mutex);
|
||||
|
||||
private:
|
||||
static void LogAndNotifyTableFileCreation(
|
||||
|
||||
@@ -29,7 +29,8 @@
|
||||
namespace rocksdb {
|
||||
|
||||
Status ExternalSstFileIngestionJob::Prepare(
|
||||
const std::vector<std::string>& external_files_paths, SuperVersion* sv) {
|
||||
const std::vector<std::string>& external_files_paths,
|
||||
uint64_t next_file_number, SuperVersion* sv) {
|
||||
Status status;
|
||||
|
||||
// Read the information of files we are ingesting
|
||||
@@ -90,7 +91,7 @@ Status ExternalSstFileIngestionJob::Prepare(
|
||||
|
||||
// Copy/Move external files into DB
|
||||
for (IngestedFileInfo& f : files_to_ingest_) {
|
||||
f.fd = FileDescriptor(versions_->NewFileNumber(), 0, f.file_size);
|
||||
f.fd = FileDescriptor(next_file_number++, 0, f.file_size);
|
||||
|
||||
const std::string path_outside_db = f.external_file_path;
|
||||
const std::string path_inside_db =
|
||||
@@ -343,7 +344,7 @@ Status ExternalSstFileIngestionJob::GetIngestedFileInfo(
|
||||
file_to_ingest->global_seqno_offset = 0;
|
||||
return Status::Corruption("Was not able to find file global seqno field");
|
||||
}
|
||||
file_to_ingest->global_seqno_offset = offsets_iter->second;
|
||||
file_to_ingest->global_seqno_offset = static_cast<size_t>(offsets_iter->second);
|
||||
} else if (file_to_ingest->version == 1) {
|
||||
// SST file V1 should not have global seqno field
|
||||
assert(seqno_iter == uprops.end());
|
||||
@@ -475,9 +476,9 @@ Status ExternalSstFileIngestionJob::AssignLevelAndSeqnoForIngestedFile(
|
||||
const SequenceNumber level_largest_seqno =
|
||||
(*max_element(level_files.begin(), level_files.end(),
|
||||
[](FileMetaData* f1, FileMetaData* f2) {
|
||||
return f1->largest_seqno < f2->largest_seqno;
|
||||
return f1->fd.largest_seqno < f2->fd.largest_seqno;
|
||||
}))
|
||||
->largest_seqno;
|
||||
->fd.largest_seqno;
|
||||
// should only assign seqno to current level's largest seqno when
|
||||
// the file fits
|
||||
if (level_largest_seqno != 0 &&
|
||||
@@ -522,7 +523,7 @@ Status ExternalSstFileIngestionJob::CheckLevelForIngestedBehindFile(
|
||||
// at some upper level
|
||||
for (int lvl = 0; lvl < cfd_->NumberLevels() - 1; lvl++) {
|
||||
for (auto file : vstorage->LevelFiles(lvl)) {
|
||||
if (file->smallest_seqno == 0) {
|
||||
if (file->fd.smallest_seqno == 0) {
|
||||
return Status::InvalidArgument(
|
||||
"Can't ingest_behind file as despite allow_ingest_behind=true "
|
||||
"there are files with 0 seqno in database at upper levels!");
|
||||
@@ -547,24 +548,27 @@ Status ExternalSstFileIngestionJob::AssignGlobalSeqnoForIngestedFile(
|
||||
"field");
|
||||
}
|
||||
|
||||
std::unique_ptr<RandomRWFile> rwfile;
|
||||
Status status = env_->NewRandomRWFile(file_to_ingest->internal_file_path,
|
||||
&rwfile, env_options_);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
if (ingestion_options_.write_global_seqno) {
|
||||
// Determine if we can write global_seqno to a given offset of file.
|
||||
// If the file system does not support random write, then we should not.
|
||||
// Otherwise we should.
|
||||
std::unique_ptr<RandomRWFile> rwfile;
|
||||
Status status = env_->NewRandomRWFile(file_to_ingest->internal_file_path,
|
||||
&rwfile, env_options_);
|
||||
if (status.ok()) {
|
||||
std::string seqno_val;
|
||||
PutFixed64(&seqno_val, seqno);
|
||||
status = rwfile->Write(file_to_ingest->global_seqno_offset, seqno_val);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
} else if (!status.IsNotSupported()) {
|
||||
return status;
|
||||
}
|
||||
}
|
||||
|
||||
// Write the new seqno in the global sequence number field in the file
|
||||
std::string seqno_val;
|
||||
PutFixed64(&seqno_val, seqno);
|
||||
status = rwfile->Write(file_to_ingest->global_seqno_offset, seqno_val);
|
||||
if (status.ok()) {
|
||||
status = rwfile->Fsync();
|
||||
}
|
||||
if (status.ok()) {
|
||||
file_to_ingest->assigned_seqno = seqno;
|
||||
}
|
||||
return status;
|
||||
file_to_ingest->assigned_seqno = seqno;
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
bool ExternalSstFileIngestionJob::IngestedFileFitInLevel(
|
||||
|
||||
@@ -89,7 +89,7 @@ class ExternalSstFileIngestionJob {
|
||||
|
||||
// Prepare the job by copying external files into the DB.
|
||||
Status Prepare(const std::vector<std::string>& external_files_paths,
|
||||
SuperVersion* sv);
|
||||
uint64_t next_file_number, SuperVersion* sv);
|
||||
|
||||
// Check if we need to flush the memtable before running the ingestion job
|
||||
// This will be true if the files we are ingesting are overlapping with any
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include "port/port.h"
|
||||
#include "port/stack_trace.h"
|
||||
#include "rocksdb/sst_file_writer.h"
|
||||
#include "util/filename.h"
|
||||
#include "util/testutil.h"
|
||||
|
||||
namespace rocksdb {
|
||||
@@ -1292,6 +1293,39 @@ TEST_F(ExternalSSTFileTest, PickedLevelBug) {
|
||||
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
|
||||
TEST_F(ExternalSSTFileTest, IngestNonExistingFile) {
|
||||
Options options = CurrentOptions();
|
||||
DestroyAndReopen(options);
|
||||
|
||||
Status s = db_->IngestExternalFile({"non_existing_file"},
|
||||
IngestExternalFileOptions());
|
||||
ASSERT_NOK(s);
|
||||
|
||||
// Verify file deletion is not impacted (verify a bug fix)
|
||||
ASSERT_OK(Put(Key(1), Key(1)));
|
||||
ASSERT_OK(Put(Key(9), Key(9)));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
ASSERT_OK(Put(Key(1), Key(1)));
|
||||
ASSERT_OK(Put(Key(9), Key(9)));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
||||
|
||||
// After full compaction, there should be only 1 file.
|
||||
std::vector<std::string> files;
|
||||
env_->GetChildren(dbname_, &files);
|
||||
int num_sst_files = 0;
|
||||
for (auto& f : files) {
|
||||
uint64_t number;
|
||||
FileType type;
|
||||
if (ParseFileName(f, &number, &type) && type == kTableFile) {
|
||||
num_sst_files++;
|
||||
}
|
||||
}
|
||||
ASSERT_EQ(1, num_sst_files);
|
||||
}
|
||||
|
||||
TEST_F(ExternalSSTFileTest, CompactDuringAddFileRandom) {
|
||||
Options options = CurrentOptions();
|
||||
options.disable_auto_compactions = false;
|
||||
|
||||
+5
-3
@@ -78,6 +78,8 @@ const char* GetFlushReasonString (FlushReason flush_reason) {
|
||||
return "Auto Compaction";
|
||||
case FlushReason::kManualFlush:
|
||||
return "Manual Flush";
|
||||
case FlushReason::kErrorRecovery:
|
||||
return "Error Recovery";
|
||||
default:
|
||||
return "Invalid";
|
||||
}
|
||||
@@ -371,8 +373,8 @@ Status FlushJob::WriteLevel0Table() {
|
||||
s.ToString().c_str(),
|
||||
meta_.marked_for_compaction ? " (needs compaction)" : "");
|
||||
|
||||
if (output_file_directory_ != nullptr) {
|
||||
output_file_directory_->Fsync();
|
||||
if (s.ok() && output_file_directory_ != nullptr) {
|
||||
s = output_file_directory_->Fsync();
|
||||
}
|
||||
TEST_SYNC_POINT("FlushJob::WriteLevel0Table");
|
||||
db_mutex_->Lock();
|
||||
@@ -389,7 +391,7 @@ Status FlushJob::WriteLevel0Table() {
|
||||
// Add file to L0
|
||||
edit_->AddFile(0 /* level */, meta_.fd.GetNumber(), meta_.fd.GetPathId(),
|
||||
meta_.fd.GetFileSize(), meta_.smallest, meta_.largest,
|
||||
meta_.smallest_seqno, meta_.largest_seqno,
|
||||
meta_.fd.smallest_seqno, meta_.fd.largest_seqno,
|
||||
meta_.marked_for_compaction);
|
||||
}
|
||||
|
||||
|
||||
@@ -62,7 +62,7 @@ class FlushJobTest : public testing::Test {
|
||||
manifest, &file, env_->OptimizeForManifestWrite(env_options_));
|
||||
ASSERT_OK(s);
|
||||
unique_ptr<WritableFileWriter> file_writer(
|
||||
new WritableFileWriter(std::move(file), EnvOptions()));
|
||||
new WritableFileWriter(std::move(file), manifest, EnvOptions()));
|
||||
{
|
||||
log::Writer log(std::move(file_writer), 0, false);
|
||||
std::string record;
|
||||
@@ -147,19 +147,20 @@ TEST_F(FlushJobTest, NonEmpty) {
|
||||
db_options_.statistics.get(), &event_logger, true);
|
||||
|
||||
HistogramData hist;
|
||||
FileMetaData fd;
|
||||
FileMetaData file_meta;
|
||||
mutex_.Lock();
|
||||
flush_job.PickMemTable();
|
||||
ASSERT_OK(flush_job.Run(nullptr, &fd));
|
||||
ASSERT_OK(flush_job.Run(nullptr, &file_meta));
|
||||
mutex_.Unlock();
|
||||
db_options_.statistics->histogramData(FLUSH_TIME, &hist);
|
||||
ASSERT_GT(hist.average, 0.0);
|
||||
|
||||
ASSERT_EQ(ToString(0), fd.smallest.user_key().ToString());
|
||||
ASSERT_EQ("9999a",
|
||||
fd.largest.user_key().ToString()); // range tombstone end key
|
||||
ASSERT_EQ(1, fd.smallest_seqno);
|
||||
ASSERT_EQ(10000, fd.largest_seqno); // range tombstone seqnum 10000
|
||||
ASSERT_EQ(ToString(0), file_meta.smallest.user_key().ToString());
|
||||
ASSERT_EQ(
|
||||
"9999a",
|
||||
file_meta.largest.user_key().ToString()); // range tombstone end key
|
||||
ASSERT_EQ(1, file_meta.fd.smallest_seqno);
|
||||
ASSERT_EQ(10000, file_meta.fd.largest_seqno); // range tombstone seqnum 10000
|
||||
mock_table_factory_->AssertSingleFile(inserted_keys);
|
||||
job_context.Clean();
|
||||
}
|
||||
|
||||
+7
-15
@@ -916,21 +916,13 @@ bool ForwardIterator::TEST_CheckDeletedIters(int* pdeleted_iters,
|
||||
uint32_t ForwardIterator::FindFileInRange(
|
||||
const std::vector<FileMetaData*>& files, const Slice& internal_key,
|
||||
uint32_t left, uint32_t right) {
|
||||
while (left < right) {
|
||||
uint32_t mid = (left + right) / 2;
|
||||
const FileMetaData* f = files[mid];
|
||||
if (cfd_->internal_comparator().InternalKeyComparator::Compare(
|
||||
f->largest.Encode(), internal_key) < 0) {
|
||||
// Key at "mid.largest" is < "target". Therefore all
|
||||
// files at or before "mid" are uninteresting.
|
||||
left = mid + 1;
|
||||
} else {
|
||||
// Key at "mid.largest" is >= "target". Therefore all files
|
||||
// after "mid" are uninteresting.
|
||||
right = mid;
|
||||
}
|
||||
}
|
||||
return right;
|
||||
auto cmp = [&](const FileMetaData* f, const Slice& key) -> bool {
|
||||
return cfd_->internal_comparator().InternalKeyComparator::Compare(
|
||||
f->largest.Encode(), key) < 0;
|
||||
};
|
||||
const auto &b = files.begin();
|
||||
return static_cast<uint32_t>(std::lower_bound(b + left,
|
||||
b + right, internal_key, cmp) - b);
|
||||
}
|
||||
|
||||
void ForwardIterator::DeleteIterator(InternalIterator* iter, bool is_arena) {
|
||||
|
||||
+24
-5
@@ -35,6 +35,14 @@ struct SuperVersionContext {
|
||||
explicit SuperVersionContext(bool create_superversion = false)
|
||||
: new_superversion(create_superversion ? new SuperVersion() : nullptr) {}
|
||||
|
||||
explicit SuperVersionContext(SuperVersionContext&& other)
|
||||
: superversions_to_free(std::move(other.superversions_to_free)),
|
||||
#ifndef ROCKSDB_DISABLE_STALL_NOTIFICATION
|
||||
write_stall_notifications(std::move(other.write_stall_notifications)),
|
||||
#endif
|
||||
new_superversion(std::move(other.new_superversion)) {
|
||||
}
|
||||
|
||||
void NewSuperVersion() {
|
||||
new_superversion = unique_ptr<SuperVersion>(new SuperVersion());
|
||||
}
|
||||
@@ -98,8 +106,15 @@ struct JobContext {
|
||||
}
|
||||
|
||||
inline bool HaveSomethingToClean() const {
|
||||
bool sv_have_sth = false;
|
||||
for (const auto& sv_ctx : superversion_contexts) {
|
||||
if (sv_ctx.HaveSomethingToDelete()) {
|
||||
sv_have_sth = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return memtables_to_free.size() > 0 || logs_to_free.size() > 0 ||
|
||||
superversion_context.HaveSomethingToDelete();
|
||||
sv_have_sth;
|
||||
}
|
||||
|
||||
// Structure to store information for candidate files to delete.
|
||||
@@ -142,7 +157,8 @@ struct JobContext {
|
||||
// a list of memtables to be free
|
||||
autovector<MemTable*> memtables_to_free;
|
||||
|
||||
SuperVersionContext superversion_context;
|
||||
// contexts for installing superversions for multiple column families
|
||||
std::vector<SuperVersionContext> superversion_contexts;
|
||||
|
||||
autovector<log::Writer*> logs_to_free;
|
||||
|
||||
@@ -158,13 +174,14 @@ struct JobContext {
|
||||
size_t num_alive_log_files = 0;
|
||||
uint64_t size_log_to_delete = 0;
|
||||
|
||||
explicit JobContext(int _job_id, bool create_superversion = false)
|
||||
: superversion_context(create_superversion) {
|
||||
explicit JobContext(int _job_id, bool create_superversion = false) {
|
||||
job_id = _job_id;
|
||||
manifest_file_number = 0;
|
||||
pending_manifest_file_number = 0;
|
||||
log_number = 0;
|
||||
prev_log_number = 0;
|
||||
superversion_contexts.emplace_back(
|
||||
SuperVersionContext(create_superversion));
|
||||
}
|
||||
|
||||
// For non-empty JobContext Clean() has to be called at least once before
|
||||
@@ -173,7 +190,9 @@ struct JobContext {
|
||||
// doing potentially slow Clean() with locked DB mutex.
|
||||
void Clean() {
|
||||
// free superversions
|
||||
superversion_context.Clean();
|
||||
for (auto& sv_context : superversion_contexts) {
|
||||
sv_context.Clean();
|
||||
}
|
||||
// free pending memtables
|
||||
for (auto m : memtables_to_free) {
|
||||
delete m;
|
||||
|
||||
+7
-2
@@ -417,7 +417,9 @@ TEST_F(EventListenerTest, DisableBGCompaction) {
|
||||
for (int i = 0; static_cast<int>(cf_meta.file_count) < kSlowdownTrigger * 10;
|
||||
++i) {
|
||||
Put(1, ToString(i), std::string(10000, 'x'), WriteOptions());
|
||||
db_->Flush(FlushOptions(), handles_[1]);
|
||||
FlushOptions fo;
|
||||
fo.allow_write_stall = true;
|
||||
db_->Flush(fo, handles_[1]);
|
||||
db_->GetColumnFamilyMetaData(handles_[1], &cf_meta);
|
||||
}
|
||||
ASSERT_GE(listener->slowdown_count, kSlowdownTrigger * 9);
|
||||
@@ -880,10 +882,13 @@ TEST_F(EventListenerTest, BackgroundErrorListenerFailedCompactionTest) {
|
||||
ASSERT_EQ(1, listener->counter());
|
||||
|
||||
// trigger flush so compaction is triggered again; this time it succeeds
|
||||
// The previous failed compaction may get retried automatically, so we may
|
||||
// be left with 0 or 1 files in level 1, depending on when the retry gets
|
||||
// scheduled
|
||||
ASSERT_OK(Put("key0", "val"));
|
||||
ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable());
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
ASSERT_EQ(0, NumTableFilesAtLevel(0));
|
||||
ASSERT_LE(1, NumTableFilesAtLevel(0));
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
+3
-3
@@ -37,9 +37,9 @@ static const unsigned int kBlockSize = 32768;
|
||||
// Header is checksum (4 bytes), length (2 bytes), type (1 byte)
|
||||
static const int kHeaderSize = 4 + 2 + 1;
|
||||
|
||||
// Recyclable header is checksum (4 bytes), type (1 byte), log number
|
||||
// (4 bytes), length (2 bytes).
|
||||
static const int kRecyclableHeaderSize = 4 + 1 + 4 + 2;
|
||||
// Recyclable header is checksum (4 bytes), length (2 bytes), type (1 byte),
|
||||
// log number (4 bytes).
|
||||
static const int kRecyclableHeaderSize = 4 + 2 + 1 + 4;
|
||||
|
||||
} // namespace log
|
||||
} // namespace rocksdb
|
||||
+5
-43
@@ -24,7 +24,7 @@ Reader::Reporter::~Reporter() {
|
||||
|
||||
Reader::Reader(std::shared_ptr<Logger> info_log,
|
||||
unique_ptr<SequentialFileReader>&& _file, Reporter* reporter,
|
||||
bool checksum, uint64_t initial_offset, uint64_t log_num)
|
||||
bool checksum, uint64_t log_num)
|
||||
: info_log_(info_log),
|
||||
file_(std::move(_file)),
|
||||
reporter_(reporter),
|
||||
@@ -36,7 +36,6 @@ Reader::Reader(std::shared_ptr<Logger> info_log,
|
||||
eof_offset_(0),
|
||||
last_record_offset_(0),
|
||||
end_of_buffer_offset_(0),
|
||||
initial_offset_(initial_offset),
|
||||
log_number_(log_num),
|
||||
recycled_(false) {}
|
||||
|
||||
@@ -44,29 +43,6 @@ Reader::~Reader() {
|
||||
delete[] backing_store_;
|
||||
}
|
||||
|
||||
bool Reader::SkipToInitialBlock() {
|
||||
size_t initial_offset_in_block = initial_offset_ % kBlockSize;
|
||||
uint64_t block_start_location = initial_offset_ - initial_offset_in_block;
|
||||
|
||||
// Don't search a block if we'd be in the trailer
|
||||
if (initial_offset_in_block > kBlockSize - 6) {
|
||||
block_start_location += kBlockSize;
|
||||
}
|
||||
|
||||
end_of_buffer_offset_ = block_start_location;
|
||||
|
||||
// Skip to start of first block that can contain the initial record
|
||||
if (block_start_location > 0) {
|
||||
Status skip_status = file_->Skip(block_start_location);
|
||||
if (!skip_status.ok()) {
|
||||
ReportDrop(static_cast<size_t>(block_start_location), skip_status);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// For kAbsoluteConsistency, on clean shutdown we don't expect any error
|
||||
// in the log files. For other modes, we can ignore only incomplete records
|
||||
// in the last log file, which are presumably due to a write in progress
|
||||
@@ -76,12 +52,6 @@ bool Reader::SkipToInitialBlock() {
|
||||
// restrict the inconsistency to only the last log
|
||||
bool Reader::ReadRecord(Slice* record, std::string* scratch,
|
||||
WALRecoveryMode wal_recovery_mode) {
|
||||
if (last_record_offset_ < initial_offset_) {
|
||||
if (!SkipToInitialBlock()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
scratch->clear();
|
||||
record->clear();
|
||||
bool in_fragmented_record = false;
|
||||
@@ -299,8 +269,7 @@ void Reader::ReportCorruption(size_t bytes, const char* reason) {
|
||||
}
|
||||
|
||||
void Reader::ReportDrop(size_t bytes, const Status& reason) {
|
||||
if (reporter_ != nullptr &&
|
||||
end_of_buffer_offset_ - buffer_.size() - bytes >= initial_offset_) {
|
||||
if (reporter_ != nullptr) {
|
||||
reporter_->Corruption(bytes, reason);
|
||||
}
|
||||
}
|
||||
@@ -317,7 +286,7 @@ bool Reader::ReadMore(size_t* drop_size, int *error) {
|
||||
read_error_ = true;
|
||||
*error = kEof;
|
||||
return false;
|
||||
} else if (buffer_.size() < (size_t)kBlockSize) {
|
||||
} else if (buffer_.size() < static_cast<size_t>(kBlockSize)) {
|
||||
eof_ = true;
|
||||
eof_offset_ = buffer_.size();
|
||||
}
|
||||
@@ -342,7 +311,7 @@ bool Reader::ReadMore(size_t* drop_size, int *error) {
|
||||
unsigned int Reader::ReadPhysicalRecord(Slice* result, size_t* drop_size) {
|
||||
while (true) {
|
||||
// We need at least the minimum header size
|
||||
if (buffer_.size() < (size_t)kHeaderSize) {
|
||||
if (buffer_.size() < static_cast<size_t>(kHeaderSize)) {
|
||||
int r;
|
||||
if (!ReadMore(drop_size, &r)) {
|
||||
return r;
|
||||
@@ -363,7 +332,7 @@ unsigned int Reader::ReadPhysicalRecord(Slice* result, size_t* drop_size) {
|
||||
}
|
||||
header_size = kRecyclableHeaderSize;
|
||||
// We need enough for the larger header
|
||||
if (buffer_.size() < (size_t)kRecyclableHeaderSize) {
|
||||
if (buffer_.size() < static_cast<size_t>(kRecyclableHeaderSize)) {
|
||||
int r;
|
||||
if (!ReadMore(drop_size, &r)) {
|
||||
return r;
|
||||
@@ -417,13 +386,6 @@ unsigned int Reader::ReadPhysicalRecord(Slice* result, size_t* drop_size) {
|
||||
|
||||
buffer_.remove_prefix(header_size + length);
|
||||
|
||||
// Skip physical record that started before initial_offset_
|
||||
if (end_of_buffer_offset_ - buffer_.size() - header_size - length <
|
||||
initial_offset_) {
|
||||
result->clear();
|
||||
return kBadRecord;
|
||||
}
|
||||
|
||||
*result = Slice(header + header_size, length);
|
||||
return type;
|
||||
}
|
||||
|
||||
+3
-16
@@ -50,14 +50,10 @@ class Reader {
|
||||
// live while this Reader is in use.
|
||||
//
|
||||
// If "checksum" is true, verify checksums if available.
|
||||
//
|
||||
// The Reader will start reading at the first record located at physical
|
||||
// position >= initial_offset within the file.
|
||||
Reader(std::shared_ptr<Logger> info_log,
|
||||
// @lint-ignore TXT2 T25377293 Grandfathered in
|
||||
unique_ptr<SequentialFileReader>&& file,
|
||||
Reporter* reporter, bool checksum, uint64_t initial_offset,
|
||||
uint64_t log_num);
|
||||
// @lint-ignore TXT2 T25377293 Grandfathered in
|
||||
unique_ptr<SequentialFileReader>&& file, Reporter* reporter,
|
||||
bool checksum, uint64_t log_num);
|
||||
|
||||
~Reader();
|
||||
|
||||
@@ -108,9 +104,6 @@ class Reader {
|
||||
// Offset of the first location past the end of buffer_.
|
||||
uint64_t end_of_buffer_offset_;
|
||||
|
||||
// Offset at which to start looking for the first record to return
|
||||
uint64_t const initial_offset_;
|
||||
|
||||
// which log number this is
|
||||
uint64_t const log_number_;
|
||||
|
||||
@@ -124,7 +117,6 @@ class Reader {
|
||||
// Currently there are three situations in which this happens:
|
||||
// * The record has an invalid CRC (ReadPhysicalRecord reports a drop)
|
||||
// * The record is a 0-length record (No drop is reported)
|
||||
// * The record is below constructor's initial_offset (No drop is reported)
|
||||
kBadRecord = kMaxRecordType + 2,
|
||||
// Returned when we fail to read a valid header.
|
||||
kBadHeader = kMaxRecordType + 3,
|
||||
@@ -136,11 +128,6 @@ class Reader {
|
||||
kBadRecordChecksum = kMaxRecordType + 6,
|
||||
};
|
||||
|
||||
// Skips all blocks that are completely before "initial_offset_".
|
||||
//
|
||||
// Returns true on success. Handles reporting.
|
||||
bool SkipToInitialBlock();
|
||||
|
||||
// Return type, or one of the preceding special values
|
||||
unsigned int ReadPhysicalRecord(Slice* result, size_t* drop_size);
|
||||
|
||||
|
||||
+5
-83
@@ -159,12 +159,12 @@ class LogTest : public ::testing::TestWithParam<int> {
|
||||
LogTest()
|
||||
: reader_contents_(),
|
||||
dest_holder_(test::GetWritableFileWriter(
|
||||
new test::StringSink(&reader_contents_))),
|
||||
new test::StringSink(&reader_contents_), "" /* don't care */)),
|
||||
source_holder_(test::GetSequentialFileReader(
|
||||
new StringSource(reader_contents_), "" /* file name */)),
|
||||
writer_(std::move(dest_holder_), 123, GetParam()),
|
||||
reader_(nullptr, std::move(source_holder_), &report_, true /*checksum*/,
|
||||
0 /*initial_offset*/, 123) {
|
||||
reader_(nullptr, std::move(source_holder_), &report_,
|
||||
true /* checksum */, 123 /* log_number */) {
|
||||
int header_size = GetParam() ? kRecyclableHeaderSize : kHeaderSize;
|
||||
initial_offset_last_record_offsets_[0] = 0;
|
||||
initial_offset_last_record_offsets_[1] = header_size + 10000;
|
||||
@@ -266,36 +266,6 @@ class LogTest : public ::testing::TestWithParam<int> {
|
||||
}
|
||||
}
|
||||
|
||||
void CheckOffsetPastEndReturnsNoRecords(uint64_t offset_past_end) {
|
||||
WriteInitialOffsetLog();
|
||||
unique_ptr<SequentialFileReader> file_reader(test::GetSequentialFileReader(
|
||||
new StringSource(reader_contents_), "" /* fname */));
|
||||
unique_ptr<Reader> offset_reader(
|
||||
new Reader(nullptr, std::move(file_reader), &report_,
|
||||
true /*checksum*/, WrittenBytes() + offset_past_end, 123));
|
||||
Slice record;
|
||||
std::string scratch;
|
||||
ASSERT_TRUE(!offset_reader->ReadRecord(&record, &scratch));
|
||||
}
|
||||
|
||||
void CheckInitialOffsetRecord(uint64_t initial_offset,
|
||||
int expected_record_offset) {
|
||||
WriteInitialOffsetLog();
|
||||
unique_ptr<SequentialFileReader> file_reader(test::GetSequentialFileReader(
|
||||
new StringSource(reader_contents_), "" /* fname */));
|
||||
unique_ptr<Reader> offset_reader(
|
||||
new Reader(nullptr, std::move(file_reader), &report_,
|
||||
true /*checksum*/, initial_offset, 123));
|
||||
Slice record;
|
||||
std::string scratch;
|
||||
ASSERT_TRUE(offset_reader->ReadRecord(&record, &scratch));
|
||||
ASSERT_EQ(initial_offset_record_sizes_[expected_record_offset],
|
||||
record.size());
|
||||
ASSERT_EQ(initial_offset_last_record_offsets_[expected_record_offset],
|
||||
offset_reader->LastRecordOffset());
|
||||
ASSERT_EQ((char)('a' + expected_record_offset), record.data()[0]);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
size_t LogTest::initial_offset_record_sizes_[] =
|
||||
@@ -590,55 +560,6 @@ TEST_P(LogTest, ErrorJoinsRecords) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(LogTest, ReadStart) { CheckInitialOffsetRecord(0, 0); }
|
||||
|
||||
TEST_P(LogTest, ReadSecondOneOff) { CheckInitialOffsetRecord(1, 1); }
|
||||
|
||||
TEST_P(LogTest, ReadSecondTenThousand) { CheckInitialOffsetRecord(10000, 1); }
|
||||
|
||||
TEST_P(LogTest, ReadSecondStart) {
|
||||
int header_size = GetParam() ? kRecyclableHeaderSize : kHeaderSize;
|
||||
CheckInitialOffsetRecord(10000 + header_size, 1);
|
||||
}
|
||||
|
||||
TEST_P(LogTest, ReadThirdOneOff) {
|
||||
int header_size = GetParam() ? kRecyclableHeaderSize : kHeaderSize;
|
||||
CheckInitialOffsetRecord(10000 + header_size + 1, 2);
|
||||
}
|
||||
|
||||
TEST_P(LogTest, ReadThirdStart) {
|
||||
int header_size = GetParam() ? kRecyclableHeaderSize : kHeaderSize;
|
||||
CheckInitialOffsetRecord(20000 + 2 * header_size, 2);
|
||||
}
|
||||
|
||||
TEST_P(LogTest, ReadFourthOneOff) {
|
||||
int header_size = GetParam() ? kRecyclableHeaderSize : kHeaderSize;
|
||||
CheckInitialOffsetRecord(20000 + 2 * header_size + 1, 3);
|
||||
}
|
||||
|
||||
TEST_P(LogTest, ReadFourthFirstBlockTrailer) {
|
||||
CheckInitialOffsetRecord(log::kBlockSize - 4, 3);
|
||||
}
|
||||
|
||||
TEST_P(LogTest, ReadFourthMiddleBlock) {
|
||||
CheckInitialOffsetRecord(log::kBlockSize + 1, 3);
|
||||
}
|
||||
|
||||
TEST_P(LogTest, ReadFourthLastBlock) {
|
||||
CheckInitialOffsetRecord(2 * log::kBlockSize + 1, 3);
|
||||
}
|
||||
|
||||
TEST_P(LogTest, ReadFourthStart) {
|
||||
int header_size = GetParam() ? kRecyclableHeaderSize : kHeaderSize;
|
||||
CheckInitialOffsetRecord(
|
||||
2 * (header_size + 1000) + (2 * log::kBlockSize - 1000) + 3 * header_size,
|
||||
3);
|
||||
}
|
||||
|
||||
TEST_P(LogTest, ReadEnd) { CheckOffsetPastEndReturnsNoRecords(0); }
|
||||
|
||||
TEST_P(LogTest, ReadPastEnd) { CheckOffsetPastEndReturnsNoRecords(5); }
|
||||
|
||||
TEST_P(LogTest, ClearEofSingleBlock) {
|
||||
Write("foo");
|
||||
Write("bar");
|
||||
@@ -718,7 +639,8 @@ TEST_P(LogTest, Recycle) {
|
||||
Write("xxxxxxxxxxxxxxxx");
|
||||
}
|
||||
unique_ptr<WritableFileWriter> dest_holder(test::GetWritableFileWriter(
|
||||
new test::OverwritingStringSink(get_reader_contents())));
|
||||
new test::OverwritingStringSink(get_reader_contents()),
|
||||
"" /* don't care */));
|
||||
Writer recycle_writer(std::move(dest_holder), 123, true);
|
||||
recycle_writer.AddRecord(Slice("foooo"));
|
||||
recycle_writer.AddRecord(Slice("bar"));
|
||||
|
||||
+4
-2
@@ -18,9 +18,11 @@ namespace rocksdb {
|
||||
#ifdef ROCKSDB_JEMALLOC
|
||||
#ifdef __FreeBSD__
|
||||
#include <malloc_np.h>
|
||||
#define je_malloc_stats_print malloc_stats_print
|
||||
#else
|
||||
#include "jemalloc/jemalloc.h"
|
||||
#ifdef JEMALLOC_NO_RENAME
|
||||
#define malloc_stats_print je_malloc_stats_print
|
||||
#endif
|
||||
#endif
|
||||
|
||||
typedef struct {
|
||||
@@ -48,7 +50,7 @@ void DumpMallocStats(std::string* stats) {
|
||||
std::unique_ptr<char[]> buf{new char[kMallocStatusLen + 1]};
|
||||
mstat.cur = buf.get();
|
||||
mstat.end = buf.get() + kMallocStatusLen;
|
||||
je_malloc_stats_print(GetJemallocStatus, &mstat, "");
|
||||
malloc_stats_print(GetJemallocStatus, &mstat, "");
|
||||
stats->append(buf.get());
|
||||
}
|
||||
#else
|
||||
|
||||
@@ -1,254 +0,0 @@
|
||||
// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under both the GPLv2 (found in the
|
||||
// COPYING file in the root directory) and Apache 2.0 License
|
||||
// (found in the LICENSE.Apache file in the root directory).
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
|
||||
#include "db/managed_iterator.h"
|
||||
|
||||
#include <limits>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include "db/column_family.h"
|
||||
#include "db/db_impl.h"
|
||||
#include "db/db_iter.h"
|
||||
#include "db/dbformat.h"
|
||||
#include "rocksdb/env.h"
|
||||
#include "rocksdb/slice.h"
|
||||
#include "rocksdb/slice_transform.h"
|
||||
#include "table/merging_iterator.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
namespace {
|
||||
// Helper class that locks a mutex on construction and unlocks the mutex when
|
||||
// the destructor of the MutexLock object is invoked.
|
||||
//
|
||||
// Typical usage:
|
||||
//
|
||||
// void MyClass::MyMethod() {
|
||||
// MILock l(&mu_); // mu_ is an instance variable
|
||||
// ... some complex code, possibly with multiple return paths ...
|
||||
// }
|
||||
|
||||
class MILock {
|
||||
public:
|
||||
explicit MILock(std::mutex* mu, ManagedIterator* mi) : mu_(mu), mi_(mi) {
|
||||
this->mu_->lock();
|
||||
}
|
||||
~MILock() {
|
||||
this->mu_->unlock();
|
||||
}
|
||||
ManagedIterator* GetManagedIterator() { return mi_; }
|
||||
|
||||
private:
|
||||
std::mutex* const mu_;
|
||||
ManagedIterator* mi_;
|
||||
// No copying allowed
|
||||
MILock(const MILock&) = delete;
|
||||
void operator=(const MILock&) = delete;
|
||||
};
|
||||
} // anonymous namespace
|
||||
|
||||
//
|
||||
// Synchronization between modifiers, releasers, creators
|
||||
// If iterator operation, wait till (!in_use), set in_use, do op, reset in_use
|
||||
// if modifying mutable_iter, atomically exchange in_use:
|
||||
// return if in_use set / otherwise set in use,
|
||||
// atomically replace new iter with old , reset in use
|
||||
// The releaser is the new operation and it holds a lock for a very short time
|
||||
// The existing non-const iterator operations are supposed to be single
|
||||
// threaded and hold the lock for the duration of the operation
|
||||
// The existing const iterator operations use the cached key/values
|
||||
// and don't do any locking.
|
||||
ManagedIterator::ManagedIterator(DBImpl* db, const ReadOptions& read_options,
|
||||
ColumnFamilyData* cfd)
|
||||
: db_(db),
|
||||
read_options_(read_options),
|
||||
cfd_(cfd),
|
||||
svnum_(cfd->GetSuperVersionNumber()),
|
||||
mutable_iter_(nullptr),
|
||||
valid_(false),
|
||||
snapshot_created_(false),
|
||||
release_supported_(true) {
|
||||
read_options_.managed = false;
|
||||
if ((!read_options_.tailing) && (read_options_.snapshot == nullptr)) {
|
||||
assert(nullptr != (read_options_.snapshot = db_->GetSnapshot()));
|
||||
snapshot_created_ = true;
|
||||
}
|
||||
cfh_.SetCFD(cfd);
|
||||
mutable_iter_ = unique_ptr<Iterator>(db->NewIterator(read_options_, &cfh_));
|
||||
}
|
||||
|
||||
ManagedIterator::~ManagedIterator() {
|
||||
Lock();
|
||||
if (snapshot_created_) {
|
||||
db_->ReleaseSnapshot(read_options_.snapshot);
|
||||
snapshot_created_ = false;
|
||||
read_options_.snapshot = nullptr;
|
||||
}
|
||||
UnLock();
|
||||
}
|
||||
|
||||
bool ManagedIterator::Valid() const { return valid_; }
|
||||
|
||||
void ManagedIterator::SeekToLast() {
|
||||
MILock l(&in_use_, this);
|
||||
if (NeedToRebuild()) {
|
||||
RebuildIterator();
|
||||
}
|
||||
assert(mutable_iter_ != nullptr);
|
||||
mutable_iter_->SeekToLast();
|
||||
UpdateCurrent();
|
||||
}
|
||||
|
||||
void ManagedIterator::SeekToFirst() {
|
||||
MILock l(&in_use_, this);
|
||||
SeekInternal(Slice(), true);
|
||||
}
|
||||
|
||||
void ManagedIterator::Seek(const Slice& user_key) {
|
||||
MILock l(&in_use_, this);
|
||||
SeekInternal(user_key, false);
|
||||
}
|
||||
|
||||
void ManagedIterator::SeekForPrev(const Slice& user_key) {
|
||||
MILock l(&in_use_, this);
|
||||
if (NeedToRebuild()) {
|
||||
RebuildIterator();
|
||||
}
|
||||
assert(mutable_iter_ != nullptr);
|
||||
mutable_iter_->SeekForPrev(user_key);
|
||||
UpdateCurrent();
|
||||
}
|
||||
|
||||
void ManagedIterator::SeekInternal(const Slice& user_key, bool seek_to_first) {
|
||||
if (NeedToRebuild()) {
|
||||
RebuildIterator();
|
||||
}
|
||||
assert(mutable_iter_ != nullptr);
|
||||
if (seek_to_first) {
|
||||
mutable_iter_->SeekToFirst();
|
||||
} else {
|
||||
mutable_iter_->Seek(user_key);
|
||||
}
|
||||
UpdateCurrent();
|
||||
}
|
||||
|
||||
void ManagedIterator::Prev() {
|
||||
if (!valid_) {
|
||||
status_ = Status::InvalidArgument("Iterator value invalid");
|
||||
return;
|
||||
}
|
||||
MILock l(&in_use_, this);
|
||||
if (NeedToRebuild()) {
|
||||
RebuildIterator(true);
|
||||
if (!valid_) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
mutable_iter_->Prev();
|
||||
UpdateCurrent();
|
||||
}
|
||||
|
||||
void ManagedIterator::Next() {
|
||||
if (!valid_) {
|
||||
status_ = Status::InvalidArgument("Iterator value invalid");
|
||||
return;
|
||||
}
|
||||
MILock l(&in_use_, this);
|
||||
if (NeedToRebuild()) {
|
||||
RebuildIterator(true);
|
||||
if (!valid_) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
mutable_iter_->Next();
|
||||
UpdateCurrent();
|
||||
}
|
||||
|
||||
Slice ManagedIterator::key() const {
|
||||
assert(valid_);
|
||||
return cached_key_.GetUserKey();
|
||||
}
|
||||
|
||||
Slice ManagedIterator::value() const {
|
||||
assert(valid_);
|
||||
return cached_value_.GetUserKey();
|
||||
}
|
||||
|
||||
Status ManagedIterator::status() const { return status_; }
|
||||
|
||||
void ManagedIterator::RebuildIterator(bool reseek) {
|
||||
std::string current_key;
|
||||
if (reseek) {
|
||||
current_key = key().ToString();
|
||||
}
|
||||
|
||||
svnum_ = cfd_->GetSuperVersionNumber();
|
||||
mutable_iter_ = unique_ptr<Iterator>(db_->NewIterator(read_options_, &cfh_));
|
||||
|
||||
if (reseek) {
|
||||
Slice old_key(current_key.data(), current_key.size());
|
||||
SeekInternal(old_key, false);
|
||||
UpdateCurrent();
|
||||
if (!valid_ || key().compare(old_key) != 0) {
|
||||
valid_ = false;
|
||||
status_ = Status::Incomplete(
|
||||
"Next/Prev failed because current key has "
|
||||
"been removed");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ManagedIterator::UpdateCurrent() {
|
||||
assert(mutable_iter_ != nullptr);
|
||||
|
||||
valid_ = mutable_iter_->Valid();
|
||||
status_ = mutable_iter_->status();
|
||||
|
||||
if (!valid_) {
|
||||
return;
|
||||
}
|
||||
|
||||
cached_key_.SetUserKey(mutable_iter_->key());
|
||||
cached_value_.SetUserKey(mutable_iter_->value());
|
||||
}
|
||||
|
||||
void ManagedIterator::ReleaseIter(bool only_old) {
|
||||
if ((mutable_iter_ == nullptr) || (!release_supported_)) {
|
||||
return;
|
||||
}
|
||||
if (svnum_ != cfd_->GetSuperVersionNumber() || !only_old) {
|
||||
if (!TryLock()) { // Don't release iter if in use
|
||||
return;
|
||||
}
|
||||
mutable_iter_ = nullptr; // in_use for a very short time
|
||||
UnLock();
|
||||
}
|
||||
}
|
||||
|
||||
bool ManagedIterator::NeedToRebuild() {
|
||||
if ((mutable_iter_ == nullptr) || (status_.IsIncomplete()) ||
|
||||
(!only_drop_old_ && (svnum_ != cfd_->GetSuperVersionNumber()))) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void ManagedIterator::Lock() {
|
||||
in_use_.lock();
|
||||
return;
|
||||
}
|
||||
|
||||
bool ManagedIterator::TryLock() { return in_use_.try_lock(); }
|
||||
|
||||
void ManagedIterator::UnLock() {
|
||||
in_use_.unlock();
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
#endif // ROCKSDB_LITE
|
||||
@@ -1,85 +0,0 @@
|
||||
// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under both the GPLv2 (found in the
|
||||
// COPYING file in the root directory) and Apache 2.0 License
|
||||
// (found in the LICENSE.Apache file in the root directory).
|
||||
#pragma once
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
|
||||
#include <mutex>
|
||||
#include <queue>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "db/column_family.h"
|
||||
#include "rocksdb/db.h"
|
||||
#include "rocksdb/iterator.h"
|
||||
#include "rocksdb/options.h"
|
||||
#include "util/arena.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
class DBImpl;
|
||||
struct SuperVersion;
|
||||
class ColumnFamilyData;
|
||||
|
||||
/**
|
||||
* ManagedIterator is a special type of iterator that supports freeing the
|
||||
* underlying iterator and still being able to access the current key/value
|
||||
* pair. This is done by copying the key/value pair so that clients can
|
||||
* continue to access the data without getting a SIGSEGV.
|
||||
* The underlying iterator can be freed manually through the call to
|
||||
* ReleaseIter or automatically (as needed on space pressure or age.)
|
||||
* The iterator is recreated using the saved original arguments.
|
||||
*/
|
||||
class ManagedIterator : public Iterator {
|
||||
public:
|
||||
ManagedIterator(DBImpl* db, const ReadOptions& read_options,
|
||||
ColumnFamilyData* cfd);
|
||||
virtual ~ManagedIterator();
|
||||
|
||||
virtual void SeekToLast() override;
|
||||
virtual void Prev() override;
|
||||
virtual bool Valid() const override;
|
||||
void SeekToFirst() override;
|
||||
virtual void Seek(const Slice& target) override;
|
||||
virtual void SeekForPrev(const Slice& target) override;
|
||||
virtual void Next() override;
|
||||
virtual Slice key() const override;
|
||||
virtual Slice value() const override;
|
||||
virtual Status status() const override;
|
||||
void ReleaseIter(bool only_old);
|
||||
void SetDropOld(bool only_old) {
|
||||
only_drop_old_ = read_options_.tailing || only_old;
|
||||
}
|
||||
|
||||
private:
|
||||
void RebuildIterator(bool reseek = false);
|
||||
void UpdateCurrent();
|
||||
void SeekInternal(const Slice& user_key, bool seek_to_first);
|
||||
bool NeedToRebuild();
|
||||
void Lock();
|
||||
bool TryLock();
|
||||
void UnLock();
|
||||
DBImpl* const db_;
|
||||
ReadOptions read_options_;
|
||||
ColumnFamilyData* const cfd_;
|
||||
ColumnFamilyHandleInternal cfh_;
|
||||
|
||||
uint64_t svnum_;
|
||||
std::unique_ptr<Iterator> mutable_iter_;
|
||||
// internal iterator status
|
||||
Status status_;
|
||||
bool valid_;
|
||||
|
||||
IterKey cached_key_;
|
||||
IterKey cached_value_;
|
||||
|
||||
bool only_drop_old_ = true;
|
||||
bool snapshot_created_;
|
||||
bool release_supported_;
|
||||
std::mutex in_use_; // is managed iterator in use
|
||||
};
|
||||
|
||||
} // namespace rocksdb
|
||||
#endif // !ROCKSDB_LITE
|
||||
+1
-1
@@ -697,7 +697,7 @@ static bool SaveValue(void* arg, const char* entry) {
|
||||
*(s->merge_in_progress) = true;
|
||||
merge_context->PushOperand(
|
||||
v, s->inplace_update_support == false /* operand_pinned */);
|
||||
if (merge_operator->ShouldMerge(merge_context->GetOperands())) {
|
||||
if (merge_operator->ShouldMerge(merge_context->GetOperandsDirectionBackward())) {
|
||||
*(s->status) = MergeHelper::TimedFullMerge(
|
||||
merge_operator, s->key->user_key(), nullptr,
|
||||
merge_context->GetOperands(), s->value, s->logger, s->statistics,
|
||||
|
||||
+8
-1
@@ -34,7 +34,6 @@ namespace rocksdb {
|
||||
class Mutex;
|
||||
class MemTableIterator;
|
||||
class MergeContext;
|
||||
class InternalIterator;
|
||||
|
||||
struct ImmutableMemTableOptions {
|
||||
explicit ImmutableMemTableOptions(const ImmutableCFOptions& ioptions,
|
||||
@@ -337,6 +336,14 @@ class MemTable {
|
||||
mem_tracker_.DoneAllocating();
|
||||
}
|
||||
|
||||
// Notify the underlying storage that all data it contained has been
|
||||
// persisted.
|
||||
// REQUIRES: external synchronization to prevent simultaneous
|
||||
// operations on the same MemTable.
|
||||
void MarkFlushed() {
|
||||
table_->MarkFlushed();
|
||||
}
|
||||
|
||||
// return true if the current MemTableRep supports merge operator.
|
||||
bool IsMergeOperatorSupported() const {
|
||||
return table_->IsMergeOperatorSupported();
|
||||
|
||||
@@ -248,6 +248,7 @@ void MemTableListVersion::Remove(MemTable* m,
|
||||
assert(refs_ == 1); // only when refs_ == 1 is MemTableListVersion mutable
|
||||
memlist_.remove(m);
|
||||
|
||||
m->MarkFlushed();
|
||||
if (max_write_buffer_number_to_maintain_ > 0) {
|
||||
memlist_history_.push_front(m);
|
||||
TrimHistory(to_delete);
|
||||
|
||||
+16
-1
@@ -74,8 +74,13 @@ class MergeContext {
|
||||
return (*operand_list_)[index];
|
||||
}
|
||||
|
||||
// Return all the operands.
|
||||
// Same as GetOperandsDirectionForward
|
||||
const std::vector<Slice>& GetOperands() {
|
||||
return GetOperandsDirectionForward();
|
||||
}
|
||||
|
||||
// Return all the operands in the order as they were merged (passed to FullMerge or FullMergeV2)
|
||||
const std::vector<Slice>& GetOperandsDirectionForward() {
|
||||
if (!operand_list_) {
|
||||
return empty_operand_list;
|
||||
}
|
||||
@@ -84,6 +89,16 @@ class MergeContext {
|
||||
return *operand_list_;
|
||||
}
|
||||
|
||||
// Return all the operands in the reversed order relative to how they were merged (passed to FullMerge or FullMergeV2)
|
||||
const std::vector<Slice>& GetOperandsDirectionBackward() {
|
||||
if (!operand_list_) {
|
||||
return empty_operand_list;
|
||||
}
|
||||
|
||||
SetDirectionBackward();
|
||||
return *operand_list_;
|
||||
}
|
||||
|
||||
private:
|
||||
void Initialize() {
|
||||
if (!operand_list_) {
|
||||
|
||||
+1
-5
@@ -3,8 +3,7 @@
|
||||
// COPYING file in the root directory) and Apache 2.0 License
|
||||
// (found in the LICENSE.Apache file in the root directory).
|
||||
//
|
||||
#ifndef MERGE_HELPER_H
|
||||
#define MERGE_HELPER_H
|
||||
#pragma once
|
||||
|
||||
#include <deque>
|
||||
#include <string>
|
||||
@@ -26,7 +25,6 @@ class Iterator;
|
||||
class Logger;
|
||||
class MergeOperator;
|
||||
class Statistics;
|
||||
class InternalIterator;
|
||||
|
||||
class MergeHelper {
|
||||
public:
|
||||
@@ -194,5 +192,3 @@ class MergeOutputIterator {
|
||||
};
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
#endif
|
||||
@@ -227,16 +227,24 @@ TEST_F(ObsoleteFilesTest, DeleteObsoleteOptionsFile) {
|
||||
}
|
||||
ASSERT_OK(dbi->EnableFileDeletions(true /* force */));
|
||||
ASSERT_EQ(optsfiles_nums.size(), optsfiles_keep.size());
|
||||
int size = static_cast<int>(optsfiles_nums.size());
|
||||
int kept_opts_files_count = 0;
|
||||
for (int i = 0; i != size; ++i) {
|
||||
if (optsfiles_keep[i]) {
|
||||
++kept_opts_files_count;
|
||||
}
|
||||
}
|
||||
ASSERT_EQ(2, kept_opts_files_count);
|
||||
|
||||
CloseDB();
|
||||
|
||||
std::vector<std::string> files;
|
||||
int opts_file_count = 0;
|
||||
ASSERT_OK(env_->GetChildren(dbname_, &files));
|
||||
for (const auto& file : files) {
|
||||
uint64_t file_num;
|
||||
Slice dummy_info_log_name_prefix;
|
||||
FileType type;
|
||||
WalFileType log_type;
|
||||
if (ParseFileName(file, &file_num, dummy_info_log_name_prefix, &type,
|
||||
&log_type) &&
|
||||
type == kOptionsFile) {
|
||||
opts_file_count++;
|
||||
}
|
||||
}
|
||||
ASSERT_EQ(2, opts_file_count);
|
||||
}
|
||||
|
||||
} //namespace rocksdb
|
||||
|
||||
@@ -469,7 +469,7 @@ void ProfileQueries(bool enabled_time = false) {
|
||||
ASSERT_GT(hist_num_memtable_checked.Average(), 0);
|
||||
// In read-only mode Get(), no super version operation is needed
|
||||
ASSERT_EQ(hist_get_post_process.Average(), 0);
|
||||
ASSERT_EQ(hist_get_snapshot.Average(), 0);
|
||||
ASSERT_GT(hist_get_snapshot.Average(), 0);
|
||||
|
||||
ASSERT_GT(hist_mget.Average(), 0);
|
||||
ASSERT_GT(hist_mget_snapshot.Average(), 0);
|
||||
|
||||
+78
-43
@@ -50,7 +50,7 @@ class UncollapsedRangeDelMap : public RangeDelMap {
|
||||
: rep_(TombstoneStartKeyComparator(ucmp)), ucmp_(ucmp) {}
|
||||
|
||||
bool ShouldDelete(const ParsedInternalKey& parsed,
|
||||
RangeDelPositioningMode mode) {
|
||||
RangeDelPositioningMode mode) override {
|
||||
(void)mode;
|
||||
assert(mode == RangeDelPositioningMode::kFullScan);
|
||||
for (const auto& tombstone : rep_) {
|
||||
@@ -65,7 +65,7 @@ class UncollapsedRangeDelMap : public RangeDelMap {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IsRangeOverlapped(const Slice& start, const Slice& end) {
|
||||
bool IsRangeOverlapped(const Slice& start, const Slice& end) override {
|
||||
for (const auto& tombstone : rep_) {
|
||||
if (ucmp_->Compare(start, tombstone.end_key_) < 0 &&
|
||||
ucmp_->Compare(tombstone.start_key_, end) <= 0 &&
|
||||
@@ -76,13 +76,15 @@ class UncollapsedRangeDelMap : public RangeDelMap {
|
||||
return false;
|
||||
}
|
||||
|
||||
void AddTombstone(RangeTombstone tombstone) { rep_.emplace(tombstone); }
|
||||
void AddTombstone(RangeTombstone tombstone) override {
|
||||
rep_.emplace(tombstone);
|
||||
}
|
||||
|
||||
size_t Size() const { return rep_.size(); }
|
||||
size_t Size() const override { return rep_.size(); }
|
||||
|
||||
void InvalidatePosition() {} // no-op
|
||||
void InvalidatePosition() override {} // no-op
|
||||
|
||||
std::unique_ptr<RangeDelIterator> NewIterator() {
|
||||
std::unique_ptr<RangeDelIterator> NewIterator() override {
|
||||
return std::unique_ptr<RangeDelIterator>(new Iterator(this->rep_));
|
||||
}
|
||||
};
|
||||
@@ -155,6 +157,9 @@ class CollapsedRangeDelMap : public RangeDelMap {
|
||||
}
|
||||
|
||||
RangeTombstone Tombstone() const override {
|
||||
assert(Valid());
|
||||
assert(std::next(iter_) != rep_.end());
|
||||
assert(iter_->second != 0);
|
||||
RangeTombstone tombstone;
|
||||
tombstone.start_key_ = iter_->first;
|
||||
tombstone.end_key_ = std::next(iter_)->first;
|
||||
@@ -168,12 +173,14 @@ class CollapsedRangeDelMap : public RangeDelMap {
|
||||
const Comparator* ucmp_;
|
||||
|
||||
public:
|
||||
CollapsedRangeDelMap(const Comparator* ucmp) : ucmp_(ucmp) {
|
||||
explicit CollapsedRangeDelMap(const Comparator* ucmp)
|
||||
: rep_(stl_wrappers::LessOfComparator(ucmp)),
|
||||
ucmp_(ucmp) {
|
||||
InvalidatePosition();
|
||||
}
|
||||
|
||||
bool ShouldDelete(const ParsedInternalKey& parsed,
|
||||
RangeDelPositioningMode mode) {
|
||||
RangeDelPositioningMode mode) override {
|
||||
if (iter_ == rep_.end() &&
|
||||
(mode == RangeDelPositioningMode::kForwardTraversal ||
|
||||
mode == RangeDelPositioningMode::kBackwardTraversal)) {
|
||||
@@ -224,15 +231,15 @@ class CollapsedRangeDelMap : public RangeDelMap {
|
||||
return parsed.sequence < iter_->second;
|
||||
}
|
||||
|
||||
bool IsRangeOverlapped(const Slice&, const Slice&) {
|
||||
bool IsRangeOverlapped(const Slice&, const Slice&) override {
|
||||
// Unimplemented because the only client of this method, file ingestion,
|
||||
// uses uncollapsed maps.
|
||||
fprintf(stderr, "CollapsedRangeDelMap::IsRangeOverlapped unimplemented");
|
||||
abort();
|
||||
}
|
||||
|
||||
void AddTombstone(RangeTombstone t) {
|
||||
if (ucmp_->Compare(t.start_key_, t.end_key_) >= 0) {
|
||||
void AddTombstone(RangeTombstone t) override {
|
||||
if (ucmp_->Compare(t.start_key_, t.end_key_) >= 0 || t.seq_ == 0) {
|
||||
// The tombstone covers no keys. Nothing to do.
|
||||
return;
|
||||
}
|
||||
@@ -262,22 +269,36 @@ class CollapsedRangeDelMap : public RangeDelMap {
|
||||
// 2: c--- OR 2: c--- OR 2: c--- OR 2: c------
|
||||
// 1: A--C 1: 1: A------ 1: C------
|
||||
// ^ ^ ^ ^
|
||||
// Insert a new transition at the new tombstone's start point, or raise
|
||||
// the existing transition at that point to the new tombstone's seqno.
|
||||
end_seq = prev_seq();
|
||||
rep_[t.start_key_] = t.seq_; // operator[] will overwrite existing entry
|
||||
Rep::iterator pit;
|
||||
if (it != rep_.begin() && (pit = std::prev(it)) != rep_.begin() &&
|
||||
ucmp_->Compare(pit->first, t.start_key_) == 0 && std::prev(pit)->second == t.seq_) {
|
||||
// The new tombstone starts at the end of an existing tombstone with an
|
||||
// identical seqno:
|
||||
//
|
||||
// 3:
|
||||
// 2: A--C---
|
||||
// 1:
|
||||
// ^
|
||||
// Merge the tombstones by removing the existing tombstone's end key.
|
||||
it = rep_.erase(std::prev(it));
|
||||
} else {
|
||||
// Insert a new transition at the new tombstone's start point, or raise
|
||||
// the existing transition at that point to the new tombstone's seqno.
|
||||
rep_[t.start_key_] = t.seq_; // operator[] will overwrite existing entry
|
||||
}
|
||||
} else {
|
||||
// The new tombstone's start point is covered by an existing tombstone:
|
||||
//
|
||||
// 3: A----- OR 3: C------
|
||||
// 2: c--- 2: c------
|
||||
// ^ ^
|
||||
// 3: A----- OR 3: C------ OR
|
||||
// 2: c--- 2: c------ 2: C------
|
||||
// ^ ^ ^
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
// Look at all the existing transitions that overlap the new tombstone.
|
||||
while (it != rep_.end() && ucmp_->Compare(it->first, t.end_key_) < 0) {
|
||||
if (t.seq_ > it->second) {
|
||||
if (t.seq_ >= it->second) {
|
||||
// The transition is to an existing tombstone that the new tombstone
|
||||
// covers. Save the covered tombstone's seqno. We'll need to return to
|
||||
// it if the new tombstone ends before the existing tombstone.
|
||||
@@ -321,15 +342,29 @@ class CollapsedRangeDelMap : public RangeDelMap {
|
||||
}
|
||||
|
||||
if (t.seq_ == prev_seq()) {
|
||||
// The new tombstone is unterminated in the map:
|
||||
//
|
||||
// 3: OR 3: --G OR 3: --G K--
|
||||
// 2: C-------k 2: G---k 2: G---k
|
||||
// ^ ^ ^
|
||||
// End it now, returning to the last seqno we covered. Because end keys
|
||||
// are exclusive, if there's an existing transition at t.end_key_, it
|
||||
// takes precedence over the transition that we install here.
|
||||
rep_.emplace(t.end_key_, end_seq); // emplace is a noop if existing entry
|
||||
// The new tombstone is unterminated in the map.
|
||||
if (it != rep_.end() && t.seq_ == it->second && ucmp_->Compare(it->first, t.end_key_) == 0) {
|
||||
// The new tombstone ends at the start of another tombstone with an
|
||||
// identical seqno. Merge the tombstones by removing the existing
|
||||
// tombstone's start key.
|
||||
rep_.erase(it);
|
||||
} else if (end_seq == prev_seq() || (it != rep_.end() && end_seq == it->second)) {
|
||||
// The new tombstone is implicitly ended because its end point is
|
||||
// contained within an existing tombstone with the same seqno:
|
||||
//
|
||||
// 2: ---k--N
|
||||
// ^
|
||||
} else {
|
||||
// The new tombstone needs an explicit end point.
|
||||
//
|
||||
// 3: OR 3: --G OR 3: --G K--
|
||||
// 2: C-------k 2: G---k 2: G---k
|
||||
// ^ ^ ^
|
||||
// Install one that returns to the last seqno we covered. Because end
|
||||
// keys are exclusive, if there's an existing transition at t.end_key_,
|
||||
// it takes precedence over the transition that we install here.
|
||||
rep_.emplace(t.end_key_, end_seq); // emplace is a noop if existing entry
|
||||
}
|
||||
} else {
|
||||
// The new tombstone is implicitly ended because its end point is covered
|
||||
// by an existing tombstone with a higher seqno.
|
||||
@@ -341,11 +376,11 @@ class CollapsedRangeDelMap : public RangeDelMap {
|
||||
}
|
||||
}
|
||||
|
||||
size_t Size() const { return rep_.size() - 1; }
|
||||
size_t Size() const override { return rep_.empty() ? 0 : rep_.size() - 1; }
|
||||
|
||||
void InvalidatePosition() { iter_ = rep_.end(); }
|
||||
void InvalidatePosition() override { iter_ = rep_.end(); }
|
||||
|
||||
std::unique_ptr<RangeDelIterator> NewIterator() {
|
||||
std::unique_ptr<RangeDelIterator> NewIterator() override {
|
||||
return std::unique_ptr<RangeDelIterator>(new Iterator(this->rep_));
|
||||
}
|
||||
};
|
||||
@@ -475,22 +510,22 @@ Status RangeDelAggregator::AddTombstones(
|
||||
}
|
||||
}
|
||||
if (largest != nullptr) {
|
||||
// This is subtly correct despite the discrepancy between
|
||||
// FileMetaData::largest being inclusive while RangeTombstone::end_key_
|
||||
// is exclusive. A tombstone will only extend past the bounds of an
|
||||
// sstable if its end-key is the largest key in the table. If that
|
||||
// occurs, the largest key for the table is set based on the smallest
|
||||
// key in the next table in the level. In that case, largest->user_key()
|
||||
// is not actually a key in the current table and thus we can use it as
|
||||
// the exclusive end-key for the tombstone.
|
||||
if (icmp_.user_comparator()->Compare(
|
||||
tombstone.end_key_, largest->user_key()) > 0) {
|
||||
// The largest key should be a tombstone sentinel key.
|
||||
assert(GetInternalKeySeqno(largest->Encode()) == kMaxSequenceNumber);
|
||||
// To safely truncate the range tombstone's end key, it must extend past
|
||||
// the largest key in the sstable (which may have been extended to the
|
||||
// smallest key in the next sstable), and largest must be a tombstone
|
||||
// sentinel key. A range tombstone may straddle two sstables and not be
|
||||
// the tombstone sentinel key in the first sstable if a user-key also
|
||||
// straddles the sstables (possible if there is a snapshot between the
|
||||
// two versions of the user-key), in which case we cannot truncate the
|
||||
// range tombstone.
|
||||
if (icmp_.user_comparator()->Compare(tombstone.end_key_,
|
||||
largest->user_key()) > 0 &&
|
||||
GetInternalKeySeqno(largest->Encode()) == kMaxSequenceNumber) {
|
||||
tombstone.end_key_ = largest->user_key();
|
||||
}
|
||||
}
|
||||
GetRangeDelMap(tombstone.seq_).AddTombstone(std::move(tombstone));
|
||||
auto seq = tombstone.seq_;
|
||||
GetRangeDelMap(seq).AddTombstone(std::move(tombstone));
|
||||
input->Next();
|
||||
}
|
||||
if (!first_iter) {
|
||||
|
||||
@@ -0,0 +1,244 @@
|
||||
// Copyright (c) 2018-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).
|
||||
|
||||
#ifndef GFLAGS
|
||||
#include <cstdio>
|
||||
int main() {
|
||||
fprintf(stderr, "Please install gflags to run rocksdb tools\n");
|
||||
return 1;
|
||||
}
|
||||
#else
|
||||
|
||||
#include <iostream>
|
||||
#include <iomanip>
|
||||
#include <memory>
|
||||
#include <random>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "db/range_del_aggregator.h"
|
||||
#include "rocksdb/comparator.h"
|
||||
#include "rocksdb/env.h"
|
||||
#include "util/coding.h"
|
||||
#include "util/random.h"
|
||||
#include "util/stop_watch.h"
|
||||
#include "util/testutil.h"
|
||||
|
||||
#include "util/gflags_compat.h"
|
||||
|
||||
using GFLAGS_NAMESPACE::ParseCommandLineFlags;
|
||||
|
||||
DEFINE_int32(num_range_tombstones, 1000, "number of range tombstones created");
|
||||
|
||||
DEFINE_int32(num_runs, 10000, "number of test runs");
|
||||
|
||||
DEFINE_int32(tombstone_start_upper_bound, 1000,
|
||||
"exclusive upper bound on range tombstone start keys");
|
||||
|
||||
DEFINE_int32(should_delete_upper_bound, 1000,
|
||||
"exclusive upper bound on keys passed to ShouldDelete");
|
||||
|
||||
DEFINE_double(tombstone_width_mean, 100.0, "average range tombstone width");
|
||||
|
||||
DEFINE_double(tombstone_width_stddev, 0.0,
|
||||
"standard deviation of range tombstone width");
|
||||
|
||||
DEFINE_bool(use_collapsed, true, "use the collapsed range tombstone map");
|
||||
|
||||
DEFINE_int32(seed, 0, "random number generator seed");
|
||||
|
||||
DEFINE_int32(should_deletes_per_run, 1, "number of ShouldDelete calls per run");
|
||||
|
||||
DEFINE_int32(add_tombstones_per_run, 1,
|
||||
"number of AddTombstones calls per run");
|
||||
|
||||
namespace {
|
||||
|
||||
struct Stats {
|
||||
uint64_t time_add_tombstones = 0;
|
||||
uint64_t time_first_should_delete = 0;
|
||||
uint64_t time_rest_should_delete = 0;
|
||||
};
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const Stats& s) {
|
||||
std::ios fmt_holder(nullptr);
|
||||
fmt_holder.copyfmt(os);
|
||||
|
||||
os << std::left;
|
||||
os << std::setw(25) << "AddTombstones: "
|
||||
<< s.time_add_tombstones /
|
||||
(FLAGS_add_tombstones_per_run * FLAGS_num_runs * 1.0e3)
|
||||
<< " us\n";
|
||||
os << std::setw(25) << "ShouldDelete (first): "
|
||||
<< s.time_first_should_delete / (FLAGS_num_runs * 1.0e3) << " us\n";
|
||||
if (FLAGS_should_deletes_per_run > 1) {
|
||||
os << std::setw(25) << "ShouldDelete (rest): "
|
||||
<< s.time_rest_should_delete /
|
||||
((FLAGS_should_deletes_per_run - 1) * FLAGS_num_runs * 1.0e3)
|
||||
<< " us\n";
|
||||
}
|
||||
|
||||
os.copyfmt(fmt_holder);
|
||||
return os;
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
namespace {
|
||||
|
||||
// A wrapper around RangeTombstones and the underlying data of its start and end
|
||||
// keys.
|
||||
struct PersistentRangeTombstone {
|
||||
std::string start_key;
|
||||
std::string end_key;
|
||||
RangeTombstone tombstone;
|
||||
|
||||
PersistentRangeTombstone(std::string start, std::string end,
|
||||
SequenceNumber seq)
|
||||
: start_key(std::move(start)), end_key(std::move(end)) {
|
||||
tombstone = RangeTombstone(start_key, end_key, seq);
|
||||
}
|
||||
|
||||
PersistentRangeTombstone() = default;
|
||||
|
||||
PersistentRangeTombstone(const PersistentRangeTombstone& t) { *this = t; }
|
||||
|
||||
PersistentRangeTombstone& operator=(const PersistentRangeTombstone& t) {
|
||||
start_key = t.start_key;
|
||||
end_key = t.end_key;
|
||||
tombstone = RangeTombstone(start_key, end_key, t.tombstone.seq_);
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
PersistentRangeTombstone(PersistentRangeTombstone&& t) noexcept { *this = t; }
|
||||
|
||||
PersistentRangeTombstone& operator=(PersistentRangeTombstone&& t) {
|
||||
start_key = std::move(t.start_key);
|
||||
end_key = std::move(t.end_key);
|
||||
tombstone = RangeTombstone(start_key, end_key, t.tombstone.seq_);
|
||||
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
|
||||
struct TombstoneStartKeyComparator {
|
||||
explicit TombstoneStartKeyComparator(const Comparator* c) : cmp(c) {}
|
||||
|
||||
bool operator()(const RangeTombstone& a, const RangeTombstone& b) const {
|
||||
return cmp->Compare(a.start_key_, b.start_key_) < 0;
|
||||
}
|
||||
|
||||
const Comparator* cmp;
|
||||
};
|
||||
|
||||
std::unique_ptr<InternalIterator> MakeRangeDelIterator(
|
||||
const std::vector<PersistentRangeTombstone>& range_dels) {
|
||||
std::vector<std::string> keys, values;
|
||||
for (const auto& range_del : range_dels) {
|
||||
auto key_and_value = range_del.tombstone.Serialize();
|
||||
keys.push_back(key_and_value.first.Encode().ToString());
|
||||
values.push_back(key_and_value.second.ToString());
|
||||
}
|
||||
return std::unique_ptr<test::VectorIterator>(
|
||||
new test::VectorIterator(keys, values));
|
||||
}
|
||||
|
||||
// convert long to a big-endian slice key
|
||||
static std::string Key(int64_t val) {
|
||||
std::string little_endian_key;
|
||||
std::string big_endian_key;
|
||||
PutFixed64(&little_endian_key, val);
|
||||
assert(little_endian_key.size() == sizeof(val));
|
||||
big_endian_key.resize(sizeof(val));
|
||||
for (size_t i = 0; i < sizeof(val); ++i) {
|
||||
big_endian_key[i] = little_endian_key[sizeof(val) - 1 - i];
|
||||
}
|
||||
return big_endian_key;
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
ParseCommandLineFlags(&argc, &argv, true);
|
||||
|
||||
Stats stats;
|
||||
rocksdb::Random64 rnd(FLAGS_seed);
|
||||
std::default_random_engine random_gen(FLAGS_seed);
|
||||
std::normal_distribution<double> normal_dist(FLAGS_tombstone_width_mean,
|
||||
FLAGS_tombstone_width_stddev);
|
||||
std::vector<std::vector<rocksdb::PersistentRangeTombstone> >
|
||||
all_persistent_range_tombstones(FLAGS_add_tombstones_per_run);
|
||||
for (int i = 0; i < FLAGS_add_tombstones_per_run; i++) {
|
||||
all_persistent_range_tombstones[i] =
|
||||
std::vector<rocksdb::PersistentRangeTombstone>(
|
||||
FLAGS_num_range_tombstones);
|
||||
}
|
||||
auto mode = FLAGS_use_collapsed
|
||||
? rocksdb::RangeDelPositioningMode::kForwardTraversal
|
||||
: rocksdb::RangeDelPositioningMode::kFullScan;
|
||||
|
||||
for (int i = 0; i < FLAGS_num_runs; i++) {
|
||||
auto icmp = rocksdb::InternalKeyComparator(rocksdb::BytewiseComparator());
|
||||
rocksdb::RangeDelAggregator range_del_agg(icmp, {} /* snapshots */,
|
||||
FLAGS_use_collapsed);
|
||||
|
||||
for (auto& persistent_range_tombstones : all_persistent_range_tombstones) {
|
||||
// TODO(abhimadan): consider whether creating the range tombstones right
|
||||
// before AddTombstones is artificially warming the cache compared to
|
||||
// real workloads.
|
||||
for (int j = 0; j < FLAGS_num_range_tombstones; j++) {
|
||||
uint64_t start = rnd.Uniform(FLAGS_tombstone_start_upper_bound);
|
||||
uint64_t end = start + std::max(1.0, normal_dist(random_gen));
|
||||
persistent_range_tombstones[j] = rocksdb::PersistentRangeTombstone(
|
||||
rocksdb::Key(start), rocksdb::Key(end), j);
|
||||
}
|
||||
|
||||
auto range_del_iter =
|
||||
rocksdb::MakeRangeDelIterator(persistent_range_tombstones);
|
||||
rocksdb::StopWatchNano stop_watch_add_tombstones(rocksdb::Env::Default(),
|
||||
true /* auto_start */);
|
||||
range_del_agg.AddTombstones(std::move(range_del_iter));
|
||||
stats.time_add_tombstones += stop_watch_add_tombstones.ElapsedNanos();
|
||||
}
|
||||
|
||||
rocksdb::ParsedInternalKey parsed_key;
|
||||
parsed_key.sequence = FLAGS_num_range_tombstones / 2;
|
||||
parsed_key.type = rocksdb::kTypeValue;
|
||||
|
||||
uint64_t first_key = rnd.Uniform(FLAGS_should_delete_upper_bound -
|
||||
FLAGS_should_deletes_per_run + 1);
|
||||
|
||||
for (int j = 0; j < FLAGS_should_deletes_per_run; j++) {
|
||||
std::string key_string = rocksdb::Key(first_key + j);
|
||||
parsed_key.user_key = key_string;
|
||||
|
||||
rocksdb::StopWatchNano stop_watch_should_delete(rocksdb::Env::Default(),
|
||||
true /* auto_start */);
|
||||
range_del_agg.ShouldDelete(parsed_key, mode);
|
||||
uint64_t call_time = stop_watch_should_delete.ElapsedNanos();
|
||||
|
||||
if (j == 0) {
|
||||
stats.time_first_should_delete += call_time;
|
||||
} else {
|
||||
stats.time_rest_should_delete += call_time;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::cout << "=========================\n"
|
||||
<< "Results:\n"
|
||||
<< "=========================\n"
|
||||
<< stats;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif // GFLAGS
|
||||
@@ -27,7 +27,7 @@ enum Direction {
|
||||
kReverse,
|
||||
};
|
||||
|
||||
static auto icmp = InternalKeyComparator(BytewiseComparator());
|
||||
static auto bytewise_icmp = InternalKeyComparator(BytewiseComparator());
|
||||
|
||||
void AddTombstones(RangeDelAggregator* range_del_agg,
|
||||
const std::vector<RangeTombstone>& range_dels,
|
||||
@@ -66,8 +66,8 @@ void VerifyRangeDels(
|
||||
const std::vector<RangeTombstone>& range_dels_in,
|
||||
const std::vector<ExpectedPoint>& expected_points,
|
||||
const std::vector<RangeTombstone>& expected_collapsed_range_dels,
|
||||
const InternalKey* smallest = nullptr,
|
||||
const InternalKey* largest = nullptr) {
|
||||
const InternalKey* smallest = nullptr, const InternalKey* largest = nullptr,
|
||||
const InternalKeyComparator& icmp = bytewise_icmp) {
|
||||
// Test same result regardless of which order the range deletions are added
|
||||
// and regardless of collapsed mode.
|
||||
for (bool collapsed : {false, true}) {
|
||||
@@ -164,6 +164,14 @@ TEST_F(RangeDelAggregatorTest, OverlapAboveMiddle) {
|
||||
{{"a", "b", 5}, {"b", "c", 10}, {"c", "d", 5}});
|
||||
}
|
||||
|
||||
TEST_F(RangeDelAggregatorTest, OverlapAboveMiddleReverse) {
|
||||
VerifyRangeDels({{"d", "a", 5}, {"c", "b", 10}},
|
||||
{{"z", 0}, {"d", 5}, {"c", 10}, {"b", 5}, {"a", 0}},
|
||||
{{"d", "c", 5}, {"c", "b", 10}, {"b", "a", 5}},
|
||||
nullptr /* smallest */, nullptr /* largest */,
|
||||
InternalKeyComparator(ReverseBytewiseComparator()));
|
||||
}
|
||||
|
||||
TEST_F(RangeDelAggregatorTest, OverlapFully) {
|
||||
VerifyRangeDels({{"a", "d", 10}, {"b", "c", 5}},
|
||||
{{" ", 0}, {"a", 10}, {"d", 0}}, {{"a", "d", 10}});
|
||||
@@ -200,6 +208,30 @@ TEST_F(RangeDelAggregatorTest, GapsBetweenRanges) {
|
||||
{{"a", "b", 5}, {"c", "d", 10}, {"e", "f", 15}});
|
||||
}
|
||||
|
||||
TEST_F(RangeDelAggregatorTest, IdenticalSameSeqNo) {
|
||||
VerifyRangeDels({{"a", "b", 5}, {"a", "b", 5}},
|
||||
{{" ", 0}, {"a", 5}, {"b", 0}},
|
||||
{{"a", "b", 5}});
|
||||
}
|
||||
|
||||
TEST_F(RangeDelAggregatorTest, ContiguousSameSeqNo) {
|
||||
VerifyRangeDels({{"a", "b", 5}, {"b", "c", 5}},
|
||||
{{" ", 0}, {"a", 5}, {"b", 5}, {"c", 0}},
|
||||
{{"a", "c", 5}});
|
||||
}
|
||||
|
||||
TEST_F(RangeDelAggregatorTest, OverlappingSameSeqNo) {
|
||||
VerifyRangeDels({{"a", "c", 5}, {"b", "d", 5}},
|
||||
{{" ", 0}, {"a", 5}, {"b", 5}, {"c", 5}, {"d", 0}},
|
||||
{{"a", "d", 5}});
|
||||
}
|
||||
|
||||
TEST_F(RangeDelAggregatorTest, CoverSameSeqNo) {
|
||||
VerifyRangeDels({{"a", "d", 5}, {"b", "c", 5}},
|
||||
{{" ", 0}, {"a", 5}, {"b", 5}, {"c", 5}, {"d", 0}},
|
||||
{{"a", "d", 5}});
|
||||
}
|
||||
|
||||
// Note the Cover* tests also test cases where tombstones are inserted under a
|
||||
// larger one when VerifyRangeDels() runs them in reverse
|
||||
TEST_F(RangeDelAggregatorTest, CoverMultipleFromLeft) {
|
||||
@@ -235,14 +267,14 @@ TEST_F(RangeDelAggregatorTest, AlternateMultipleAboveBelow) {
|
||||
|
||||
TEST_F(RangeDelAggregatorTest, MergingIteratorAllEmptyStripes) {
|
||||
for (bool collapsed : {true, false}) {
|
||||
RangeDelAggregator range_del_agg(icmp, {1, 2}, collapsed);
|
||||
RangeDelAggregator range_del_agg(bytewise_icmp, {1, 2}, collapsed);
|
||||
VerifyRangeDelIter(range_del_agg.NewIterator().get(), {});
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(RangeDelAggregatorTest, MergingIteratorOverlappingStripes) {
|
||||
for (bool collapsed : {true, false}) {
|
||||
RangeDelAggregator range_del_agg(icmp, {5, 15, 25, 35}, collapsed);
|
||||
RangeDelAggregator range_del_agg(bytewise_icmp, {5, 15, 25, 35}, collapsed);
|
||||
AddTombstones(
|
||||
&range_del_agg,
|
||||
{{"d", "e", 10}, {"aa", "b", 20}, {"c", "d", 30}, {"a", "b", 10}});
|
||||
@@ -253,7 +285,8 @@ TEST_F(RangeDelAggregatorTest, MergingIteratorOverlappingStripes) {
|
||||
}
|
||||
|
||||
TEST_F(RangeDelAggregatorTest, MergingIteratorSeek) {
|
||||
RangeDelAggregator range_del_agg(icmp, {5, 15}, true /* collapsed */);
|
||||
RangeDelAggregator range_del_agg(bytewise_icmp, {5, 15},
|
||||
true /* collapsed */);
|
||||
AddTombstones(&range_del_agg, {{"a", "c", 10},
|
||||
{"b", "c", 11},
|
||||
{"f", "g", 10},
|
||||
@@ -300,6 +333,21 @@ TEST_F(RangeDelAggregatorTest, TruncateTombstones) {
|
||||
&smallest, &largest);
|
||||
}
|
||||
|
||||
TEST_F(RangeDelAggregatorTest, OverlappingLargestKeyTruncateTombstones) {
|
||||
const InternalKey smallest("b", 1, kTypeRangeDeletion);
|
||||
const InternalKey largest(
|
||||
"e", 3, // could happen if "e" is in consecutive sstables
|
||||
kTypeValue);
|
||||
VerifyRangeDels(
|
||||
{{"a", "c", 10}, {"d", "f", 10}},
|
||||
{{"a", 10, true}, // truncated
|
||||
{"b", 10, false}, // not truncated
|
||||
{"d", 10, false}, // not truncated
|
||||
{"e", 10, false}}, // not truncated
|
||||
{{"b", "c", 10}, {"d", "f", 10}},
|
||||
&smallest, &largest);
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
+1
-1
@@ -353,7 +353,7 @@ class Repairer {
|
||||
// propagating bad information (like overly large sequence
|
||||
// numbers).
|
||||
log::Reader reader(db_options_.info_log, std::move(lfile_reader), &reporter,
|
||||
true /*enable checksum*/, 0 /*initial_offset*/, log);
|
||||
true /*enable checksum*/, log);
|
||||
|
||||
// Initialize per-column family memtables
|
||||
for (auto* cfd : *vset_.GetColumnFamilySet()) {
|
||||
|
||||
+2
-2
@@ -74,7 +74,7 @@ TEST_F(RepairTest, CorruptManifest) {
|
||||
|
||||
Close();
|
||||
ASSERT_OK(env_->FileExists(manifest_path));
|
||||
CreateFile(env_, manifest_path, "blah");
|
||||
CreateFile(env_, manifest_path, "blah", false /* use_fsync */);
|
||||
ASSERT_OK(RepairDB(dbname_, CurrentOptions()));
|
||||
Reopen(CurrentOptions());
|
||||
|
||||
@@ -153,7 +153,7 @@ TEST_F(RepairTest, CorruptSst) {
|
||||
Flush();
|
||||
auto sst_path = GetFirstSstPath();
|
||||
ASSERT_FALSE(sst_path.empty());
|
||||
CreateFile(env_, sst_path, "blah");
|
||||
CreateFile(env_, sst_path, "blah", false /* use_fsync */);
|
||||
|
||||
Close();
|
||||
ASSERT_OK(RepairDB(dbname_, CurrentOptions()));
|
||||
|
||||
@@ -19,8 +19,9 @@ class SnapshotChecker {
|
||||
class DisableGCSnapshotChecker : public SnapshotChecker {
|
||||
public:
|
||||
virtual ~DisableGCSnapshotChecker() {}
|
||||
virtual bool IsInSnapshot(SequenceNumber /*sequence*/,
|
||||
SequenceNumber /*snapshot_sequence*/) const {
|
||||
virtual bool IsInSnapshot(
|
||||
SequenceNumber /*sequence*/,
|
||||
SequenceNumber /*snapshot_sequence*/) const override {
|
||||
// By returning false, we prevent all the values from being GCed
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -56,6 +56,9 @@ class SnapshotList {
|
||||
count_ = 0;
|
||||
}
|
||||
|
||||
// No copy-construct.
|
||||
SnapshotList(const SnapshotList&) = delete;
|
||||
|
||||
bool empty() const { return list_.next_ == &list_; }
|
||||
SnapshotImpl* oldest() const { assert(!empty()); return list_.next_; }
|
||||
SnapshotImpl* newest() const { assert(!empty()); return list_.prev_; }
|
||||
|
||||
+3
-3
@@ -120,7 +120,7 @@ Status TableCache::GetTableReader(
|
||||
s = ioptions_.table_factory->NewTableReader(
|
||||
TableReaderOptions(ioptions_, prefix_extractor, env_options,
|
||||
internal_comparator, skip_filters, immortal_tables_,
|
||||
level),
|
||||
level, fd.largest_seqno),
|
||||
std::move(file_reader), fd.GetFileSize(), table_reader,
|
||||
prefetch_index_and_filter_in_cache);
|
||||
TEST_SYNC_POINT("TableCache::GetTableReader:0");
|
||||
@@ -238,7 +238,7 @@ InternalIterator* TableCache::NewIterator(
|
||||
if (s.ok()) {
|
||||
if (options.table_filter &&
|
||||
!options.table_filter(*table_reader->GetTableProperties())) {
|
||||
result = NewEmptyInternalIterator(arena);
|
||||
result = NewEmptyInternalIterator<Slice>(arena);
|
||||
} else {
|
||||
result = table_reader->NewIterator(options, prefix_extractor, arena,
|
||||
skip_filters, for_compaction);
|
||||
@@ -279,7 +279,7 @@ InternalIterator* TableCache::NewIterator(
|
||||
}
|
||||
if (!s.ok()) {
|
||||
assert(result == nullptr);
|
||||
result = NewErrorInternalIterator(s, arena);
|
||||
result = NewErrorInternalIterator<Slice>(s, arena);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -31,7 +31,6 @@ class Arena;
|
||||
struct FileDescriptor;
|
||||
class GetContext;
|
||||
class HistogramImpl;
|
||||
class InternalIterator;
|
||||
|
||||
class TableCache {
|
||||
public:
|
||||
|
||||
@@ -46,7 +46,8 @@ void MakeBuilder(const Options& options, const ImmutableCFOptions& ioptions,
|
||||
std::unique_ptr<WritableFileWriter>* writable,
|
||||
std::unique_ptr<TableBuilder>* builder) {
|
||||
unique_ptr<WritableFile> wf(new test::StringSink);
|
||||
writable->reset(new WritableFileWriter(std::move(wf), EnvOptions()));
|
||||
writable->reset(
|
||||
new WritableFileWriter(std::move(wf), "" /* don't care */, EnvOptions()));
|
||||
int unknown_level = -1;
|
||||
builder->reset(NewTableBuilder(
|
||||
ioptions, moptions, internal_comparator, int_tbl_prop_collector_factories,
|
||||
|
||||
@@ -104,7 +104,7 @@ void TransactionLogIteratorImpl::SeekToStartSequence(
|
||||
if (files_->size() <= startFileIndex) {
|
||||
return;
|
||||
}
|
||||
Status s = OpenLogReader(files_->at(startFileIndex).get());
|
||||
Status s = OpenLogReader(files_->at(static_cast<size_t>(startFileIndex)).get());
|
||||
if (!s.ok()) {
|
||||
currentStatus_ = s;
|
||||
reporter_.Info(currentStatus_.ToString().c_str());
|
||||
@@ -312,9 +312,9 @@ Status TransactionLogIteratorImpl::OpenLogReader(const LogFile* logFile) {
|
||||
return s;
|
||||
}
|
||||
assert(file);
|
||||
currentLogReader_.reset(new log::Reader(
|
||||
options_->info_log, std::move(file), &reporter_,
|
||||
read_options_.verify_checksums_, 0, logFile->LogNumber()));
|
||||
currentLogReader_.reset(
|
||||
new log::Reader(options_->info_log, std::move(file), &reporter_,
|
||||
read_options_.verify_checksums_, logFile->LogNumber()));
|
||||
return Status::OK();
|
||||
}
|
||||
} // namespace rocksdb
|
||||
|
||||
+34
-37
@@ -35,11 +35,11 @@
|
||||
namespace rocksdb {
|
||||
|
||||
bool NewestFirstBySeqNo(FileMetaData* a, FileMetaData* b) {
|
||||
if (a->largest_seqno != b->largest_seqno) {
|
||||
return a->largest_seqno > b->largest_seqno;
|
||||
if (a->fd.largest_seqno != b->fd.largest_seqno) {
|
||||
return a->fd.largest_seqno > b->fd.largest_seqno;
|
||||
}
|
||||
if (a->smallest_seqno != b->smallest_seqno) {
|
||||
return a->smallest_seqno > b->smallest_seqno;
|
||||
if (a->fd.smallest_seqno != b->fd.smallest_seqno) {
|
||||
return a->fd.smallest_seqno > b->fd.smallest_seqno;
|
||||
}
|
||||
// Break ties by file number
|
||||
return a->fd.GetNumber() > b->fd.GetNumber();
|
||||
@@ -162,22 +162,24 @@ class VersionBuilder::Rep {
|
||||
abort();
|
||||
}
|
||||
|
||||
if (f2->smallest_seqno == f2->largest_seqno) {
|
||||
if (f2->fd.smallest_seqno == f2->fd.largest_seqno) {
|
||||
// This is an external file that we ingested
|
||||
SequenceNumber external_file_seqno = f2->smallest_seqno;
|
||||
if (!(external_file_seqno < f1->largest_seqno ||
|
||||
SequenceNumber external_file_seqno = f2->fd.smallest_seqno;
|
||||
if (!(external_file_seqno < f1->fd.largest_seqno ||
|
||||
external_file_seqno == 0)) {
|
||||
fprintf(stderr, "L0 file with seqno %" PRIu64 " %" PRIu64
|
||||
" vs. file with global_seqno %" PRIu64 "\n",
|
||||
f1->smallest_seqno, f1->largest_seqno,
|
||||
fprintf(stderr,
|
||||
"L0 file with seqno %" PRIu64 " %" PRIu64
|
||||
" vs. file with global_seqno %" PRIu64 "\n",
|
||||
f1->fd.smallest_seqno, f1->fd.largest_seqno,
|
||||
external_file_seqno);
|
||||
abort();
|
||||
}
|
||||
} else if (f1->smallest_seqno <= f2->smallest_seqno) {
|
||||
fprintf(stderr, "L0 files seqno %" PRIu64 " %" PRIu64
|
||||
" vs. %" PRIu64 " %" PRIu64 "\n",
|
||||
f1->smallest_seqno, f1->largest_seqno, f2->smallest_seqno,
|
||||
f2->largest_seqno);
|
||||
} else if (f1->fd.smallest_seqno <= f2->fd.smallest_seqno) {
|
||||
fprintf(stderr,
|
||||
"L0 files seqno %" PRIu64 " %" PRIu64 " vs. %" PRIu64
|
||||
" %" PRIu64 "\n",
|
||||
f1->fd.smallest_seqno, f1->fd.largest_seqno,
|
||||
f2->fd.smallest_seqno, f2->fd.largest_seqno);
|
||||
abort();
|
||||
}
|
||||
} else {
|
||||
@@ -322,8 +324,6 @@ class VersionBuilder::Rep {
|
||||
// Merge the set of added files with the set of pre-existing files.
|
||||
// Drop any deleted files. Store the result in *v.
|
||||
const auto& base_files = base_vstorage_->LevelFiles(level);
|
||||
auto base_iter = base_files.begin();
|
||||
auto base_end = base_files.end();
|
||||
const auto& unordered_added_files = levels_[level].added_files;
|
||||
vstorage->Reserve(level,
|
||||
base_files.size() + unordered_added_files.size());
|
||||
@@ -337,30 +337,27 @@ class VersionBuilder::Rep {
|
||||
std::sort(added_files.begin(), added_files.end(), cmp);
|
||||
|
||||
#ifndef NDEBUG
|
||||
FileMetaData* prev_file = nullptr;
|
||||
#endif
|
||||
|
||||
FileMetaData* prev_added_file = nullptr;
|
||||
for (const auto& added : added_files) {
|
||||
#ifndef NDEBUG
|
||||
if (level > 0 && prev_file != nullptr) {
|
||||
if (level > 0 && prev_added_file != nullptr) {
|
||||
assert(base_vstorage_->InternalComparator()->Compare(
|
||||
prev_file->smallest, added->smallest) <= 0);
|
||||
prev_added_file->smallest, added->smallest) <= 0);
|
||||
}
|
||||
prev_file = added;
|
||||
prev_added_file = added;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Add all smaller files listed in base_
|
||||
for (auto bpos = std::upper_bound(base_iter, base_end, added, cmp);
|
||||
base_iter != bpos; ++base_iter) {
|
||||
MaybeAddFile(vstorage, level, *base_iter);
|
||||
auto base_iter = base_files.begin();
|
||||
auto base_end = base_files.end();
|
||||
auto added_iter = added_files.begin();
|
||||
auto added_end = added_files.end();
|
||||
while (added_iter != added_end || base_iter != base_end) {
|
||||
if (base_iter == base_end ||
|
||||
(added_iter != added_end && cmp(*added_iter, *base_iter))) {
|
||||
MaybeAddFile(vstorage, level, *added_iter++);
|
||||
} else {
|
||||
MaybeAddFile(vstorage, level, *base_iter++);
|
||||
}
|
||||
|
||||
MaybeAddFile(vstorage, level, added);
|
||||
}
|
||||
|
||||
// Add remaining base files
|
||||
for (; base_iter != base_end; ++base_iter) {
|
||||
MaybeAddFile(vstorage, level, *base_iter);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -382,7 +379,7 @@ class VersionBuilder::Rep {
|
||||
}
|
||||
|
||||
std::atomic<size_t> next_file_meta_idx(0);
|
||||
std::function<void()> load_handlers_func = [&]() {
|
||||
std::function<void()> load_handlers_func([&]() {
|
||||
while (true) {
|
||||
size_t file_idx = next_file_meta_idx.fetch_add(1);
|
||||
if (file_idx >= files_meta.size()) {
|
||||
@@ -403,7 +400,7 @@ class VersionBuilder::Rep {
|
||||
file_meta->table_reader_handle);
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
std::vector<port::Thread> threads;
|
||||
for (int i = 1; i < max_threads; i++) {
|
||||
|
||||
@@ -63,8 +63,8 @@ class VersionBuilderTest : public testing::Test {
|
||||
f->fd = FileDescriptor(file_number, path_id, file_size);
|
||||
f->smallest = GetInternalKey(smallest, smallest_seq);
|
||||
f->largest = GetInternalKey(largest, largest_seq);
|
||||
f->smallest_seqno = smallest_seqno;
|
||||
f->largest_seqno = largest_seqno;
|
||||
f->fd.smallest_seqno = smallest_seqno;
|
||||
f->fd.largest_seqno = largest_seqno;
|
||||
f->compensated_file_size = file_size;
|
||||
f->refs = 0;
|
||||
f->num_entries = num_entries;
|
||||
|
||||
+47
-12
@@ -40,13 +40,15 @@ enum Tag : uint32_t {
|
||||
kColumnFamilyAdd = 201,
|
||||
kColumnFamilyDrop = 202,
|
||||
kMaxColumnFamily = 203,
|
||||
|
||||
kInAtomicGroup = 300,
|
||||
};
|
||||
|
||||
enum CustomTag : uint32_t {
|
||||
kTerminate = 1, // The end of customized fields
|
||||
kNeedCompaction = 2,
|
||||
// Since Manifest is not entirely currently forward-compatible, and the only
|
||||
// forward-compatbile part is the CutsomtTag of kNewFile, we currently encode
|
||||
// forward-compatible part is the CutsomtTag of kNewFile, we currently encode
|
||||
// kMinLogNumberToKeep as part of a CustomTag as a hack. This should be
|
||||
// removed when manifest becomes forward-comptabile.
|
||||
kMinLogNumberToKeepHack = 3,
|
||||
@@ -83,6 +85,8 @@ void VersionEdit::Clear() {
|
||||
is_column_family_add_ = 0;
|
||||
is_column_family_drop_ = 0;
|
||||
column_family_name_.clear();
|
||||
is_in_atomic_group_ = false;
|
||||
remaining_entries_ = 0;
|
||||
}
|
||||
|
||||
bool VersionEdit::EncodeTo(std::string* dst) const {
|
||||
@@ -135,7 +139,7 @@ bool VersionEdit::EncodeTo(std::string* dst) const {
|
||||
PutVarint64(dst, f.fd.GetFileSize());
|
||||
PutLengthPrefixedSlice(dst, f.smallest.Encode());
|
||||
PutLengthPrefixedSlice(dst, f.largest.Encode());
|
||||
PutVarint64Varint64(dst, f.smallest_seqno, f.largest_seqno);
|
||||
PutVarint64Varint64(dst, f.fd.smallest_seqno, f.fd.largest_seqno);
|
||||
if (has_customized_fields) {
|
||||
// Customized fields' format:
|
||||
// +-----------------------------+
|
||||
@@ -200,6 +204,11 @@ bool VersionEdit::EncodeTo(std::string* dst) const {
|
||||
if (is_column_family_drop_) {
|
||||
PutVarint32(dst, kColumnFamilyDrop);
|
||||
}
|
||||
|
||||
if (is_in_atomic_group_) {
|
||||
PutVarint32(dst, kInAtomicGroup);
|
||||
PutVarint32(dst, remaining_entries_);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -233,14 +242,16 @@ const char* VersionEdit::DecodeNewFile4From(Slice* input) {
|
||||
uint64_t number;
|
||||
uint32_t path_id = 0;
|
||||
uint64_t file_size;
|
||||
SequenceNumber smallest_seqno;
|
||||
SequenceNumber largest_seqno;
|
||||
// Since this is the only forward-compatible part of the code, we hack new
|
||||
// extension into this record. When we do, we set this boolean to distinguish
|
||||
// the record from the normal NewFile records.
|
||||
if (GetLevel(input, &level, &msg) && GetVarint64(input, &number) &&
|
||||
GetVarint64(input, &file_size) && GetInternalKey(input, &f.smallest) &&
|
||||
GetInternalKey(input, &f.largest) &&
|
||||
GetVarint64(input, &f.smallest_seqno) &&
|
||||
GetVarint64(input, &f.largest_seqno)) {
|
||||
GetVarint64(input, &smallest_seqno) &&
|
||||
GetVarint64(input, &largest_seqno)) {
|
||||
// See comments in VersionEdit::EncodeTo() for format of customized fields
|
||||
while (true) {
|
||||
uint32_t custom_tag;
|
||||
@@ -272,7 +283,7 @@ const char* VersionEdit::DecodeNewFile4From(Slice* input) {
|
||||
break;
|
||||
case kMinLogNumberToKeepHack:
|
||||
// This is a hack to encode kMinLogNumberToKeep in a
|
||||
// forward-compatbile fashion.
|
||||
// forward-compatible fashion.
|
||||
if (!GetFixed64(&field, &min_log_number_to_keep_)) {
|
||||
return "deleted log number malformatted";
|
||||
}
|
||||
@@ -289,7 +300,8 @@ const char* VersionEdit::DecodeNewFile4From(Slice* input) {
|
||||
} else {
|
||||
return "new-file4 entry";
|
||||
}
|
||||
f.fd = FileDescriptor(number, path_id, file_size);
|
||||
f.fd =
|
||||
FileDescriptor(number, path_id, file_size, smallest_seqno, largest_seqno);
|
||||
new_files_.push_back(std::make_pair(level, f));
|
||||
return nullptr;
|
||||
}
|
||||
@@ -409,13 +421,16 @@ Status VersionEdit::DecodeFrom(const Slice& src) {
|
||||
case kNewFile2: {
|
||||
uint64_t number;
|
||||
uint64_t file_size;
|
||||
SequenceNumber smallest_seqno;
|
||||
SequenceNumber largest_seqno;
|
||||
if (GetLevel(&input, &level, &msg) && GetVarint64(&input, &number) &&
|
||||
GetVarint64(&input, &file_size) &&
|
||||
GetInternalKey(&input, &f.smallest) &&
|
||||
GetInternalKey(&input, &f.largest) &&
|
||||
GetVarint64(&input, &f.smallest_seqno) &&
|
||||
GetVarint64(&input, &f.largest_seqno)) {
|
||||
f.fd = FileDescriptor(number, 0, file_size);
|
||||
GetVarint64(&input, &smallest_seqno) &&
|
||||
GetVarint64(&input, &largest_seqno)) {
|
||||
f.fd = FileDescriptor(number, 0, file_size, smallest_seqno,
|
||||
largest_seqno);
|
||||
new_files_.push_back(std::make_pair(level, f));
|
||||
} else {
|
||||
if (!msg) {
|
||||
@@ -429,13 +444,16 @@ Status VersionEdit::DecodeFrom(const Slice& src) {
|
||||
uint64_t number;
|
||||
uint32_t path_id;
|
||||
uint64_t file_size;
|
||||
SequenceNumber smallest_seqno;
|
||||
SequenceNumber largest_seqno;
|
||||
if (GetLevel(&input, &level, &msg) && GetVarint64(&input, &number) &&
|
||||
GetVarint32(&input, &path_id) && GetVarint64(&input, &file_size) &&
|
||||
GetInternalKey(&input, &f.smallest) &&
|
||||
GetInternalKey(&input, &f.largest) &&
|
||||
GetVarint64(&input, &f.smallest_seqno) &&
|
||||
GetVarint64(&input, &f.largest_seqno)) {
|
||||
f.fd = FileDescriptor(number, path_id, file_size);
|
||||
GetVarint64(&input, &smallest_seqno) &&
|
||||
GetVarint64(&input, &largest_seqno)) {
|
||||
f.fd = FileDescriptor(number, path_id, file_size, smallest_seqno,
|
||||
largest_seqno);
|
||||
new_files_.push_back(std::make_pair(level, f));
|
||||
} else {
|
||||
if (!msg) {
|
||||
@@ -473,6 +491,15 @@ Status VersionEdit::DecodeFrom(const Slice& src) {
|
||||
is_column_family_drop_ = true;
|
||||
break;
|
||||
|
||||
case kInAtomicGroup:
|
||||
is_in_atomic_group_ = true;
|
||||
if (!GetVarint32(&input, &remaining_entries_)) {
|
||||
if (!msg) {
|
||||
msg = "remaining entries";
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
msg = "unknown tag";
|
||||
break;
|
||||
@@ -551,6 +578,11 @@ std::string VersionEdit::DebugString(bool hex_key) const {
|
||||
r.append("\n MaxColumnFamily: ");
|
||||
AppendNumberTo(&r, max_column_family_);
|
||||
}
|
||||
if (is_in_atomic_group_) {
|
||||
r.append("\n AtomicGroup: ");
|
||||
AppendNumberTo(&r, remaining_entries_);
|
||||
r.append(" entries remains");
|
||||
}
|
||||
r.append("\n}\n");
|
||||
return r;
|
||||
}
|
||||
@@ -623,6 +655,9 @@ std::string VersionEdit::DebugJSON(int edit_num, bool hex_key) const {
|
||||
if (has_min_log_number_to_keep_) {
|
||||
jw << "MinLogNumberToKeep" << min_log_number_to_keep_;
|
||||
}
|
||||
if (is_in_atomic_group_) {
|
||||
jw << "AtomicGroup" << remaining_entries_;
|
||||
}
|
||||
|
||||
jw.EndObject();
|
||||
|
||||
|
||||
+31
-16
@@ -27,7 +27,7 @@ const uint64_t kFileNumberMask = 0x3FFFFFFFFFFFFFFF;
|
||||
extern uint64_t PackFileNumberAndPathId(uint64_t number, uint64_t path_id);
|
||||
|
||||
// A copyable structure contains information needed to read data from an SST
|
||||
// file. It can contains a pointer to a table reader opened for the file, or
|
||||
// file. It can contain a pointer to a table reader opened for the file, or
|
||||
// file number and size, which can be used to create a new table reader for it.
|
||||
// The behavior is undefined when a copied of the structure is used when the
|
||||
// file is not in any live version any more.
|
||||
@@ -36,18 +36,28 @@ struct FileDescriptor {
|
||||
TableReader* table_reader;
|
||||
uint64_t packed_number_and_path_id;
|
||||
uint64_t file_size; // File size in bytes
|
||||
SequenceNumber smallest_seqno; // The smallest seqno in this file
|
||||
SequenceNumber largest_seqno; // The largest seqno in this file
|
||||
|
||||
FileDescriptor() : FileDescriptor(0, 0, 0) {}
|
||||
|
||||
FileDescriptor(uint64_t number, uint32_t path_id, uint64_t _file_size)
|
||||
: FileDescriptor(number, path_id, _file_size, kMaxSequenceNumber, 0) {}
|
||||
|
||||
FileDescriptor(uint64_t number, uint32_t path_id, uint64_t _file_size,
|
||||
SequenceNumber _smallest_seqno, SequenceNumber _largest_seqno)
|
||||
: table_reader(nullptr),
|
||||
packed_number_and_path_id(PackFileNumberAndPathId(number, path_id)),
|
||||
file_size(_file_size) {}
|
||||
file_size(_file_size),
|
||||
smallest_seqno(_smallest_seqno),
|
||||
largest_seqno(_largest_seqno) {}
|
||||
|
||||
FileDescriptor& operator=(const FileDescriptor& fd) {
|
||||
table_reader = fd.table_reader;
|
||||
packed_number_and_path_id = fd.packed_number_and_path_id;
|
||||
file_size = fd.file_size;
|
||||
smallest_seqno = fd.smallest_seqno;
|
||||
largest_seqno = fd.largest_seqno;
|
||||
return *this;
|
||||
}
|
||||
|
||||
@@ -77,8 +87,6 @@ struct FileMetaData {
|
||||
FileDescriptor fd;
|
||||
InternalKey smallest; // Smallest internal key served by table
|
||||
InternalKey largest; // Largest internal key served by table
|
||||
SequenceNumber smallest_seqno; // The smallest seqno in this file
|
||||
SequenceNumber largest_seqno; // The largest seqno in this file
|
||||
|
||||
// Needs to be disposed when refs becomes 0.
|
||||
Cache::Handle* table_reader_handle;
|
||||
@@ -108,9 +116,7 @@ struct FileMetaData {
|
||||
// file.
|
||||
|
||||
FileMetaData()
|
||||
: smallest_seqno(kMaxSequenceNumber),
|
||||
largest_seqno(0),
|
||||
table_reader_handle(nullptr),
|
||||
: table_reader_handle(nullptr),
|
||||
compensated_file_size(0),
|
||||
num_entries(0),
|
||||
num_deletions(0),
|
||||
@@ -128,8 +134,8 @@ struct FileMetaData {
|
||||
smallest.DecodeFrom(key);
|
||||
}
|
||||
largest.DecodeFrom(key);
|
||||
smallest_seqno = std::min(smallest_seqno, seqno);
|
||||
largest_seqno = std::max(largest_seqno, seqno);
|
||||
fd.smallest_seqno = std::min(fd.smallest_seqno, seqno);
|
||||
fd.largest_seqno = std::max(fd.largest_seqno, seqno);
|
||||
}
|
||||
|
||||
// Unlike UpdateBoundaries, ranges do not need to be presented in any
|
||||
@@ -143,8 +149,8 @@ struct FileMetaData {
|
||||
if (largest.size() == 0 || icmp.Compare(largest, end) < 0) {
|
||||
largest = end;
|
||||
}
|
||||
smallest_seqno = std::min(smallest_seqno, seqno);
|
||||
largest_seqno = std::max(largest_seqno, seqno);
|
||||
fd.smallest_seqno = std::min(fd.smallest_seqno, seqno);
|
||||
fd.largest_seqno = std::max(fd.largest_seqno, seqno);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -233,17 +239,18 @@ class VersionEdit {
|
||||
bool marked_for_compaction) {
|
||||
assert(smallest_seqno <= largest_seqno);
|
||||
FileMetaData f;
|
||||
f.fd = FileDescriptor(file, file_path_id, file_size);
|
||||
f.fd = FileDescriptor(file, file_path_id, file_size, smallest_seqno,
|
||||
largest_seqno);
|
||||
f.smallest = smallest;
|
||||
f.largest = largest;
|
||||
f.smallest_seqno = smallest_seqno;
|
||||
f.largest_seqno = largest_seqno;
|
||||
f.fd.smallest_seqno = smallest_seqno;
|
||||
f.fd.largest_seqno = largest_seqno;
|
||||
f.marked_for_compaction = marked_for_compaction;
|
||||
new_files_.emplace_back(level, std::move(f));
|
||||
}
|
||||
|
||||
void AddFile(int level, const FileMetaData& f) {
|
||||
assert(f.smallest_seqno <= f.largest_seqno);
|
||||
assert(f.fd.smallest_seqno <= f.fd.largest_seqno);
|
||||
new_files_.emplace_back(level, f);
|
||||
}
|
||||
|
||||
@@ -293,6 +300,11 @@ class VersionEdit {
|
||||
return new_files_;
|
||||
}
|
||||
|
||||
void MarkAtomicGroup(uint32_t remaining_entries) {
|
||||
is_in_atomic_group_ = true;
|
||||
remaining_entries_ = remaining_entries;
|
||||
}
|
||||
|
||||
std::string DebugString(bool hex_key = false) const;
|
||||
std::string DebugJSON(int edit_num, bool hex_key = false) const;
|
||||
|
||||
@@ -322,7 +334,7 @@ class VersionEdit {
|
||||
DeletedFileSet deleted_files_;
|
||||
std::vector<std::pair<int, FileMetaData>> new_files_;
|
||||
|
||||
// Each version edit record should have column_family_id set
|
||||
// Each version edit record should have column_family_ set
|
||||
// If it's not set, it is default (0)
|
||||
uint32_t column_family_;
|
||||
// a version edit can be either column_family add or
|
||||
@@ -331,6 +343,9 @@ class VersionEdit {
|
||||
bool is_column_family_drop_;
|
||||
bool is_column_family_add_;
|
||||
std::string column_family_name_;
|
||||
|
||||
bool is_in_atomic_group_;
|
||||
uint32_t remaining_entries_;
|
||||
};
|
||||
|
||||
} // namespace rocksdb
|
||||
Loaded 100 of 384 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user