mirror of
https://github.com/facebook/rocksdb.git
synced 2026-07-08 07:05:23 +08:00
Compare commits
120 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 196c68703a | |||
| 35e5689e11 | |||
| 32a6dd9a41 | |||
| 158da7a6ee | |||
| d0d484b132 | |||
| 95604d13e9 | |||
| 4978caaa6f | |||
| ee1818081f | |||
| e2547103fd | |||
| e242fa4664 | |||
| bc7d1661a8 | |||
| f184bee77b | |||
| e1de88c8c7 | |||
| fc53839bfa | |||
| e07aa8669d | |||
| 8ec3e72551 | |||
| 43defe9872 | |||
| d94aa2f7db | |||
| 27054d837b | |||
| 0cead31d10 | |||
| 5bf941966b | |||
| 1eded07f00 | |||
| cbe0239270 | |||
| 08b8cea69f | |||
| b2ba06856a | |||
| 8189c184ec | |||
| ce8e88d2d7 | |||
| 16a5ac5b69 | |||
| 01013ae766 | |||
| b1ad6ebba8 | |||
| 7fd9813b9f | |||
| 73ff15c07b | |||
| dd9eca1c58 | |||
| 3cfc7515fc | |||
| 128f532858 | |||
| e79df377c5 | |||
| 5d4fddfa52 | |||
| cad99a6031 | |||
| 7d13f307ff | |||
| 55e03b67df | |||
| d50c10ed37 | |||
| 301da345ae | |||
| 4e37251b4d | |||
| 856ac24484 | |||
| 6a4ec41fed | |||
| 349c7cceff | |||
| 1fb2e274c5 | |||
| d56ac22b44 | |||
| 75714b4c08 | |||
| ffc9f84649 | |||
| f3a99e8a4d | |||
| 8641e9adf7 | |||
| cd227d74ba | |||
| e686caffec | |||
| 0ed98bf89e | |||
| 42135523a0 | |||
| a3ab59d030 | |||
| cf852fdf55 | |||
| 9e2c804fe6 | |||
| a07175af65 | |||
| 77a8d4d476 | |||
| 8c79f79208 | |||
| 74f7d7551e | |||
| e4feb78606 | |||
| 073285363e | |||
| 565b5bdc42 | |||
| ace543a815 | |||
| e8210e44da | |||
| b9d6eccac1 | |||
| 7d65bd5ce4 | |||
| f8d5c1b012 | |||
| ec68091d19 | |||
| f0dda35d7d | |||
| 46e3209e0d | |||
| b1288cdc24 | |||
| ca32fc1718 | |||
| 80bf8975fd | |||
| e0be1bc4f1 | |||
| da1c64b6e7 | |||
| 02bfc5831e | |||
| 8bf73208a4 | |||
| 71a69d9b68 | |||
| 1b0c9ce396 | |||
| 257b458121 | |||
| 671a7eb36f | |||
| c15df15f07 | |||
| 68d949b3e3 | |||
| 81b6b09f6b | |||
| 311cd8cf2f | |||
| 75a333ad2d | |||
| a62c6626e0 | |||
| 84001cfa96 | |||
| abf931afa6 | |||
| a914a1c6dc | |||
| 4ed3c1eb88 | |||
| d6dfe516ff | |||
| 8d2b74d287 | |||
| 349542332a | |||
| 4fce44fc8b | |||
| 67e5b5420e | |||
| 2670fe8c73 | |||
| a8b9891f95 | |||
| 0aa17c1002 | |||
| 4862720e08 | |||
| ae25546a7a | |||
| 49666d76cf | |||
| cad248f5c6 | |||
| d3daa0db8b | |||
| dde3ef1116 | |||
| f8943ec003 | |||
| 8261e0026b | |||
| 64aabc9183 | |||
| 10e7de7705 | |||
| 21fca397cc | |||
| f307479ba6 | |||
| 1b01d23be2 | |||
| 4048762cbe | |||
| 0463f61837 | |||
| c41c60be13 | |||
| b878f93c70 |
+9
-21
@@ -94,7 +94,13 @@ else()
|
||||
if(WITH_ZLIB)
|
||||
find_package(zlib REQUIRED)
|
||||
add_definitions(-DZLIB)
|
||||
include_directories(${ZLIB_INCLUDE_DIR})
|
||||
if(ZLIB_INCLUDE_DIRS)
|
||||
# CMake 3
|
||||
include_directories(${ZLIB_INCLUDE_DIRS})
|
||||
else()
|
||||
# CMake 2
|
||||
include_directories(${ZLIB_INCLUDE_DIR})
|
||||
endif()
|
||||
list(APPEND THIRDPARTY_LIBS ${ZLIB_LIBRARIES})
|
||||
endif()
|
||||
|
||||
@@ -504,7 +510,6 @@ set(SOURCES
|
||||
db/merge_helper.cc
|
||||
db/merge_operator.cc
|
||||
db/range_del_aggregator.cc
|
||||
db/range_del_aggregator_v2.cc
|
||||
db/range_tombstone_fragmenter.cc
|
||||
db/repair.cc
|
||||
db/snapshot_impl.cc
|
||||
@@ -597,6 +602,7 @@ set(SOURCES
|
||||
util/comparator.cc
|
||||
util/compression_context_cache.cc
|
||||
util/concurrent_arena.cc
|
||||
util/concurrent_task_limiter_impl.cc
|
||||
util/crc32c.cc
|
||||
util/delete_scheduler.cc
|
||||
util/dynamic_bloom.cc
|
||||
@@ -637,18 +643,10 @@ set(SOURCES
|
||||
utilities/cassandra/format.cc
|
||||
utilities/cassandra/merge_operator.cc
|
||||
utilities/checkpoint/checkpoint_impl.cc
|
||||
utilities/col_buf_decoder.cc
|
||||
utilities/col_buf_encoder.cc
|
||||
utilities/column_aware_encoding_util.cc
|
||||
utilities/compaction_filters/remove_emptyvalue_compactionfilter.cc
|
||||
utilities/date_tiered/date_tiered_db_impl.cc
|
||||
utilities/debug.cc
|
||||
utilities/document/document_db.cc
|
||||
utilities/document/json_document.cc
|
||||
utilities/document/json_document_builder.cc
|
||||
utilities/env_mirror.cc
|
||||
utilities/env_timed.cc
|
||||
utilities/geodb/geodb_impl.cc
|
||||
utilities/leveldb_options/leveldb_options.cc
|
||||
utilities/lua/rocks_lua_compaction_filter.cc
|
||||
utilities/memory/memory_util.cc
|
||||
@@ -665,9 +663,7 @@ set(SOURCES
|
||||
utilities/persistent_cache/block_cache_tier_metadata.cc
|
||||
utilities/persistent_cache/persistent_cache_tier.cc
|
||||
utilities/persistent_cache/volatile_tier_impl.cc
|
||||
utilities/redis/redis_lists.cc
|
||||
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
|
||||
@@ -823,6 +819,7 @@ if(NOT WIN32 OR ROCKSDB_INSTALL_ON_WINDOWS)
|
||||
TARGETS ${ROCKSDB_SHARED_LIB}
|
||||
EXPORT RocksDBTargets
|
||||
COMPONENT runtime
|
||||
ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}"
|
||||
RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}"
|
||||
LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}"
|
||||
INCLUDES DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}"
|
||||
@@ -907,7 +904,6 @@ if(WITH_TESTS)
|
||||
db/plain_table_db_test.cc
|
||||
db/prefix_test.cc
|
||||
db/range_del_aggregator_test.cc
|
||||
db/range_del_aggregator_v2_test.cc
|
||||
db/range_tombstone_fragmenter_test.cc
|
||||
db/repair_test.cc
|
||||
db/table_properties_collector_test.cc
|
||||
@@ -969,11 +965,6 @@ if(WITH_TESTS)
|
||||
utilities/cassandra/cassandra_row_merge_test.cc
|
||||
utilities/cassandra/cassandra_serialize_test.cc
|
||||
utilities/checkpoint/checkpoint_test.cc
|
||||
utilities/column_aware_encoding_test.cc
|
||||
utilities/date_tiered/date_tiered_test.cc
|
||||
utilities/document/document_db_test.cc
|
||||
utilities/document/json_document_test.cc
|
||||
utilities/geodb/geodb_test.cc
|
||||
utilities/lua/rocks_lua_test.cc
|
||||
utilities/memory/memory_test.cc
|
||||
utilities/merge_operators/string_append/stringappend_test.cc
|
||||
@@ -982,8 +973,6 @@ if(WITH_TESTS)
|
||||
utilities/options/options_util_test.cc
|
||||
utilities/persistent_cache/hash_table_test.cc
|
||||
utilities/persistent_cache/persistent_cache_test.cc
|
||||
utilities/redis/redis_lists_test.cc
|
||||
utilities/spatialdb/spatial_db_test.cc
|
||||
utilities/simulator_cache/sim_cache_test.cc
|
||||
utilities/table_properties_collectors/compact_on_deletion_collector_test.cc
|
||||
utilities/transactions/optimistic_transaction_test.cc
|
||||
@@ -1003,7 +992,6 @@ if(WITH_TESTS)
|
||||
db/range_del_aggregator_bench.cc
|
||||
tools/db_bench.cc
|
||||
table/table_reader_bench.cc
|
||||
utilities/column_aware_encoding_exp.cc
|
||||
utilities/persistent_cache/hash_table_bench.cc)
|
||||
add_library(testharness OBJECT util/testharness.cc)
|
||||
foreach(sourcefile ${BENCHMARKS})
|
||||
|
||||
+22
@@ -1,11 +1,33 @@
|
||||
# Rocksdb Change Log
|
||||
## Unreleased
|
||||
### New Features
|
||||
* Enabled checkpoint on readonly db (DBImplReadOnly).
|
||||
* Make DB ignore dropped column families while committing results of atomic flush.
|
||||
* RocksDB may choose to preopen some files even if options.max_open_files != -1. This may make DB open slightly longer.
|
||||
* For users of dictionary compression with ZSTD v0.7.0+, we now reuse the same digested dictionary when compressing each of an SST file's data blocks for faster compression speeds.
|
||||
* For all users of dictionary compression who set `cache_index_and_filter_blocks == true`, we now store dictionary data used for decompression in the block cache for better control over memory usage. For users of ZSTD v1.1.4+ who compile with -DZSTD_STATIC_LINKING_ONLY, this includes a digested dictionary, which is used to increase decompression speed.
|
||||
* Add support for block checksums verification for external SST files before ingestion.
|
||||
|
||||
### Public API Change
|
||||
* CompactionPri = kMinOverlappingRatio also uses compensated file size, which boosts file with lots of tombstones to be compacted first.
|
||||
* Transaction::GetForUpdate is extended with a do_validate parameter with default value of true. If false it skips validating the snapshot before doing the read. Similarly ::Merge, ::Put, ::Delete, and ::SingleDelete are extended with assume_tracked with default value of false. If true it indicates that call is assumed to be after a ::GetForUpdate.
|
||||
* `TableProperties::num_entries` and `TableProperties::num_deletions` now also account for number of range tombstones.
|
||||
* Remove geodb, spatial_db, document_db, json_document, date_tiered_db, and redis_lists.
|
||||
* With "ldb ----try_load_options", when wal_dir specified by the option file doesn't exist, ignore it.
|
||||
* Change time resolution in FileOperationInfo.
|
||||
* Deleting Blob files also go through SStFileManager.
|
||||
* Remove PlainTable's store_index_in_file feature. When opening an existing DB with index in SST files, the index and bloom filter will still be rebuild while SST files are opened, in the same way as there is no index in the file.
|
||||
|
||||
### Bug Fixes
|
||||
* Fix a deadlock caused by compaction and file ingestion waiting for each other in the event of write stalls.
|
||||
* Fix a memory leak when files with range tombstones are read in mmap mode and block cache is enabled
|
||||
* Fix handling of corrupt range tombstone blocks such that corruptions cannot cause deleted keys to reappear
|
||||
* Lock free MultiGet
|
||||
* Fix incorrect `NotFound` point lookup result when querying the endpoint of a file that has been extended by a range tombstone.
|
||||
* Fix with pipelined write, write leaders's callback failure lead to the whole write group fail.
|
||||
|
||||
### Change Default Options
|
||||
* Change options.compaction_pri's default to kMinOverlappingRatio
|
||||
|
||||
## 5.18.0 (11/30/2018)
|
||||
### New Features
|
||||
|
||||
@@ -43,6 +43,8 @@ to build a portable binary, add `PORTABLE=1` before your make commands, like thi
|
||||
command line flags processing. You can compile rocksdb library even
|
||||
if you don't have gflags installed.
|
||||
|
||||
* If you wish to build the RocksJava static target, then cmake is required for building Snappy.
|
||||
|
||||
## Supported platforms
|
||||
|
||||
* **Linux - Ubuntu**
|
||||
|
||||
@@ -198,7 +198,7 @@ include make_config.mk
|
||||
CLEAN_FILES += make_config.mk
|
||||
|
||||
missing_make_config_paths := $(shell \
|
||||
grep "\/\S*" -o $(CURDIR)/make_config.mk | \
|
||||
grep "\./\S*\|/\S*" -o $(CURDIR)/make_config.mk | \
|
||||
while read path; \
|
||||
do [ -e $$path ] || echo $$path; \
|
||||
done | sort | uniq)
|
||||
@@ -403,7 +403,7 @@ BENCHTOOLOBJECTS = $(BENCH_LIB_SOURCES:.cc=.o) $(LIBOBJECTS) $(TESTUTIL)
|
||||
|
||||
ANALYZETOOLOBJECTS = $(ANALYZER_LIB_SOURCES:.cc=.o)
|
||||
|
||||
EXPOBJECTS = $(EXP_LIB_SOURCES:.cc=.o) $(LIBOBJECTS) $(TESTUTIL)
|
||||
EXPOBJECTS = $(LIBOBJECTS) $(TESTUTIL)
|
||||
|
||||
TESTS = \
|
||||
db_basic_test \
|
||||
@@ -482,7 +482,6 @@ TESTS = \
|
||||
merger_test \
|
||||
util_merge_operators_test \
|
||||
options_file_test \
|
||||
redis_test \
|
||||
reduce_levels_test \
|
||||
plain_table_db_test \
|
||||
comparator_db_test \
|
||||
@@ -496,12 +495,8 @@ TESTS = \
|
||||
cassandra_row_merge_test \
|
||||
cassandra_serialize_test \
|
||||
ttl_test \
|
||||
date_tiered_test \
|
||||
backupable_db_test \
|
||||
document_db_test \
|
||||
json_document_test \
|
||||
sim_cache_test \
|
||||
spatial_db_test \
|
||||
version_edit_test \
|
||||
version_set_test \
|
||||
compaction_picker_test \
|
||||
@@ -513,7 +508,6 @@ TESTS = \
|
||||
deletefile_test \
|
||||
obsolete_files_test \
|
||||
table_test \
|
||||
geodb_test \
|
||||
delete_scheduler_test \
|
||||
options_test \
|
||||
options_settable_test \
|
||||
@@ -530,7 +524,6 @@ TESTS = \
|
||||
compaction_job_test \
|
||||
thread_list_test \
|
||||
sst_dump_test \
|
||||
column_aware_encoding_test \
|
||||
compact_files_test \
|
||||
optimistic_transaction_test \
|
||||
write_callback_test \
|
||||
@@ -543,7 +536,6 @@ TESTS = \
|
||||
persistent_cache_test \
|
||||
statistics_test \
|
||||
lua_test \
|
||||
range_del_aggregator_test \
|
||||
lru_cache_test \
|
||||
object_registry_test \
|
||||
repair_test \
|
||||
@@ -554,7 +546,7 @@ TESTS = \
|
||||
trace_analyzer_test \
|
||||
repeatable_thread_test \
|
||||
range_tombstone_fragmenter_test \
|
||||
range_del_aggregator_v2_test \
|
||||
range_del_aggregator_test \
|
||||
sst_file_reader_test \
|
||||
|
||||
PARALLEL_TEST = \
|
||||
@@ -605,7 +597,7 @@ 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 range_del_aggregator_bench
|
||||
BENCHMARKS = db_bench table_reader_bench cache_bench memtablerep_bench persistent_cache_bench range_del_aggregator_bench
|
||||
|
||||
# if user didn't config LIBNAME, set the default
|
||||
ifeq ($(LIBNAME),)
|
||||
@@ -825,7 +817,6 @@ J ?= 100%
|
||||
# Use this regexp to select the subset of tests whose names match.
|
||||
tests-regexp = .
|
||||
|
||||
t_run = $(wildcard t/run-*)
|
||||
.PHONY: check_0
|
||||
check_0:
|
||||
$(AM_V_GEN)export TEST_TMPDIR=$(TMPD); \
|
||||
@@ -835,7 +826,7 @@ check_0:
|
||||
test -t 1 && eta=--eta || eta=; \
|
||||
{ \
|
||||
printf './%s\n' $(filter-out $(PARALLEL_TEST),$(TESTS)); \
|
||||
printf '%s\n' $(t_run); \
|
||||
find t -name 'run-*' -print; \
|
||||
} \
|
||||
| $(prioritize_long_running_tests) \
|
||||
| grep -E '$(tests-regexp)' \
|
||||
@@ -852,7 +843,7 @@ valgrind_check_0:
|
||||
test -t 1 && eta=--eta || eta=; \
|
||||
{ \
|
||||
printf './%s\n' $(filter-out $(PARALLEL_TEST) %skiplist_test options_settable_test, $(TESTS)); \
|
||||
printf '%s\n' $(t_run); \
|
||||
find t -name 'run-*' -print; \
|
||||
} \
|
||||
| $(prioritize_long_running_tests) \
|
||||
| grep -E '$(tests-regexp)' \
|
||||
@@ -1154,9 +1145,6 @@ cassandra_row_merge_test: utilities/cassandra/cassandra_row_merge_test.o utiliti
|
||||
cassandra_serialize_test: utilities/cassandra/cassandra_serialize_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
redis_test: utilities/redis/redis_lists_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
hash_table_test: utilities/persistent_cache/hash_table_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
@@ -1295,18 +1283,9 @@ backupable_db_test: utilities/backupable/backupable_db_test.o $(LIBOBJECTS) $(TE
|
||||
checkpoint_test: utilities/checkpoint/checkpoint_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
document_db_test: utilities/document/document_db_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
json_document_test: utilities/document/json_document_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
sim_cache_test: utilities/simulator_cache/sim_cache_test.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
spatial_db_test: utilities/spatialdb/spatial_db_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
env_mirror_test: utilities/env_mirror_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
@@ -1324,9 +1303,6 @@ object_registry_test: utilities/object_registry_test.o $(LIBOBJECTS) $(TESTHARNE
|
||||
ttl_test: utilities/ttl/ttl_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
date_tiered_test: utilities/date_tiered/date_tiered_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
write_batch_with_index_test: utilities/write_batch_with_index/write_batch_with_index_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
@@ -1453,9 +1429,6 @@ deletefile_test: db/deletefile_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
obsolete_files_test: db/obsolete_files_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
geodb_test: utilities/geodb/geodb_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
rocksdb_dump: tools/dump/rocksdb_dump.o $(LIBOBJECTS)
|
||||
$(AM_LINK)
|
||||
|
||||
@@ -1504,9 +1477,6 @@ timer_queue_test: util/timer_queue_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
sst_dump_test: tools/sst_dump_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
column_aware_encoding_test: utilities/column_aware_encoding_test.o $(TESTHARNESS) $(EXPOBJECTS)
|
||||
$(AM_LINK)
|
||||
|
||||
optimistic_transaction_test: utilities/transactions/optimistic_transaction_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
@@ -1546,9 +1516,6 @@ sst_dump: tools/sst_dump.o $(LIBOBJECTS)
|
||||
blob_dump: tools/blob_dump.o $(LIBOBJECTS)
|
||||
$(AM_LINK)
|
||||
|
||||
column_aware_encoding_exp: utilities/column_aware_encoding_exp.o $(EXPOBJECTS)
|
||||
$(AM_LINK)
|
||||
|
||||
repair_test: db/repair_test.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
@@ -1588,9 +1555,6 @@ repeatable_thread_test: util/repeatable_thread_test.o $(LIBOBJECTS) $(TESTHARNES
|
||||
range_tombstone_fragmenter_test: db/range_tombstone_fragmenter_test.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
range_del_aggregator_v2_test: db/range_del_aggregator_v2_test.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
sst_file_reader_test: table/sst_file_reader_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
@@ -1665,14 +1629,14 @@ ZLIB_DOWNLOAD_BASE ?= http://zlib.net
|
||||
BZIP2_VER ?= 1.0.6
|
||||
BZIP2_SHA256 ?= a2848f34fcd5d6cf47def00461fcb528a0484d8edef8208d6d2e2909dc61d9cd
|
||||
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
|
||||
LZ4_VER ?= 1.8.0
|
||||
LZ4_SHA256 ?= 2ca482ea7a9bb103603108b5a7510b7592b90158c151ff50a28f1ca8389fccf6
|
||||
SNAPPY_VER ?= 1.1.7
|
||||
SNAPPY_SHA256 ?= 3dfa02e873ff51a11ee02b9ca391807f0c8ea0529a4924afa645fbf97163f9d4
|
||||
SNAPPY_DOWNLOAD_BASE ?= https://github.com/google/snappy/archive
|
||||
LZ4_VER ?= 1.8.3
|
||||
LZ4_SHA256 ?= 33af5936ac06536805f9745e0b6d61da606a1f8b4cc5c04dd3cbaca3b9b4fc43
|
||||
LZ4_DOWNLOAD_BASE ?= https://github.com/lz4/lz4/archive
|
||||
ZSTD_VER ?= 1.3.3
|
||||
ZSTD_SHA256 ?= a77c47153ee7de02626c5b2a097005786b71688be61e9fb81806a011f90b297b
|
||||
ZSTD_VER ?= 1.3.7
|
||||
ZSTD_SHA256 ?= 5dd1e90eb16c25425880c8a91327f63de22891ffed082fcc17e5ae84fce0d5fb
|
||||
ZSTD_DOWNLOAD_BASE ?= https://github.com/facebook/zstd/archive
|
||||
CURL_SSL_OPTS ?= --tlsv1
|
||||
|
||||
@@ -1711,7 +1675,9 @@ endif
|
||||
|
||||
libz.a:
|
||||
-rm -rf zlib-$(ZLIB_VER)
|
||||
curl -O -L ${ZLIB_DOWNLOAD_BASE}/zlib-$(ZLIB_VER).tar.gz
|
||||
ifeq (,$(wildcard ./zlib-$(ZLIB_VER).tar.gz))
|
||||
curl --output zlib-$(ZLIB_VER).tar.gz -L ${ZLIB_DOWNLOAD_BASE}/zlib-$(ZLIB_VER).tar.gz
|
||||
endif
|
||||
ZLIB_SHA256_ACTUAL=`$(SHA256_CMD) zlib-$(ZLIB_VER).tar.gz | cut -d ' ' -f 1`; \
|
||||
if [ "$(ZLIB_SHA256)" != "$$ZLIB_SHA256_ACTUAL" ]; then \
|
||||
echo zlib-$(ZLIB_VER).tar.gz checksum mismatch, expected=\"$(ZLIB_SHA256)\" actual=\"$$ZLIB_SHA256_ACTUAL\"; \
|
||||
@@ -1723,7 +1689,9 @@ libz.a:
|
||||
|
||||
libbz2.a:
|
||||
-rm -rf bzip2-$(BZIP2_VER)
|
||||
curl -O -L ${BZIP2_DOWNLOAD_BASE}/$(BZIP2_VER)/bzip2-$(BZIP2_VER).tar.gz
|
||||
ifeq (,$(wildcard ./bzip2-$(BZIP2_VER).tar.gz))
|
||||
curl --output bzip2-$(BZIP2_VER).tar.gz -L ${BZIP2_DOWNLOAD_BASE}/$(BZIP2_VER)/bzip2-$(BZIP2_VER).tar.gz
|
||||
endif
|
||||
BZIP2_SHA256_ACTUAL=`$(SHA256_CMD) bzip2-$(BZIP2_VER).tar.gz | cut -d ' ' -f 1`; \
|
||||
if [ "$(BZIP2_SHA256)" != "$$BZIP2_SHA256_ACTUAL" ]; then \
|
||||
echo bzip2-$(BZIP2_VER).tar.gz checksum mismatch, expected=\"$(BZIP2_SHA256)\" actual=\"$$BZIP2_SHA256_ACTUAL\"; \
|
||||
@@ -1735,21 +1703,24 @@ libbz2.a:
|
||||
|
||||
libsnappy.a:
|
||||
-rm -rf snappy-$(SNAPPY_VER)
|
||||
curl -O -L ${CURL_SSL_OPTS} ${SNAPPY_DOWNLOAD_BASE}/$(SNAPPY_VER)/snappy-$(SNAPPY_VER).tar.gz
|
||||
ifeq (,$(wildcard ./snappy-$(SNAPPY_VER).tar.gz))
|
||||
curl --output snappy-$(SNAPPY_VER).tar.gz -L ${CURL_SSL_OPTS} ${SNAPPY_DOWNLOAD_BASE}/$(SNAPPY_VER).tar.gz
|
||||
endif
|
||||
SNAPPY_SHA256_ACTUAL=`$(SHA256_CMD) snappy-$(SNAPPY_VER).tar.gz | cut -d ' ' -f 1`; \
|
||||
if [ "$(SNAPPY_SHA256)" != "$$SNAPPY_SHA256_ACTUAL" ]; then \
|
||||
echo snappy-$(SNAPPY_VER).tar.gz checksum mismatch, expected=\"$(SNAPPY_SHA256)\" actual=\"$$SNAPPY_SHA256_ACTUAL\"; \
|
||||
exit 1; \
|
||||
fi
|
||||
tar xvzf snappy-$(SNAPPY_VER).tar.gz
|
||||
cd snappy-$(SNAPPY_VER) && CFLAGS='${EXTRA_CFLAGS}' CXXFLAGS='${EXTRA_CXXFLAGS}' LDFLAGS='${EXTRA_LDFLAGS}' ./configure --with-pic --enable-static --disable-shared
|
||||
cd snappy-$(SNAPPY_VER) && $(MAKE) ${SNAPPY_MAKE_TARGET}
|
||||
cp snappy-$(SNAPPY_VER)/.libs/libsnappy.a .
|
||||
mkdir snappy-$(SNAPPY_VER)/build
|
||||
cd snappy-$(SNAPPY_VER)/build && CFLAGS='${EXTRA_CFLAGS}' CXXFLAGS='${EXTRA_CXXFLAGS}' LDFLAGS='${EXTRA_LDFLAGS}' cmake .. && $(MAKE) ${SNAPPY_MAKE_TARGET}
|
||||
cp snappy-$(SNAPPY_VER)/build/libsnappy.a .
|
||||
|
||||
liblz4.a:
|
||||
-rm -rf lz4-$(LZ4_VER)
|
||||
curl -O -L ${CURL_SSL_OPTS} ${LZ4_DOWNLOAD_BASE}/v$(LZ4_VER).tar.gz
|
||||
mv v$(LZ4_VER).tar.gz lz4-$(LZ4_VER).tar.gz
|
||||
ifeq (,$(wildcard ./lz4-$(LZ4_VER).tar.gz))
|
||||
curl --output lz4-$(LZ4_VER).tar.gz -L ${CURL_SSL_OPTS} ${LZ4_DOWNLOAD_BASE}/v$(LZ4_VER).tar.gz
|
||||
endif
|
||||
LZ4_SHA256_ACTUAL=`$(SHA256_CMD) lz4-$(LZ4_VER).tar.gz | cut -d ' ' -f 1`; \
|
||||
if [ "$(LZ4_SHA256)" != "$$LZ4_SHA256_ACTUAL" ]; then \
|
||||
echo lz4-$(LZ4_VER).tar.gz checksum mismatch, expected=\"$(LZ4_SHA256)\" actual=\"$$LZ4_SHA256_ACTUAL\"; \
|
||||
@@ -1761,8 +1732,9 @@ liblz4.a:
|
||||
|
||||
libzstd.a:
|
||||
-rm -rf zstd-$(ZSTD_VER)
|
||||
curl -O -L ${CURL_SSL_OPTS} ${ZSTD_DOWNLOAD_BASE}/v$(ZSTD_VER).tar.gz
|
||||
mv v$(ZSTD_VER).tar.gz zstd-$(ZSTD_VER).tar.gz
|
||||
ifeq (,$(wildcard ./zstd-$(ZSTD_VER).tar.gz))
|
||||
curl --output zstd-$(ZSTD_VER).tar.gz -L ${CURL_SSL_OPTS} ${ZSTD_DOWNLOAD_BASE}/v$(ZSTD_VER).tar.gz
|
||||
endif
|
||||
ZSTD_SHA256_ACTUAL=`$(SHA256_CMD) zstd-$(ZSTD_VER).tar.gz | cut -d ' ' -f 1`; \
|
||||
if [ "$(ZSTD_SHA256)" != "$$ZSTD_SHA256_ACTUAL" ]; then \
|
||||
echo zstd-$(ZSTD_VER).tar.gz checksum mismatch, expected=\"$(ZSTD_SHA256)\" actual=\"$$ZSTD_SHA256_ACTUAL\"; \
|
||||
@@ -1931,7 +1903,8 @@ commit_prereq: build_tools/rocksdb-lego-determinator \
|
||||
ifeq ($(PLATFORM), IOS)
|
||||
# For iOS, create universal object files to be used on both the simulator and
|
||||
# a device.
|
||||
PLATFORMSROOT=/Applications/Xcode.app/Contents/Developer/Platforms
|
||||
XCODEROOT=$(shell xcode-select -print-path)
|
||||
PLATFORMSROOT=$(XCODEROOT)/Platforms
|
||||
SIMULATORROOT=$(PLATFORMSROOT)/iPhoneSimulator.platform/Developer
|
||||
DEVICEROOT=$(PLATFORMSROOT)/iPhoneOS.platform/Developer
|
||||
IOSVERSION=$(shell defaults read $(PLATFORMSROOT)/iPhoneOS.platform/version CFBundleShortVersionString)
|
||||
@@ -1968,7 +1941,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) $(ANALYZER_LIB_SOURCES)
|
||||
all_sources = $(LIB_SOURCES) $(MAIN_SOURCES) $(MOCK_LIB_SOURCES) $(TOOL_LIB_SOURCES) $(BENCH_LIB_SOURCES) $(TEST_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,12 +1,10 @@
|
||||
load("@fbcode_macros//build_defs:auto_headers.bzl", "AutoHeaders")
|
||||
load("@fbcode_macros//build_defs:cpp_library.bzl", "cpp_library")
|
||||
load(":defs.bzl", "test_binary")
|
||||
|
||||
REPO_PATH = package_name() + "/"
|
||||
|
||||
BUCK_BINS = "buck-out/gen/" + REPO_PATH
|
||||
|
||||
TEST_RUNNER = REPO_PATH + "buckifier/rocks_test_runner.sh"
|
||||
|
||||
rocksdb_compiler_flags = [
|
||||
ROCKSDB_COMPILER_FLAGS = [
|
||||
"-fno-builtin-memcmp",
|
||||
"-DROCKSDB_PLATFORM_POSIX",
|
||||
"-DROCKSDB_LIB_IO_POSIX",
|
||||
@@ -22,6 +20,7 @@ rocksdb_compiler_flags = [
|
||||
"-DBZIP2",
|
||||
"-DLZ4",
|
||||
"-DZSTD",
|
||||
"-DZSTD_STATIC_LINKING_ONLY",
|
||||
"-DGFLAGS=gflags",
|
||||
"-DNUMA",
|
||||
"-DTBB",
|
||||
@@ -33,7 +32,7 @@ rocksdb_compiler_flags = [
|
||||
"-Wnarrowing",
|
||||
]
|
||||
|
||||
rocksdb_external_deps = [
|
||||
ROCKSDB_EXTERNAL_DEPS = [
|
||||
("bzip2", None, "bz2"),
|
||||
("snappy", None, "snappy"),
|
||||
("zlib", None, "z"),
|
||||
@@ -45,13 +44,13 @@ rocksdb_external_deps = [
|
||||
("googletest", None, "gtest"),
|
||||
]
|
||||
|
||||
rocksdb_preprocessor_flags = [
|
||||
ROCKSDB_PREPROCESSOR_FLAGS = [
|
||||
# Directories with files for #include
|
||||
"-I" + REPO_PATH + "include/",
|
||||
"-I" + REPO_PATH,
|
||||
]
|
||||
|
||||
rocksdb_arch_preprocessor_flags = {
|
||||
ROCKSDB_ARCH_PREPROCESSOR_FLAGS = {
|
||||
"x86_64": [
|
||||
"-DHAVE_SSE42",
|
||||
"-DHAVE_PCLMUL",
|
||||
@@ -64,18 +63,15 @@ is_opt_mode = build_mode.startswith("opt")
|
||||
|
||||
# -DNDEBUG is added by default in opt mode in fbcode. But adding it twice
|
||||
# doesn't harm and avoid forgetting to add it.
|
||||
if is_opt_mode:
|
||||
rocksdb_compiler_flags.append("-DNDEBUG")
|
||||
|
||||
default_allocator = read_config("fbcode", "default_allocator")
|
||||
ROCKSDB_COMPILER_FLAGS += (["-DNDEBUG"] if is_opt_mode else [])
|
||||
|
||||
sanitizer = read_config("fbcode", "sanitizer")
|
||||
|
||||
# Let RocksDB aware of jemalloc existence.
|
||||
# Do not enable it if sanitizer presents.
|
||||
if is_opt_mode and default_allocator.startswith("jemalloc") and sanitizer == "":
|
||||
rocksdb_compiler_flags.append("-DROCKSDB_JEMALLOC")
|
||||
rocksdb_external_deps.append(("jemalloc", None, "headers"))
|
||||
# Do not enable jemalloc if sanitizer presents. RocksDB will further detect
|
||||
# whether the binary is linked with jemalloc at runtime.
|
||||
ROCKSDB_COMPILER_FLAGS += (["-DROCKSDB_JEMALLOC"] if sanitizer == "" else [])
|
||||
|
||||
ROCKSDB_EXTERNAL_DEPS += ([("jemalloc", None, "headers")] if sanitizer == "" else [])
|
||||
|
||||
cpp_library(
|
||||
name = "rocksdb_lib",
|
||||
@@ -124,7 +120,6 @@ cpp_library(
|
||||
"db/merge_helper.cc",
|
||||
"db/merge_operator.cc",
|
||||
"db/range_del_aggregator.cc",
|
||||
"db/range_del_aggregator_v2.cc",
|
||||
"db/range_tombstone_fragmenter.cc",
|
||||
"db/repair.cc",
|
||||
"db/snapshot_impl.cc",
|
||||
@@ -220,6 +215,7 @@ cpp_library(
|
||||
"util/comparator.cc",
|
||||
"util/compression_context_cache.cc",
|
||||
"util/concurrent_arena.cc",
|
||||
"util/concurrent_task_limiter_impl.cc",
|
||||
"util/crc32c.cc",
|
||||
"util/delete_scheduler.cc",
|
||||
"util/dynamic_bloom.cc",
|
||||
@@ -261,14 +257,9 @@ cpp_library(
|
||||
"utilities/checkpoint/checkpoint_impl.cc",
|
||||
"utilities/compaction_filters/remove_emptyvalue_compactionfilter.cc",
|
||||
"utilities/convenience/info_log_finder.cc",
|
||||
"utilities/date_tiered/date_tiered_db_impl.cc",
|
||||
"utilities/debug.cc",
|
||||
"utilities/document/document_db.cc",
|
||||
"utilities/document/json_document.cc",
|
||||
"utilities/document/json_document_builder.cc",
|
||||
"utilities/env_mirror.cc",
|
||||
"utilities/env_timed.cc",
|
||||
"utilities/geodb/geodb_impl.cc",
|
||||
"utilities/leveldb_options/leveldb_options.cc",
|
||||
"utilities/lua/rocks_lua_compaction_filter.cc",
|
||||
"utilities/memory/memory_util.cc",
|
||||
@@ -285,9 +276,7 @@ cpp_library(
|
||||
"utilities/persistent_cache/block_cache_tier_metadata.cc",
|
||||
"utilities/persistent_cache/persistent_cache_tier.cc",
|
||||
"utilities/persistent_cache/volatile_tier_impl.cc",
|
||||
"utilities/redis/redis_lists.cc",
|
||||
"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",
|
||||
@@ -308,11 +297,11 @@ cpp_library(
|
||||
"utilities/write_batch_with_index/write_batch_with_index_internal.cc",
|
||||
],
|
||||
auto_headers = AutoHeaders.RECURSIVE_GLOB,
|
||||
arch_preprocessor_flags = rocksdb_arch_preprocessor_flags,
|
||||
compiler_flags = rocksdb_compiler_flags,
|
||||
preprocessor_flags = rocksdb_preprocessor_flags,
|
||||
arch_preprocessor_flags = ROCKSDB_ARCH_PREPROCESSOR_FLAGS,
|
||||
compiler_flags = ROCKSDB_COMPILER_FLAGS,
|
||||
preprocessor_flags = ROCKSDB_PREPROCESSOR_FLAGS,
|
||||
deps = [],
|
||||
external_deps = rocksdb_external_deps,
|
||||
external_deps = ROCKSDB_EXTERNAL_DEPS,
|
||||
)
|
||||
|
||||
cpp_library(
|
||||
@@ -325,16 +314,13 @@ cpp_library(
|
||||
"util/testharness.cc",
|
||||
"util/testutil.cc",
|
||||
"utilities/cassandra/test_utils.cc",
|
||||
"utilities/col_buf_decoder.cc",
|
||||
"utilities/col_buf_encoder.cc",
|
||||
"utilities/column_aware_encoding_util.cc",
|
||||
],
|
||||
auto_headers = AutoHeaders.RECURSIVE_GLOB,
|
||||
arch_preprocessor_flags = rocksdb_arch_preprocessor_flags,
|
||||
compiler_flags = rocksdb_compiler_flags,
|
||||
preprocessor_flags = rocksdb_preprocessor_flags,
|
||||
arch_preprocessor_flags = ROCKSDB_ARCH_PREPROCESSOR_FLAGS,
|
||||
compiler_flags = ROCKSDB_COMPILER_FLAGS,
|
||||
preprocessor_flags = ROCKSDB_PREPROCESSOR_FLAGS,
|
||||
deps = [":rocksdb_lib"],
|
||||
external_deps = rocksdb_external_deps,
|
||||
external_deps = ROCKSDB_EXTERNAL_DEPS,
|
||||
)
|
||||
|
||||
cpp_library(
|
||||
@@ -345,22 +331,22 @@ cpp_library(
|
||||
"util/testutil.cc",
|
||||
],
|
||||
auto_headers = AutoHeaders.RECURSIVE_GLOB,
|
||||
arch_preprocessor_flags = rocksdb_arch_preprocessor_flags,
|
||||
compiler_flags = rocksdb_compiler_flags,
|
||||
preprocessor_flags = rocksdb_preprocessor_flags,
|
||||
arch_preprocessor_flags = ROCKSDB_ARCH_PREPROCESSOR_FLAGS,
|
||||
compiler_flags = ROCKSDB_COMPILER_FLAGS,
|
||||
preprocessor_flags = ROCKSDB_PREPROCESSOR_FLAGS,
|
||||
deps = [":rocksdb_lib"],
|
||||
external_deps = rocksdb_external_deps,
|
||||
external_deps = ROCKSDB_EXTERNAL_DEPS,
|
||||
)
|
||||
|
||||
cpp_library(
|
||||
name = "env_basic_test_lib",
|
||||
srcs = ["env/env_basic_test.cc"],
|
||||
auto_headers = AutoHeaders.RECURSIVE_GLOB,
|
||||
arch_preprocessor_flags = rocksdb_arch_preprocessor_flags,
|
||||
compiler_flags = rocksdb_compiler_flags,
|
||||
preprocessor_flags = rocksdb_preprocessor_flags,
|
||||
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,
|
||||
external_deps = ROCKSDB_EXTERNAL_DEPS,
|
||||
)
|
||||
|
||||
# [test_name, test_src, test_type]
|
||||
@@ -450,11 +436,6 @@ ROCKS_TESTS = [
|
||||
"util/coding_test.cc",
|
||||
"serial",
|
||||
],
|
||||
[
|
||||
"column_aware_encoding_test",
|
||||
"utilities/column_aware_encoding_test.cc",
|
||||
"serial",
|
||||
],
|
||||
[
|
||||
"column_family_test",
|
||||
"db/column_family_test.cc",
|
||||
@@ -525,11 +506,6 @@ ROCKS_TESTS = [
|
||||
"table/data_block_hash_index_test.cc",
|
||||
"serial",
|
||||
],
|
||||
[
|
||||
"date_tiered_test",
|
||||
"utilities/date_tiered/date_tiered_test.cc",
|
||||
"serial",
|
||||
],
|
||||
[
|
||||
"db_basic_test",
|
||||
"db/db_basic_test.cc",
|
||||
@@ -690,11 +666,6 @@ ROCKS_TESTS = [
|
||||
"db/deletefile_test.cc",
|
||||
"serial",
|
||||
],
|
||||
[
|
||||
"document_db_test",
|
||||
"utilities/document/document_db_test.cc",
|
||||
"serial",
|
||||
],
|
||||
[
|
||||
"dynamic_bloom_test",
|
||||
"util/dynamic_bloom_test.cc",
|
||||
@@ -770,11 +741,6 @@ ROCKS_TESTS = [
|
||||
"table/full_filter_block_test.cc",
|
||||
"serial",
|
||||
],
|
||||
[
|
||||
"geodb_test",
|
||||
"utilities/geodb/geodb_test.cc",
|
||||
"serial",
|
||||
],
|
||||
[
|
||||
"hash_table_test",
|
||||
"utilities/persistent_cache/hash_table_test.cc",
|
||||
@@ -805,11 +771,6 @@ ROCKS_TESTS = [
|
||||
"monitoring/iostats_context_test.cc",
|
||||
"serial",
|
||||
],
|
||||
[
|
||||
"json_document_test",
|
||||
"utilities/document/json_document_test.cc",
|
||||
"serial",
|
||||
],
|
||||
[
|
||||
"ldb_cmd_test",
|
||||
"tools/ldb_cmd_test.cc",
|
||||
@@ -935,11 +896,6 @@ ROCKS_TESTS = [
|
||||
"db/range_del_aggregator_test.cc",
|
||||
"serial",
|
||||
],
|
||||
[
|
||||
"range_del_aggregator_v2_test",
|
||||
"db/range_del_aggregator_v2_test.cc",
|
||||
"serial",
|
||||
],
|
||||
[
|
||||
"range_tombstone_fragmenter_test",
|
||||
"db/range_tombstone_fragmenter_test.cc",
|
||||
@@ -980,11 +936,6 @@ ROCKS_TESTS = [
|
||||
"util/slice_transform_test.cc",
|
||||
"serial",
|
||||
],
|
||||
[
|
||||
"spatial_db_test",
|
||||
"utilities/spatialdb/spatial_db_test.cc",
|
||||
"serial",
|
||||
],
|
||||
[
|
||||
"sst_dump_test",
|
||||
"tools/sst_dump_test.cc",
|
||||
@@ -1110,26 +1061,16 @@ ROCKS_TESTS = [
|
||||
# Generate a test rule for each entry in ROCKS_TESTS
|
||||
# Do not build the tests in opt mode, since SyncPoint and other test code
|
||||
# will not be included.
|
||||
if not is_opt_mode:
|
||||
for test_cfg in ROCKS_TESTS:
|
||||
test_name = test_cfg[0]
|
||||
test_cc = test_cfg[1]
|
||||
ttype = "gtest" if test_cfg[2] == "parallel" else "simple"
|
||||
test_bin = test_name + "_bin"
|
||||
|
||||
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,
|
||||
command = [TEST_RUNNER, BUCK_BINS + test_bin],
|
||||
type = ttype,
|
||||
deps = [":" + test_bin],
|
||||
)
|
||||
[
|
||||
test_binary(
|
||||
parallelism = parallelism,
|
||||
rocksdb_arch_preprocessor_flags = ROCKSDB_ARCH_PREPROCESSOR_FLAGS,
|
||||
rocksdb_compiler_flags = ROCKSDB_COMPILER_FLAGS,
|
||||
rocksdb_external_deps = ROCKSDB_EXTERNAL_DEPS,
|
||||
rocksdb_preprocessor_flags = ROCKSDB_PREPROCESSOR_FLAGS,
|
||||
test_cc = test_cc,
|
||||
test_name = test_name,
|
||||
)
|
||||
for test_name, test_cc, parallelism in ROCKS_TESTS
|
||||
if not is_opt_mode
|
||||
]
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@ image: Visual Studio 2017
|
||||
before_build:
|
||||
- md %APPVEYOR_BUILD_FOLDER%\build
|
||||
- cd %APPVEYOR_BUILD_FOLDER%\build
|
||||
- cmake -G "Visual Studio 15 Win64" -DOPTDBG=1 -DWITH_XPRESS=1 -DPORTABLE=1 ..
|
||||
- cmake -G "Visual Studio 15 Win64" -DOPTDBG=1 -DWITH_XPRESS=1 -DPORTABLE=1 -DJNI=1 ..
|
||||
- cd ..
|
||||
build:
|
||||
project: build\rocksdb.sln
|
||||
|
||||
+32
-48
@@ -3,14 +3,11 @@ from __future__ import division
|
||||
from __future__ import print_function
|
||||
from __future__ import unicode_literals
|
||||
rocksdb_target_header = """load("@fbcode_macros//build_defs:auto_headers.bzl", "AutoHeaders")
|
||||
load(":defs.bzl", "test_binary")
|
||||
|
||||
REPO_PATH = package_name() + "/"
|
||||
|
||||
BUCK_BINS = "buck-out/gen/" + REPO_PATH
|
||||
|
||||
TEST_RUNNER = REPO_PATH + "buckifier/rocks_test_runner.sh"
|
||||
|
||||
rocksdb_compiler_flags = [
|
||||
ROCKSDB_COMPILER_FLAGS = [
|
||||
"-fno-builtin-memcmp",
|
||||
"-DROCKSDB_PLATFORM_POSIX",
|
||||
"-DROCKSDB_LIB_IO_POSIX",
|
||||
@@ -37,7 +34,7 @@ rocksdb_compiler_flags = [
|
||||
"-Wnarrowing",
|
||||
]
|
||||
|
||||
rocksdb_external_deps = [
|
||||
ROCKSDB_EXTERNAL_DEPS = [
|
||||
("bzip2", None, "bz2"),
|
||||
("snappy", None, "snappy"),
|
||||
("zlib", None, "z"),
|
||||
@@ -49,13 +46,13 @@ rocksdb_external_deps = [
|
||||
("googletest", None, "gtest"),
|
||||
]
|
||||
|
||||
rocksdb_preprocessor_flags = [
|
||||
ROCKSDB_PREPROCESSOR_FLAGS = [
|
||||
# Directories with files for #include
|
||||
"-I" + REPO_PATH + "include/",
|
||||
"-I" + REPO_PATH,
|
||||
]
|
||||
|
||||
rocksdb_arch_preprocessor_flags = {
|
||||
ROCKSDB_ARCH_PREPROCESSOR_FLAGS = {
|
||||
"x86_64": [
|
||||
"-DHAVE_SSE42",
|
||||
"-DHAVE_PCLMUL",
|
||||
@@ -68,18 +65,15 @@ is_opt_mode = build_mode.startswith("opt")
|
||||
|
||||
# -DNDEBUG is added by default in opt mode in fbcode. But adding it twice
|
||||
# doesn't harm and avoid forgetting to add it.
|
||||
if is_opt_mode:
|
||||
rocksdb_compiler_flags.append("-DNDEBUG")
|
||||
|
||||
default_allocator = read_config("fbcode", "default_allocator")
|
||||
ROCKSDB_COMPILER_FLAGS += (["-DNDEBUG"] if is_opt_mode else [])
|
||||
|
||||
sanitizer = read_config("fbcode", "sanitizer")
|
||||
|
||||
# Let RocksDB aware of jemalloc existence.
|
||||
# Do not enable it if sanitizer presents.
|
||||
if is_opt_mode and default_allocator.startswith("jemalloc") and sanitizer == "":
|
||||
rocksdb_compiler_flags.append("-DROCKSDB_JEMALLOC")
|
||||
rocksdb_external_deps.append(("jemalloc", None, "headers"))
|
||||
# Do not enable jemalloc if sanitizer presents. RocksDB will further detect
|
||||
# whether the binary is linked with jemalloc at runtime.
|
||||
ROCKSDB_COMPILER_FLAGS += (["-DROCKSDB_JEMALLOC"] if sanitizer == "" else [])
|
||||
|
||||
ROCKSDB_EXTERNAL_DEPS += ([("jemalloc", None, "headers")] if sanitizer == "" else [])
|
||||
"""
|
||||
|
||||
|
||||
@@ -88,11 +82,11 @@ cpp_library(
|
||||
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,
|
||||
arch_preprocessor_flags = ROCKSDB_ARCH_PREPROCESSOR_FLAGS,
|
||||
compiler_flags = ROCKSDB_COMPILER_FLAGS,
|
||||
preprocessor_flags = ROCKSDB_PREPROCESSOR_FLAGS,
|
||||
deps = [{deps}],
|
||||
external_deps = rocksdb_external_deps,
|
||||
external_deps = ROCKSDB_EXTERNAL_DEPS,
|
||||
)
|
||||
"""
|
||||
|
||||
@@ -100,11 +94,11 @@ binary_template = """
|
||||
cpp_binary(
|
||||
name = "%s",
|
||||
srcs = [%s],
|
||||
arch_preprocessor_flags = rocksdb_arch_preprocessor_flags,
|
||||
compiler_flags = rocksdb_compiler_flags,
|
||||
preprocessor_flags = rocksdb_preprocessor_flags,
|
||||
arch_preprocessor_flags = ROCKSDB_ARCH_PREPROCESSOR_FLAGS,
|
||||
compiler_flags = ROCKSDB_COMPILER_FLAGS,
|
||||
preprocessor_flags = ROCKSDB_PREPROCESSOR_FLAGS,
|
||||
deps = [%s],
|
||||
external_deps = rocksdb_external_deps,
|
||||
external_deps = ROCKSDB_EXTERNAL_DEPS,
|
||||
)
|
||||
"""
|
||||
|
||||
@@ -123,27 +117,17 @@ ROCKS_TESTS = [
|
||||
# Generate a test rule for each entry in ROCKS_TESTS
|
||||
# Do not build the tests in opt mode, since SyncPoint and other test code
|
||||
# will not be included.
|
||||
if not is_opt_mode:
|
||||
for test_cfg in ROCKS_TESTS:
|
||||
test_name = test_cfg[0]
|
||||
test_cc = test_cfg[1]
|
||||
ttype = "gtest" if test_cfg[2] == "parallel" else "simple"
|
||||
test_bin = test_name + "_bin"
|
||||
|
||||
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,
|
||||
command = [TEST_RUNNER, BUCK_BINS + test_bin],
|
||||
type = ttype,
|
||||
deps = [":" + test_bin],
|
||||
)
|
||||
[
|
||||
test_binary(
|
||||
parallelism = parallelism,
|
||||
rocksdb_arch_preprocessor_flags = ROCKSDB_ARCH_PREPROCESSOR_FLAGS,
|
||||
rocksdb_compiler_flags = ROCKSDB_COMPILER_FLAGS,
|
||||
rocksdb_external_deps = ROCKSDB_EXTERNAL_DEPS,
|
||||
rocksdb_preprocessor_flags = ROCKSDB_PREPROCESSOR_FLAGS,
|
||||
test_cc = test_cc,
|
||||
test_name = test_name,
|
||||
)
|
||||
for test_name, test_cc, parallelism in ROCKS_TESTS
|
||||
if not is_opt_mode
|
||||
]
|
||||
"""
|
||||
|
||||
@@ -97,7 +97,7 @@ function getSteps($applyDiff, $diffID, $username, $test) {
|
||||
}
|
||||
|
||||
// fbcode is a sub-repo. We cannot patch until we add it to ignore otherwise
|
||||
// Git thinks it is an uncommited change.
|
||||
// Git thinks it is an uncommitted change.
|
||||
$fix_git_ignore = array(
|
||||
"name" => "Fix git ignore",
|
||||
"shell" => "echo fbcode >> .git/info/exclude",
|
||||
|
||||
@@ -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_PLATFORM007" ]; then
|
||||
source "$PWD/build_tools/fbcode_config_platform007.sh"
|
||||
else
|
||||
source "$PWD/build_tools/fbcode_config.sh"
|
||||
fi
|
||||
fi
|
||||
|
||||
@@ -368,6 +370,16 @@ EOF
|
||||
# JEMALLOC can be enabled either using the flag (like here) or by
|
||||
# providing direct link to the jemalloc library
|
||||
WITH_JEMALLOC_FLAG=1
|
||||
# check for JEMALLOC installed with HomeBrew
|
||||
if [ "$PLATFORM" == "OS_MACOSX" ]; then
|
||||
if hash brew 2>/dev/null && brew ls --versions jemalloc > /dev/null; then
|
||||
JEMALLOC_VER=$(brew ls --versions jemalloc | tail -n 1 | cut -f 2 -d ' ')
|
||||
JEMALLOC_INCLUDE="-I/usr/local/Cellar/jemalloc/${JEMALLOC_VER}/include"
|
||||
JEMALLOC_LIB="/usr/local/Cellar/jemalloc/${JEMALLOC_VER}/lib/libjemalloc_pic.a"
|
||||
PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS $JEMALLOC_LIB"
|
||||
JAVA_STATIC_LDFLAGS="$JAVA_STATIC_LDFLAGS $JEMALLOC_LIB"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
if ! test $JEMALLOC && ! test $ROCKSDB_DISABLE_TCMALLOC; then
|
||||
|
||||
@@ -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
|
||||
@@ -51,7 +51,7 @@ if test -z $PIC_BUILD; then
|
||||
else
|
||||
ZSTD_LIBS=" $ZSTD_BASE/lib/libzstd_pic.a"
|
||||
fi
|
||||
CFLAGS+=" -DZSTD"
|
||||
CFLAGS+=" -DZSTD -DZSTD_STATIC_LINKING_ONLY"
|
||||
|
||||
# location of gflags headers and libraries
|
||||
GFLAGS_INCLUDE=" -I $GFLAGS_BASE/include/"
|
||||
|
||||
@@ -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,7 +85,9 @@ NON_SHM="TMPD=/tmp/rocksdb_test_tmp"
|
||||
GCC_481="ROCKSDB_FBCODE_BUILD_WITH_481=1"
|
||||
ASAN="COMPILE_WITH_ASAN=1"
|
||||
CLANG="USE_CLANG=1"
|
||||
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"
|
||||
|
||||
@@ -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 #
|
||||
###########################################################
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
# - Find zlib
|
||||
# Find the zlib compression library and includes
|
||||
#
|
||||
# ZLIB_INCLUDE_DIR - where to find zlib.h, etc.
|
||||
# ZLIB_LIBRARIES - List of libraries when using zlib.
|
||||
# ZLIB_FOUND - True if zlib found.
|
||||
|
||||
find_path(ZLIB_INCLUDE_DIR
|
||||
NAMES zlib.h
|
||||
HINTS ${ZLIB_ROOT_DIR}/include)
|
||||
|
||||
find_library(ZLIB_LIBRARIES
|
||||
NAMES z
|
||||
HINTS ${ZLIB_ROOT_DIR}/lib)
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(zlib DEFAULT_MSG ZLIB_LIBRARIES ZLIB_INCLUDE_DIR)
|
||||
|
||||
mark_as_advanced(
|
||||
ZLIB_LIBRARIES
|
||||
ZLIB_INCLUDE_DIR)
|
||||
+15
-13
@@ -18,6 +18,7 @@
|
||||
#include "db/event_helpers.h"
|
||||
#include "db/internal_stats.h"
|
||||
#include "db/merge_helper.h"
|
||||
#include "db/range_del_aggregator.h"
|
||||
#include "db/table_cache.h"
|
||||
#include "db/version_edit.h"
|
||||
#include "monitoring/iostats_context_imp.h"
|
||||
@@ -65,8 +66,9 @@ Status BuildTable(
|
||||
const std::string& dbname, Env* env, const ImmutableCFOptions& ioptions,
|
||||
const MutableCFOptions& mutable_cf_options, const EnvOptions& env_options,
|
||||
TableCache* table_cache, InternalIterator* iter,
|
||||
std::unique_ptr<InternalIterator> range_del_iter, FileMetaData* meta,
|
||||
const InternalKeyComparator& internal_comparator,
|
||||
std::vector<std::unique_ptr<FragmentedRangeTombstoneIterator>>
|
||||
range_del_iters,
|
||||
FileMetaData* meta, const InternalKeyComparator& internal_comparator,
|
||||
const std::vector<std::unique_ptr<IntTblPropCollectorFactory>>*
|
||||
int_tbl_prop_collector_factories,
|
||||
uint32_t column_family_id, const std::string& column_family_name,
|
||||
@@ -86,12 +88,10 @@ Status BuildTable(
|
||||
Status s;
|
||||
meta->fd.file_size = 0;
|
||||
iter->SeekToFirst();
|
||||
std::unique_ptr<RangeDelAggregator> range_del_agg(
|
||||
new RangeDelAggregator(internal_comparator, snapshots));
|
||||
s = range_del_agg->AddTombstones(std::move(range_del_iter));
|
||||
if (!s.ok()) {
|
||||
// may be non-ok if a range tombstone key is unparsable
|
||||
return s;
|
||||
std::unique_ptr<CompactionRangeDelAggregator> range_del_agg(
|
||||
new CompactionRangeDelAggregator(&internal_comparator, snapshots));
|
||||
for (auto& range_del_iter : range_del_iters) {
|
||||
range_del_agg->AddTombstones(std::move(range_del_iter));
|
||||
}
|
||||
|
||||
std::string fname = TableFileName(ioptions.cf_paths, meta->fd.GetNumber(),
|
||||
@@ -121,9 +121,9 @@ Status BuildTable(
|
||||
file->SetIOPriority(io_priority);
|
||||
file->SetWriteLifeTimeHint(write_hint);
|
||||
|
||||
file_writer.reset(new WritableFileWriter(std::move(file), fname,
|
||||
env_options, ioptions.statistics,
|
||||
ioptions.listeners));
|
||||
file_writer.reset(
|
||||
new WritableFileWriter(std::move(file), fname, env_options, env,
|
||||
ioptions.statistics, ioptions.listeners));
|
||||
builder = NewTableBuilder(
|
||||
ioptions, mutable_cf_options, internal_comparator,
|
||||
int_tbl_prop_collector_factories, column_family_id,
|
||||
@@ -158,8 +158,10 @@ Status BuildTable(
|
||||
}
|
||||
}
|
||||
|
||||
for (auto it = range_del_agg->NewIterator(); it->Valid(); it->Next()) {
|
||||
auto tombstone = it->Tombstone();
|
||||
auto range_del_it = range_del_agg->NewIterator();
|
||||
for (range_del_it->SeekToFirst(); range_del_it->Valid();
|
||||
range_del_it->Next()) {
|
||||
auto tombstone = range_del_it->Tombstone();
|
||||
auto kv = tombstone.Serialize();
|
||||
builder->Add(kv.first.Encode(), kv.second);
|
||||
meta->UpdateBoundariesForRange(kv.first, tombstone.SerializeEndKey(),
|
||||
|
||||
+4
-2
@@ -9,6 +9,7 @@
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include "db/range_tombstone_fragmenter.h"
|
||||
#include "db/table_properties_collector.h"
|
||||
#include "options/cf_options.h"
|
||||
#include "rocksdb/comparator.h"
|
||||
@@ -65,8 +66,9 @@ extern Status BuildTable(
|
||||
const std::string& dbname, Env* env, const ImmutableCFOptions& options,
|
||||
const MutableCFOptions& mutable_cf_options, const EnvOptions& env_options,
|
||||
TableCache* table_cache, InternalIterator* iter,
|
||||
std::unique_ptr<InternalIterator> range_del_iter, FileMetaData* meta,
|
||||
const InternalKeyComparator& internal_comparator,
|
||||
std::vector<std::unique_ptr<FragmentedRangeTombstoneIterator>>
|
||||
range_del_iters,
|
||||
FileMetaData* meta, const InternalKeyComparator& internal_comparator,
|
||||
const std::vector<std::unique_ptr<IntTblPropCollectorFactory>>*
|
||||
int_tbl_prop_collector_factories,
|
||||
uint32_t column_family_id, const std::string& column_family_name,
|
||||
|
||||
@@ -1784,11 +1784,11 @@ rocksdb_iterator_t* rocksdb_writebatch_wi_create_iterator_with_base(
|
||||
}
|
||||
|
||||
rocksdb_iterator_t* rocksdb_writebatch_wi_create_iterator_with_base_cf(
|
||||
rocksdb_writebatch_wi_t* wbwi,
|
||||
rocksdb_iterator_t* base_iterator,
|
||||
rocksdb_writebatch_wi_t* wbwi, rocksdb_iterator_t* base_iterator,
|
||||
rocksdb_column_family_handle_t* column_family) {
|
||||
rocksdb_iterator_t* result = new rocksdb_iterator_t;
|
||||
result->rep = wbwi->rep->NewIteratorWithBase(column_family->rep, base_iterator->rep);
|
||||
result->rep =
|
||||
wbwi->rep->NewIteratorWithBase(column_family->rep, base_iterator->rep);
|
||||
delete base_iterator;
|
||||
return result;
|
||||
}
|
||||
@@ -2067,6 +2067,15 @@ void rocksdb_set_options(
|
||||
db->rep->SetOptions(options_map));
|
||||
}
|
||||
|
||||
void rocksdb_set_options_cf(
|
||||
rocksdb_t* db, rocksdb_column_family_handle_t* handle, int count, const char* const keys[], const char* const values[], char** errptr) {
|
||||
std::unordered_map<std::string, std::string> options_map;
|
||||
for (int i=0; i<count; i++)
|
||||
options_map[keys[i]] = values[i];
|
||||
SaveError(errptr,
|
||||
db->rep->SetOptions(handle->rep, options_map));
|
||||
}
|
||||
|
||||
rocksdb_options_t* rocksdb_options_create() {
|
||||
return new rocksdb_options_t;
|
||||
}
|
||||
|
||||
+3
-2
@@ -917,7 +917,8 @@ int main(int argc, char** argv) {
|
||||
rocksdb_writebatch_wi_t* wbi = rocksdb_writebatch_wi_create(0, 1);
|
||||
rocksdb_writebatch_wi_put(wbi, "bar", 3, "b", 1);
|
||||
rocksdb_writebatch_wi_delete(wbi, "foo", 3);
|
||||
rocksdb_iterator_t* iter = rocksdb_writebatch_wi_create_iterator_with_base(wbi, base_iter);
|
||||
rocksdb_iterator_t* iter =
|
||||
rocksdb_writebatch_wi_create_iterator_with_base(wbi, base_iter);
|
||||
CheckCondition(!rocksdb_iter_valid(iter));
|
||||
rocksdb_iter_seek_to_first(iter);
|
||||
CheckCondition(rocksdb_iter_valid(iter));
|
||||
@@ -1527,7 +1528,7 @@ int main(int argc, char** argv) {
|
||||
const rocksdb_snapshot_t* snapshot;
|
||||
snapshot = rocksdb_transactiondb_create_snapshot(txn_db);
|
||||
rocksdb_readoptions_set_snapshot(roptions, snapshot);
|
||||
|
||||
|
||||
rocksdb_transactiondb_put(txn_db, woptions, "foo", 3, "hey", 3, &err);
|
||||
CheckNoError(err);
|
||||
|
||||
|
||||
+5
-9
@@ -25,7 +25,7 @@
|
||||
#include "db/db_impl.h"
|
||||
#include "db/internal_stats.h"
|
||||
#include "db/job_context.h"
|
||||
#include "db/range_del_aggregator_v2.h"
|
||||
#include "db/range_del_aggregator.h"
|
||||
#include "db/table_properties_collector.h"
|
||||
#include "db/version_set.h"
|
||||
#include "db/write_controller.h"
|
||||
@@ -129,14 +129,10 @@ Status CheckCompressionSupported(const ColumnFamilyOptions& cf_options) {
|
||||
}
|
||||
}
|
||||
if (cf_options.compression_opts.zstd_max_train_bytes > 0) {
|
||||
if (!CompressionTypeSupported(CompressionType::kZSTD)) {
|
||||
// Dictionary trainer is available since v0.6.1, but ZSTD was marked
|
||||
// stable only since v0.8.0. For now we enable the feature in stable
|
||||
// versions only.
|
||||
if (!ZSTD_TrainDictionarySupported()) {
|
||||
return Status::InvalidArgument(
|
||||
"zstd dictionary trainer cannot be used because " +
|
||||
CompressionTypeToString(CompressionType::kZSTD) +
|
||||
" is not linked with the binary.");
|
||||
"zstd dictionary trainer cannot be used because ZSTD 1.1.3+ "
|
||||
"is not linked with the binary.");
|
||||
}
|
||||
if (cf_options.compression_opts.max_dict_bytes == 0) {
|
||||
return Status::InvalidArgument(
|
||||
@@ -945,7 +941,7 @@ Status ColumnFamilyData::RangesOverlapWithMemtables(
|
||||
ScopedArenaIterator memtable_iter(merge_iter_builder.Finish());
|
||||
|
||||
auto read_seq = super_version->current->version_set()->LastSequence();
|
||||
RangeDelAggregatorV2 range_del_agg(&internal_comparator_, read_seq);
|
||||
ReadRangeDelAggregator range_del_agg(&internal_comparator_, read_seq);
|
||||
auto* active_range_del_iter =
|
||||
super_version->mem->NewRangeTombstoneIterator(read_opts, read_seq);
|
||||
range_del_agg.AddTombstones(
|
||||
|
||||
@@ -383,6 +383,8 @@ class ColumnFamilyData {
|
||||
|
||||
Directory* GetDataDir(size_t path_id) const;
|
||||
|
||||
ThreadLocalPtr* TEST_GetLocalSV() { return local_sv_.get(); }
|
||||
|
||||
private:
|
||||
friend class ColumnFamilySet;
|
||||
ColumnFamilyData(uint32_t id, const std::string& name,
|
||||
|
||||
@@ -2042,7 +2042,7 @@ TEST_P(ColumnFamilyTest, SameCFAutomaticManualCompactions) {
|
||||
}
|
||||
#endif // !ROCKSDB_LITE
|
||||
|
||||
#ifndef ROCKSDB_LITE // Tailing interator not supported
|
||||
#ifndef ROCKSDB_LITE // Tailing iterator not supported
|
||||
namespace {
|
||||
std::string IterStatus(Iterator* iter) {
|
||||
std::string result;
|
||||
|
||||
@@ -357,6 +357,51 @@ TEST_F(CompactFilesTest, SentinelCompressionType) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(CompactFilesTest, GetCompactionJobInfo) {
|
||||
Options options;
|
||||
options.create_if_missing = true;
|
||||
// Disable RocksDB background compaction.
|
||||
options.compaction_style = kCompactionStyleNone;
|
||||
options.level0_slowdown_writes_trigger = 1000;
|
||||
options.level0_stop_writes_trigger = 1000;
|
||||
options.write_buffer_size = 65536;
|
||||
options.max_write_buffer_number = 2;
|
||||
options.compression = kNoCompression;
|
||||
options.max_compaction_bytes = 5000;
|
||||
|
||||
// Add listener
|
||||
FlushedFileCollector* collector = new FlushedFileCollector();
|
||||
options.listeners.emplace_back(collector);
|
||||
|
||||
DB* db = nullptr;
|
||||
DestroyDB(db_name_, options);
|
||||
Status s = DB::Open(options, db_name_, &db);
|
||||
assert(s.ok());
|
||||
assert(db);
|
||||
|
||||
// create couple files
|
||||
for (int i = 0; i < 500; ++i) {
|
||||
db->Put(WriteOptions(), ToString(i), std::string(1000, 'a' + (i % 26)));
|
||||
}
|
||||
reinterpret_cast<DBImpl*>(db)->TEST_WaitForFlushMemTable();
|
||||
auto l0_files_1 = collector->GetFlushedFiles();
|
||||
CompactionOptions co;
|
||||
co.compression = CompressionType::kLZ4Compression;
|
||||
CompactionJobInfo compaction_job_info;
|
||||
ASSERT_OK(
|
||||
db->CompactFiles(co, l0_files_1, 0, -1, nullptr, &compaction_job_info));
|
||||
ASSERT_EQ(compaction_job_info.base_input_level, 0);
|
||||
ASSERT_EQ(compaction_job_info.cf_id, db->DefaultColumnFamily()->GetID());
|
||||
ASSERT_EQ(compaction_job_info.cf_name, db->DefaultColumnFamily()->GetName());
|
||||
ASSERT_EQ(compaction_job_info.compaction_reason,
|
||||
CompactionReason::kManualCompaction);
|
||||
ASSERT_EQ(compaction_job_info.compression, CompressionType::kLZ4Compression);
|
||||
ASSERT_EQ(compaction_job_info.output_level, 0);
|
||||
ASSERT_OK(compaction_job_info.status);
|
||||
// no assertion failure
|
||||
delete db;
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
@@ -67,10 +67,11 @@ class CompactedDBImpl : public DBImpl {
|
||||
virtual Status EnableFileDeletions(bool /*force*/) override {
|
||||
return Status::NotSupported("Not supported in compacted db mode.");
|
||||
}
|
||||
virtual Status GetLiveFiles(std::vector<std::string>&,
|
||||
uint64_t* /*manifest_file_size*/,
|
||||
bool /*flush_memtable*/ = true) override {
|
||||
return Status::NotSupported("Not supported in compacted db mode.");
|
||||
virtual Status GetLiveFiles(std::vector<std::string>& ret,
|
||||
uint64_t* manifest_file_size,
|
||||
bool /*flush_memtable*/) override {
|
||||
return DBImpl::GetLiveFiles(ret, manifest_file_size,
|
||||
false /* flush_memtable */);
|
||||
}
|
||||
using DBImpl::Flush;
|
||||
virtual Status Flush(const FlushOptions& /*options*/,
|
||||
|
||||
+2
-2
@@ -373,8 +373,8 @@ bool Compaction::KeyNotExistsBeyondOutputLevel(
|
||||
if (user_cmp->Compare(user_key, f->largest.user_key()) <= 0) {
|
||||
// We've advanced far enough
|
||||
if (user_cmp->Compare(user_key, f->smallest.user_key()) >= 0) {
|
||||
// Key falls in this file's range, so definitely
|
||||
// exists beyond output level
|
||||
// Key falls in this file's range, so it may
|
||||
// exist beyond output level
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
|
||||
+96
-48
@@ -9,6 +9,23 @@
|
||||
#include "port/likely.h"
|
||||
#include "rocksdb/listener.h"
|
||||
#include "table/internal_iterator.h"
|
||||
#include "util/sync_point.h"
|
||||
|
||||
#define DEFINITELY_IN_SNAPSHOT(seq, snapshot) \
|
||||
((seq) <= (snapshot) && \
|
||||
(snapshot_checker_ == nullptr || \
|
||||
LIKELY(snapshot_checker_->CheckInSnapshot((seq), (snapshot)) == \
|
||||
SnapshotCheckerResult::kInSnapshot)))
|
||||
|
||||
#define DEFINITELY_NOT_IN_SNAPSHOT(seq, snapshot) \
|
||||
((seq) > (snapshot) || \
|
||||
(snapshot_checker_ != nullptr && \
|
||||
UNLIKELY(snapshot_checker_->CheckInSnapshot((seq), (snapshot)) == \
|
||||
SnapshotCheckerResult::kNotInSnapshot)))
|
||||
|
||||
#define IN_EARLIEST_SNAPSHOT(seq) \
|
||||
((seq) <= earliest_snapshot_ && \
|
||||
(snapshot_checker_ == nullptr || LIKELY(IsInEarliestSnapshot(seq))))
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
@@ -18,7 +35,7 @@ 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,
|
||||
CompactionRangeDelAggregator* range_del_agg, const Compaction* compaction,
|
||||
const CompactionFilter* compaction_filter,
|
||||
const std::atomic<bool>* shutting_down,
|
||||
const SequenceNumber preserve_deletes_seqnum)
|
||||
@@ -36,7 +53,7 @@ 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,
|
||||
CompactionRangeDelAggregator* range_del_agg,
|
||||
std::unique_ptr<CompactionProxy> compaction,
|
||||
const CompactionFilter* compaction_filter,
|
||||
const std::atomic<bool>* shutting_down,
|
||||
@@ -61,26 +78,28 @@ CompactionIterator::CompactionIterator(
|
||||
merge_out_iter_(merge_helper_),
|
||||
current_key_committed_(false) {
|
||||
assert(compaction_filter_ == nullptr || compaction_ != nullptr);
|
||||
assert(snapshots_ != nullptr);
|
||||
bottommost_level_ =
|
||||
compaction_ == nullptr ? false : compaction_->bottommost_level();
|
||||
if (compaction_ != nullptr) {
|
||||
level_ptrs_ = std::vector<size_t>(compaction_->number_levels(), 0);
|
||||
}
|
||||
|
||||
if (snapshots_->size() == 0) {
|
||||
// optimize for fast path if there are no snapshots
|
||||
visible_at_tip_ = true;
|
||||
earliest_snapshot_iter_ = snapshots_->end();
|
||||
earliest_snapshot_ = kMaxSequenceNumber;
|
||||
latest_snapshot_ = 0;
|
||||
} else {
|
||||
visible_at_tip_ = false;
|
||||
earliest_snapshot_iter_ = snapshots_->begin();
|
||||
earliest_snapshot_ = snapshots_->at(0);
|
||||
latest_snapshot_ = snapshots_->back();
|
||||
}
|
||||
#ifndef NDEBUG
|
||||
// findEarliestVisibleSnapshot assumes this ordering.
|
||||
for (size_t i = 1; i < snapshots_->size(); ++i) {
|
||||
assert(snapshots_->at(i - 1) <= snapshots_->at(i));
|
||||
assert(snapshots_->at(i - 1) < snapshots_->at(i));
|
||||
}
|
||||
#endif
|
||||
if (compaction_filter_ != nullptr) {
|
||||
@@ -91,6 +110,7 @@ CompactionIterator::CompactionIterator(
|
||||
ignore_snapshots_ = false;
|
||||
}
|
||||
input_->SetPinnedItersMgr(&pinned_iters_mgr_);
|
||||
TEST_SYNC_POINT_CALLBACK("CompactionIterator:AfterInit", compaction_.get());
|
||||
}
|
||||
|
||||
CompactionIterator::~CompactionIterator() {
|
||||
@@ -162,10 +182,7 @@ void CompactionIterator::InvokeFilterIfNeeded(bool* need_skip,
|
||||
if (compaction_filter_ != nullptr &&
|
||||
(ikey_.type == kTypeValue || ikey_.type == kTypeBlobIndex) &&
|
||||
(visible_at_tip_ || ignore_snapshots_ ||
|
||||
ikey_.sequence > latest_snapshot_ ||
|
||||
(snapshot_checker_ != nullptr &&
|
||||
UNLIKELY(!snapshot_checker_->IsInSnapshot(ikey_.sequence,
|
||||
latest_snapshot_))))) {
|
||||
DEFINITELY_NOT_IN_SNAPSHOT(ikey_.sequence, latest_snapshot_))) {
|
||||
// If the user has specified a compaction filter and the sequence
|
||||
// number is greater than any external snapshot, then invoke the
|
||||
// filter. If the return value of the compaction filter is true,
|
||||
@@ -241,6 +258,7 @@ void CompactionIterator::NextFromInput() {
|
||||
valid_ = true;
|
||||
break;
|
||||
}
|
||||
TEST_SYNC_POINT_CALLBACK("CompactionIterator:ProcessKV", &ikey_);
|
||||
|
||||
// Update input statistics
|
||||
if (ikey_.type == kTypeDeletion || ikey_.type == kTypeSingleDeletion) {
|
||||
@@ -269,9 +287,7 @@ void CompactionIterator::NextFromInput() {
|
||||
has_outputted_key_ = false;
|
||||
current_user_key_sequence_ = kMaxSequenceNumber;
|
||||
current_user_key_snapshot_ = 0;
|
||||
current_key_committed_ =
|
||||
(snapshot_checker_ == nullptr ||
|
||||
snapshot_checker_->IsInSnapshot(ikey_.sequence, kMaxSequenceNumber));
|
||||
current_key_committed_ = KeyCommitted(ikey_.sequence);
|
||||
|
||||
// Apply the compaction filter to the first committed version of the user
|
||||
// key.
|
||||
@@ -293,8 +309,7 @@ void CompactionIterator::NextFromInput() {
|
||||
// to query snapshot_checker_ in that case.
|
||||
if (UNLIKELY(!current_key_committed_)) {
|
||||
assert(snapshot_checker_ != nullptr);
|
||||
current_key_committed_ =
|
||||
snapshot_checker_->IsInSnapshot(ikey_.sequence, kMaxSequenceNumber);
|
||||
current_key_committed_ = KeyCommitted(ikey_.sequence);
|
||||
// Apply the compaction filter to the first committed version of the
|
||||
// user key.
|
||||
if (current_key_committed_) {
|
||||
@@ -378,10 +393,8 @@ void CompactionIterator::NextFromInput() {
|
||||
cmp_->Equal(ikey_.user_key, next_ikey.user_key)) {
|
||||
// Check whether the next key belongs to the same snapshot as the
|
||||
// SingleDelete.
|
||||
if (prev_snapshot == 0 || next_ikey.sequence > prev_snapshot ||
|
||||
(snapshot_checker_ != nullptr &&
|
||||
UNLIKELY(!snapshot_checker_->IsInSnapshot(next_ikey.sequence,
|
||||
prev_snapshot)))) {
|
||||
if (prev_snapshot == 0 ||
|
||||
DEFINITELY_NOT_IN_SNAPSHOT(next_ikey.sequence, prev_snapshot)) {
|
||||
if (next_ikey.type == kTypeSingleDeletion) {
|
||||
// We encountered two SingleDeletes in a row. This could be due to
|
||||
// unexpected user input.
|
||||
@@ -393,11 +406,8 @@ void CompactionIterator::NextFromInput() {
|
||||
++iter_stats_.num_record_drop_obsolete;
|
||||
++iter_stats_.num_single_del_mismatch;
|
||||
} else if (has_outputted_key_ ||
|
||||
(ikey_.sequence <= earliest_write_conflict_snapshot_ &&
|
||||
(snapshot_checker_ == nullptr ||
|
||||
LIKELY(snapshot_checker_->IsInSnapshot(
|
||||
ikey_.sequence,
|
||||
earliest_write_conflict_snapshot_))))) {
|
||||
DEFINITELY_IN_SNAPSHOT(
|
||||
ikey_.sequence, earliest_write_conflict_snapshot_)) {
|
||||
// Found a matching value, we can drop the single delete and the
|
||||
// value. It is safe to drop both records since we've already
|
||||
// outputted a key in this snapshot, or there is no earlier
|
||||
@@ -407,7 +417,8 @@ void CompactionIterator::NextFromInput() {
|
||||
// is an unexpected Merge or Delete. We will compact it out
|
||||
// either way. We will maintain counts of how many mismatches
|
||||
// happened
|
||||
if (next_ikey.type != kTypeValue) {
|
||||
if (next_ikey.type != kTypeValue &&
|
||||
next_ikey.type != kTypeBlobIndex) {
|
||||
++iter_stats_.num_single_del_mismatch;
|
||||
}
|
||||
|
||||
@@ -444,10 +455,7 @@ void CompactionIterator::NextFromInput() {
|
||||
// iteration. If the next key is corrupt, we return before the
|
||||
// comparison, so the value of has_current_user_key does not matter.
|
||||
has_current_user_key_ = false;
|
||||
if (compaction_ != nullptr && ikey_.sequence <= earliest_snapshot_ &&
|
||||
(snapshot_checker_ == nullptr ||
|
||||
LIKELY(snapshot_checker_->IsInSnapshot(ikey_.sequence,
|
||||
earliest_snapshot_))) &&
|
||||
if (compaction_ != nullptr && IN_EARLIEST_SNAPSHOT(ikey_.sequence) &&
|
||||
compaction_->KeyNotExistsBeyondOutputLevel(ikey_.user_key,
|
||||
&level_ptrs_)) {
|
||||
// Key doesn't exist outside of this range.
|
||||
@@ -466,24 +474,34 @@ void CompactionIterator::NextFromInput() {
|
||||
if (valid_) {
|
||||
at_next_ = true;
|
||||
}
|
||||
} else if (last_snapshot == current_user_key_snapshot_) {
|
||||
} else if (last_snapshot == current_user_key_snapshot_ ||
|
||||
(last_snapshot > 0 &&
|
||||
last_snapshot < current_user_key_snapshot_)) {
|
||||
// If the earliest snapshot is which this key is visible in
|
||||
// is the same as the visibility of a previous instance of the
|
||||
// same key, then this kv is not visible in any snapshot.
|
||||
// Hidden by an newer entry for same user key
|
||||
// TODO(noetzli): why not > ?
|
||||
//
|
||||
// Note: Dropping this key will not affect TransactionDB write-conflict
|
||||
// checking since there has already been a record returned for this key
|
||||
// in this snapshot.
|
||||
assert(last_sequence >= current_user_key_sequence_);
|
||||
|
||||
// Note2: if last_snapshot < current_user_key_snapshot, it can only
|
||||
// mean last_snapshot is released between we process last value and
|
||||
// this value, and findEarliestVisibleSnapshot returns the next snapshot
|
||||
// as current_user_key_snapshot. In this case last value and current
|
||||
// value are both in current_user_key_snapshot currently.
|
||||
assert(last_snapshot == current_user_key_snapshot_ ||
|
||||
(snapshot_checker_ != nullptr &&
|
||||
snapshot_checker_->CheckInSnapshot(current_user_key_sequence_,
|
||||
last_snapshot) ==
|
||||
SnapshotCheckerResult::kSnapshotReleased));
|
||||
|
||||
++iter_stats_.num_record_drop_hidden; // (A)
|
||||
input_->Next();
|
||||
} else if (compaction_ != nullptr && ikey_.type == kTypeDeletion &&
|
||||
ikey_.sequence <= earliest_snapshot_ &&
|
||||
(snapshot_checker_ == nullptr ||
|
||||
LIKELY(snapshot_checker_->IsInSnapshot(ikey_.sequence,
|
||||
earliest_snapshot_))) &&
|
||||
IN_EARLIEST_SNAPSHOT(ikey_.sequence) &&
|
||||
ikeyNotNeededForIncrementalSnapshot() &&
|
||||
compaction_->KeyNotExistsBeyondOutputLevel(ikey_.user_key,
|
||||
&level_ptrs_)) {
|
||||
@@ -520,13 +538,10 @@ void CompactionIterator::NextFromInput() {
|
||||
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) &&
|
||||
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))))) {
|
||||
(prev_snapshot == 0 ||
|
||||
DEFINITELY_NOT_IN_SNAPSHOT(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
|
||||
@@ -617,13 +632,9 @@ void CompactionIterator::PrepareOutput() {
|
||||
//
|
||||
// Can we do the same for levels above bottom level as long as
|
||||
// KeyNotExistsBeyondOutputLevel() return true?
|
||||
if ((compaction_ != nullptr &&
|
||||
!compaction_->allow_ingest_behind()) &&
|
||||
ikeyNotNeededForIncrementalSnapshot() &&
|
||||
bottommost_level_ && valid_ && ikey_.sequence <= earliest_snapshot_ &&
|
||||
(snapshot_checker_ == nullptr || LIKELY(snapshot_checker_->IsInSnapshot(
|
||||
ikey_.sequence, earliest_snapshot_))) &&
|
||||
ikey_.type != kTypeMerge &&
|
||||
if ((compaction_ != nullptr && !compaction_->allow_ingest_behind()) &&
|
||||
ikeyNotNeededForIncrementalSnapshot() && bottommost_level_ && valid_ &&
|
||||
IN_EARLIEST_SNAPSHOT(ikey_.sequence) && ikey_.type != kTypeMerge &&
|
||||
!cmp_->Equal(compaction_->GetLargestUserKey(), ikey_.user_key)) {
|
||||
assert(ikey_.type != kTypeDeletion && ikey_.type != kTypeSingleDeletion);
|
||||
ikey_.sequence = 0;
|
||||
@@ -642,12 +653,23 @@ inline SequenceNumber CompactionIterator::findEarliestVisibleSnapshot(
|
||||
*prev_snapshot = *std::prev(snapshots_iter);
|
||||
assert(*prev_snapshot < in);
|
||||
}
|
||||
if (snapshot_checker_ == nullptr) {
|
||||
return snapshots_iter != snapshots_->end()
|
||||
? *snapshots_iter : kMaxSequenceNumber;
|
||||
}
|
||||
bool has_released_snapshot = !released_snapshots_.empty();
|
||||
for (; snapshots_iter != snapshots_->end(); ++snapshots_iter) {
|
||||
auto cur = *snapshots_iter;
|
||||
assert(in <= cur);
|
||||
if (snapshot_checker_ == nullptr ||
|
||||
snapshot_checker_->IsInSnapshot(in, cur)) {
|
||||
// Skip if cur is in released_snapshots.
|
||||
if (has_released_snapshot && released_snapshots_.count(cur) > 0) {
|
||||
continue;
|
||||
}
|
||||
auto res = snapshot_checker_->CheckInSnapshot(in, cur);
|
||||
if (res == SnapshotCheckerResult::kInSnapshot) {
|
||||
return cur;
|
||||
} else if (res == SnapshotCheckerResult::kSnapshotReleased) {
|
||||
released_snapshots_.insert(cur);
|
||||
}
|
||||
*prev_snapshot = cur;
|
||||
}
|
||||
@@ -661,4 +683,30 @@ inline bool CompactionIterator::ikeyNotNeededForIncrementalSnapshot() {
|
||||
(ikey_.sequence < preserve_deletes_seqnum_);
|
||||
}
|
||||
|
||||
bool CompactionIterator::IsInEarliestSnapshot(SequenceNumber sequence) {
|
||||
assert(snapshot_checker_ != nullptr);
|
||||
assert(earliest_snapshot_ == kMaxSequenceNumber ||
|
||||
(earliest_snapshot_iter_ != snapshots_->end() &&
|
||||
*earliest_snapshot_iter_ == earliest_snapshot_));
|
||||
auto in_snapshot =
|
||||
snapshot_checker_->CheckInSnapshot(sequence, earliest_snapshot_);
|
||||
while (UNLIKELY(in_snapshot == SnapshotCheckerResult::kSnapshotReleased)) {
|
||||
// Avoid the the current earliest_snapshot_ being return as
|
||||
// earliest visible snapshot for the next value. So if a value's sequence
|
||||
// is zero-ed out by PrepareOutput(), the next value will be compact out.
|
||||
released_snapshots_.insert(earliest_snapshot_);
|
||||
earliest_snapshot_iter_++;
|
||||
|
||||
if (earliest_snapshot_iter_ == snapshots_->end()) {
|
||||
earliest_snapshot_ = kMaxSequenceNumber;
|
||||
} else {
|
||||
earliest_snapshot_ = *earliest_snapshot_iter_;
|
||||
}
|
||||
in_snapshot =
|
||||
snapshot_checker_->CheckInSnapshot(sequence, earliest_snapshot_);
|
||||
}
|
||||
assert(in_snapshot != SnapshotCheckerResult::kSnapshotReleased);
|
||||
return in_snapshot == SnapshotCheckerResult::kInSnapshot;
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <algorithm>
|
||||
#include <deque>
|
||||
#include <string>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
#include "db/compaction.h"
|
||||
@@ -64,7 +65,7 @@ class 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,
|
||||
CompactionRangeDelAggregator* range_del_agg,
|
||||
const Compaction* compaction = nullptr,
|
||||
const CompactionFilter* compaction_filter = nullptr,
|
||||
const std::atomic<bool>* shutting_down = nullptr,
|
||||
@@ -77,7 +78,7 @@ class 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,
|
||||
CompactionRangeDelAggregator* range_del_agg,
|
||||
std::unique_ptr<CompactionProxy> compaction,
|
||||
const CompactionFilter* compaction_filter = nullptr,
|
||||
const std::atomic<bool>* shutting_down = nullptr,
|
||||
@@ -132,16 +133,31 @@ class CompactionIterator {
|
||||
// or seqnum be zero-ed out even if all other conditions for it are met.
|
||||
inline bool ikeyNotNeededForIncrementalSnapshot();
|
||||
|
||||
inline bool KeyCommitted(SequenceNumber sequence) {
|
||||
return snapshot_checker_ == nullptr ||
|
||||
snapshot_checker_->CheckInSnapshot(sequence, kMaxSequenceNumber) ==
|
||||
SnapshotCheckerResult::kInSnapshot;
|
||||
}
|
||||
|
||||
bool IsInEarliestSnapshot(SequenceNumber sequence);
|
||||
|
||||
InternalIterator* input_;
|
||||
const Comparator* cmp_;
|
||||
MergeHelper* merge_helper_;
|
||||
const std::vector<SequenceNumber>* snapshots_;
|
||||
// List of snapshots released during compaction.
|
||||
// findEarliestVisibleSnapshot() find them out from return of
|
||||
// snapshot_checker, and make sure they will not be returned as
|
||||
// earliest visible snapshot of an older value.
|
||||
// See WritePreparedTransactionTest::ReleaseSnapshotDuringCompaction3.
|
||||
std::unordered_set<SequenceNumber> released_snapshots_;
|
||||
std::vector<SequenceNumber>::const_iterator earliest_snapshot_iter_;
|
||||
const SequenceNumber earliest_write_conflict_snapshot_;
|
||||
const SnapshotChecker* const snapshot_checker_;
|
||||
Env* env_;
|
||||
bool report_detailed_time_;
|
||||
bool expect_valid_internal_key_;
|
||||
RangeDelAggregator* range_del_agg_;
|
||||
CompactionRangeDelAggregator* range_del_agg_;
|
||||
std::unique_ptr<CompactionProxy> compaction_;
|
||||
const CompactionFilter* compaction_filter_;
|
||||
const std::atomic<bool>* shutting_down_;
|
||||
@@ -151,6 +167,7 @@ class CompactionIterator {
|
||||
bool visible_at_tip_;
|
||||
SequenceNumber earliest_snapshot_;
|
||||
SequenceNumber latest_snapshot_;
|
||||
|
||||
bool ignore_snapshots_;
|
||||
|
||||
// State
|
||||
|
||||
@@ -190,13 +190,17 @@ class TestSnapshotChecker : public SnapshotChecker {
|
||||
: last_committed_sequence_(last_committed_sequence),
|
||||
snapshots_(snapshots) {}
|
||||
|
||||
bool IsInSnapshot(SequenceNumber seq,
|
||||
SequenceNumber snapshot_seq) const override {
|
||||
SnapshotCheckerResult CheckInSnapshot(
|
||||
SequenceNumber seq, SequenceNumber snapshot_seq) const override {
|
||||
if (snapshot_seq == kMaxSequenceNumber) {
|
||||
return seq <= last_committed_sequence_;
|
||||
return seq <= last_committed_sequence_
|
||||
? SnapshotCheckerResult::kInSnapshot
|
||||
: SnapshotCheckerResult::kNotInSnapshot;
|
||||
}
|
||||
assert(snapshots_.count(snapshot_seq) > 0);
|
||||
return seq <= snapshots_.at(snapshot_seq);
|
||||
return seq <= snapshots_.at(snapshot_seq)
|
||||
? SnapshotCheckerResult::kInSnapshot
|
||||
: SnapshotCheckerResult::kNotInSnapshot;
|
||||
}
|
||||
|
||||
private:
|
||||
@@ -221,10 +225,15 @@ class CompactionIteratorTest : public testing::TestWithParam<bool> {
|
||||
MergeOperator* merge_op = nullptr, CompactionFilter* filter = nullptr,
|
||||
bool bottommost_level = false,
|
||||
SequenceNumber earliest_write_conflict_snapshot = kMaxSequenceNumber) {
|
||||
std::unique_ptr<InternalIterator> range_del_iter(
|
||||
std::unique_ptr<InternalIterator> unfragmented_range_del_iter(
|
||||
new test::VectorIterator(range_del_ks, range_del_vs));
|
||||
range_del_agg_.reset(new RangeDelAggregator(icmp_, snapshots_));
|
||||
ASSERT_OK(range_del_agg_->AddTombstones(std::move(range_del_iter)));
|
||||
auto tombstone_list = std::make_shared<FragmentedRangeTombstoneList>(
|
||||
std::move(unfragmented_range_del_iter), icmp_);
|
||||
std::unique_ptr<FragmentedRangeTombstoneIterator> range_del_iter(
|
||||
new FragmentedRangeTombstoneIterator(tombstone_list, icmp_,
|
||||
kMaxSequenceNumber));
|
||||
range_del_agg_.reset(new CompactionRangeDelAggregator(&icmp_, snapshots_));
|
||||
range_del_agg_->AddTombstones(std::move(range_del_iter));
|
||||
|
||||
std::unique_ptr<CompactionIterator::CompactionProxy> compaction;
|
||||
if (filter || bottommost_level) {
|
||||
@@ -292,7 +301,7 @@ class CompactionIteratorTest : public testing::TestWithParam<bool> {
|
||||
std::unique_ptr<MergeHelper> merge_helper_;
|
||||
std::unique_ptr<LoggingForwardVectorIterator> iter_;
|
||||
std::unique_ptr<CompactionIterator> c_iter_;
|
||||
std::unique_ptr<RangeDelAggregator> range_del_agg_;
|
||||
std::unique_ptr<CompactionRangeDelAggregator> range_del_agg_;
|
||||
std::unique_ptr<SnapshotChecker> snapshot_checker_;
|
||||
std::atomic<bool> shutting_down_{false};
|
||||
FakeCompaction* compaction_proxy_;
|
||||
|
||||
+83
-30
@@ -36,7 +36,7 @@
|
||||
#include "db/memtable_list.h"
|
||||
#include "db/merge_context.h"
|
||||
#include "db/merge_helper.h"
|
||||
#include "db/range_del_aggregator_v2.h"
|
||||
#include "db/range_del_aggregator.h"
|
||||
#include "db/version_set.h"
|
||||
#include "monitoring/iostats_context_imp.h"
|
||||
#include "monitoring/perf_context_imp.h"
|
||||
@@ -604,7 +604,14 @@ Status CompactionJob::Run() {
|
||||
}
|
||||
|
||||
compaction_stats_.micros = env_->NowMicros() - start_micros;
|
||||
compaction_stats_.cpu_micros = 0;
|
||||
for (size_t i = 0; i < compact_->sub_compact_states.size(); i++) {
|
||||
compaction_stats_.cpu_micros +=
|
||||
compact_->sub_compact_states[i].compaction_job_stats.cpu_micros;
|
||||
}
|
||||
|
||||
MeasureTime(stats_, COMPACTION_TIME, compaction_stats_.micros);
|
||||
MeasureTime(stats_, COMPACTION_CPU_TIME, compaction_stats_.cpu_micros);
|
||||
|
||||
TEST_SYNC_POINT("CompactionJob::Run:BeforeVerify");
|
||||
|
||||
@@ -767,6 +774,7 @@ Status CompactionJob::Install(const MutableCFOptions& mutable_cf_options) {
|
||||
stream << "job" << job_id_ << "event"
|
||||
<< "compaction_finished"
|
||||
<< "compaction_time_micros" << compaction_stats_.micros
|
||||
<< "compaction_time_cpu_micros" << compaction_stats_.cpu_micros
|
||||
<< "output_level" << compact_->compaction->output_level()
|
||||
<< "num_output_files" << compact_->NumOutputFiles()
|
||||
<< "total_output_size" << compact_->total_bytes << "num_input_records"
|
||||
@@ -804,16 +812,17 @@ Status CompactionJob::Install(const MutableCFOptions& mutable_cf_options) {
|
||||
|
||||
void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
|
||||
assert(sub_compact != nullptr);
|
||||
|
||||
uint64_t prev_cpu_micros = env_->NowCPUNanos() / 1000;
|
||||
|
||||
ColumnFamilyData* cfd = sub_compact->compaction->column_family_data();
|
||||
RangeDelAggregatorV2 range_del_agg_v2(&cfd->internal_comparator(),
|
||||
kMaxSequenceNumber /* upper_bound */);
|
||||
auto* range_del_agg =
|
||||
range_del_agg_v2.DelegateToRangeDelAggregator(existing_snapshots_);
|
||||
CompactionRangeDelAggregator range_del_agg(&cfd->internal_comparator(),
|
||||
existing_snapshots_);
|
||||
|
||||
// Although the v2 aggregator is what the level iterator(s) know about,
|
||||
// the AddTombstones calls will be propagated down to the v1 aggregator.
|
||||
std::unique_ptr<InternalIterator> input(versions_->MakeInputIterator(
|
||||
sub_compact->compaction, &range_del_agg_v2, env_optiosn_for_read_));
|
||||
sub_compact->compaction, &range_del_agg, env_optiosn_for_read_));
|
||||
|
||||
AutoThreadOperationStageUpdater stage_updater(
|
||||
ThreadStatus::STAGE_COMPACTION_PROCESS_KV);
|
||||
@@ -825,13 +834,17 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
|
||||
uint64_t prev_fsync_nanos = 0;
|
||||
uint64_t prev_range_sync_nanos = 0;
|
||||
uint64_t prev_prepare_write_nanos = 0;
|
||||
uint64_t prev_cpu_write_nanos = 0;
|
||||
uint64_t prev_cpu_read_nanos = 0;
|
||||
if (measure_io_stats_) {
|
||||
prev_perf_level = GetPerfLevel();
|
||||
SetPerfLevel(PerfLevel::kEnableTime);
|
||||
SetPerfLevel(PerfLevel::kEnableTimeAndCPUTimeExceptForMutex);
|
||||
prev_write_nanos = IOSTATS(write_nanos);
|
||||
prev_fsync_nanos = IOSTATS(fsync_nanos);
|
||||
prev_range_sync_nanos = IOSTATS(range_sync_nanos);
|
||||
prev_prepare_write_nanos = IOSTATS(prepare_write_nanos);
|
||||
prev_cpu_write_nanos = IOSTATS(cpu_write_nanos);
|
||||
prev_cpu_read_nanos = IOSTATS(cpu_read_nanos);
|
||||
}
|
||||
|
||||
const MutableCFOptions* mutable_cf_options =
|
||||
@@ -902,8 +915,8 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
|
||||
input.get(), cfd->user_comparator(), &merge, versions_->LastSequence(),
|
||||
&existing_snapshots_, earliest_write_conflict_snapshot_,
|
||||
snapshot_checker_, env_, ShouldReportDetailedTime(env_, stats_), false,
|
||||
range_del_agg, sub_compact->compaction, compaction_filter, shutting_down_,
|
||||
preserve_deletes_seqnum_));
|
||||
&range_del_agg, sub_compact->compaction, compaction_filter,
|
||||
shutting_down_, preserve_deletes_seqnum_));
|
||||
auto c_iter = sub_compact->c_iter.get();
|
||||
c_iter->SeekToFirst();
|
||||
if (c_iter->Valid() && sub_compact->compaction->output_level() != 0) {
|
||||
@@ -1041,7 +1054,7 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
|
||||
}
|
||||
CompactionIterationStats range_del_out_stats;
|
||||
status =
|
||||
FinishCompactionOutputFile(input_status, sub_compact, range_del_agg,
|
||||
FinishCompactionOutputFile(input_status, sub_compact, &range_del_agg,
|
||||
&range_del_out_stats, next_key);
|
||||
RecordDroppedKeys(range_del_out_stats,
|
||||
&sub_compact->compaction_job_stats);
|
||||
@@ -1092,8 +1105,7 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
|
||||
}
|
||||
|
||||
if (status.ok() && sub_compact->builder == nullptr &&
|
||||
sub_compact->outputs.size() == 0 &&
|
||||
!range_del_agg->IsEmpty()) {
|
||||
sub_compact->outputs.size() == 0 && !range_del_agg.IsEmpty()) {
|
||||
// handle subcompaction containing only range deletions
|
||||
status = OpenCompactionOutputFile(sub_compact);
|
||||
}
|
||||
@@ -1102,7 +1114,7 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
|
||||
// close the output file.
|
||||
if (sub_compact->builder != nullptr) {
|
||||
CompactionIterationStats range_del_out_stats;
|
||||
Status s = FinishCompactionOutputFile(status, sub_compact, range_del_agg,
|
||||
Status s = FinishCompactionOutputFile(status, sub_compact, &range_del_agg,
|
||||
&range_del_out_stats);
|
||||
if (status.ok()) {
|
||||
status = s;
|
||||
@@ -1110,6 +1122,9 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
|
||||
RecordDroppedKeys(range_del_out_stats, &sub_compact->compaction_job_stats);
|
||||
}
|
||||
|
||||
sub_compact->compaction_job_stats.cpu_micros =
|
||||
env_->NowCPUNanos() / 1000 - prev_cpu_micros;
|
||||
|
||||
if (measure_io_stats_) {
|
||||
sub_compact->compaction_job_stats.file_write_nanos +=
|
||||
IOSTATS(write_nanos) - prev_write_nanos;
|
||||
@@ -1119,7 +1134,11 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
|
||||
IOSTATS(range_sync_nanos) - prev_range_sync_nanos;
|
||||
sub_compact->compaction_job_stats.file_prepare_write_nanos +=
|
||||
IOSTATS(prepare_write_nanos) - prev_prepare_write_nanos;
|
||||
if (prev_perf_level != PerfLevel::kEnableTime) {
|
||||
sub_compact->compaction_job_stats.cpu_micros -=
|
||||
(IOSTATS(cpu_write_nanos) - prev_cpu_write_nanos +
|
||||
IOSTATS(cpu_read_nanos) - prev_cpu_read_nanos) /
|
||||
1000;
|
||||
if (prev_perf_level != PerfLevel::kEnableTimeAndCPUTimeExceptForMutex) {
|
||||
SetPerfLevel(prev_perf_level);
|
||||
}
|
||||
}
|
||||
@@ -1168,7 +1187,7 @@ void CompactionJob::RecordDroppedKeys(
|
||||
|
||||
Status CompactionJob::FinishCompactionOutputFile(
|
||||
const Status& input_status, SubcompactionState* sub_compact,
|
||||
RangeDelAggregator* range_del_agg,
|
||||
CompactionRangeDelAggregator* range_del_agg,
|
||||
CompactionIterationStats* range_del_out_stats,
|
||||
const Slice* next_table_min_key /* = nullptr */) {
|
||||
AutoThreadOperationStageUpdater stage_updater(
|
||||
@@ -1192,10 +1211,12 @@ Status CompactionJob::FinishCompactionOutputFile(
|
||||
Slice lower_bound_guard, upper_bound_guard;
|
||||
std::string smallest_user_key;
|
||||
const Slice *lower_bound, *upper_bound;
|
||||
bool lower_bound_from_sub_compact = false;
|
||||
if (sub_compact->outputs.size() == 1) {
|
||||
// For the first output table, include range tombstones before the min key
|
||||
// but after the subcompaction boundary.
|
||||
lower_bound = sub_compact->start;
|
||||
lower_bound_from_sub_compact = true;
|
||||
} else if (meta->smallest.size() > 0) {
|
||||
// For subsequent output tables, only include range tombstones from min
|
||||
// key onwards since the previous file was extended to contain range
|
||||
@@ -1220,11 +1241,6 @@ Status CompactionJob::FinishCompactionOutputFile(
|
||||
if (existing_snapshots_.size() > 0) {
|
||||
earliest_snapshot = existing_snapshots_[0];
|
||||
}
|
||||
auto it = range_del_agg->NewIterator();
|
||||
if (lower_bound != nullptr) {
|
||||
it->Seek(*lower_bound);
|
||||
}
|
||||
|
||||
bool has_overlapping_endpoints;
|
||||
if (upper_bound != nullptr && meta->largest.size() > 0) {
|
||||
has_overlapping_endpoints =
|
||||
@@ -1232,6 +1248,17 @@ Status CompactionJob::FinishCompactionOutputFile(
|
||||
} else {
|
||||
has_overlapping_endpoints = false;
|
||||
}
|
||||
|
||||
auto it = range_del_agg->NewIterator(lower_bound, upper_bound,
|
||||
has_overlapping_endpoints);
|
||||
// Position the range tombstone output iterator. There may be tombstone
|
||||
// fragments that are entirely out of range, so make sure that we do not
|
||||
// include those.
|
||||
if (lower_bound != nullptr) {
|
||||
it->Seek(*lower_bound);
|
||||
} else {
|
||||
it->SeekToFirst();
|
||||
}
|
||||
for (; it->Valid(); it->Next()) {
|
||||
auto tombstone = it->Tombstone();
|
||||
if (upper_bound != nullptr) {
|
||||
@@ -1257,6 +1284,8 @@ Status CompactionJob::FinishCompactionOutputFile(
|
||||
}
|
||||
|
||||
auto kv = tombstone.Serialize();
|
||||
assert(lower_bound == nullptr ||
|
||||
ucmp->Compare(*lower_bound, kv.second) < 0);
|
||||
sub_compact->builder->Add(kv.first.Encode(), kv.second);
|
||||
InternalKey smallest_candidate = std::move(kv.first);
|
||||
if (lower_bound != nullptr &&
|
||||
@@ -1265,11 +1294,24 @@ Status CompactionJob::FinishCompactionOutputFile(
|
||||
// (the max key in the previous table or subcompaction) in order for
|
||||
// files to appear key-space partitioned.
|
||||
//
|
||||
// Choose lowest seqnum so this file's smallest internal key comes
|
||||
// after the previous file's/subcompaction's largest. The fake seqnum
|
||||
// is OK because the read path's file-picking code only considers user
|
||||
// key.
|
||||
smallest_candidate = InternalKey(*lower_bound, 0, kTypeRangeDeletion);
|
||||
// When lower_bound is chosen by a subcompaction, we know that
|
||||
// subcompactions over smaller keys cannot contain any keys at
|
||||
// lower_bound. We also know that smaller subcompactions exist, because
|
||||
// otherwise the subcompaction woud be unbounded on the left. As a
|
||||
// result, we know that no other files on the output level will contain
|
||||
// actual keys at lower_bound (an output file may have a largest key of
|
||||
// lower_bound@kMaxSequenceNumber, but this only indicates a large range
|
||||
// tombstone was truncated). Therefore, it is safe to use the
|
||||
// tombstone's sequence number, to ensure that keys at lower_bound at
|
||||
// lower levels are covered by truncated tombstones.
|
||||
//
|
||||
// If lower_bound was chosen by the smallest data key in the file,
|
||||
// choose lowest seqnum so this file's smallest internal key comes after
|
||||
// the previous file's largest. The fake seqnum is OK because the read
|
||||
// path's file-picking code only considers user key.
|
||||
smallest_candidate = InternalKey(
|
||||
*lower_bound, lower_bound_from_sub_compact ? tombstone.seq_ : 0,
|
||||
kTypeRangeDeletion);
|
||||
}
|
||||
InternalKey largest_candidate = tombstone.SerializeEndKey();
|
||||
if (upper_bound != nullptr &&
|
||||
@@ -1291,9 +1333,23 @@ Status CompactionJob::FinishCompactionOutputFile(
|
||||
largest_candidate =
|
||||
InternalKey(*upper_bound, kMaxSequenceNumber, kTypeRangeDeletion);
|
||||
}
|
||||
#ifndef NDEBUG
|
||||
SequenceNumber smallest_ikey_seqnum = kMaxSequenceNumber;
|
||||
if (meta->smallest.size() > 0) {
|
||||
smallest_ikey_seqnum = GetInternalKeySeqno(meta->smallest.Encode());
|
||||
}
|
||||
#endif
|
||||
meta->UpdateBoundariesForRange(smallest_candidate, largest_candidate,
|
||||
tombstone.seq_,
|
||||
cfd->internal_comparator());
|
||||
|
||||
// The smallest key in a file is used for range tombstone truncation, so
|
||||
// it cannot have a seqnum of 0 (unless the smallest data key in a file
|
||||
// has a seqnum of 0). Otherwise, the truncated tombstone may expose
|
||||
// deleted keys at lower levels.
|
||||
assert(smallest_ikey_seqnum == 0 ||
|
||||
ExtractInternalKeyFooter(meta->smallest.Encode()) !=
|
||||
PackSequenceAndType(0, kTypeRangeDeletion));
|
||||
}
|
||||
meta->marked_for_compaction = sub_compact->builder->NeedCompact();
|
||||
}
|
||||
@@ -1371,10 +1427,7 @@ Status CompactionJob::FinishCompactionOutputFile(
|
||||
auto sfm =
|
||||
static_cast<SstFileManagerImpl*>(db_options_.sst_file_manager.get());
|
||||
if (sfm && meta != nullptr && meta->fd.GetPathId() == 0) {
|
||||
auto fn =
|
||||
TableFileName(sub_compact->compaction->immutable_cf_options()->cf_paths,
|
||||
meta->fd.GetNumber(), meta->fd.GetPathId());
|
||||
sfm->OnAddFile(fn);
|
||||
sfm->OnAddFile(fname);
|
||||
if (sfm->IsMaxAllowedSpaceReached()) {
|
||||
// TODO(ajkr): should we return OK() if max space was reached by the final
|
||||
// compaction output file (similarly to how flush works when full)?
|
||||
@@ -1496,7 +1549,7 @@ Status CompactionJob::OpenCompactionOutputFile(
|
||||
sub_compact->compaction->immutable_cf_options()->listeners;
|
||||
sub_compact->outfile.reset(
|
||||
new WritableFileWriter(std::move(writable_file), fname, env_options_,
|
||||
db_options_.statistics.get(), listeners));
|
||||
env_, db_options_.statistics.get(), listeners));
|
||||
|
||||
// If the Column family flag is to only optimize filters for hits,
|
||||
// we can skip creating filters if this is the bottommost_level where
|
||||
|
||||
+2
-2
@@ -29,8 +29,8 @@
|
||||
#include "db/version_edit.h"
|
||||
#include "db/write_controller.h"
|
||||
#include "db/write_thread.h"
|
||||
#include "options/db_options.h"
|
||||
#include "options/cf_options.h"
|
||||
#include "options/db_options.h"
|
||||
#include "port/port.h"
|
||||
#include "rocksdb/compaction_filter.h"
|
||||
#include "rocksdb/compaction_job_stats.h"
|
||||
@@ -104,7 +104,7 @@ class CompactionJob {
|
||||
|
||||
Status FinishCompactionOutputFile(
|
||||
const Status& input_status, SubcompactionState* sub_compact,
|
||||
RangeDelAggregator* range_del_agg,
|
||||
CompactionRangeDelAggregator* range_del_agg,
|
||||
CompactionIterationStats* range_del_out_stats,
|
||||
const Slice* next_table_min_key = nullptr);
|
||||
Status InstallCompactionResults(const MutableCFOptions& mutable_cf_options);
|
||||
|
||||
@@ -456,8 +456,7 @@ TEST_F(CompactionJobTest, NonAssocMerge) {
|
||||
|
||||
auto expected_results =
|
||||
mock::MakeMockFile({{KeyStr("a", 0U, kTypeValue), "3,4,5"},
|
||||
{KeyStr("b", 2U, kTypeMerge), "2"},
|
||||
{KeyStr("b", 1U, kTypeMerge), "1"}});
|
||||
{KeyStr("b", 2U, kTypeValue), "1,2"}});
|
||||
|
||||
SetLastSequence(5U);
|
||||
auto files = cfd_->current()->storage_info()->LevelFiles(0);
|
||||
@@ -484,7 +483,7 @@ TEST_F(CompactionJobTest, MergeOperandFilter) {
|
||||
|
||||
auto expected_results =
|
||||
mock::MakeMockFile({{KeyStr("a", 0U, kTypeValue), test::EncodeInt(8U)},
|
||||
{KeyStr("b", 2U, kTypeMerge), test::EncodeInt(2U)}});
|
||||
{KeyStr("b", 2U, kTypeValue), test::EncodeInt(2U)}});
|
||||
|
||||
SetLastSequence(5U);
|
||||
auto files = cfd_->current()->storage_info()->LevelFiles(0);
|
||||
|
||||
@@ -59,6 +59,9 @@ class CompactionPickerTest : public testing::Test {
|
||||
log_buffer_(InfoLogLevel::INFO_LEVEL, &logger_),
|
||||
file_num_(1),
|
||||
vstorage_(nullptr) {
|
||||
// ioptions_.compaction_pri = kMinOverlappingRatio has its own set of
|
||||
// tests to cover.
|
||||
ioptions_.compaction_pri = kByCompensatedSize;
|
||||
fifo_options_.max_table_files_size = 1;
|
||||
mutable_cf_options_.RefreshDerivedOptions(ioptions_);
|
||||
ioptions_.cf_paths.emplace_back("dummy",
|
||||
@@ -85,8 +88,8 @@ class CompactionPickerTest : public testing::Test {
|
||||
|
||||
void Add(int level, uint32_t file_number, const char* smallest,
|
||||
const char* largest, uint64_t file_size = 1, uint32_t path_id = 0,
|
||||
SequenceNumber smallest_seq = 100,
|
||||
SequenceNumber largest_seq = 100) {
|
||||
SequenceNumber smallest_seq = 100, SequenceNumber largest_seq = 100,
|
||||
size_t compensated_file_size = 0) {
|
||||
assert(level < vstorage_->num_levels());
|
||||
FileMetaData* f = new FileMetaData;
|
||||
f->fd = FileDescriptor(file_number, path_id, file_size);
|
||||
@@ -94,7 +97,8 @@ class CompactionPickerTest : public testing::Test {
|
||||
f->largest = InternalKey(largest, largest_seq, kTypeValue);
|
||||
f->fd.smallest_seqno = smallest_seq;
|
||||
f->fd.largest_seqno = largest_seq;
|
||||
f->compensated_file_size = file_size;
|
||||
f->compensated_file_size =
|
||||
(compensated_file_size != 0) ? compensated_file_size : file_size;
|
||||
f->refs = 0;
|
||||
vstorage_->AddFile(level, f);
|
||||
files_.emplace_back(f);
|
||||
@@ -617,6 +621,35 @@ TEST_F(CompactionPickerTest, CompactionPriMinOverlapping3) {
|
||||
ASSERT_EQ(8U, compaction->input(0, 0)->fd.GetNumber());
|
||||
}
|
||||
|
||||
TEST_F(CompactionPickerTest, CompactionPriMinOverlapping4) {
|
||||
NewVersionStorage(6, kCompactionStyleLevel);
|
||||
ioptions_.compaction_pri = kMinOverlappingRatio;
|
||||
mutable_cf_options_.max_bytes_for_level_base = 10000000;
|
||||
mutable_cf_options_.max_bytes_for_level_multiplier = 10;
|
||||
|
||||
// file 7 and 8 over lap with the same file, but file 8 is smaller so
|
||||
// it will be picked.
|
||||
// Overlaps with file 26, 27. And the file is compensated so will be
|
||||
// picked up.
|
||||
Add(2, 6U, "150", "167", 60000000U, 0, 100, 100, 180000000U);
|
||||
Add(2, 7U, "168", "169", 60000000U); // Overlaps with file 27
|
||||
Add(2, 8U, "201", "300", 61000000U); // Overlaps with file 28
|
||||
|
||||
Add(3, 26U, "160", "165", 60000000U);
|
||||
// Boosted file size in output level is not considered.
|
||||
Add(3, 27U, "166", "170", 60000000U, 0, 100, 100, 260000000U);
|
||||
Add(3, 28U, "180", "400", 60000000U);
|
||||
Add(3, 29U, "401", "500", 60000000U);
|
||||
UpdateVersionStorageInfo();
|
||||
|
||||
std::unique_ptr<Compaction> compaction(level_compaction_picker.PickCompaction(
|
||||
cf_name_, mutable_cf_options_, vstorage_.get(), &log_buffer_));
|
||||
ASSERT_TRUE(compaction.get() != nullptr);
|
||||
ASSERT_EQ(1U, compaction->num_input_files(0));
|
||||
// Picking file 8 because overlapping ratio is the biggest.
|
||||
ASSERT_EQ(6U, compaction->input(0, 0)->fd.GetNumber());
|
||||
}
|
||||
|
||||
// This test exhibits the bug where we don't properly reset parent_index in
|
||||
// PickCompaction()
|
||||
TEST_F(CompactionPickerTest, ParentIndexResetBug) {
|
||||
|
||||
@@ -24,6 +24,8 @@
|
||||
#include "rocksdb/env.h"
|
||||
#include "rocksdb/table.h"
|
||||
#include "rocksdb/write_batch.h"
|
||||
#include "table/block_based_table_builder.h"
|
||||
#include "table/meta_blocks.h"
|
||||
#include "util/filename.h"
|
||||
#include "util/string_util.h"
|
||||
#include "util/testharness.h"
|
||||
@@ -467,6 +469,39 @@ TEST_F(CorruptionTest, UnrelatedKeys) {
|
||||
ASSERT_EQ(Value(1000, &tmp2).ToString(), v);
|
||||
}
|
||||
|
||||
TEST_F(CorruptionTest, RangeDeletionCorrupted) {
|
||||
ASSERT_OK(
|
||||
db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), "a", "b"));
|
||||
ASSERT_OK(db_->Flush(FlushOptions()));
|
||||
std::vector<LiveFileMetaData> metadata;
|
||||
db_->GetLiveFilesMetaData(&metadata);
|
||||
ASSERT_EQ(static_cast<size_t>(1), metadata.size());
|
||||
std::string filename = dbname_ + metadata[0].name;
|
||||
|
||||
std::unique_ptr<RandomAccessFile> file;
|
||||
ASSERT_OK(options_.env->NewRandomAccessFile(filename, &file, EnvOptions()));
|
||||
std::unique_ptr<RandomAccessFileReader> file_reader(
|
||||
new RandomAccessFileReader(std::move(file), filename));
|
||||
|
||||
uint64_t file_size;
|
||||
ASSERT_OK(options_.env->GetFileSize(filename, &file_size));
|
||||
|
||||
BlockHandle range_del_handle;
|
||||
ASSERT_OK(FindMetaBlock(
|
||||
file_reader.get(), file_size, kBlockBasedTableMagicNumber,
|
||||
ImmutableCFOptions(options_), kRangeDelBlock, &range_del_handle));
|
||||
|
||||
ASSERT_OK(TryReopen());
|
||||
CorruptFile(filename, static_cast<int>(range_del_handle.offset()), 1);
|
||||
// The test case does not fail on TryReopen because failure to preload table
|
||||
// handlers is not considered critical.
|
||||
ASSERT_OK(TryReopen());
|
||||
std::string val;
|
||||
// However, it does fail on any read involving that file since that file
|
||||
// cannot be opened with a corrupt range deletion meta-block.
|
||||
ASSERT_TRUE(db_->Get(ReadOptions(), "a", &val).IsCorruption());
|
||||
}
|
||||
|
||||
TEST_F(CorruptionTest, FileSystemStateCorrupted) {
|
||||
for (int iter = 0; iter < 2; ++iter) {
|
||||
Options options;
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
// Copyright (c) 2011 The LevelDB Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
// #include <iostream>
|
||||
#include "db/db_test_util.h"
|
||||
#include "port/stack_trace.h"
|
||||
#include "rocksdb/perf_context.h"
|
||||
@@ -956,6 +957,184 @@ TEST_F(DBBasicTest, DBCloseFlushError) {
|
||||
Destroy(options);
|
||||
}
|
||||
|
||||
TEST_F(DBBasicTest, MultiGetMultiCF) {
|
||||
Options options = CurrentOptions();
|
||||
CreateAndReopenWithCF({"pikachu", "ilya", "muromec", "dobrynia", "nikitich",
|
||||
"alyosha", "popovich"},
|
||||
options);
|
||||
|
||||
for (int i = 0; i < 8; ++i) {
|
||||
ASSERT_OK(Put(i, "cf" + std::to_string(i) + "_key",
|
||||
"cf" + std::to_string(i) + "_val"));
|
||||
}
|
||||
|
||||
int get_sv_count = 0;
|
||||
rocksdb::DBImpl* db = reinterpret_cast<DBImpl*>(db_);
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::MultiGet::AfterRefSV", [&](void* /*arg*/) {
|
||||
if (++get_sv_count == 2) {
|
||||
// After MultiGet refs a couple of CFs, flush all CFs so MultiGet
|
||||
// is forced to repeat the process
|
||||
for (int i = 0; i < 8; ++i) {
|
||||
ASSERT_OK(Flush(i));
|
||||
ASSERT_OK(Put(i, "cf" + std::to_string(i) + "_key",
|
||||
"cf" + std::to_string(i) + "_val2"));
|
||||
}
|
||||
}
|
||||
if (get_sv_count == 11) {
|
||||
for (int i = 0; i < 8; ++i) {
|
||||
auto* cfd = reinterpret_cast<ColumnFamilyHandleImpl*>(
|
||||
db->GetColumnFamilyHandle(i))
|
||||
->cfd();
|
||||
ASSERT_EQ(cfd->TEST_GetLocalSV()->Get(), SuperVersion::kSVInUse);
|
||||
}
|
||||
}
|
||||
});
|
||||
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
std::vector<int> cfs;
|
||||
std::vector<std::string> keys;
|
||||
std::vector<std::string> values;
|
||||
|
||||
for (int i = 0; i < 8; ++i) {
|
||||
cfs.push_back(i);
|
||||
keys.push_back("cf" + std::to_string(i) + "_key");
|
||||
}
|
||||
|
||||
values = MultiGet(cfs, keys);
|
||||
ASSERT_EQ(values.size(), 8);
|
||||
for (unsigned int j = 0; j < values.size(); ++j) {
|
||||
ASSERT_EQ(values[j], "cf" + std::to_string(j) + "_val2");
|
||||
}
|
||||
for (int i = 0; i < 8; ++i) {
|
||||
auto* cfd = reinterpret_cast<ColumnFamilyHandleImpl*>(
|
||||
reinterpret_cast<DBImpl*>(db_)->GetColumnFamilyHandle(i))
|
||||
->cfd();
|
||||
ASSERT_NE(cfd->TEST_GetLocalSV()->Get(), SuperVersion::kSVInUse);
|
||||
ASSERT_NE(cfd->TEST_GetLocalSV()->Get(), SuperVersion::kSVObsolete);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBBasicTest, MultiGetMultiCFMutex) {
|
||||
Options options = CurrentOptions();
|
||||
CreateAndReopenWithCF({"pikachu", "ilya", "muromec", "dobrynia", "nikitich",
|
||||
"alyosha", "popovich"},
|
||||
options);
|
||||
|
||||
for (int i = 0; i < 8; ++i) {
|
||||
ASSERT_OK(Put(i, "cf" + std::to_string(i) + "_key",
|
||||
"cf" + std::to_string(i) + "_val"));
|
||||
}
|
||||
|
||||
int get_sv_count = 0;
|
||||
int retries = 0;
|
||||
bool last_try = false;
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::MultiGet::LastTry", [&](void* /*arg*/) {
|
||||
last_try = true;
|
||||
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
||||
});
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::MultiGet::AfterRefSV", [&](void* /*arg*/) {
|
||||
if (last_try) {
|
||||
return;
|
||||
}
|
||||
if (++get_sv_count == 2) {
|
||||
++retries;
|
||||
get_sv_count = 0;
|
||||
for (int i = 0; i < 8; ++i) {
|
||||
ASSERT_OK(Flush(i));
|
||||
ASSERT_OK(Put(
|
||||
i, "cf" + std::to_string(i) + "_key",
|
||||
"cf" + std::to_string(i) + "_val" + std::to_string(retries)));
|
||||
}
|
||||
}
|
||||
});
|
||||
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
std::vector<int> cfs;
|
||||
std::vector<std::string> keys;
|
||||
std::vector<std::string> values;
|
||||
|
||||
for (int i = 0; i < 8; ++i) {
|
||||
cfs.push_back(i);
|
||||
keys.push_back("cf" + std::to_string(i) + "_key");
|
||||
}
|
||||
|
||||
values = MultiGet(cfs, keys);
|
||||
ASSERT_TRUE(last_try);
|
||||
ASSERT_EQ(values.size(), 8);
|
||||
for (unsigned int j = 0; j < values.size(); ++j) {
|
||||
ASSERT_EQ(values[j],
|
||||
"cf" + std::to_string(j) + "_val" + std::to_string(retries));
|
||||
}
|
||||
for (int i = 0; i < 8; ++i) {
|
||||
auto* cfd = reinterpret_cast<ColumnFamilyHandleImpl*>(
|
||||
reinterpret_cast<DBImpl*>(db_)->GetColumnFamilyHandle(i))
|
||||
->cfd();
|
||||
ASSERT_NE(cfd->TEST_GetLocalSV()->Get(), SuperVersion::kSVInUse);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBBasicTest, MultiGetMultiCFSnapshot) {
|
||||
Options options = CurrentOptions();
|
||||
CreateAndReopenWithCF({"pikachu", "ilya", "muromec", "dobrynia", "nikitich",
|
||||
"alyosha", "popovich"},
|
||||
options);
|
||||
|
||||
for (int i = 0; i < 8; ++i) {
|
||||
ASSERT_OK(Put(i, "cf" + std::to_string(i) + "_key",
|
||||
"cf" + std::to_string(i) + "_val"));
|
||||
}
|
||||
|
||||
int get_sv_count = 0;
|
||||
rocksdb::DBImpl* db = reinterpret_cast<DBImpl*>(db_);
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::MultiGet::AfterRefSV", [&](void* /*arg*/) {
|
||||
if (++get_sv_count == 2) {
|
||||
for (int i = 0; i < 8; ++i) {
|
||||
ASSERT_OK(Flush(i));
|
||||
ASSERT_OK(Put(i, "cf" + std::to_string(i) + "_key",
|
||||
"cf" + std::to_string(i) + "_val2"));
|
||||
}
|
||||
}
|
||||
if (get_sv_count == 8) {
|
||||
for (int i = 0; i < 8; ++i) {
|
||||
auto* cfd = reinterpret_cast<ColumnFamilyHandleImpl*>(
|
||||
db->GetColumnFamilyHandle(i))
|
||||
->cfd();
|
||||
ASSERT_TRUE(
|
||||
(cfd->TEST_GetLocalSV()->Get() == SuperVersion::kSVInUse) ||
|
||||
(cfd->TEST_GetLocalSV()->Get() == SuperVersion::kSVObsolete));
|
||||
}
|
||||
}
|
||||
});
|
||||
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
std::vector<int> cfs;
|
||||
std::vector<std::string> keys;
|
||||
std::vector<std::string> values;
|
||||
|
||||
for (int i = 0; i < 8; ++i) {
|
||||
cfs.push_back(i);
|
||||
keys.push_back("cf" + std::to_string(i) + "_key");
|
||||
}
|
||||
|
||||
const Snapshot* snapshot = db_->GetSnapshot();
|
||||
values = MultiGet(cfs, keys, snapshot);
|
||||
db_->ReleaseSnapshot(snapshot);
|
||||
ASSERT_EQ(values.size(), 8);
|
||||
for (unsigned int j = 0; j < values.size(); ++j) {
|
||||
ASSERT_EQ(values[j], "cf" + std::to_string(j) + "_val");
|
||||
}
|
||||
for (int i = 0; i < 8; ++i) {
|
||||
auto* cfd = reinterpret_cast<ColumnFamilyHandleImpl*>(
|
||||
reinterpret_cast<DBImpl*>(db_)->GetColumnFamilyHandle(i))
|
||||
->cfd();
|
||||
ASSERT_NE(cfd->TEST_GetLocalSV()->Get(), SuperVersion::kSVInUse);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
@@ -173,7 +173,7 @@ TEST_F(DBBlockCacheTest, TestWithoutCompressedBlockCache) {
|
||||
delete iter;
|
||||
iter = nullptr;
|
||||
|
||||
// Release interators and access cache again.
|
||||
// Release iterators and access cache again.
|
||||
for (size_t i = 0; i < kNumBlocks - 1; i++) {
|
||||
iterators[i].reset();
|
||||
CheckCacheCounters(options, 0, 0, 0, 0);
|
||||
@@ -631,6 +631,74 @@ TEST_F(DBBlockCacheTest, CompressedCache) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBBlockCacheTest, CacheCompressionDict) {
|
||||
const int kNumFiles = 4;
|
||||
const int kNumEntriesPerFile = 32;
|
||||
const int kNumBytesPerEntry = 1024;
|
||||
|
||||
// Try all the available libraries that support dictionary compression
|
||||
std::vector<CompressionType> compression_types;
|
||||
#ifdef ZLIB
|
||||
compression_types.push_back(kZlibCompression);
|
||||
#endif // ZLIB
|
||||
#if LZ4_VERSION_NUMBER >= 10400
|
||||
compression_types.push_back(kLZ4Compression);
|
||||
compression_types.push_back(kLZ4HCCompression);
|
||||
#endif // LZ4_VERSION_NUMBER >= 10400
|
||||
#if ZSTD_VERSION_NUMBER >= 500
|
||||
compression_types.push_back(kZSTD);
|
||||
#endif // ZSTD_VERSION_NUMBER >= 500
|
||||
Random rnd(301);
|
||||
for (auto compression_type : compression_types) {
|
||||
Options options = CurrentOptions();
|
||||
options.compression = compression_type;
|
||||
options.compression_opts.max_dict_bytes = 4096;
|
||||
options.create_if_missing = true;
|
||||
options.num_levels = 2;
|
||||
options.statistics = rocksdb::CreateDBStatistics();
|
||||
options.target_file_size_base = kNumEntriesPerFile * kNumBytesPerEntry;
|
||||
BlockBasedTableOptions table_options;
|
||||
table_options.cache_index_and_filter_blocks = true;
|
||||
table_options.block_cache.reset(new MockCache());
|
||||
options.table_factory.reset(new BlockBasedTableFactory(table_options));
|
||||
DestroyAndReopen(options);
|
||||
|
||||
for (int i = 0; i < kNumFiles; ++i) {
|
||||
ASSERT_EQ(i, NumTableFilesAtLevel(0, 0));
|
||||
for (int j = 0; j < kNumEntriesPerFile; ++j) {
|
||||
std::string value = RandomString(&rnd, kNumBytesPerEntry);
|
||||
ASSERT_OK(Put(Key(j * kNumFiles + i), value.c_str()));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
ASSERT_EQ(0, NumTableFilesAtLevel(0));
|
||||
ASSERT_EQ(kNumFiles, NumTableFilesAtLevel(1));
|
||||
|
||||
// Seek to a key in a file. It should cause the SST's dictionary meta-block
|
||||
// to be read.
|
||||
RecordCacheCounters(options);
|
||||
ASSERT_EQ(0,
|
||||
TestGetTickerCount(options, BLOCK_CACHE_COMPRESSION_DICT_MISS));
|
||||
ASSERT_EQ(0, TestGetTickerCount(options, BLOCK_CACHE_COMPRESSION_DICT_ADD));
|
||||
ASSERT_EQ(
|
||||
TestGetTickerCount(options, BLOCK_CACHE_COMPRESSION_DICT_BYTES_INSERT),
|
||||
0);
|
||||
ReadOptions read_options;
|
||||
ASSERT_NE("NOT_FOUND", Get(Key(kNumFiles * kNumEntriesPerFile - 1)));
|
||||
// Two blocks missed/added: dictionary and data block
|
||||
// One block hit: index since it's prefetched
|
||||
CheckCacheCounters(options, 2 /* expected_misses */, 1 /* expected_hits */,
|
||||
2 /* expected_inserts */, 0 /* expected_failures */);
|
||||
ASSERT_EQ(1,
|
||||
TestGetTickerCount(options, BLOCK_CACHE_COMPRESSION_DICT_MISS));
|
||||
ASSERT_EQ(1, TestGetTickerCount(options, BLOCK_CACHE_COMPRESSION_DICT_ADD));
|
||||
ASSERT_GT(
|
||||
TestGetTickerCount(options, BLOCK_CACHE_COMPRESSION_DICT_BYTES_INSERT),
|
||||
0);
|
||||
}
|
||||
}
|
||||
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
@@ -340,8 +340,8 @@ TEST_F(DBTestCompactionFilter, CompactionFilter) {
|
||||
Arena arena;
|
||||
{
|
||||
InternalKeyComparator icmp(options.comparator);
|
||||
RangeDelAggregatorV2 range_del_agg(&icmp,
|
||||
kMaxSequenceNumber /* upper_bound */);
|
||||
ReadRangeDelAggregator range_del_agg(&icmp,
|
||||
kMaxSequenceNumber /* upper_bound */);
|
||||
ScopedArenaIterator iter(dbfull()->NewInternalIterator(
|
||||
&arena, &range_del_agg, kMaxSequenceNumber, handles_[1]));
|
||||
iter->SeekToFirst();
|
||||
@@ -430,8 +430,8 @@ TEST_F(DBTestCompactionFilter, CompactionFilter) {
|
||||
count = 0;
|
||||
{
|
||||
InternalKeyComparator icmp(options.comparator);
|
||||
RangeDelAggregatorV2 range_del_agg(&icmp,
|
||||
kMaxSequenceNumber /* upper_bound */);
|
||||
ReadRangeDelAggregator range_del_agg(&icmp,
|
||||
kMaxSequenceNumber /* upper_bound */);
|
||||
ScopedArenaIterator iter(dbfull()->NewInternalIterator(
|
||||
&arena, &range_del_agg, kMaxSequenceNumber, handles_[1]));
|
||||
iter->SeekToFirst();
|
||||
@@ -648,8 +648,8 @@ TEST_F(DBTestCompactionFilter, CompactionFilterContextManual) {
|
||||
int total = 0;
|
||||
Arena arena;
|
||||
InternalKeyComparator icmp(options.comparator);
|
||||
RangeDelAggregatorV2 range_del_agg(&icmp,
|
||||
kMaxSequenceNumber /* snapshots */);
|
||||
ReadRangeDelAggregator range_del_agg(&icmp,
|
||||
kMaxSequenceNumber /* snapshots */);
|
||||
ScopedArenaIterator iter(dbfull()->NewInternalIterator(
|
||||
&arena, &range_del_agg, kMaxSequenceNumber));
|
||||
iter->SeekToFirst();
|
||||
|
||||
+205
-9
@@ -10,8 +10,10 @@
|
||||
#include "db/db_test_util.h"
|
||||
#include "port/port.h"
|
||||
#include "port/stack_trace.h"
|
||||
#include "rocksdb/concurrent_task_limiter.h"
|
||||
#include "rocksdb/experimental.h"
|
||||
#include "rocksdb/utilities/convenience.h"
|
||||
#include "util/concurrent_task_limiter_impl.h"
|
||||
#include "util/fault_injection_test_env.h"
|
||||
#include "util/sync_point.h"
|
||||
|
||||
@@ -412,6 +414,7 @@ TEST_F(DBCompactionTest, SkipStatsUpdateTest) {
|
||||
|
||||
// Reopen the DB with stats-update disabled
|
||||
options.skip_stats_update_on_db_open = true;
|
||||
options.max_open_files = 20;
|
||||
env_->random_file_open_counter_.store(0);
|
||||
Reopen(options);
|
||||
|
||||
@@ -437,7 +440,7 @@ TEST_F(DBCompactionTest, TestTableReaderForCompaction) {
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
options.new_table_reader_for_compaction_inputs = true;
|
||||
options.max_open_files = 100;
|
||||
options.max_open_files = 20;
|
||||
options.level0_file_num_compaction_trigger = 3;
|
||||
DestroyAndReopen(options);
|
||||
Random rnd(301);
|
||||
@@ -466,15 +469,16 @@ TEST_F(DBCompactionTest, TestTableReaderForCompaction) {
|
||||
Flush();
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
// preloading iterator issues one table cache lookup and create
|
||||
// a new table reader.
|
||||
ASSERT_EQ(num_table_cache_lookup, 1);
|
||||
// a new table reader, if not preloaded.
|
||||
int old_num_table_cache_lookup = num_table_cache_lookup;
|
||||
ASSERT_GE(num_table_cache_lookup, 1);
|
||||
ASSERT_EQ(num_new_table_reader, 1);
|
||||
|
||||
num_table_cache_lookup = 0;
|
||||
num_new_table_reader = 0;
|
||||
ASSERT_EQ(Key(k), Get(Key(k)));
|
||||
// lookup iterator from table cache and no need to create a new one.
|
||||
ASSERT_EQ(num_table_cache_lookup, 1);
|
||||
ASSERT_EQ(old_num_table_cache_lookup + num_table_cache_lookup, 2);
|
||||
ASSERT_EQ(num_new_table_reader, 0);
|
||||
}
|
||||
}
|
||||
@@ -487,7 +491,10 @@ TEST_F(DBCompactionTest, TestTableReaderForCompaction) {
|
||||
// a new table reader. One file is created for flush and one for compaction.
|
||||
// Compaction inputs make no table cache look-up for data/range deletion
|
||||
// iterators
|
||||
ASSERT_EQ(num_table_cache_lookup, 2);
|
||||
// May preload table cache too.
|
||||
ASSERT_GE(num_table_cache_lookup, 2);
|
||||
int old_num_table_cache_lookup2 = num_table_cache_lookup;
|
||||
|
||||
// Create new iterator for:
|
||||
// (1) 1 for verifying flush results
|
||||
// (2) 3 for compaction input files
|
||||
@@ -497,7 +504,7 @@ TEST_F(DBCompactionTest, TestTableReaderForCompaction) {
|
||||
num_table_cache_lookup = 0;
|
||||
num_new_table_reader = 0;
|
||||
ASSERT_EQ(Key(1), Get(Key(1)));
|
||||
ASSERT_EQ(num_table_cache_lookup, 1);
|
||||
ASSERT_EQ(num_table_cache_lookup + old_num_table_cache_lookup2, 3);
|
||||
ASSERT_EQ(num_new_table_reader, 0);
|
||||
|
||||
num_table_cache_lookup = 0;
|
||||
@@ -509,14 +516,16 @@ TEST_F(DBCompactionTest, TestTableReaderForCompaction) {
|
||||
db_->CompactRange(cro, nullptr, nullptr);
|
||||
// Only verifying compaction outputs issues one table cache lookup
|
||||
// for both data block and range deletion block).
|
||||
ASSERT_EQ(num_table_cache_lookup, 1);
|
||||
// May preload table cache too.
|
||||
ASSERT_GE(num_table_cache_lookup, 1);
|
||||
old_num_table_cache_lookup2 = num_table_cache_lookup;
|
||||
// One for compaction input, one for verifying compaction results.
|
||||
ASSERT_EQ(num_new_table_reader, 2);
|
||||
|
||||
num_table_cache_lookup = 0;
|
||||
num_new_table_reader = 0;
|
||||
ASSERT_EQ(Key(1), Get(Key(1)));
|
||||
ASSERT_EQ(num_table_cache_lookup, 1);
|
||||
ASSERT_EQ(num_table_cache_lookup + old_num_table_cache_lookup2, 2);
|
||||
ASSERT_EQ(num_new_table_reader, 0);
|
||||
|
||||
rocksdb::SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
@@ -1594,7 +1603,8 @@ TEST_F(DBCompactionTest, DeleteFileRange) {
|
||||
// Note that we don't delete level 0 files
|
||||
compact_options.change_level = true;
|
||||
compact_options.target_level = 1;
|
||||
ASSERT_OK(dbfull()->TEST_CompactRange(0, nullptr, nullptr));
|
||||
ASSERT_OK(db_->CompactRange(compact_options, nullptr, nullptr));
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
|
||||
ASSERT_OK(
|
||||
DeleteFilesInRange(db_, db_->DefaultColumnFamily(), nullptr, nullptr));
|
||||
@@ -3890,6 +3900,192 @@ TEST_F(DBCompactionTest, CompactionHasEmptyOutput) {
|
||||
ASSERT_EQ(2, collector->num_ssts_creation_started());
|
||||
}
|
||||
|
||||
TEST_F(DBCompactionTest, CompactionLimiter) {
|
||||
const int kNumKeysPerFile = 10;
|
||||
const int kMaxBackgroundThreads = 64;
|
||||
|
||||
struct CompactionLimiter {
|
||||
std::string name;
|
||||
int limit_tasks;
|
||||
int max_tasks;
|
||||
int tasks;
|
||||
std::shared_ptr<ConcurrentTaskLimiter> limiter;
|
||||
};
|
||||
|
||||
std::vector<CompactionLimiter> limiter_settings;
|
||||
limiter_settings.push_back({"limiter_1", 1, 0, 0, nullptr});
|
||||
limiter_settings.push_back({"limiter_2", 2, 0, 0, nullptr});
|
||||
limiter_settings.push_back({"limiter_3", 3, 0, 0, nullptr});
|
||||
|
||||
for (auto& ls : limiter_settings) {
|
||||
ls.limiter.reset(NewConcurrentTaskLimiter(ls.name, ls.limit_tasks));
|
||||
}
|
||||
|
||||
std::shared_ptr<ConcurrentTaskLimiter> unique_limiter(
|
||||
NewConcurrentTaskLimiter("unique_limiter", -1));
|
||||
|
||||
const char* cf_names[] = {"default", "0", "1", "2", "3", "4", "5",
|
||||
"6", "7", "8", "9", "a", "b", "c", "d", "e", "f" };
|
||||
const int cf_count = sizeof cf_names / sizeof cf_names[0];
|
||||
|
||||
std::unordered_map<std::string, CompactionLimiter*> cf_to_limiter;
|
||||
|
||||
Options options = CurrentOptions();
|
||||
options.write_buffer_size = 110 * 1024; // 110KB
|
||||
options.arena_block_size = 4096;
|
||||
options.num_levels = 3;
|
||||
options.level0_file_num_compaction_trigger = 4;
|
||||
options.level0_slowdown_writes_trigger = 64;
|
||||
options.level0_stop_writes_trigger = 64;
|
||||
options.max_background_jobs = kMaxBackgroundThreads; // Enough threads
|
||||
options.memtable_factory.reset(new SpecialSkipListFactory(kNumKeysPerFile));
|
||||
options.max_write_buffer_number = 10; // Enough memtables
|
||||
DestroyAndReopen(options);
|
||||
|
||||
std::vector<Options> option_vector;
|
||||
option_vector.reserve(cf_count);
|
||||
|
||||
for (int cf = 0; cf < cf_count; cf++) {
|
||||
ColumnFamilyOptions cf_opt(options);
|
||||
if (cf == 0) {
|
||||
// "Default" CF does't use compaction limiter
|
||||
cf_opt.compaction_thread_limiter = nullptr;
|
||||
} else if (cf == 1) {
|
||||
// "1" CF uses bypass compaction limiter
|
||||
unique_limiter->SetMaxOutstandingTask(-1);
|
||||
cf_opt.compaction_thread_limiter = unique_limiter;
|
||||
} else {
|
||||
// Assign limiter by mod
|
||||
auto& ls = limiter_settings[cf % 3];
|
||||
cf_opt.compaction_thread_limiter = ls.limiter;
|
||||
cf_to_limiter[cf_names[cf]] = &ls;
|
||||
}
|
||||
option_vector.emplace_back(DBOptions(options), cf_opt);
|
||||
}
|
||||
|
||||
for (int cf = 1; cf < cf_count; cf++) {
|
||||
CreateColumnFamilies({cf_names[cf]}, option_vector[cf]);
|
||||
}
|
||||
|
||||
ReopenWithColumnFamilies(std::vector<std::string>(cf_names,
|
||||
cf_names + cf_count),
|
||||
option_vector);
|
||||
|
||||
port::Mutex mutex;
|
||||
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::BackgroundCompaction:BeforeCompaction", [&](void* arg) {
|
||||
const auto& cf_name = static_cast<ColumnFamilyData*>(arg)->GetName();
|
||||
auto iter = cf_to_limiter.find(cf_name);
|
||||
if (iter != cf_to_limiter.end()) {
|
||||
MutexLock l(&mutex);
|
||||
ASSERT_GE(iter->second->limit_tasks, ++iter->second->tasks);
|
||||
iter->second->max_tasks = std::max(iter->second->max_tasks,
|
||||
iter->second->limit_tasks);
|
||||
}
|
||||
});
|
||||
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::BackgroundCompaction:AfterCompaction", [&](void* arg) {
|
||||
const auto& cf_name = static_cast<ColumnFamilyData*>(arg)->GetName();
|
||||
auto iter = cf_to_limiter.find(cf_name);
|
||||
if (iter != cf_to_limiter.end()) {
|
||||
MutexLock l(&mutex);
|
||||
ASSERT_GE(--iter->second->tasks, 0);
|
||||
}
|
||||
});
|
||||
|
||||
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
// Block all compact threads in thread pool.
|
||||
const size_t kTotalFlushTasks = kMaxBackgroundThreads / 4;
|
||||
const size_t kTotalCompactTasks = kMaxBackgroundThreads - kTotalFlushTasks;
|
||||
env_->SetBackgroundThreads((int)kTotalFlushTasks, Env::HIGH);
|
||||
env_->SetBackgroundThreads((int)kTotalCompactTasks, Env::LOW);
|
||||
|
||||
test::SleepingBackgroundTask sleeping_compact_tasks[kTotalCompactTasks];
|
||||
|
||||
// Block all compaction threads in thread pool.
|
||||
for (size_t i = 0; i < kTotalCompactTasks; i++) {
|
||||
env_->Schedule(&test::SleepingBackgroundTask::DoSleepTask,
|
||||
&sleeping_compact_tasks[i], Env::LOW);
|
||||
sleeping_compact_tasks[i].WaitUntilSleeping();
|
||||
}
|
||||
|
||||
int keyIndex = 0;
|
||||
|
||||
for (int n = 0; n < options.level0_file_num_compaction_trigger; n++) {
|
||||
for (int cf = 0; cf < cf_count; cf++) {
|
||||
for (int i = 0; i < kNumKeysPerFile; i++) {
|
||||
ASSERT_OK(Put(cf, Key(keyIndex++), ""));
|
||||
}
|
||||
// put extra key to trigger flush
|
||||
ASSERT_OK(Put(cf, "", ""));
|
||||
}
|
||||
|
||||
for (int cf = 0; cf < cf_count; cf++) {
|
||||
dbfull()->TEST_WaitForFlushMemTable(handles_[cf]);
|
||||
}
|
||||
}
|
||||
|
||||
// Enough L0 files to trigger compaction
|
||||
for (int cf = 0; cf < cf_count; cf++) {
|
||||
ASSERT_EQ(NumTableFilesAtLevel(0, cf),
|
||||
options.level0_file_num_compaction_trigger);
|
||||
}
|
||||
|
||||
// Create more files for one column family, which triggers speed up
|
||||
// condition, all compactions will be scheduled.
|
||||
for (int num = 0; num < options.level0_file_num_compaction_trigger; num++) {
|
||||
for (int i = 0; i < kNumKeysPerFile; i++) {
|
||||
ASSERT_OK(Put(0, Key(i), ""));
|
||||
}
|
||||
// put extra key to trigger flush
|
||||
ASSERT_OK(Put(0, "", ""));
|
||||
dbfull()->TEST_WaitForFlushMemTable(handles_[0]);
|
||||
ASSERT_EQ(options.level0_file_num_compaction_trigger + num + 1,
|
||||
NumTableFilesAtLevel(0, 0));
|
||||
}
|
||||
|
||||
// All CFs are pending compaction
|
||||
ASSERT_EQ(cf_count, env_->GetThreadPoolQueueLen(Env::LOW));
|
||||
|
||||
// Unblock all compaction threads
|
||||
for (size_t i = 0; i < kTotalCompactTasks; i++) {
|
||||
sleeping_compact_tasks[i].WakeUp();
|
||||
sleeping_compact_tasks[i].WaitUntilDone();
|
||||
}
|
||||
|
||||
for (int cf = 0; cf < cf_count; cf++) {
|
||||
dbfull()->TEST_WaitForFlushMemTable(handles_[cf]);
|
||||
}
|
||||
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
|
||||
// Max outstanding compact tasks reached limit
|
||||
for (auto& ls : limiter_settings) {
|
||||
ASSERT_EQ(ls.limit_tasks, ls.max_tasks);
|
||||
ASSERT_EQ(0, ls.limiter->GetOutstandingTask());
|
||||
}
|
||||
|
||||
// test manual compaction under a fully throttled limiter
|
||||
int cf_test = 1;
|
||||
unique_limiter->SetMaxOutstandingTask(0);
|
||||
|
||||
// flush one more file to cf 1
|
||||
for (int i = 0; i < kNumKeysPerFile; i++) {
|
||||
ASSERT_OK(Put(cf_test, Key(keyIndex++), ""));
|
||||
}
|
||||
// put extra key to trigger flush
|
||||
ASSERT_OK(Put(cf_test, "", ""));
|
||||
|
||||
dbfull()->TEST_WaitForFlushMemTable(handles_[cf_test]);
|
||||
ASSERT_EQ(1, NumTableFilesAtLevel(0, cf_test));
|
||||
|
||||
Compact(cf_test, Key(0), Key(keyIndex));
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(DBCompactionTestWithParam, DBCompactionTestWithParam,
|
||||
::testing::Values(std::make_tuple(1, true),
|
||||
std::make_tuple(1, false),
|
||||
|
||||
@@ -407,6 +407,87 @@ TEST_P(DBAtomicFlushTest, AtomicFlushRollbackSomeJobs) {
|
||||
Destroy(options);
|
||||
}
|
||||
|
||||
TEST_P(DBAtomicFlushTest, FlushMultipleCFs_DropSomeBeforeRequestFlush) {
|
||||
bool atomic_flush = GetParam();
|
||||
if (!atomic_flush) {
|
||||
return;
|
||||
}
|
||||
Options options = CurrentOptions();
|
||||
options.create_if_missing = true;
|
||||
options.atomic_flush = atomic_flush;
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
CreateAndReopenWithCF({"pikachu", "eevee"}, options);
|
||||
size_t num_cfs = handles_.size();
|
||||
ASSERT_EQ(3, num_cfs);
|
||||
WriteOptions wopts;
|
||||
wopts.disableWAL = true;
|
||||
std::vector<int> cf_ids;
|
||||
for (size_t i = 0; i != num_cfs; ++i) {
|
||||
int cf_id = static_cast<int>(i);
|
||||
ASSERT_OK(Put(cf_id, "key", "value", wopts));
|
||||
cf_ids.push_back(cf_id);
|
||||
}
|
||||
ASSERT_OK(dbfull()->DropColumnFamily(handles_[1]));
|
||||
ASSERT_TRUE(Flush(cf_ids).IsShutdownInProgress());
|
||||
Destroy(options);
|
||||
}
|
||||
|
||||
TEST_P(DBAtomicFlushTest,
|
||||
FlushMultipleCFs_DropSomeAfterScheduleFlushBeforeFlushJobRun) {
|
||||
bool atomic_flush = GetParam();
|
||||
if (!atomic_flush) {
|
||||
return;
|
||||
}
|
||||
Options options = CurrentOptions();
|
||||
options.create_if_missing = true;
|
||||
options.atomic_flush = atomic_flush;
|
||||
|
||||
CreateAndReopenWithCF({"pikachu", "eevee"}, options);
|
||||
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
SyncPoint::GetInstance()->LoadDependency(
|
||||
{{"DBImpl::AtomicFlushMemTables:AfterScheduleFlush",
|
||||
"DBAtomicFlushTest::BeforeDropCF"},
|
||||
{"DBAtomicFlushTest::AfterDropCF",
|
||||
"DBImpl::BackgroundCallFlush:start"}});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
size_t num_cfs = handles_.size();
|
||||
ASSERT_EQ(3, num_cfs);
|
||||
WriteOptions wopts;
|
||||
wopts.disableWAL = true;
|
||||
for (size_t i = 0; i != num_cfs; ++i) {
|
||||
int cf_id = static_cast<int>(i);
|
||||
ASSERT_OK(Put(cf_id, "key", "value", wopts));
|
||||
}
|
||||
port::Thread user_thread([&]() {
|
||||
TEST_SYNC_POINT("DBAtomicFlushTest::BeforeDropCF");
|
||||
ASSERT_OK(dbfull()->DropColumnFamily(handles_[1]));
|
||||
TEST_SYNC_POINT("DBAtomicFlushTest::AfterDropCF");
|
||||
});
|
||||
FlushOptions flush_opts;
|
||||
flush_opts.wait = true;
|
||||
ASSERT_OK(dbfull()->Flush(flush_opts, handles_));
|
||||
user_thread.join();
|
||||
for (size_t i = 0; i != num_cfs; ++i) {
|
||||
int cf_id = static_cast<int>(i);
|
||||
ASSERT_EQ("value", Get(cf_id, "key"));
|
||||
}
|
||||
|
||||
ReopenWithColumnFamilies({kDefaultColumnFamilyName, "eevee"}, options);
|
||||
num_cfs = handles_.size();
|
||||
ASSERT_EQ(2, num_cfs);
|
||||
for (size_t i = 0; i != num_cfs; ++i) {
|
||||
int cf_id = static_cast<int>(i);
|
||||
ASSERT_EQ("value", Get(cf_id, "key"));
|
||||
}
|
||||
Destroy(options);
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(DBFlushDirectIOTest, DBFlushDirectIOTest,
|
||||
testing::Bool());
|
||||
|
||||
|
||||
+126
-54
@@ -45,7 +45,6 @@
|
||||
#include "db/memtable_list.h"
|
||||
#include "db/merge_context.h"
|
||||
#include "db/merge_helper.h"
|
||||
#include "db/range_del_aggregator.h"
|
||||
#include "db/range_tombstone_fragmenter.h"
|
||||
#include "db/table_cache.h"
|
||||
#include "db/table_properties_collector.h"
|
||||
@@ -221,7 +220,7 @@ DBImpl::DBImpl(const DBOptions& options, const std::string& dbname,
|
||||
preserve_deletes_(options.preserve_deletes),
|
||||
closed_(false),
|
||||
error_handler_(this, immutable_db_options_, &mutex_),
|
||||
atomic_flush_commit_in_progress_(false) {
|
||||
atomic_flush_install_cv_(&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_);
|
||||
@@ -1033,7 +1032,7 @@ bool DBImpl::SetPreserveDeletesSequenceNumber(SequenceNumber seqnum) {
|
||||
}
|
||||
|
||||
InternalIterator* DBImpl::NewInternalIterator(
|
||||
Arena* arena, RangeDelAggregatorV2* range_del_agg, SequenceNumber sequence,
|
||||
Arena* arena, RangeDelAggregator* range_del_agg, SequenceNumber sequence,
|
||||
ColumnFamilyHandle* column_family) {
|
||||
ColumnFamilyData* cfd;
|
||||
if (column_family == nullptr) {
|
||||
@@ -1150,10 +1149,12 @@ static void CleanupIteratorState(void* arg1, void* /*arg2*/) {
|
||||
}
|
||||
} // namespace
|
||||
|
||||
InternalIterator* DBImpl::NewInternalIterator(
|
||||
const ReadOptions& read_options, ColumnFamilyData* cfd,
|
||||
SuperVersion* super_version, Arena* arena,
|
||||
RangeDelAggregatorV2* range_del_agg, SequenceNumber sequence) {
|
||||
InternalIterator* DBImpl::NewInternalIterator(const ReadOptions& read_options,
|
||||
ColumnFamilyData* cfd,
|
||||
SuperVersion* super_version,
|
||||
Arena* arena,
|
||||
RangeDelAggregator* range_del_agg,
|
||||
SequenceNumber sequence) {
|
||||
InternalIterator* internal_iter;
|
||||
assert(arena != nullptr);
|
||||
assert(range_del_agg != nullptr);
|
||||
@@ -1215,6 +1216,7 @@ Status DBImpl::GetImpl(const ReadOptions& read_options,
|
||||
PinnableSlice* pinnable_val, bool* value_found,
|
||||
ReadCallback* callback, bool* is_blob_index) {
|
||||
assert(pinnable_val != nullptr);
|
||||
PERF_CPU_TIMER_GUARD(get_cpu_nanos, env_);
|
||||
StopWatch sw(env_, stats_, DB_GET);
|
||||
PERF_TIMER_GUARD(get_snapshot_time);
|
||||
|
||||
@@ -1329,6 +1331,7 @@ std::vector<Status> DBImpl::MultiGet(
|
||||
const ReadOptions& read_options,
|
||||
const std::vector<ColumnFamilyHandle*>& column_family,
|
||||
const std::vector<Slice>& keys, std::vector<std::string>* values) {
|
||||
PERF_CPU_TIMER_GUARD(get_cpu_nanos, env_);
|
||||
StopWatch sw(env_, stats_, DB_MULTIGET);
|
||||
PERF_TIMER_GUARD(get_snapshot_time);
|
||||
|
||||
@@ -1337,33 +1340,96 @@ std::vector<Status> DBImpl::MultiGet(
|
||||
struct MultiGetColumnFamilyData {
|
||||
ColumnFamilyData* cfd;
|
||||
SuperVersion* super_version;
|
||||
MultiGetColumnFamilyData(ColumnFamilyData* cf, SuperVersion* sv)
|
||||
: cfd(cf), super_version(sv) {}
|
||||
};
|
||||
std::unordered_map<uint32_t, MultiGetColumnFamilyData*> multiget_cf_data;
|
||||
// fill up and allocate outside of mutex
|
||||
std::unordered_map<uint32_t, MultiGetColumnFamilyData> multiget_cf_data(
|
||||
column_family.size());
|
||||
for (auto cf : column_family) {
|
||||
auto cfh = reinterpret_cast<ColumnFamilyHandleImpl*>(cf);
|
||||
auto cfd = cfh->cfd();
|
||||
if (multiget_cf_data.find(cfd->GetID()) == multiget_cf_data.end()) {
|
||||
auto mgcfd = new MultiGetColumnFamilyData();
|
||||
mgcfd->cfd = cfd;
|
||||
multiget_cf_data.insert({cfd->GetID(), mgcfd});
|
||||
multiget_cf_data.emplace(cfd->GetID(),
|
||||
MultiGetColumnFamilyData(cfd, nullptr));
|
||||
}
|
||||
}
|
||||
|
||||
mutex_.Lock();
|
||||
if (read_options.snapshot != nullptr) {
|
||||
snapshot =
|
||||
reinterpret_cast<const SnapshotImpl*>(read_options.snapshot)->number_;
|
||||
} else {
|
||||
snapshot = last_seq_same_as_publish_seq_
|
||||
? versions_->LastSequence()
|
||||
: versions_->LastPublishedSequence();
|
||||
bool last_try = false;
|
||||
{
|
||||
// If we end up with the same issue of memtable geting sealed during 2
|
||||
// consecutive retries, it means the write rate is very high. In that case
|
||||
// its probably ok to take the mutex on the 3rd try so we can succeed for
|
||||
// sure
|
||||
static const int num_retries = 3;
|
||||
for (auto i = 0; i < num_retries; ++i) {
|
||||
last_try = (i == num_retries - 1);
|
||||
bool retry = false;
|
||||
|
||||
if (i > 0) {
|
||||
for (auto mgd_iter = multiget_cf_data.begin();
|
||||
mgd_iter != multiget_cf_data.end(); ++mgd_iter) {
|
||||
auto super_version = mgd_iter->second.super_version;
|
||||
auto cfd = mgd_iter->second.cfd;
|
||||
if (super_version != nullptr) {
|
||||
ReturnAndCleanupSuperVersion(cfd, super_version);
|
||||
}
|
||||
mgd_iter->second.super_version = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
if (read_options.snapshot == nullptr) {
|
||||
if (last_try) {
|
||||
TEST_SYNC_POINT("DBImpl::MultiGet::LastTry");
|
||||
// We're close to max number of retries. For the last retry,
|
||||
// acquire the lock so we're sure to succeed
|
||||
mutex_.Lock();
|
||||
}
|
||||
snapshot = last_seq_same_as_publish_seq_
|
||||
? versions_->LastSequence()
|
||||
: versions_->LastPublishedSequence();
|
||||
} else {
|
||||
snapshot = reinterpret_cast<const SnapshotImpl*>(read_options.snapshot)
|
||||
->number_;
|
||||
}
|
||||
|
||||
for (auto mgd_iter = multiget_cf_data.begin();
|
||||
mgd_iter != multiget_cf_data.end(); ++mgd_iter) {
|
||||
if (!last_try) {
|
||||
mgd_iter->second.super_version =
|
||||
GetAndRefSuperVersion(mgd_iter->second.cfd);
|
||||
} else {
|
||||
mgd_iter->second.super_version =
|
||||
mgd_iter->second.cfd->GetSuperVersion()->Ref();
|
||||
}
|
||||
TEST_SYNC_POINT("DBImpl::MultiGet::AfterRefSV");
|
||||
if (read_options.snapshot != nullptr || last_try) {
|
||||
// If user passed a snapshot, then we don't care if a memtable is
|
||||
// sealed or compaction happens because the snapshot would ensure
|
||||
// that older key versions are kept around. If this is the last
|
||||
// retry, then we have the lock so nothing bad can happen
|
||||
continue;
|
||||
}
|
||||
// We could get the earliest sequence number for the whole list of
|
||||
// memtables, which will include immutable memtables as well, but that
|
||||
// might be tricky to maintain in case we decide, in future, to do
|
||||
// memtable compaction.
|
||||
if (!last_try) {
|
||||
auto seq =
|
||||
mgd_iter->second.super_version->mem->GetEarliestSequenceNumber();
|
||||
if (seq > snapshot) {
|
||||
retry = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!retry) {
|
||||
if (last_try) {
|
||||
mutex_.Unlock();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (auto mgd_iter : multiget_cf_data) {
|
||||
mgd_iter.second->super_version =
|
||||
mgd_iter.second->cfd->GetSuperVersion()->Ref();
|
||||
}
|
||||
mutex_.Unlock();
|
||||
|
||||
// Contain a list of merge operations if merge occurs.
|
||||
MergeContext merge_context;
|
||||
@@ -1393,7 +1459,7 @@ std::vector<Status> DBImpl::MultiGet(
|
||||
auto mgd_iter = multiget_cf_data.find(cfh->cfd()->GetID());
|
||||
assert(mgd_iter != multiget_cf_data.end());
|
||||
auto mgd = mgd_iter->second;
|
||||
auto super_version = mgd->super_version;
|
||||
auto super_version = mgd.super_version;
|
||||
bool skip_memtable =
|
||||
(read_options.read_tier == kPersistedTier &&
|
||||
has_unpersisted_data_.load(std::memory_order_relaxed));
|
||||
@@ -1429,24 +1495,14 @@ std::vector<Status> DBImpl::MultiGet(
|
||||
PERF_TIMER_GUARD(get_post_process_time);
|
||||
autovector<SuperVersion*> superversions_to_delete;
|
||||
|
||||
// TODO(icanadi) do we need lock here or just around Cleanup()?
|
||||
mutex_.Lock();
|
||||
for (auto mgd_iter : multiget_cf_data) {
|
||||
auto mgd = mgd_iter.second;
|
||||
if (mgd->super_version->Unref()) {
|
||||
mgd->super_version->Cleanup();
|
||||
superversions_to_delete.push_back(mgd->super_version);
|
||||
if (!last_try) {
|
||||
ReturnAndCleanupSuperVersion(mgd.cfd, mgd.super_version);
|
||||
} else {
|
||||
mgd.cfd->GetSuperVersion()->Unref();
|
||||
}
|
||||
}
|
||||
mutex_.Unlock();
|
||||
|
||||
for (auto td : superversions_to_delete) {
|
||||
delete td;
|
||||
}
|
||||
for (auto mgd : multiget_cf_data) {
|
||||
delete mgd.second;
|
||||
}
|
||||
|
||||
RecordTick(stats_, NUMBER_MULTIGET_CALLS);
|
||||
RecordTick(stats_, NUMBER_MULTIGET_KEYS_READ, num_keys);
|
||||
RecordTick(stats_, NUMBER_MULTIGET_KEYS_FOUND, num_found);
|
||||
@@ -1865,7 +1921,7 @@ Status DBImpl::NewIterators(
|
||||
if (read_options.tailing) {
|
||||
#ifdef ROCKSDB_LITE
|
||||
return Status::InvalidArgument(
|
||||
"Tailing interator not supported in RocksDB lite");
|
||||
"Tailing iterator not supported in RocksDB lite");
|
||||
#else
|
||||
for (auto cfh : column_families) {
|
||||
auto cfd = reinterpret_cast<ColumnFamilyHandleImpl*>(cfh)->cfd();
|
||||
@@ -1904,21 +1960,32 @@ const Snapshot* DBImpl::GetSnapshotForWriteConflictBoundary() {
|
||||
}
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
SnapshotImpl* DBImpl::GetSnapshotImpl(bool is_write_conflict_boundary) {
|
||||
SnapshotImpl* DBImpl::GetSnapshotImpl(bool is_write_conflict_boundary,
|
||||
bool lock) {
|
||||
int64_t unix_time = 0;
|
||||
env_->GetCurrentTime(&unix_time); // Ignore error
|
||||
SnapshotImpl* s = new SnapshotImpl;
|
||||
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
if (lock) {
|
||||
mutex_.Lock();
|
||||
}
|
||||
// returns null if the underlying memtable does not support snapshot.
|
||||
if (!is_snapshot_supported_) {
|
||||
if (lock) {
|
||||
mutex_.Unlock();
|
||||
}
|
||||
delete s;
|
||||
return nullptr;
|
||||
}
|
||||
auto snapshot_seq = last_seq_same_as_publish_seq_
|
||||
? versions_->LastSequence()
|
||||
: versions_->LastPublishedSequence();
|
||||
return snapshots_.New(s, snapshot_seq, unix_time, is_write_conflict_boundary);
|
||||
SnapshotImpl* snapshot =
|
||||
snapshots_.New(s, snapshot_seq, unix_time, is_write_conflict_boundary);
|
||||
if (lock) {
|
||||
mutex_.Unlock();
|
||||
}
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
void DBImpl::ReleaseSnapshot(const Snapshot* s) {
|
||||
@@ -2386,9 +2453,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_contexts[0],
|
||||
*cfd->GetLatestMutableCFOptions(), FlushReason::kDeleteFiles);
|
||||
InstallSuperVersionAndScheduleWork(cfd,
|
||||
&job_context.superversion_contexts[0],
|
||||
*cfd->GetLatestMutableCFOptions());
|
||||
}
|
||||
FindObsoleteFiles(&job_context, false);
|
||||
} // lock released here
|
||||
@@ -2471,9 +2538,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_contexts[0],
|
||||
*cfd->GetLatestMutableCFOptions(), FlushReason::kDeleteFiles);
|
||||
InstallSuperVersionAndScheduleWork(cfd,
|
||||
&job_context.superversion_contexts[0],
|
||||
*cfd->GetLatestMutableCFOptions());
|
||||
}
|
||||
for (auto* deleted_file : deleted_files) {
|
||||
deleted_file->being_compacted = false;
|
||||
@@ -2863,11 +2930,15 @@ Status DBImpl::RenameTempFileToOptionsFile(const std::string& file_name) {
|
||||
#ifndef ROCKSDB_LITE
|
||||
Status s;
|
||||
|
||||
versions_->options_file_number_ = versions_->NewFileNumber();
|
||||
uint64_t options_file_number = versions_->NewFileNumber();
|
||||
std::string options_file_name =
|
||||
OptionsFileName(GetName(), versions_->options_file_number_);
|
||||
OptionsFileName(GetName(), options_file_number);
|
||||
// Retry if the file name happen to conflict with an existing one.
|
||||
s = GetEnv()->RenameFile(file_name, options_file_name);
|
||||
if (s.ok()) {
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
versions_->options_file_number_ = options_file_number;
|
||||
}
|
||||
|
||||
if (0 == disable_delete_obsolete_files_) {
|
||||
DeleteObsoleteOptionsFiles();
|
||||
@@ -2918,6 +2989,8 @@ void DumpRocksDBBuildVersion(Logger* log) {
|
||||
ROCKSDB_MINOR, ROCKSDB_PATCH);
|
||||
ROCKS_LOG_HEADER(log, "Git sha %s", rocksdb_build_git_sha);
|
||||
ROCKS_LOG_HEADER(log, "Compile date %s", rocksdb_build_compile_date);
|
||||
#else
|
||||
(void)log; // ignore "-Wunused-parameter"
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -3163,8 +3236,7 @@ Status DBImpl::IngestExternalFile(
|
||||
&mutex_, directories_.GetDbDir());
|
||||
}
|
||||
if (status.ok()) {
|
||||
InstallSuperVersionAndScheduleWork(cfd, &sv_context, *mutable_cf_options,
|
||||
FlushReason::kExternalFileIngestion);
|
||||
InstallSuperVersionAndScheduleWork(cfd, &sv_context, *mutable_cf_options);
|
||||
}
|
||||
|
||||
// Resume writes to the DB
|
||||
|
||||
+70
-36
@@ -31,7 +31,7 @@
|
||||
#include "db/log_writer.h"
|
||||
#include "db/logs_with_prep_tracker.h"
|
||||
#include "db/pre_release_callback.h"
|
||||
#include "db/range_del_aggregator_v2.h"
|
||||
#include "db/range_del_aggregator.h"
|
||||
#include "db/read_callback.h"
|
||||
#include "db/snapshot_checker.h"
|
||||
#include "db/snapshot_impl.h"
|
||||
@@ -65,6 +65,7 @@ class Arena;
|
||||
class ArenaWrappedDBIter;
|
||||
class MemTable;
|
||||
class TableCache;
|
||||
class TaskLimiterToken;
|
||||
class Version;
|
||||
class VersionEdit;
|
||||
class VersionSet;
|
||||
@@ -191,13 +192,13 @@ class DBImpl : public DB {
|
||||
const Slice* begin, const Slice* end) override;
|
||||
|
||||
using DB::CompactFiles;
|
||||
virtual Status CompactFiles(const CompactionOptions& compact_options,
|
||||
ColumnFamilyHandle* column_family,
|
||||
const std::vector<std::string>& input_file_names,
|
||||
const int output_level,
|
||||
const int output_path_id = -1,
|
||||
std::vector<std::string>* const output_file_names
|
||||
= nullptr) override;
|
||||
virtual Status CompactFiles(
|
||||
const CompactionOptions& compact_options,
|
||||
ColumnFamilyHandle* column_family,
|
||||
const std::vector<std::string>& input_file_names, const int output_level,
|
||||
const int output_path_id = -1,
|
||||
std::vector<std::string>* const output_file_names = nullptr,
|
||||
CompactionJobInfo* compaction_job_info = nullptr) override;
|
||||
|
||||
virtual Status PauseBackgroundWork() override;
|
||||
virtual Status ContinueBackgroundWork() override;
|
||||
@@ -237,6 +238,13 @@ class DBImpl : public DB {
|
||||
virtual Status SyncWAL() override;
|
||||
|
||||
virtual SequenceNumber GetLatestSequenceNumber() const override;
|
||||
virtual SequenceNumber GetLastPublishedSequence() const {
|
||||
if (last_seq_same_as_publish_seq_) {
|
||||
return versions_->LastSequence();
|
||||
} else {
|
||||
return versions_->LastPublishedSequence();
|
||||
}
|
||||
}
|
||||
// REQUIRES: joined the main write queue if two_write_queues is disabled, and
|
||||
// the second write queue otherwise.
|
||||
virtual void SetLastPublishedSequence(SequenceNumber seq);
|
||||
@@ -374,8 +382,8 @@ class DBImpl : public DB {
|
||||
// The keys of this iterator are internal keys (see format.h).
|
||||
// The returned iterator should be deleted when no longer needed.
|
||||
InternalIterator* NewInternalIterator(
|
||||
Arena* arena, RangeDelAggregatorV2* range_del_agg,
|
||||
SequenceNumber sequence, ColumnFamilyHandle* column_family = nullptr);
|
||||
Arena* arena, RangeDelAggregator* range_del_agg, SequenceNumber sequence,
|
||||
ColumnFamilyHandle* column_family = nullptr);
|
||||
|
||||
LogsWithPrepTracker* logs_with_prep_tracker() {
|
||||
return &logs_with_prep_tracker_;
|
||||
@@ -578,12 +586,9 @@ class DBImpl : public DB {
|
||||
|
||||
const WriteController& write_controller() { return write_controller_; }
|
||||
|
||||
InternalIterator* NewInternalIterator(const ReadOptions&,
|
||||
ColumnFamilyData* cfd,
|
||||
SuperVersion* super_version,
|
||||
Arena* arena,
|
||||
RangeDelAggregatorV2* range_del_agg,
|
||||
SequenceNumber sequence);
|
||||
InternalIterator* NewInternalIterator(
|
||||
const ReadOptions&, ColumnFamilyData* cfd, SuperVersion* super_version,
|
||||
Arena* arena, RangeDelAggregator* range_del_agg, SequenceNumber sequence);
|
||||
|
||||
// hollow transactions shell used for recovery.
|
||||
// these will then be passed to TransactionDB so that
|
||||
@@ -695,6 +700,12 @@ class DBImpl : public DB {
|
||||
|
||||
void SetSnapshotChecker(SnapshotChecker* snapshot_checker);
|
||||
|
||||
// Fill JobContext with snapshot information needed by flush and compaction.
|
||||
void GetSnapshotContext(JobContext* job_context,
|
||||
std::vector<SequenceNumber>* snapshot_seqs,
|
||||
SequenceNumber* earliest_write_conflict_snapshot,
|
||||
SnapshotChecker** snapshot_checker);
|
||||
|
||||
// Not thread-safe.
|
||||
void SetRecoverableStatePreReleaseCallback(PreReleaseCallback* callback);
|
||||
|
||||
@@ -912,11 +923,13 @@ class DBImpl : public DB {
|
||||
// Flush the in-memory write buffer to storage. Switches to a new
|
||||
// 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);
|
||||
Status FlushMemTableToOutputFile(
|
||||
ColumnFamilyData* cfd, const MutableCFOptions& mutable_cf_options,
|
||||
bool* madeProgress, JobContext* job_context,
|
||||
SuperVersionContext* superversion_context,
|
||||
std::vector<SequenceNumber>& snapshot_seqs,
|
||||
SequenceNumber earliest_write_conflict_snapshot,
|
||||
SnapshotChecker* snapshot_checker, LogBuffer* log_buffer);
|
||||
|
||||
// Argument required by background flush thread.
|
||||
struct BGFlushArg {
|
||||
@@ -1053,7 +1066,8 @@ class DBImpl : public DB {
|
||||
const std::vector<std::string>& input_file_names,
|
||||
std::vector<std::string>* const output_file_names,
|
||||
const int output_level, int output_path_id,
|
||||
JobContext* job_context, LogBuffer* log_buffer);
|
||||
JobContext* job_context, LogBuffer* log_buffer,
|
||||
CompactionJobInfo* compaction_job_info);
|
||||
|
||||
// Wait for current IngestExternalFile() calls to finish.
|
||||
// REQUIRES: mutex_ held
|
||||
@@ -1106,6 +1120,12 @@ class DBImpl : public DB {
|
||||
const std::vector<CompactionInputFiles>& inputs,
|
||||
bool* sfm_bookkeeping, LogBuffer* log_buffer);
|
||||
|
||||
// Request compaction tasks token from compaction thread limiter.
|
||||
// It always succeeds if force = true or limiter is disable.
|
||||
bool RequestCompactionToken(ColumnFamilyData* cfd, bool force,
|
||||
std::unique_ptr<TaskLimiterToken>* token,
|
||||
LogBuffer* log_buffer);
|
||||
|
||||
// Schedule background tasks
|
||||
void StartTimedTasks();
|
||||
|
||||
@@ -1129,10 +1149,15 @@ class DBImpl : public DB {
|
||||
ColumnFamilyData* PopFirstFromCompactionQueue();
|
||||
FlushRequest PopFirstFromFlushQueue();
|
||||
|
||||
// Pick the first unthrottled compaction with task token from queue.
|
||||
ColumnFamilyData* PickCompactionFromQueue(
|
||||
std::unique_ptr<TaskLimiterToken>* token, LogBuffer* log_buffer);
|
||||
|
||||
// helper function to call after some of the logs_ were synced
|
||||
void MarkLogsSynced(uint64_t up_to, bool synced_dir, const Status& status);
|
||||
|
||||
SnapshotImpl* GetSnapshotImpl(bool is_write_conflict_boundary);
|
||||
SnapshotImpl* GetSnapshotImpl(bool is_write_conflict_boundary,
|
||||
bool lock = true);
|
||||
|
||||
uint64_t GetMaxTotalWalSize() const;
|
||||
|
||||
@@ -1355,7 +1380,7 @@ class DBImpl : public DB {
|
||||
// compacted. Consumers of these queues are flush and compaction threads. When
|
||||
// column family is put on this queue, we increase unscheduled_flushes_ and
|
||||
// unscheduled_compactions_. When these variables are bigger than zero, that
|
||||
// means we need to schedule background threads for compaction and thread.
|
||||
// means we need to schedule background threads for flush and compaction.
|
||||
// Once the background threads are scheduled, we decrease unscheduled_flushes_
|
||||
// and unscheduled_compactions_. That way we keep track of number of
|
||||
// compaction and flush threads we need to schedule. This scheduling is done
|
||||
@@ -1422,6 +1447,8 @@ class DBImpl : public DB {
|
||||
// caller retains ownership of `manual_compaction_state` as it is reused
|
||||
// across background compactions.
|
||||
ManualCompactionState* manual_compaction_state; // nullptr if non-manual
|
||||
// task limiter token is requested during compaction picking.
|
||||
std::unique_ptr<TaskLimiterToken> task_token;
|
||||
};
|
||||
std::deque<ManualCompactionState*> manual_compaction_dequeue_;
|
||||
|
||||
@@ -1461,7 +1488,7 @@ class DBImpl : public DB {
|
||||
std::atomic<bool> has_unpersisted_data_;
|
||||
|
||||
// if an attempt was made to flush all column families that
|
||||
// the oldest log depends on but uncommited data in the oldest
|
||||
// the oldest log depends on but uncommitted data in the oldest
|
||||
// log prevents the log from being released.
|
||||
// We must attempt to free the dependent memtables again
|
||||
// at a later time after the transaction in the oldest
|
||||
@@ -1530,8 +1557,7 @@ class DBImpl : public DB {
|
||||
// state needs flush or compaction.
|
||||
void InstallSuperVersionAndScheduleWork(
|
||||
ColumnFamilyData* cfd, SuperVersionContext* sv_context,
|
||||
const MutableCFOptions& mutable_cf_options,
|
||||
FlushReason flush_reason = FlushReason::kOthers);
|
||||
const MutableCFOptions& mutable_cf_options);
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
using DB::GetPropertiesOfAllTables;
|
||||
@@ -1556,6 +1582,13 @@ class DBImpl : public DB {
|
||||
bool ShouldntRunManualCompaction(ManualCompactionState* m);
|
||||
bool HaveManualCompaction(ColumnFamilyData* cfd);
|
||||
bool MCOverlap(ManualCompactionState* m, ManualCompactionState* m1);
|
||||
#ifndef ROCKSDB_LITE
|
||||
void BuildCompactionJobInfo(const ColumnFamilyData* cfd, Compaction* c,
|
||||
const Status& st,
|
||||
const CompactionJobStats& compaction_job_stats,
|
||||
const int job_id, const Version* current,
|
||||
CompactionJobInfo* compaction_job_info) const;
|
||||
#endif
|
||||
|
||||
bool ShouldPurge(uint64_t file_number) const;
|
||||
void MarkAsGrabbedForPurge(uint64_t file_number);
|
||||
@@ -1613,15 +1646,16 @@ class DBImpl : public DB {
|
||||
|
||||
ErrorHandler error_handler_;
|
||||
|
||||
// True if the DB is committing atomic flush.
|
||||
// TODO (yanqin) the current impl assumes that the entire DB belongs to
|
||||
// a single atomic flush group. In the future we need to add a new class
|
||||
// (struct) similar to the following to make it more general.
|
||||
// struct AtomicFlushGroup {
|
||||
// bool commit_in_progress_;
|
||||
// std::vector<MemTableList*> imm_lists;
|
||||
// };
|
||||
bool atomic_flush_commit_in_progress_;
|
||||
// Conditional variable to coordinate installation of atomic flush results.
|
||||
// With atomic flush, each bg thread installs the result of flushing multiple
|
||||
// column families, and different threads can flush different column
|
||||
// families. It's difficult to rely on one thread to perform batch
|
||||
// installation for all threads. This is different from the non-atomic flush
|
||||
// case.
|
||||
// atomic_flush_install_cv_ makes sure that threads install atomic flush
|
||||
// results sequentially. Flush results of memtables with lower IDs get
|
||||
// installed to MANIFEST first.
|
||||
InstrumentedCondVar atomic_flush_install_cv_;
|
||||
};
|
||||
|
||||
extern Options SanitizeOptions(const std::string& db,
|
||||
|
||||
+328
-152
@@ -20,6 +20,7 @@
|
||||
#include "monitoring/perf_context_imp.h"
|
||||
#include "monitoring/thread_status_updater.h"
|
||||
#include "monitoring/thread_status_util.h"
|
||||
#include "util/concurrent_task_limiter_impl.h"
|
||||
#include "util/sst_file_manager_impl.h"
|
||||
#include "util/sync_point.h"
|
||||
|
||||
@@ -59,6 +60,27 @@ bool DBImpl::EnoughRoomForCompaction(
|
||||
return enough_room;
|
||||
}
|
||||
|
||||
bool DBImpl::RequestCompactionToken(ColumnFamilyData* cfd, bool force,
|
||||
std::unique_ptr<TaskLimiterToken>* token,
|
||||
LogBuffer* log_buffer) {
|
||||
assert(*token == nullptr);
|
||||
auto limiter = static_cast<ConcurrentTaskLimiterImpl*>(
|
||||
cfd->ioptions()->compaction_thread_limiter.get());
|
||||
if (limiter == nullptr) {
|
||||
return true;
|
||||
}
|
||||
*token = limiter->GetToken(force);
|
||||
if (*token != nullptr) {
|
||||
ROCKS_LOG_BUFFER(log_buffer,
|
||||
"Thread limiter [%s] increase [%s] compaction task, "
|
||||
"force: %s, tasks after: %d",
|
||||
limiter->GetName().c_str(), cfd->GetName().c_str(),
|
||||
force ? "true" : "false", limiter->GetOutstandingTask());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Status DBImpl::SyncClosedLogs(JobContext* job_context) {
|
||||
TEST_SYNC_POINT("DBImpl::SyncClosedLogs:Start");
|
||||
mutex_.AssertHeld();
|
||||
@@ -110,19 +132,15 @@ Status DBImpl::SyncClosedLogs(JobContext* job_context) {
|
||||
Status DBImpl::FlushMemTableToOutputFile(
|
||||
ColumnFamilyData* cfd, const MutableCFOptions& mutable_cf_options,
|
||||
bool* made_progress, JobContext* job_context,
|
||||
SuperVersionContext* superversion_context, LogBuffer* log_buffer) {
|
||||
SuperVersionContext* superversion_context,
|
||||
std::vector<SequenceNumber>& snapshot_seqs,
|
||||
SequenceNumber earliest_write_conflict_snapshot,
|
||||
SnapshotChecker* snapshot_checker, LogBuffer* log_buffer) {
|
||||
mutex_.AssertHeld();
|
||||
assert(cfd->imm()->NumNotFlushed() != 0);
|
||||
assert(cfd->imm()->IsFlushPending());
|
||||
|
||||
SequenceNumber earliest_write_conflict_snapshot;
|
||||
std::vector<SequenceNumber> snapshot_seqs =
|
||||
snapshots_.GetAll(&earliest_write_conflict_snapshot);
|
||||
|
||||
auto snapshot_checker = snapshot_checker_.get();
|
||||
if (use_custom_gc_ && snapshot_checker == nullptr) {
|
||||
snapshot_checker = DisableGCSnapshotChecker::Instance();
|
||||
}
|
||||
FlushJob flush_job(
|
||||
dbname_, cfd, immutable_db_options_, mutable_cf_options,
|
||||
nullptr /* memtable_id */, env_options_for_compaction_, versions_.get(),
|
||||
@@ -219,20 +237,30 @@ Status DBImpl::FlushMemTablesToOutputFiles(
|
||||
return AtomicFlushMemTablesToOutputFiles(bg_flush_args, made_progress,
|
||||
job_context, log_buffer);
|
||||
}
|
||||
Status s;
|
||||
std::vector<SequenceNumber> snapshot_seqs;
|
||||
SequenceNumber earliest_write_conflict_snapshot;
|
||||
SnapshotChecker* snapshot_checker;
|
||||
GetSnapshotContext(job_context, &snapshot_seqs,
|
||||
&earliest_write_conflict_snapshot, &snapshot_checker);
|
||||
Status status;
|
||||
for (auto& arg : bg_flush_args) {
|
||||
ColumnFamilyData* cfd = arg.cfd_;
|
||||
const MutableCFOptions& mutable_cf_options =
|
||||
*cfd->GetLatestMutableCFOptions();
|
||||
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);
|
||||
Status s = FlushMemTableToOutputFile(
|
||||
cfd, mutable_cf_options, made_progress, job_context,
|
||||
superversion_context, snapshot_seqs, earliest_write_conflict_snapshot,
|
||||
snapshot_checker, log_buffer);
|
||||
if (!s.ok()) {
|
||||
break;
|
||||
status = s;
|
||||
if (!s.IsShutdownInProgress()) {
|
||||
// At this point, DB is not shutting down, nor is cfd dropped.
|
||||
// Something is wrong, thus we break out of the loop.
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return s;
|
||||
return status;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -261,17 +289,17 @@ Status DBImpl::AtomicFlushMemTablesToOutputFiles(
|
||||
}
|
||||
#endif /* !NDEBUG */
|
||||
|
||||
std::vector<SequenceNumber> snapshot_seqs;
|
||||
SequenceNumber earliest_write_conflict_snapshot;
|
||||
std::vector<SequenceNumber> snapshot_seqs =
|
||||
snapshots_.GetAll(&earliest_write_conflict_snapshot);
|
||||
SnapshotChecker* snapshot_checker;
|
||||
GetSnapshotContext(job_context, &snapshot_seqs,
|
||||
&earliest_write_conflict_snapshot, &snapshot_checker);
|
||||
|
||||
auto snapshot_checker = snapshot_checker_.get();
|
||||
if (use_custom_gc_ && snapshot_checker == nullptr) {
|
||||
snapshot_checker = DisableGCSnapshotChecker::Instance();
|
||||
}
|
||||
autovector<Directory*> distinct_output_dirs;
|
||||
std::vector<FlushJob> jobs;
|
||||
std::vector<MutableCFOptions> all_mutable_cf_options;
|
||||
int num_cfs = static_cast<int>(cfds.size());
|
||||
all_mutable_cf_options.reserve(num_cfs);
|
||||
for (int i = 0; i < num_cfs; ++i) {
|
||||
auto cfd = cfds[i];
|
||||
Directory* data_dir = GetDataDir(cfd, 0U);
|
||||
@@ -290,8 +318,8 @@ Status DBImpl::AtomicFlushMemTablesToOutputFiles(
|
||||
distinct_output_dirs.emplace_back(data_dir);
|
||||
}
|
||||
|
||||
const MutableCFOptions& mutable_cf_options =
|
||||
*cfd->GetLatestMutableCFOptions();
|
||||
all_mutable_cf_options.emplace_back(*cfd->GetLatestMutableCFOptions());
|
||||
const MutableCFOptions& mutable_cf_options = all_mutable_cf_options.back();
|
||||
const uint64_t* max_memtable_id = &(bg_flush_args[i].max_memtable_id_);
|
||||
jobs.emplace_back(
|
||||
dbname_, cfds[i], immutable_db_options_, mutable_cf_options,
|
||||
@@ -331,8 +359,7 @@ Status DBImpl::AtomicFlushMemTablesToOutputFiles(
|
||||
autovector<std::pair<bool, Status>> exec_status;
|
||||
for (int i = 0; i != num_cfs; ++i) {
|
||||
// Initially all jobs are not executed, with status OK.
|
||||
std::pair<bool, Status> elem(false, Status::OK());
|
||||
exec_status.emplace_back(elem);
|
||||
exec_status.emplace_back(false, Status::OK());
|
||||
}
|
||||
|
||||
if (s.ok()) {
|
||||
@@ -341,10 +368,6 @@ Status DBImpl::AtomicFlushMemTablesToOutputFiles(
|
||||
exec_status[i].second =
|
||||
jobs[i].Run(&logs_with_prep_tracker_, &file_meta[i]);
|
||||
exec_status[i].first = true;
|
||||
if (!exec_status[i].second.ok()) {
|
||||
s = exec_status[i].second;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (num_cfs > 1) {
|
||||
TEST_SYNC_POINT(
|
||||
@@ -352,17 +375,27 @@ Status DBImpl::AtomicFlushMemTablesToOutputFiles(
|
||||
TEST_SYNC_POINT(
|
||||
"DBImpl::AtomicFlushMemTablesToOutputFiles:SomeFlushJobsComplete:2");
|
||||
}
|
||||
if (s.ok()) {
|
||||
exec_status[0].second =
|
||||
jobs[0].Run(&logs_with_prep_tracker_, &file_meta[0]);
|
||||
exec_status[0].first = true;
|
||||
if (!exec_status[0].second.ok()) {
|
||||
s = exec_status[0].second;
|
||||
exec_status[0].second =
|
||||
jobs[0].Run(&logs_with_prep_tracker_, &file_meta[0]);
|
||||
exec_status[0].first = true;
|
||||
|
||||
Status error_status;
|
||||
for (const auto& e : exec_status) {
|
||||
if (!e.second.ok()) {
|
||||
s = e.second;
|
||||
if (!e.second.IsShutdownInProgress()) {
|
||||
// If a flush job did not return OK, and the CF is not dropped, and
|
||||
// the DB is not shutting down, then we have to return this result to
|
||||
// caller later.
|
||||
error_status = e.second;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
s = error_status.ok() ? s : error_status;
|
||||
}
|
||||
|
||||
if (s.ok()) {
|
||||
if (s.ok() || s.IsShutdownInProgress()) {
|
||||
// Sync on all distinct output directories.
|
||||
for (auto dir : distinct_output_dirs) {
|
||||
if (dir != nullptr) {
|
||||
@@ -372,34 +405,73 @@ Status DBImpl::AtomicFlushMemTablesToOutputFiles(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (s.ok()) {
|
||||
autovector<const autovector<MemTable*>*> mems_list;
|
||||
for (int i = 0; i != num_cfs; ++i) {
|
||||
const auto& mems = jobs[i].GetMemTables();
|
||||
mems_list.emplace_back(&mems);
|
||||
}
|
||||
autovector<ColumnFamilyData*> all_cfds;
|
||||
autovector<MemTableList*> imm_lists;
|
||||
autovector<const MutableCFOptions*> mutable_cf_options_list;
|
||||
for (auto cfd : *versions_->GetColumnFamilySet()) {
|
||||
all_cfds.emplace_back(cfd);
|
||||
imm_lists.emplace_back(cfd->imm());
|
||||
mutable_cf_options_list.emplace_back(cfd->GetLatestMutableCFOptions());
|
||||
}
|
||||
|
||||
s = MemTableList::TryInstallMemtableFlushResults(
|
||||
imm_lists, all_cfds, mutable_cf_options_list, mems_list,
|
||||
&atomic_flush_commit_in_progress_, &logs_with_prep_tracker_,
|
||||
versions_.get(), &mutex_, file_meta, &job_context->memtables_to_free,
|
||||
directories_.GetDbDir(), log_buffer);
|
||||
}
|
||||
}
|
||||
|
||||
if (s.ok()) {
|
||||
auto wait_to_install_func = [&]() {
|
||||
bool ready = true;
|
||||
for (size_t i = 0; i != cfds.size(); ++i) {
|
||||
const auto& mems = jobs[i].GetMemTables();
|
||||
if (cfds[i]->IsDropped()) {
|
||||
// If the column family is dropped, then do not wait.
|
||||
continue;
|
||||
} else if (!mems.empty() &&
|
||||
cfds[i]->imm()->GetEarliestMemTableID() < mems[0]->GetID()) {
|
||||
// If a flush job needs to install the flush result for mems and
|
||||
// mems[0] is not the earliest memtable, it means another thread must
|
||||
// be installing flush results for the same column family, then the
|
||||
// current thread needs to wait.
|
||||
ready = false;
|
||||
break;
|
||||
} else if (mems.empty() && cfds[i]->imm()->GetEarliestMemTableID() <=
|
||||
bg_flush_args[i].max_memtable_id_) {
|
||||
// If a flush job does not need to install flush results, then it has
|
||||
// to wait until all memtables up to max_memtable_id_ (inclusive) are
|
||||
// installed.
|
||||
ready = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return ready;
|
||||
};
|
||||
|
||||
bool resuming_from_bg_err = error_handler_.IsDBStopped();
|
||||
while ((!error_handler_.IsDBStopped() ||
|
||||
error_handler_.GetRecoveryError().ok()) &&
|
||||
!wait_to_install_func()) {
|
||||
atomic_flush_install_cv_.Wait();
|
||||
}
|
||||
|
||||
s = resuming_from_bg_err ? error_handler_.GetRecoveryError()
|
||||
: error_handler_.GetBGError();
|
||||
}
|
||||
|
||||
if (s.ok()) {
|
||||
autovector<ColumnFamilyData*> tmp_cfds;
|
||||
autovector<const autovector<MemTable*>*> mems_list;
|
||||
autovector<const MutableCFOptions*> mutable_cf_options_list;
|
||||
for (int i = 0; i != num_cfs; ++i) {
|
||||
const auto& mems = jobs[i].GetMemTables();
|
||||
if (!cfds[i]->IsDropped() && !mems.empty()) {
|
||||
tmp_cfds.emplace_back(cfds[i]);
|
||||
mems_list.emplace_back(&mems);
|
||||
mutable_cf_options_list.emplace_back(&all_mutable_cf_options[i]);
|
||||
}
|
||||
}
|
||||
|
||||
s = InstallMemtableAtomicFlushResults(
|
||||
nullptr /* imm_lists */, tmp_cfds, mutable_cf_options_list, mems_list,
|
||||
versions_.get(), &mutex_, file_meta, &job_context->memtables_to_free,
|
||||
directories_.GetDbDir(), log_buffer);
|
||||
}
|
||||
|
||||
if (s.ok() || s.IsShutdownInProgress()) {
|
||||
assert(num_cfs ==
|
||||
static_cast<int>(job_context->superversion_contexts.size()));
|
||||
for (int i = 0; i != num_cfs; ++i) {
|
||||
if (cfds[i]->IsDropped()) {
|
||||
continue;
|
||||
}
|
||||
InstallSuperVersionAndScheduleWork(cfds[i],
|
||||
&job_context->superversion_contexts[i],
|
||||
*cfds[i]->GetLatestMutableCFOptions());
|
||||
@@ -415,6 +487,9 @@ Status DBImpl::AtomicFlushMemTablesToOutputFiles(
|
||||
auto sfm = static_cast<SstFileManagerImpl*>(
|
||||
immutable_db_options_.sst_file_manager.get());
|
||||
for (int i = 0; i != num_cfs; ++i) {
|
||||
if (cfds[i]->IsDropped()) {
|
||||
continue;
|
||||
}
|
||||
NotifyOnFlushCompleted(cfds[i], &file_meta[i],
|
||||
*cfds[i]->GetLatestMutableCFOptions(),
|
||||
job_context->job_id, jobs[i].GetTableProperties());
|
||||
@@ -434,7 +509,9 @@ Status DBImpl::AtomicFlushMemTablesToOutputFiles(
|
||||
#endif // ROCKSDB_LITE
|
||||
}
|
||||
|
||||
if (!s.ok()) {
|
||||
// Need to undo atomic flush if something went wrong, i.e. s is not OK and
|
||||
// it is not because of CF drop.
|
||||
if (!s.ok() && !s.IsShutdownInProgress()) {
|
||||
// Have to cancel the flush jobs that have NOT executed because we need to
|
||||
// unref the versions.
|
||||
for (int i = 0; i != num_cfs; ++i) {
|
||||
@@ -442,17 +519,15 @@ Status DBImpl::AtomicFlushMemTablesToOutputFiles(
|
||||
jobs[i].Cancel();
|
||||
}
|
||||
}
|
||||
if (!s.IsShutdownInProgress()) {
|
||||
for (int i = 0; i != num_cfs; ++i) {
|
||||
if (exec_status[i].first && exec_status[i].second.ok()) {
|
||||
auto& mems = jobs[i].GetMemTables();
|
||||
cfds[i]->imm()->RollbackMemtableFlush(mems,
|
||||
file_meta[i].fd.GetNumber());
|
||||
}
|
||||
for (int i = 0; i != num_cfs; ++i) {
|
||||
if (exec_status[i].first && exec_status[i].second.ok()) {
|
||||
auto& mems = jobs[i].GetMemTables();
|
||||
cfds[i]->imm()->RollbackMemtableFlush(mems,
|
||||
file_meta[i].fd.GetNumber());
|
||||
}
|
||||
Status new_bg_error = s;
|
||||
error_handler_.SetBGError(new_bg_error, BackgroundErrorReason::kFlush);
|
||||
}
|
||||
Status new_bg_error = s;
|
||||
error_handler_.SetBGError(new_bg_error, BackgroundErrorReason::kFlush);
|
||||
}
|
||||
|
||||
return s;
|
||||
@@ -479,6 +554,7 @@ void DBImpl::NotifyOnFlushBegin(ColumnFamilyData* cfd, FileMetaData* file_meta,
|
||||
mutex_.Unlock();
|
||||
{
|
||||
FlushJobInfo info;
|
||||
info.cf_id = cfd->GetID();
|
||||
info.cf_name = cfd->GetName();
|
||||
// TODO(yhchiang): make db_paths dynamic in case flush does not
|
||||
// go to L0 in the future.
|
||||
@@ -530,6 +606,7 @@ void DBImpl::NotifyOnFlushCompleted(ColumnFamilyData* cfd,
|
||||
mutex_.Unlock();
|
||||
{
|
||||
FlushJobInfo info;
|
||||
info.cf_id = cfd->GetID();
|
||||
info.cf_name = cfd->GetName();
|
||||
// TODO(yhchiang): make db_paths dynamic in case flush does not
|
||||
// go to L0 in the future.
|
||||
@@ -703,7 +780,8 @@ Status DBImpl::CompactFiles(const CompactionOptions& compact_options,
|
||||
ColumnFamilyHandle* column_family,
|
||||
const std::vector<std::string>& input_file_names,
|
||||
const int output_level, const int output_path_id,
|
||||
std::vector<std::string>* const output_file_names) {
|
||||
std::vector<std::string>* const output_file_names,
|
||||
CompactionJobInfo* compaction_job_info) {
|
||||
#ifdef ROCKSDB_LITE
|
||||
(void)compact_options;
|
||||
(void)column_family;
|
||||
@@ -711,6 +789,7 @@ Status DBImpl::CompactFiles(const CompactionOptions& compact_options,
|
||||
(void)output_level;
|
||||
(void)output_path_id;
|
||||
(void)output_file_names;
|
||||
(void)compaction_job_info;
|
||||
// not supported in lite version
|
||||
return Status::NotSupported("Not supported in ROCKSDB LITE");
|
||||
#else
|
||||
@@ -742,7 +821,7 @@ Status DBImpl::CompactFiles(const CompactionOptions& compact_options,
|
||||
|
||||
s = CompactFilesImpl(compact_options, cfd, current, input_file_names,
|
||||
output_file_names, output_level, output_path_id,
|
||||
&job_context, &log_buffer);
|
||||
&job_context, &log_buffer, compaction_job_info);
|
||||
|
||||
current->Unref();
|
||||
}
|
||||
@@ -782,7 +861,8 @@ Status DBImpl::CompactFilesImpl(
|
||||
const CompactionOptions& compact_options, ColumnFamilyData* cfd,
|
||||
Version* version, const std::vector<std::string>& input_file_names,
|
||||
std::vector<std::string>* const output_file_names, const int output_level,
|
||||
int output_path_id, JobContext* job_context, LogBuffer* log_buffer) {
|
||||
int output_path_id, JobContext* job_context, LogBuffer* log_buffer,
|
||||
CompactionJobInfo* compaction_job_info) {
|
||||
mutex_.AssertHeld();
|
||||
|
||||
if (shutting_down_.load(std::memory_order_acquire)) {
|
||||
@@ -856,18 +936,17 @@ Status DBImpl::CompactFilesImpl(
|
||||
// deletion compaction currently not allowed in CompactFiles.
|
||||
assert(!c->deletion_compaction());
|
||||
|
||||
std::vector<SequenceNumber> snapshot_seqs;
|
||||
SequenceNumber earliest_write_conflict_snapshot;
|
||||
std::vector<SequenceNumber> snapshot_seqs =
|
||||
snapshots_.GetAll(&earliest_write_conflict_snapshot);
|
||||
SnapshotChecker* snapshot_checker;
|
||||
GetSnapshotContext(job_context, &snapshot_seqs,
|
||||
&earliest_write_conflict_snapshot, &snapshot_checker);
|
||||
|
||||
auto pending_outputs_inserted_elem =
|
||||
CaptureCurrentFileNumberInPendingOutputs();
|
||||
|
||||
auto snapshot_checker = snapshot_checker_.get();
|
||||
if (use_custom_gc_ && snapshot_checker == nullptr) {
|
||||
snapshot_checker = DisableGCSnapshotChecker::Instance();
|
||||
}
|
||||
assert(is_snapshot_supported_ || snapshots_.empty());
|
||||
CompactionJobStats compaction_job_stats;
|
||||
CompactionJob compaction_job(
|
||||
job_context->job_id, c.get(), immutable_db_options_,
|
||||
env_options_for_compaction_, versions_.get(), &shutting_down_,
|
||||
@@ -877,19 +956,7 @@ Status DBImpl::CompactFilesImpl(
|
||||
snapshot_checker, table_cache_, &event_logger_,
|
||||
c->mutable_cf_options()->paranoid_file_checks,
|
||||
c->mutable_cf_options()->report_bg_io_stats, dbname_,
|
||||
nullptr); // Here we pass a nullptr for CompactionJobStats because
|
||||
// CompactFiles does not trigger OnCompactionCompleted(),
|
||||
// which is the only place where CompactionJobStats is
|
||||
// returned. The idea of not triggering OnCompationCompleted()
|
||||
// is that CompactFiles runs in the caller thread, so the user
|
||||
// should always know when it completes. As a result, it makes
|
||||
// less sense to notify the users something they should already
|
||||
// know.
|
||||
//
|
||||
// In the future, if we would like to add CompactionJobStats
|
||||
// support for CompactFiles, we should have CompactFiles API
|
||||
// pass a pointer of CompactionJobStats as the out-value
|
||||
// instead of using EventListener.
|
||||
&compaction_job_stats);
|
||||
|
||||
// Creating a compaction influences the compaction score because the score
|
||||
// takes running compactions into account (by skipping files that are already
|
||||
@@ -910,9 +977,9 @@ Status DBImpl::CompactFilesImpl(
|
||||
|
||||
Status status = compaction_job.Install(*c->mutable_cf_options());
|
||||
if (status.ok()) {
|
||||
InstallSuperVersionAndScheduleWork(
|
||||
c->column_family_data(), &job_context->superversion_contexts[0],
|
||||
*c->mutable_cf_options(), FlushReason::kManualCompaction);
|
||||
InstallSuperVersionAndScheduleWork(c->column_family_data(),
|
||||
&job_context->superversion_contexts[0],
|
||||
*c->mutable_cf_options());
|
||||
}
|
||||
c->ReleaseCompactionFiles(s);
|
||||
#ifndef ROCKSDB_LITE
|
||||
@@ -926,6 +993,11 @@ Status DBImpl::CompactFilesImpl(
|
||||
|
||||
ReleaseFileNumberFromPendingOutputs(pending_outputs_inserted_elem);
|
||||
|
||||
if (compaction_job_info != nullptr) {
|
||||
BuildCompactionJobInfo(cfd, c.get(), s, compaction_job_stats,
|
||||
job_context->job_id, version, compaction_job_info);
|
||||
}
|
||||
|
||||
if (status.ok()) {
|
||||
// Done
|
||||
} else if (status.IsShutdownInProgress()) {
|
||||
@@ -1068,36 +1140,8 @@ void DBImpl::NotifyOnCompactionCompleted(
|
||||
TEST_SYNC_POINT("DBImpl::NotifyOnCompactionCompleted::UnlockMutex");
|
||||
{
|
||||
CompactionJobInfo info;
|
||||
info.cf_id = cfd->GetID();
|
||||
info.cf_name = cfd->GetName();
|
||||
info.status = st;
|
||||
info.thread_id = env_->GetThreadID();
|
||||
info.job_id = job_id;
|
||||
info.base_input_level = c->start_level();
|
||||
info.output_level = c->output_level();
|
||||
info.stats = compaction_job_stats;
|
||||
info.table_properties = c->GetOutputTableProperties();
|
||||
info.compaction_reason = c->compaction_reason();
|
||||
info.compression = c->output_compression();
|
||||
for (size_t i = 0; i < c->num_input_levels(); ++i) {
|
||||
for (const auto fmd : *c->inputs(i)) {
|
||||
auto fn = TableFileName(c->immutable_cf_options()->cf_paths,
|
||||
fmd->fd.GetNumber(), fmd->fd.GetPathId());
|
||||
info.input_files.push_back(fn);
|
||||
if (info.table_properties.count(fn) == 0) {
|
||||
std::shared_ptr<const TableProperties> tp;
|
||||
auto s = current->GetTableProperties(&tp, fmd, &fn);
|
||||
if (s.ok()) {
|
||||
info.table_properties[fn] = tp;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const auto newf : c->edit()->GetNewFiles()) {
|
||||
info.output_files.push_back(TableFileName(
|
||||
c->immutable_cf_options()->cf_paths, newf.second.fd.GetNumber(),
|
||||
newf.second.fd.GetPathId()));
|
||||
}
|
||||
BuildCompactionJobInfo(cfd, c, st, compaction_job_stats, job_id, current,
|
||||
&info);
|
||||
for (auto listener : immutable_db_options_.listeners) {
|
||||
listener->OnCompactionCompleted(this, info);
|
||||
}
|
||||
@@ -1352,6 +1396,8 @@ Status DBImpl::RunManualCompaction(ColumnFamilyData* cfd, int input_level,
|
||||
ROCKS_LOG_INFO(immutable_db_options_.info_log,
|
||||
"[%s] Manual compaction starting", cfd->GetName().c_str());
|
||||
|
||||
LogBuffer log_buffer(InfoLogLevel::INFO_LEVEL,
|
||||
immutable_db_options_.info_log.get());
|
||||
// We don't check bg_error_ here, because if we get the error in compaction,
|
||||
// the compaction will set manual.status to bg_error_ and set manual.done to
|
||||
// true.
|
||||
@@ -1389,6 +1435,11 @@ Status DBImpl::RunManualCompaction(ColumnFamilyData* cfd, int input_level,
|
||||
ca->prepicked_compaction = new PrepickedCompaction;
|
||||
ca->prepicked_compaction->manual_compaction_state = &manual;
|
||||
ca->prepicked_compaction->compaction = compaction;
|
||||
if (!RequestCompactionToken(
|
||||
cfd, true, &ca->prepicked_compaction->task_token, &log_buffer)) {
|
||||
// Don't throttle manual compaction, only count outstanding tasks.
|
||||
assert(false);
|
||||
}
|
||||
manual.incomplete = false;
|
||||
bg_compaction_scheduled_++;
|
||||
env_->Schedule(&DBImpl::BGWorkCompaction, ca, Env::Priority::LOW, this,
|
||||
@@ -1397,6 +1448,7 @@ Status DBImpl::RunManualCompaction(ColumnFamilyData* cfd, int input_level,
|
||||
}
|
||||
}
|
||||
|
||||
log_buffer.FlushBufferToLog();
|
||||
assert(!manual.in_progress);
|
||||
assert(HasPendingManualCompaction());
|
||||
RemoveManualCompaction(&manual);
|
||||
@@ -1407,6 +1459,7 @@ Status DBImpl::RunManualCompaction(ColumnFamilyData* cfd, int input_level,
|
||||
void DBImpl::GenerateFlushRequest(const autovector<ColumnFamilyData*>& cfds,
|
||||
FlushRequest* req) {
|
||||
assert(req != nullptr);
|
||||
req->reserve(cfds.size());
|
||||
for (const auto cfd : cfds) {
|
||||
if (nullptr == cfd) {
|
||||
// cfd may be null, see DBImpl::ScheduleFlushes
|
||||
@@ -1440,11 +1493,16 @@ Status DBImpl::FlushMemTable(ColumnFamilyData* cfd,
|
||||
write_thread_.EnterUnbatched(&w, &mutex_);
|
||||
}
|
||||
|
||||
if (cfd->imm()->NumNotFlushed() != 0 || !cfd->mem()->IsEmpty() ||
|
||||
!cached_recoverable_state_empty_.load()) {
|
||||
if (!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()) {
|
||||
if (cfd->imm()->NumNotFlushed() != 0 || !cfd->mem()->IsEmpty() ||
|
||||
!cached_recoverable_state_empty_.load()) {
|
||||
flush_memtable_id = cfd->imm()->GetLatestMemTableID();
|
||||
flush_req.emplace_back(cfd, flush_memtable_id);
|
||||
}
|
||||
}
|
||||
|
||||
if (s.ok() && !flush_req.empty()) {
|
||||
@@ -1518,6 +1576,9 @@ Status DBImpl::AtomicFlushMemTables(
|
||||
}
|
||||
}
|
||||
for (auto cfd : cfds) {
|
||||
if (cfd->mem()->IsEmpty() && cached_recoverable_state_empty_.load()) {
|
||||
continue;
|
||||
}
|
||||
cfd->Ref();
|
||||
s = SwitchMemtable(cfd, &context);
|
||||
cfd->Unref();
|
||||
@@ -1539,6 +1600,7 @@ Status DBImpl::AtomicFlushMemTables(
|
||||
write_thread_.ExitUnbatched(&w);
|
||||
}
|
||||
}
|
||||
TEST_SYNC_POINT("DBImpl::AtomicFlushMemTables:AfterScheduleFlush");
|
||||
|
||||
if (s.ok() && flush_options.wait) {
|
||||
autovector<const uint64_t*> flush_memtable_ids;
|
||||
@@ -1822,6 +1884,32 @@ DBImpl::FlushRequest DBImpl::PopFirstFromFlushQueue() {
|
||||
return flush_req;
|
||||
}
|
||||
|
||||
ColumnFamilyData* DBImpl::PickCompactionFromQueue(
|
||||
std::unique_ptr<TaskLimiterToken>* token, LogBuffer* log_buffer) {
|
||||
assert(!compaction_queue_.empty());
|
||||
assert(*token == nullptr);
|
||||
autovector<ColumnFamilyData*> throttled_candidates;
|
||||
ColumnFamilyData* cfd = nullptr;
|
||||
while (!compaction_queue_.empty()) {
|
||||
auto first_cfd = *compaction_queue_.begin();
|
||||
compaction_queue_.pop_front();
|
||||
assert(first_cfd->queued_for_compaction());
|
||||
if (!RequestCompactionToken(first_cfd, false, token, log_buffer)) {
|
||||
throttled_candidates.push_back(first_cfd);
|
||||
continue;
|
||||
}
|
||||
cfd = first_cfd;
|
||||
cfd->set_queued_for_compaction(false);
|
||||
break;
|
||||
}
|
||||
// Add throttled compaction candidates back to queue in the original order.
|
||||
for (auto iter = throttled_candidates.rbegin();
|
||||
iter != throttled_candidates.rend(); ++iter) {
|
||||
compaction_queue_.push_front(*iter);
|
||||
}
|
||||
return cfd;
|
||||
}
|
||||
|
||||
void DBImpl::SchedulePendingFlush(const FlushRequest& flush_req,
|
||||
FlushReason flush_reason) {
|
||||
if (flush_req.empty()) {
|
||||
@@ -2046,6 +2134,7 @@ void DBImpl::BackgroundCallFlush() {
|
||||
bg_flush_scheduled_--;
|
||||
// See if there's more work to be done
|
||||
MaybeScheduleFlushOrCompaction();
|
||||
atomic_flush_install_cv_.SignalAll();
|
||||
bg_cv_.SignalAll();
|
||||
// IMPORTANT: there should be no code after calling SignalAll. This call may
|
||||
// signal the DB destructor that it's OK to proceed with destruction. In
|
||||
@@ -2079,7 +2168,12 @@ void DBImpl::BackgroundCallCompaction(PrepickedCompaction* prepicked_compaction,
|
||||
Status s = BackgroundCompaction(&made_progress, &job_context, &log_buffer,
|
||||
prepicked_compaction);
|
||||
TEST_SYNC_POINT("BackgroundCallCompaction:1");
|
||||
if (!s.ok() && !s.IsShutdownInProgress()) {
|
||||
if (s.IsBusy()) {
|
||||
bg_cv_.SignalAll(); // In case a waiter can proceed despite the error
|
||||
mutex_.Unlock();
|
||||
env_->SleepForMicroseconds(10000); // prevent hot loop
|
||||
mutex_.Lock();
|
||||
} else if (!s.ok() && !s.IsShutdownInProgress()) {
|
||||
// Wait a little bit before retrying background compaction in
|
||||
// case this is an environmental problem and we do not want to
|
||||
// chew up resources for failed compactions for the duration of
|
||||
@@ -2214,6 +2308,8 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
|
||||
manual_compaction->in_progress = true;
|
||||
}
|
||||
|
||||
std::unique_ptr<TaskLimiterToken> task_token;
|
||||
|
||||
// InternalKey manual_end_storage;
|
||||
// InternalKey* manual_end = &manual_end_storage;
|
||||
bool sfm_reserved_compact_space = false;
|
||||
@@ -2265,17 +2361,23 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
// cfd is referenced here
|
||||
auto cfd = PopFirstFromCompactionQueue();
|
||||
auto cfd = PickCompactionFromQueue(&task_token, log_buffer);
|
||||
if (cfd == nullptr) {
|
||||
// Can't find any executable task from the compaction queue.
|
||||
// All tasks have been throttled by compaction thread limiter.
|
||||
++unscheduled_compactions_;
|
||||
return Status::Busy();
|
||||
}
|
||||
|
||||
// We unreference here because the following code will take a Ref() on
|
||||
// this cfd if it is going to use it (Compaction class holds a
|
||||
// reference).
|
||||
// This will all happen under a mutex so we don't have to be afraid of
|
||||
// somebody else deleting it.
|
||||
if (cfd->Unref()) {
|
||||
delete cfd;
|
||||
// This was the last reference of the column family, so no need to
|
||||
// compact.
|
||||
delete cfd;
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
@@ -2345,6 +2447,8 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
|
||||
} else if (c->deletion_compaction()) {
|
||||
// TODO(icanadi) Do we want to honor snapshots here? i.e. not delete old
|
||||
// file if there is alive snapshot pointing to it
|
||||
TEST_SYNC_POINT_CALLBACK("DBImpl::BackgroundCompaction:BeforeCompaction",
|
||||
c->column_family_data());
|
||||
assert(c->num_input_files(1) == 0);
|
||||
assert(c->level() == 0);
|
||||
assert(c->column_family_data()->ioptions()->compaction_style ==
|
||||
@@ -2361,15 +2465,19 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
|
||||
status = versions_->LogAndApply(c->column_family_data(),
|
||||
*c->mutable_cf_options(), c->edit(),
|
||||
&mutex_, directories_.GetDbDir());
|
||||
InstallSuperVersionAndScheduleWork(
|
||||
c->column_family_data(), &job_context->superversion_contexts[0],
|
||||
*c->mutable_cf_options(), FlushReason::kAutoCompaction);
|
||||
InstallSuperVersionAndScheduleWork(c->column_family_data(),
|
||||
&job_context->superversion_contexts[0],
|
||||
*c->mutable_cf_options());
|
||||
ROCKS_LOG_BUFFER(log_buffer, "[%s] Deleted %d files\n",
|
||||
c->column_family_data()->GetName().c_str(),
|
||||
c->num_input_files(0));
|
||||
*made_progress = true;
|
||||
TEST_SYNC_POINT_CALLBACK("DBImpl::BackgroundCompaction:AfterCompaction",
|
||||
c->column_family_data());
|
||||
} else if (!trivial_move_disallowed && c->IsTrivialMove()) {
|
||||
TEST_SYNC_POINT("DBImpl::BackgroundCompaction:TrivialMove");
|
||||
TEST_SYNC_POINT_CALLBACK("DBImpl::BackgroundCompaction:BeforeCompaction",
|
||||
c->column_family_data());
|
||||
// Instrument for event update
|
||||
// TODO(yhchiang): add op details for showing trivial-move.
|
||||
ThreadStatusUtil::SetColumnFamily(
|
||||
@@ -2411,9 +2519,9 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
|
||||
*c->mutable_cf_options(), c->edit(),
|
||||
&mutex_, directories_.GetDbDir());
|
||||
// Use latest MutableCFOptions
|
||||
InstallSuperVersionAndScheduleWork(
|
||||
c->column_family_data(), &job_context->superversion_contexts[0],
|
||||
*c->mutable_cf_options(), FlushReason::kAutoCompaction);
|
||||
InstallSuperVersionAndScheduleWork(c->column_family_data(),
|
||||
&job_context->superversion_contexts[0],
|
||||
*c->mutable_cf_options());
|
||||
|
||||
VersionStorageInfo::LevelSummaryStorage tmp;
|
||||
c->column_family_data()->internal_stats()->IncBytesMoved(c->output_level(),
|
||||
@@ -2435,6 +2543,8 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
|
||||
|
||||
// Clear Instrument
|
||||
ThreadStatusUtil::ResetThreadStatus();
|
||||
TEST_SYNC_POINT_CALLBACK("DBImpl::BackgroundCompaction:AfterCompaction",
|
||||
c->column_family_data());
|
||||
} else if (!is_prepicked && c->output_level() > 0 &&
|
||||
c->output_level() ==
|
||||
c->column_family_data()
|
||||
@@ -2452,22 +2562,23 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
|
||||
ca->prepicked_compaction = new PrepickedCompaction;
|
||||
ca->prepicked_compaction->compaction = c.release();
|
||||
ca->prepicked_compaction->manual_compaction_state = nullptr;
|
||||
// Transfer requested token, so it doesn't need to do it again.
|
||||
ca->prepicked_compaction->task_token = std::move(task_token);
|
||||
++bg_bottom_compaction_scheduled_;
|
||||
env_->Schedule(&DBImpl::BGWorkBottomCompaction, ca, Env::Priority::BOTTOM,
|
||||
this, &DBImpl::UnscheduleCallback);
|
||||
} else {
|
||||
TEST_SYNC_POINT_CALLBACK("DBImpl::BackgroundCompaction:BeforeCompaction",
|
||||
c->column_family_data());
|
||||
int output_level __attribute__((__unused__));
|
||||
output_level = c->output_level();
|
||||
TEST_SYNC_POINT_CALLBACK("DBImpl::BackgroundCompaction:NonTrivial",
|
||||
&output_level);
|
||||
std::vector<SequenceNumber> snapshot_seqs;
|
||||
SequenceNumber earliest_write_conflict_snapshot;
|
||||
std::vector<SequenceNumber> snapshot_seqs =
|
||||
snapshots_.GetAll(&earliest_write_conflict_snapshot);
|
||||
|
||||
auto snapshot_checker = snapshot_checker_.get();
|
||||
if (use_custom_gc_ && snapshot_checker == nullptr) {
|
||||
snapshot_checker = DisableGCSnapshotChecker::Instance();
|
||||
}
|
||||
SnapshotChecker* snapshot_checker;
|
||||
GetSnapshotContext(job_context, &snapshot_seqs,
|
||||
&earliest_write_conflict_snapshot, &snapshot_checker);
|
||||
assert(is_snapshot_supported_ || snapshots_.empty());
|
||||
CompactionJob compaction_job(
|
||||
job_context->job_id, c.get(), immutable_db_options_,
|
||||
@@ -2491,11 +2602,13 @@ 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_contexts[0],
|
||||
*c->mutable_cf_options(), FlushReason::kAutoCompaction);
|
||||
InstallSuperVersionAndScheduleWork(c->column_family_data(),
|
||||
&job_context->superversion_contexts[0],
|
||||
*c->mutable_cf_options());
|
||||
}
|
||||
*made_progress = true;
|
||||
TEST_SYNC_POINT_CALLBACK("DBImpl::BackgroundCompaction:AfterCompaction",
|
||||
c->column_family_data());
|
||||
}
|
||||
if (c != nullptr) {
|
||||
c->ReleaseCompactionFiles(status);
|
||||
@@ -2676,6 +2789,45 @@ bool DBImpl::MCOverlap(ManualCompactionState* m, ManualCompactionState* m1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
void DBImpl::BuildCompactionJobInfo(
|
||||
const ColumnFamilyData* cfd, Compaction* c, const Status& st,
|
||||
const CompactionJobStats& compaction_job_stats, const int job_id,
|
||||
const Version* current, CompactionJobInfo* compaction_job_info) const {
|
||||
assert(compaction_job_info != nullptr);
|
||||
compaction_job_info->cf_id = cfd->GetID();
|
||||
compaction_job_info->cf_name = cfd->GetName();
|
||||
compaction_job_info->status = st;
|
||||
compaction_job_info->thread_id = env_->GetThreadID();
|
||||
compaction_job_info->job_id = job_id;
|
||||
compaction_job_info->base_input_level = c->start_level();
|
||||
compaction_job_info->output_level = c->output_level();
|
||||
compaction_job_info->stats = compaction_job_stats;
|
||||
compaction_job_info->table_properties = c->GetOutputTableProperties();
|
||||
compaction_job_info->compaction_reason = c->compaction_reason();
|
||||
compaction_job_info->compression = c->output_compression();
|
||||
for (size_t i = 0; i < c->num_input_levels(); ++i) {
|
||||
for (const auto fmd : *c->inputs(i)) {
|
||||
auto fn = TableFileName(c->immutable_cf_options()->cf_paths,
|
||||
fmd->fd.GetNumber(), fmd->fd.GetPathId());
|
||||
compaction_job_info->input_files.push_back(fn);
|
||||
if (compaction_job_info->table_properties.count(fn) == 0) {
|
||||
shared_ptr<const TableProperties> tp;
|
||||
auto s = current->GetTableProperties(&tp, fmd, &fn);
|
||||
if (s.ok()) {
|
||||
compaction_job_info->table_properties[fn] = tp;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const auto& newf : c->edit()->GetNewFiles()) {
|
||||
compaction_job_info->output_files.push_back(
|
||||
TableFileName(c->immutable_cf_options()->cf_paths,
|
||||
newf.second.fd.GetNumber(), newf.second.fd.GetPathId()));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// SuperVersionContext gets created and destructed outside of the lock --
|
||||
// we use this conveniently to:
|
||||
// * malloc one SuperVersion() outside of the lock -- new_superversion
|
||||
@@ -2690,10 +2842,7 @@ bool DBImpl::MCOverlap(ManualCompactionState* m, ManualCompactionState* m1) {
|
||||
|
||||
void DBImpl::InstallSuperVersionAndScheduleWork(
|
||||
ColumnFamilyData* cfd, SuperVersionContext* sv_context,
|
||||
const MutableCFOptions& mutable_cf_options,
|
||||
FlushReason /* flush_reason */) {
|
||||
// TODO(yanqin) investigate if 'flush_reason' can be removed since it's not
|
||||
// used.
|
||||
const MutableCFOptions& mutable_cf_options) {
|
||||
mutex_.AssertHeld();
|
||||
|
||||
// Update max_total_in_memory_state_
|
||||
@@ -2760,4 +2909,31 @@ void DBImpl::SetSnapshotChecker(SnapshotChecker* snapshot_checker) {
|
||||
assert(!snapshot_checker_);
|
||||
snapshot_checker_.reset(snapshot_checker);
|
||||
}
|
||||
|
||||
void DBImpl::GetSnapshotContext(
|
||||
JobContext* job_context, std::vector<SequenceNumber>* snapshot_seqs,
|
||||
SequenceNumber* earliest_write_conflict_snapshot,
|
||||
SnapshotChecker** snapshot_checker_ptr) {
|
||||
mutex_.AssertHeld();
|
||||
assert(job_context != nullptr);
|
||||
assert(snapshot_seqs != nullptr);
|
||||
assert(earliest_write_conflict_snapshot != nullptr);
|
||||
assert(snapshot_checker_ptr != nullptr);
|
||||
|
||||
*snapshot_checker_ptr = snapshot_checker_.get();
|
||||
if (use_custom_gc_ && *snapshot_checker_ptr == nullptr) {
|
||||
*snapshot_checker_ptr = DisableGCSnapshotChecker::Instance();
|
||||
}
|
||||
if (*snapshot_checker_ptr != nullptr) {
|
||||
// If snapshot_checker is used, that means the flush/compaction may
|
||||
// contain values not visible to snapshot taken after
|
||||
// flush/compaction job starts. Take a snapshot and it will appear
|
||||
// in snapshot_seqs and force compaction iterator to consider such
|
||||
// snapshots.
|
||||
const Snapshot* job_snapshot =
|
||||
GetSnapshotImpl(false /*write_conflict_boundary*/, false /*lock*/);
|
||||
job_context->job_snapshot.reset(new ManagedSnapshot(this, job_snapshot));
|
||||
}
|
||||
*snapshot_seqs = snapshots_.GetAll(earliest_write_conflict_snapshot);
|
||||
}
|
||||
} // namespace rocksdb
|
||||
|
||||
+57
-30
@@ -23,8 +23,7 @@
|
||||
#include "util/sync_point.h"
|
||||
|
||||
namespace rocksdb {
|
||||
Options SanitizeOptions(const std::string& dbname,
|
||||
const Options& src) {
|
||||
Options SanitizeOptions(const std::string& dbname, const Options& src) {
|
||||
auto db_options = SanitizeOptions(dbname, DBOptions(src));
|
||||
ImmutableDBOptions immutable_db_options(db_options);
|
||||
auto cf_options =
|
||||
@@ -42,6 +41,8 @@ DBOptions SanitizeOptions(const std::string& dbname, const DBOptions& src) {
|
||||
max_max_open_files = 0x400000;
|
||||
}
|
||||
ClipToRange(&result.max_open_files, 20, max_max_open_files);
|
||||
TEST_SYNC_POINT_CALLBACK("SanitizeOptions::AfterChangeMaxOpenFiles",
|
||||
&result.max_open_files);
|
||||
}
|
||||
|
||||
if (result.info_log == nullptr) {
|
||||
@@ -56,10 +57,9 @@ DBOptions SanitizeOptions(const std::string& dbname, const DBOptions& src) {
|
||||
result.write_buffer_manager.reset(
|
||||
new WriteBufferManager(result.db_write_buffer_size));
|
||||
}
|
||||
auto bg_job_limits = DBImpl::GetBGJobLimits(result.max_background_flushes,
|
||||
result.max_background_compactions,
|
||||
result.max_background_jobs,
|
||||
true /* parallelize_compactions */);
|
||||
auto bg_job_limits = DBImpl::GetBGJobLimits(
|
||||
result.max_background_flushes, result.max_background_compactions,
|
||||
result.max_background_jobs, true /* parallelize_compactions */);
|
||||
result.env->IncBackgroundThreadsIfNeeded(bg_job_limits.max_compactions,
|
||||
Env::Priority::LOW);
|
||||
result.env->IncBackgroundThreadsIfNeeded(bg_job_limits.max_flushes,
|
||||
@@ -107,14 +107,12 @@ DBOptions SanitizeOptions(const std::string& dbname, const DBOptions& src) {
|
||||
result.db_paths.emplace_back(dbname, std::numeric_limits<uint64_t>::max());
|
||||
}
|
||||
|
||||
if (result.use_direct_reads &&
|
||||
result.compaction_readahead_size == 0) {
|
||||
if (result.use_direct_reads && result.compaction_readahead_size == 0) {
|
||||
TEST_SYNC_POINT_CALLBACK("SanitizeOptions:direct_io", nullptr);
|
||||
result.compaction_readahead_size = 1024 * 1024 * 2;
|
||||
}
|
||||
|
||||
if (result.compaction_readahead_size > 0 ||
|
||||
result.use_direct_reads) {
|
||||
if (result.compaction_readahead_size > 0 || result.use_direct_reads) {
|
||||
result.new_table_reader_for_compaction_inputs = true;
|
||||
}
|
||||
|
||||
@@ -218,7 +216,7 @@ static Status ValidateOptions(
|
||||
|
||||
return Status::OK();
|
||||
}
|
||||
} // namespace
|
||||
} // namespace
|
||||
Status DBImpl::NewDB() {
|
||||
VersionEdit new_db;
|
||||
new_db.SetLogNumber(0);
|
||||
@@ -239,7 +237,7 @@ Status DBImpl::NewDB() {
|
||||
file->SetPreallocationBlockSize(
|
||||
immutable_db_options_.manifest_preallocation_size);
|
||||
std::unique_ptr<WritableFileWriter> file_writer(new WritableFileWriter(
|
||||
std::move(file), manifest, env_options, nullptr /* stats */,
|
||||
std::move(file), manifest, env_options, env_, nullptr /* stats */,
|
||||
immutable_db_options_.listeners));
|
||||
log::Writer log(std::move(file_writer), 0, false);
|
||||
std::string record;
|
||||
@@ -258,9 +256,8 @@ Status DBImpl::NewDB() {
|
||||
return s;
|
||||
}
|
||||
|
||||
Status DBImpl::CreateAndNewDirectory(
|
||||
Env* env, const std::string& dirname,
|
||||
std::unique_ptr<Directory>* directory) {
|
||||
Status DBImpl::CreateAndNewDirectory(Env* env, const std::string& dirname,
|
||||
std::unique_ptr<Directory>* directory) {
|
||||
// We call CreateDirIfMissing() as the directory may already exist (if we
|
||||
// are reopening a DB), when this happens we don't want creating the
|
||||
// directory to cause an error. However, we need to check if creating the
|
||||
@@ -341,8 +338,8 @@ Status DBImpl::Recover(
|
||||
}
|
||||
} else if (s.ok()) {
|
||||
if (immutable_db_options_.error_if_exists) {
|
||||
return Status::InvalidArgument(
|
||||
dbname_, "exists (error_if_exists is true)");
|
||||
return Status::InvalidArgument(dbname_,
|
||||
"exists (error_if_exists is true)");
|
||||
}
|
||||
} else {
|
||||
// Unexpected error reading file
|
||||
@@ -424,7 +421,10 @@ Status DBImpl::Recover(
|
||||
// produced by an older version of rocksdb.
|
||||
std::vector<std::string> filenames;
|
||||
s = env_->GetChildren(immutable_db_options_.wal_dir, &filenames);
|
||||
if (!s.ok()) {
|
||||
if (s.IsNotFound()) {
|
||||
return Status::InvalidArgument("wal_dir not found",
|
||||
immutable_db_options_.wal_dir);
|
||||
} else if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
|
||||
@@ -479,6 +479,28 @@ Status DBImpl::Recover(
|
||||
}
|
||||
}
|
||||
|
||||
if (read_only) {
|
||||
// If we are opening as read-only, we need to update options_file_number_
|
||||
// to reflect the most recent OPTIONS file. It does not matter for regular
|
||||
// read-write db instance because options_file_number_ will later be
|
||||
// updated to versions_->NewFileNumber() in RenameTempFileToOptionsFile.
|
||||
std::vector<std::string> file_names;
|
||||
if (s.ok()) {
|
||||
s = env_->GetChildren(GetName(), &file_names);
|
||||
}
|
||||
if (s.ok()) {
|
||||
uint64_t number = 0;
|
||||
uint64_t options_file_number = 0;
|
||||
FileType type;
|
||||
for (const auto& fname : file_names) {
|
||||
if (ParseFileName(fname, &number, &type) && type == kOptionsFile) {
|
||||
options_file_number = std::max(number, options_file_number);
|
||||
}
|
||||
}
|
||||
versions_->options_file_number_ = options_file_number;
|
||||
}
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
@@ -527,10 +549,9 @@ Status DBImpl::RecoverLogFiles(const std::vector<uint64_t>& log_numbers,
|
||||
std::map<std::string, uint32_t> cf_name_id_map;
|
||||
std::map<uint32_t, uint64_t> cf_lognumber_map;
|
||||
for (auto cfd : *versions_->GetColumnFamilySet()) {
|
||||
cf_name_id_map.insert(
|
||||
std::make_pair(cfd->GetName(), cfd->GetID()));
|
||||
cf_name_id_map.insert(std::make_pair(cfd->GetName(), cfd->GetID()));
|
||||
cf_lognumber_map.insert(
|
||||
std::make_pair(cfd->GetID(), cfd->GetLogNumber()));
|
||||
std::make_pair(cfd->GetID(), cfd->GetLogNumber()));
|
||||
}
|
||||
|
||||
immutable_db_options_.wal_filter->ColumnFamilyLogNumberMap(cf_lognumber_map,
|
||||
@@ -880,8 +901,8 @@ Status DBImpl::RecoverLogFiles(const std::vector<uint64_t>& log_numbers,
|
||||
// VersionSet::next_file_number_ always to be strictly greater than any
|
||||
// log number
|
||||
versions_->MarkFileNumberUsed(max_log_number + 1);
|
||||
status = versions_->LogAndApply(
|
||||
cfd, *cfd->GetLatestMutableCFOptions(), edit, &mutex_);
|
||||
status = versions_->LogAndApply(cfd, *cfd->GetLatestMutableCFOptions(),
|
||||
edit, &mutex_);
|
||||
if (!status.ok()) {
|
||||
// Recovery failed
|
||||
break;
|
||||
@@ -994,12 +1015,17 @@ Status DBImpl::WriteLevel0TableForRecovery(int job_id, ColumnFamilyData* cfd,
|
||||
if (use_custom_gc_ && snapshot_checker == nullptr) {
|
||||
snapshot_checker = DisableGCSnapshotChecker::Instance();
|
||||
}
|
||||
std::vector<std::unique_ptr<FragmentedRangeTombstoneIterator>>
|
||||
range_del_iters;
|
||||
auto range_del_iter =
|
||||
mem->NewRangeTombstoneIterator(ro, kMaxSequenceNumber);
|
||||
if (range_del_iter != nullptr) {
|
||||
range_del_iters.emplace_back(range_del_iter);
|
||||
}
|
||||
s = BuildTable(
|
||||
dbname_, env_, *cfd->ioptions(), mutable_cf_options,
|
||||
env_options_for_compaction_, cfd->table_cache(), iter.get(),
|
||||
std::unique_ptr<InternalIterator>(
|
||||
mem->NewRangeTombstoneIterator(ro, versions_->LastSequence())),
|
||||
&meta, cfd->internal_comparator(),
|
||||
std::move(range_del_iters), &meta, cfd->internal_comparator(),
|
||||
cfd->int_tbl_prop_collector_factories(), cfd->GetID(), cfd->GetName(),
|
||||
snapshot_seqs, earliest_write_conflict_snapshot, snapshot_checker,
|
||||
GetCompressionFlush(*cfd->ioptions(), mutable_cf_options),
|
||||
@@ -1033,8 +1059,8 @@ Status DBImpl::WriteLevel0TableForRecovery(int job_id, ColumnFamilyData* cfd,
|
||||
stats.bytes_written = meta.fd.GetFileSize();
|
||||
stats.num_output_files = 1;
|
||||
cfd->internal_stats()->AddCompactionStats(level, stats);
|
||||
cfd->internal_stats()->AddCFStats(
|
||||
InternalStats::BYTES_FLUSHED, meta.fd.GetFileSize());
|
||||
cfd->internal_stats()->AddCFStats(InternalStats::BYTES_FLUSHED,
|
||||
meta.fd.GetFileSize());
|
||||
RecordTick(stats_, COMPACT_WRITE_BYTES, meta.fd.GetFileSize());
|
||||
return s;
|
||||
}
|
||||
@@ -1150,7 +1176,7 @@ Status DBImpl::Open(const DBOptions& db_options, const std::string& dbname,
|
||||
const auto& listeners = impl->immutable_db_options_.listeners;
|
||||
std::unique_ptr<WritableFileWriter> file_writer(
|
||||
new WritableFileWriter(std::move(lfile), log_fname, opt_env_options,
|
||||
nullptr /* stats */, listeners));
|
||||
impl->env_, nullptr /* stats */, listeners));
|
||||
impl->logs_.emplace_back(
|
||||
new_log_number,
|
||||
new log::Writer(
|
||||
@@ -1227,7 +1253,8 @@ Status DBImpl::Open(const DBOptions& db_options, const std::string& dbname,
|
||||
!cfd->mem()->IsMergeOperatorSupported()) {
|
||||
s = Status::InvalidArgument(
|
||||
"The memtable of column family %s does not support merge operator "
|
||||
"its options.merge_operator is non-null", cfd->GetName().c_str());
|
||||
"its options.merge_operator is non-null",
|
||||
cfd->GetName().c_str());
|
||||
}
|
||||
if (!s.ok()) {
|
||||
break;
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
#include "db/db_impl.h"
|
||||
#include "db/db_iter.h"
|
||||
#include "db/merge_context.h"
|
||||
#include "db/range_del_aggregator.h"
|
||||
#include "monitoring/perf_context_imp.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
@@ -77,8 +77,8 @@ class DBImplReadOnly : public DBImpl {
|
||||
ColumnFamilyHandle* /*column_family*/,
|
||||
const std::vector<std::string>& /*input_file_names*/,
|
||||
const int /*output_level*/, const int /*output_path_id*/ = -1,
|
||||
std::vector<std::string>* const /*output_file_names*/ = nullptr
|
||||
) override {
|
||||
std::vector<std::string>* const /*output_file_names*/ = nullptr,
|
||||
CompactionJobInfo* /*compaction_job_info*/ = nullptr) override {
|
||||
return Status::NotSupported("Not supported operation in read only mode.");
|
||||
}
|
||||
|
||||
@@ -89,10 +89,11 @@ class DBImplReadOnly : public DBImpl {
|
||||
virtual Status EnableFileDeletions(bool /*force*/) override {
|
||||
return Status::NotSupported("Not supported operation in read only mode.");
|
||||
}
|
||||
virtual Status GetLiveFiles(std::vector<std::string>&,
|
||||
uint64_t* /*manifest_file_size*/,
|
||||
bool /*flush_memtable*/ = true) override {
|
||||
return Status::NotSupported("Not supported operation in read only mode.");
|
||||
virtual Status GetLiveFiles(std::vector<std::string>& ret,
|
||||
uint64_t* manifest_file_size,
|
||||
bool /*flush_memtable*/) override {
|
||||
return DBImpl::GetLiveFiles(ret, manifest_file_size,
|
||||
false /* flush_memtable */);
|
||||
}
|
||||
|
||||
using DBImpl::Flush;
|
||||
|
||||
+10
-10
@@ -475,7 +475,7 @@ Status DBImpl::PipelinedWriteImpl(const WriteOptions& write_options,
|
||||
|
||||
PERF_TIMER_STOP(write_pre_and_post_process_time);
|
||||
|
||||
if (w.ShouldWriteToWAL()) {
|
||||
if (w.status.ok() && !write_options.disableWAL) {
|
||||
PERF_TIMER_GUARD(write_wal_time);
|
||||
stats->AddDBStats(InternalStats::WRITE_DONE_BY_SELF, 1);
|
||||
RecordTick(stats_, WRITE_DONE_BY_SELF, 1);
|
||||
@@ -504,7 +504,7 @@ Status DBImpl::PipelinedWriteImpl(const WriteOptions& write_options,
|
||||
WriteThread::WriteGroup memtable_write_group;
|
||||
if (w.state == WriteThread::STATE_MEMTABLE_WRITER_LEADER) {
|
||||
PERF_TIMER_GUARD(write_memtable_time);
|
||||
assert(w.status.ok());
|
||||
assert(w.ShouldWriteToMemtable());
|
||||
write_thread_.EnterAsMemTableWriter(&w, &memtable_write_group);
|
||||
if (memtable_write_group.size > 1 &&
|
||||
immutable_db_options_.allow_concurrent_memtable_write) {
|
||||
@@ -1048,22 +1048,22 @@ Status DBImpl::SwitchWAL(WriteContext* write_context) {
|
||||
auto oldest_alive_log = alive_log_files_.begin()->number;
|
||||
bool flush_wont_release_oldest_log = false;
|
||||
if (allow_2pc()) {
|
||||
auto oldest_log_with_uncommited_prep =
|
||||
auto oldest_log_with_uncommitted_prep =
|
||||
logs_with_prep_tracker_.FindMinLogContainingOutstandingPrep();
|
||||
|
||||
assert(oldest_log_with_uncommited_prep == 0 ||
|
||||
oldest_log_with_uncommited_prep >= oldest_alive_log);
|
||||
if (oldest_log_with_uncommited_prep > 0 &&
|
||||
oldest_log_with_uncommited_prep == oldest_alive_log) {
|
||||
assert(oldest_log_with_uncommitted_prep == 0 ||
|
||||
oldest_log_with_uncommitted_prep >= oldest_alive_log);
|
||||
if (oldest_log_with_uncommitted_prep > 0 &&
|
||||
oldest_log_with_uncommitted_prep == oldest_alive_log) {
|
||||
if (unable_to_release_oldest_log_) {
|
||||
// we already attempted to flush all column families dependent on
|
||||
// the oldest alive log but the log still contained uncommited
|
||||
// the oldest alive log but the log still contained uncommitted
|
||||
// transactions so there is still nothing that we can do.
|
||||
return status;
|
||||
} else {
|
||||
ROCKS_LOG_WARN(
|
||||
immutable_db_options_.info_log,
|
||||
"Unable to release oldest log due to uncommited transaction");
|
||||
"Unable to release oldest log due to uncommitted transaction");
|
||||
unable_to_release_oldest_log_ = true;
|
||||
flush_wont_release_oldest_log = true;
|
||||
}
|
||||
@@ -1456,7 +1456,7 @@ Status DBImpl::SwitchMemtable(ColumnFamilyData* cfd, WriteContext* context) {
|
||||
lfile->SetPreallocationBlockSize(preallocate_block_size);
|
||||
lfile->SetWriteLifeTimeHint(write_hint);
|
||||
std::unique_ptr<WritableFileWriter> file_writer(new WritableFileWriter(
|
||||
std::move(lfile), log_fname, opt_env_opt, nullptr /* stats */,
|
||||
std::move(lfile), log_fname, opt_env_opt, env_, nullptr /* stats */,
|
||||
immutable_db_options_.listeners));
|
||||
new_log = new log::Writer(
|
||||
std::move(file_writer), new_log_number,
|
||||
|
||||
+3
-3
@@ -171,7 +171,7 @@ class DBIter final: public Iterator {
|
||||
iter_ = iter;
|
||||
iter_->SetPinnedItersMgr(&pinned_iters_mgr_);
|
||||
}
|
||||
virtual RangeDelAggregatorV2* GetRangeDelAggregator() {
|
||||
virtual ReadRangeDelAggregator* GetRangeDelAggregator() {
|
||||
return &range_del_agg_;
|
||||
}
|
||||
|
||||
@@ -341,7 +341,7 @@ class DBIter final: public Iterator {
|
||||
const bool total_order_seek_;
|
||||
// List of operands for merge operator.
|
||||
MergeContext merge_context_;
|
||||
RangeDelAggregatorV2 range_del_agg_;
|
||||
ReadRangeDelAggregator range_del_agg_;
|
||||
LocalStatistics local_stats_;
|
||||
PinnedIteratorsManager pinned_iters_mgr_;
|
||||
ReadCallback* read_callback_;
|
||||
@@ -1479,7 +1479,7 @@ Iterator* NewDBIterator(Env* env, const ReadOptions& read_options,
|
||||
|
||||
ArenaWrappedDBIter::~ArenaWrappedDBIter() { db_iter_->~DBIter(); }
|
||||
|
||||
RangeDelAggregatorV2* ArenaWrappedDBIter::GetRangeDelAggregator() {
|
||||
ReadRangeDelAggregator* ArenaWrappedDBIter::GetRangeDelAggregator() {
|
||||
return db_iter_->GetRangeDelAggregator();
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -12,7 +12,7 @@
|
||||
#include <string>
|
||||
#include "db/db_impl.h"
|
||||
#include "db/dbformat.h"
|
||||
#include "db/range_del_aggregator_v2.h"
|
||||
#include "db/range_del_aggregator.h"
|
||||
#include "options/cf_options.h"
|
||||
#include "rocksdb/db.h"
|
||||
#include "rocksdb/iterator.h"
|
||||
@@ -48,7 +48,7 @@ class ArenaWrappedDBIter : public Iterator {
|
||||
// Get the arena to be used to allocate memory for DBIter to be wrapped,
|
||||
// as well as child iterators in it.
|
||||
virtual Arena* GetArena() { return &arena_; }
|
||||
virtual RangeDelAggregatorV2* GetRangeDelAggregator();
|
||||
virtual ReadRangeDelAggregator* GetRangeDelAggregator();
|
||||
|
||||
// Set the internal iterator wrapped inside the DB Iterator. Usually it is
|
||||
// a merging iterator.
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
#include "db/db_test_util.h"
|
||||
#include "db/memtable.h"
|
||||
#include "db/range_del_aggregator.h"
|
||||
#include "port/stack_trace.h"
|
||||
#include "rocksdb/memtablerep.h"
|
||||
#include "rocksdb/slice_transform.h"
|
||||
@@ -135,7 +136,8 @@ TEST_F(DBMemTableTest, DuplicateSeq) {
|
||||
MergeContext merge_context;
|
||||
Options options;
|
||||
InternalKeyComparator ikey_cmp(options.comparator);
|
||||
RangeDelAggregator range_del_agg(ikey_cmp, {} /* snapshots */);
|
||||
ReadRangeDelAggregator range_del_agg(&ikey_cmp,
|
||||
kMaxSequenceNumber /* upper_bound */);
|
||||
|
||||
// Create a MemTable
|
||||
InternalKeyComparator cmp(BytewiseComparator());
|
||||
|
||||
@@ -21,7 +21,8 @@ class TestReadCallback : public ReadCallback {
|
||||
: snapshot_checker_(snapshot_checker), snapshot_seq_(snapshot_seq) {}
|
||||
|
||||
bool IsVisible(SequenceNumber seq) override {
|
||||
return snapshot_checker_->IsInSnapshot(seq, snapshot_seq_);
|
||||
return snapshot_checker_->CheckInSnapshot(seq, snapshot_seq_) ==
|
||||
SnapshotCheckerResult::kInSnapshot;
|
||||
}
|
||||
|
||||
private:
|
||||
@@ -547,8 +548,15 @@ TEST_F(DBMergeOperatorTest, SnapshotCheckerAndReadCallback) {
|
||||
DestroyAndReopen(options);
|
||||
|
||||
class TestSnapshotChecker : public SnapshotChecker {
|
||||
bool IsInSnapshot(SequenceNumber seq,
|
||||
SequenceNumber snapshot_seq) const override {
|
||||
public:
|
||||
SnapshotCheckerResult CheckInSnapshot(
|
||||
SequenceNumber seq, SequenceNumber snapshot_seq) const override {
|
||||
return IsInSnapshot(seq, snapshot_seq)
|
||||
? SnapshotCheckerResult::kInSnapshot
|
||||
: SnapshotCheckerResult::kNotInSnapshot;
|
||||
}
|
||||
|
||||
bool IsInSnapshot(SequenceNumber seq, SequenceNumber snapshot_seq) const {
|
||||
switch (snapshot_seq) {
|
||||
case 0:
|
||||
return seq == 0;
|
||||
|
||||
@@ -245,15 +245,14 @@ void GetExpectedTableProperties(
|
||||
const int kDeletionCount = kTableCount * kDeletionsPerTable;
|
||||
const int kMergeCount = kTableCount * kMergeOperandsPerTable;
|
||||
const int kRangeDeletionCount = kTableCount * kRangeDeletionsPerTable;
|
||||
const int kKeyCount = kPutCount + kDeletionCount + kMergeCount;
|
||||
const int kKeyCount = kPutCount + kDeletionCount + kMergeCount + kRangeDeletionCount;
|
||||
const int kAvgSuccessorSize = kKeySize / 5;
|
||||
const int kEncodingSavePerKey = kKeySize / 4;
|
||||
expected_tp->raw_key_size =
|
||||
(kKeyCount + kRangeDeletionCount) * (kKeySize + 8);
|
||||
expected_tp->raw_key_size = kKeyCount * (kKeySize + 8);
|
||||
expected_tp->raw_value_size =
|
||||
(kPutCount + kMergeCount + kRangeDeletionCount) * kValueSize;
|
||||
expected_tp->num_entries = kKeyCount;
|
||||
expected_tp->num_deletions = kDeletionCount;
|
||||
expected_tp->num_deletions = kDeletionCount + kRangeDeletionCount;
|
||||
expected_tp->num_merge_operands = kMergeCount;
|
||||
expected_tp->num_range_deletions = kRangeDeletionCount;
|
||||
expected_tp->num_data_blocks =
|
||||
@@ -393,7 +392,15 @@ TEST_F(DBPropertiesTest, ReadLatencyHistogramByLevel) {
|
||||
options.target_file_size_base = 98 << 10;
|
||||
options.max_write_buffer_number = 2;
|
||||
options.statistics = rocksdb::CreateDBStatistics();
|
||||
options.max_open_files = 100;
|
||||
options.max_open_files = 11; // Make sure no proloading of table readers
|
||||
|
||||
// RocksDB sanitize max open files to at least 20. Modify it back.
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"SanitizeOptions::AfterChangeMaxOpenFiles", [&](void* arg) {
|
||||
int* max_open_files = static_cast<int*>(arg);
|
||||
*max_open_files = 11;
|
||||
});
|
||||
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
BlockBasedTableOptions table_options;
|
||||
table_options.no_block_cache = true;
|
||||
@@ -441,6 +448,7 @@ TEST_F(DBPropertiesTest, ReadLatencyHistogramByLevel) {
|
||||
|
||||
// Reopen and issue iterating. See thee latency tracked
|
||||
ReopenWithColumnFamilies({"default", "pikachu"}, options);
|
||||
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
||||
ASSERT_TRUE(dbfull()->GetProperty("rocksdb.cf-file-histogram", &prop));
|
||||
ASSERT_EQ(std::string::npos, prop.find("** Level 0 read latency histogram"));
|
||||
ASSERT_EQ(std::string::npos, prop.find("** Level 1 read latency histogram"));
|
||||
|
||||
+16
-2
@@ -1041,11 +1041,16 @@ TEST_F(DBRangeDelTest, RangeTombstoneEndKeyAsSstableUpperBound) {
|
||||
// L2:
|
||||
// [key000000#1,1, key000000#1,1]
|
||||
// [key000002#6,1, key000004#72057594037927935,15]
|
||||
//
|
||||
// At the same time, verify the compaction does not cause the key at the
|
||||
// endpoint (key000002#6,1) to disappear.
|
||||
ASSERT_EQ(value, Get(Key(2)));
|
||||
auto begin_str = Key(3);
|
||||
const rocksdb::Slice begin = begin_str;
|
||||
dbfull()->TEST_CompactRange(1, &begin, nullptr);
|
||||
ASSERT_EQ(1, NumTableFilesAtLevel(1));
|
||||
ASSERT_EQ(2, NumTableFilesAtLevel(2));
|
||||
ASSERT_EQ(value, Get(Key(2)));
|
||||
}
|
||||
|
||||
{
|
||||
@@ -1133,6 +1138,14 @@ TEST_F(DBRangeDelTest, KeyAtOverlappingEndpointReappears) {
|
||||
options.target_file_size_base = kFileBytes;
|
||||
Reopen(options);
|
||||
|
||||
// Push dummy data to L3 so that our actual test files on L0-L2
|
||||
// will not be considered "bottommost" level, otherwise compaction
|
||||
// may prevent us from creating overlapping user keys
|
||||
// as on the bottommost layer MergeHelper
|
||||
ASSERT_OK(db_->Merge(WriteOptions(), "key", "dummy"));
|
||||
ASSERT_OK(db_->Flush(FlushOptions()));
|
||||
MoveFilesToLevel(3);
|
||||
|
||||
Random rnd(301);
|
||||
const Snapshot* snapshot = nullptr;
|
||||
for (int i = 0; i < kNumFiles; ++i) {
|
||||
@@ -1154,8 +1167,9 @@ TEST_F(DBRangeDelTest, KeyAtOverlappingEndpointReappears) {
|
||||
std::string value;
|
||||
ASSERT_TRUE(db_->Get(ReadOptions(), "key", &value).IsNotFound());
|
||||
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr /* begin_key */,
|
||||
nullptr /* end_key */));
|
||||
dbfull()->TEST_CompactRange(0 /* level */, nullptr /* begin */,
|
||||
nullptr /* end */, nullptr /* column_family */,
|
||||
true /* disallow_trivial_move */);
|
||||
ASSERT_EQ(0, NumTableFilesAtLevel(0));
|
||||
// Now we have multiple files at L1 all containing a single user key, thus
|
||||
// guaranteeing overlap in the file endpoints.
|
||||
|
||||
@@ -385,6 +385,7 @@ TEST_F(DBSSTTest, RateLimitedDelete) {
|
||||
|
||||
// Compaction will move the 4 files in L0 to trash and create 1 L1 file
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact(true));
|
||||
ASSERT_EQ("0,1", FilesPerLevel(0));
|
||||
|
||||
uint64_t delete_start_time = env_->NowMicros();
|
||||
|
||||
+2
-2
@@ -2606,8 +2606,8 @@ class ModelDB : public DB {
|
||||
ColumnFamilyHandle* /*column_family*/,
|
||||
const std::vector<std::string>& /*input_file_names*/,
|
||||
const int /*output_level*/, const int /*output_path_id*/ = -1,
|
||||
std::vector<std::string>* const /*output_file_names*/ = nullptr
|
||||
) override {
|
||||
std::vector<std::string>* const /*output_file_names*/ = nullptr,
|
||||
CompactionJobInfo* /*compaction_job_info*/ = nullptr) override {
|
||||
return Status::NotSupported("Not supported operation.");
|
||||
}
|
||||
|
||||
|
||||
+183
-6
@@ -342,6 +342,7 @@ TEST_P(DBTestSharedWriteBufferAcrossCFs, SharedWriteBufferAcrossCFs) {
|
||||
ASSERT_GE(cache->GetUsage(), 1024 * 1024);
|
||||
Close();
|
||||
options.write_buffer_manager.reset();
|
||||
last_options_.write_buffer_manager.reset();
|
||||
ASSERT_LT(cache->GetUsage(), 1024 * 1024);
|
||||
}
|
||||
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
||||
@@ -1345,11 +1346,49 @@ TEST_F(DBTest2, FirstSnapshotTest) {
|
||||
db_->ReleaseSnapshot(s1);
|
||||
}
|
||||
|
||||
class PinL0IndexAndFilterBlocksTest : public DBTestBase,
|
||||
public testing::WithParamInterface<bool> {
|
||||
#ifndef ROCKSDB_LITE
|
||||
TEST_F(DBTest2, DuplicateSnapshot) {
|
||||
Options options;
|
||||
options = CurrentOptions(options);
|
||||
std::vector<const Snapshot*> snapshots;
|
||||
DBImpl* dbi = reinterpret_cast<DBImpl*>(db_);
|
||||
SequenceNumber oldest_ww_snap, first_ww_snap;
|
||||
|
||||
Put("k", "v"); // inc seq
|
||||
snapshots.push_back(db_->GetSnapshot());
|
||||
snapshots.push_back(db_->GetSnapshot());
|
||||
Put("k", "v"); // inc seq
|
||||
snapshots.push_back(db_->GetSnapshot());
|
||||
snapshots.push_back(dbi->GetSnapshotForWriteConflictBoundary());
|
||||
first_ww_snap = snapshots.back()->GetSequenceNumber();
|
||||
Put("k", "v"); // inc seq
|
||||
snapshots.push_back(dbi->GetSnapshotForWriteConflictBoundary());
|
||||
snapshots.push_back(db_->GetSnapshot());
|
||||
Put("k", "v"); // inc seq
|
||||
snapshots.push_back(db_->GetSnapshot());
|
||||
|
||||
{
|
||||
InstrumentedMutexLock l(dbi->mutex());
|
||||
auto seqs = dbi->snapshots().GetAll(&oldest_ww_snap);
|
||||
ASSERT_EQ(seqs.size(), 4); // duplicates are not counted
|
||||
ASSERT_EQ(oldest_ww_snap, first_ww_snap);
|
||||
}
|
||||
|
||||
for (auto s : snapshots) {
|
||||
db_->ReleaseSnapshot(s);
|
||||
}
|
||||
}
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
class PinL0IndexAndFilterBlocksTest
|
||||
: public DBTestBase,
|
||||
public testing::WithParamInterface<std::tuple<bool, bool>> {
|
||||
public:
|
||||
PinL0IndexAndFilterBlocksTest() : DBTestBase("/db_pin_l0_index_bloom_test") {}
|
||||
virtual void SetUp() override { infinite_max_files_ = GetParam(); }
|
||||
virtual void SetUp() override {
|
||||
infinite_max_files_ = std::get<0>(GetParam());
|
||||
disallow_preload_ = std::get<1>(GetParam());
|
||||
}
|
||||
|
||||
void CreateTwoLevels(Options* options, bool close_afterwards) {
|
||||
if (infinite_max_files_) {
|
||||
@@ -1386,6 +1425,7 @@ class PinL0IndexAndFilterBlocksTest : public DBTestBase,
|
||||
}
|
||||
|
||||
bool infinite_max_files_;
|
||||
bool disallow_preload_;
|
||||
};
|
||||
|
||||
TEST_P(PinL0IndexAndFilterBlocksTest,
|
||||
@@ -1476,12 +1516,27 @@ TEST_P(PinL0IndexAndFilterBlocksTest, DisablePrefetchingNonL0IndexAndFilter) {
|
||||
uint64_t im = TestGetTickerCount(options, BLOCK_CACHE_INDEX_MISS);
|
||||
uint64_t ih = TestGetTickerCount(options, BLOCK_CACHE_INDEX_HIT);
|
||||
|
||||
if (disallow_preload_) {
|
||||
// Now we have two files. We narrow the max open files to allow 3 entries
|
||||
// so that preloading SST files won't happen.
|
||||
options.max_open_files = 13;
|
||||
// RocksDB sanitize max open files to at least 20. Modify it back.
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"SanitizeOptions::AfterChangeMaxOpenFiles", [&](void* arg) {
|
||||
int* max_open_files = static_cast<int*>(arg);
|
||||
*max_open_files = 13;
|
||||
});
|
||||
}
|
||||
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
// Reopen database. If max_open_files is set as -1, table readers will be
|
||||
// preloaded. This will trigger a BlockBasedTable::Open() and prefetch
|
||||
// L0 index and filter. Level 1's prefetching is disabled in DB::Open()
|
||||
TryReopenWithColumnFamilies({"default", "pikachu"}, options);
|
||||
|
||||
if (infinite_max_files_) {
|
||||
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
||||
|
||||
if (!disallow_preload_) {
|
||||
// After reopen, cache miss are increased by one because we read (and only
|
||||
// read) filter and index on L0
|
||||
ASSERT_EQ(fm + 1, TestGetTickerCount(options, BLOCK_CACHE_FILTER_MISS));
|
||||
@@ -1509,7 +1564,7 @@ TEST_P(PinL0IndexAndFilterBlocksTest, DisablePrefetchingNonL0IndexAndFilter) {
|
||||
|
||||
// this should be read from L1
|
||||
value = Get(1, "a");
|
||||
if (infinite_max_files_) {
|
||||
if (!disallow_preload_) {
|
||||
// In inifinite max files case, there's a cache miss in executing Get()
|
||||
// because index and filter are not prefetched before.
|
||||
ASSERT_EQ(fm + 2, TestGetTickerCount(options, BLOCK_CACHE_FILTER_MISS));
|
||||
@@ -1527,10 +1582,45 @@ TEST_P(PinL0IndexAndFilterBlocksTest, DisablePrefetchingNonL0IndexAndFilter) {
|
||||
ASSERT_EQ(im + 2, TestGetTickerCount(options, BLOCK_CACHE_INDEX_MISS));
|
||||
ASSERT_EQ(ih + 1, TestGetTickerCount(options, BLOCK_CACHE_INDEX_HIT));
|
||||
}
|
||||
|
||||
// Force a full compaction to one single file. There will be a block
|
||||
// cache read for both of index and filter. If prefetch doesn't explicitly
|
||||
// happen, it will happen when verifying the file.
|
||||
Compact(1, "a", "zzzzz");
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
|
||||
if (!disallow_preload_) {
|
||||
ASSERT_EQ(fm + 3, TestGetTickerCount(options, BLOCK_CACHE_FILTER_MISS));
|
||||
ASSERT_EQ(fh, TestGetTickerCount(options, BLOCK_CACHE_FILTER_HIT));
|
||||
ASSERT_EQ(im + 3, TestGetTickerCount(options, BLOCK_CACHE_INDEX_MISS));
|
||||
ASSERT_EQ(ih + 2, TestGetTickerCount(options, BLOCK_CACHE_INDEX_HIT));
|
||||
} else {
|
||||
ASSERT_EQ(fm + 3, TestGetTickerCount(options, BLOCK_CACHE_FILTER_MISS));
|
||||
ASSERT_EQ(fh + 1, TestGetTickerCount(options, BLOCK_CACHE_FILTER_HIT));
|
||||
ASSERT_EQ(im + 3, TestGetTickerCount(options, BLOCK_CACHE_INDEX_MISS));
|
||||
ASSERT_EQ(ih + 3, TestGetTickerCount(options, BLOCK_CACHE_INDEX_HIT));
|
||||
}
|
||||
|
||||
// Bloom and index hit will happen when a Get() happens.
|
||||
value = Get(1, "a");
|
||||
if (!disallow_preload_) {
|
||||
ASSERT_EQ(fm + 3, TestGetTickerCount(options, BLOCK_CACHE_FILTER_MISS));
|
||||
ASSERT_EQ(fh + 1, TestGetTickerCount(options, BLOCK_CACHE_FILTER_HIT));
|
||||
ASSERT_EQ(im + 3, TestGetTickerCount(options, BLOCK_CACHE_INDEX_MISS));
|
||||
ASSERT_EQ(ih + 3, TestGetTickerCount(options, BLOCK_CACHE_INDEX_HIT));
|
||||
} else {
|
||||
ASSERT_EQ(fm + 3, TestGetTickerCount(options, BLOCK_CACHE_FILTER_MISS));
|
||||
ASSERT_EQ(fh + 2, TestGetTickerCount(options, BLOCK_CACHE_FILTER_HIT));
|
||||
ASSERT_EQ(im + 3, TestGetTickerCount(options, BLOCK_CACHE_INDEX_MISS));
|
||||
ASSERT_EQ(ih + 4, TestGetTickerCount(options, BLOCK_CACHE_INDEX_HIT));
|
||||
}
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(PinL0IndexAndFilterBlocksTest,
|
||||
PinL0IndexAndFilterBlocksTest, ::testing::Bool());
|
||||
PinL0IndexAndFilterBlocksTest,
|
||||
::testing::Values(std::make_tuple(true, false),
|
||||
std::make_tuple(false, false),
|
||||
std::make_tuple(false, true)));
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
TEST_F(DBTest2, MaxCompactionBytesTest) {
|
||||
@@ -1646,6 +1736,42 @@ class MockPersistentCache : public PersistentCache {
|
||||
const size_t max_size_ = 10 * 1024; // 10KiB
|
||||
};
|
||||
|
||||
#ifdef OS_LINUX
|
||||
// Make sure that in CPU time perf context counters, Env::NowCPUNanos()
|
||||
// is used, rather than Env::CPUNanos();
|
||||
TEST_F(DBTest2, TestPerfContextCpuTime) {
|
||||
// force resizing table cache so table handle is not preloaded so that
|
||||
// we can measure find_table_nanos during Get().
|
||||
dbfull()->TEST_table_cache()->SetCapacity(0);
|
||||
ASSERT_OK(Put("foo", "bar"));
|
||||
ASSERT_OK(Flush());
|
||||
env_->now_cpu_count_.store(0);
|
||||
|
||||
// CPU timing is not enabled with kEnableTimeExceptForMutex
|
||||
SetPerfLevel(PerfLevel::kEnableTimeExceptForMutex);
|
||||
ASSERT_EQ("bar", Get("foo"));
|
||||
ASSERT_EQ(0, get_perf_context()->get_cpu_nanos);
|
||||
ASSERT_EQ(0, env_->now_cpu_count_.load());
|
||||
|
||||
uint64_t kDummyAddonTime = uint64_t{1000000000000};
|
||||
|
||||
// Add time to NowNanos() reading.
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"TableCache::FindTable:0",
|
||||
[&](void* /*arg*/) { env_->addon_time_.fetch_add(kDummyAddonTime); });
|
||||
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
SetPerfLevel(PerfLevel::kEnableTimeAndCPUTimeExceptForMutex);
|
||||
ASSERT_EQ("bar", Get("foo"));
|
||||
ASSERT_GT(env_->now_cpu_count_.load(), 2);
|
||||
ASSERT_LT(get_perf_context()->get_cpu_nanos, kDummyAddonTime);
|
||||
ASSERT_GT(get_perf_context()->find_table_nanos, kDummyAddonTime);
|
||||
|
||||
SetPerfLevel(PerfLevel::kDisable);
|
||||
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
#endif // OS_LINUX
|
||||
|
||||
#ifndef OS_SOLARIS // GetUniqueIdFromFile is not implemented
|
||||
TEST_F(DBTest2, PersistentCache) {
|
||||
int num_iter = 80;
|
||||
@@ -3088,6 +3214,57 @@ TEST_F(DBTest2, TestCompactFiles) {
|
||||
}
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
// TODO: figure out why this test fails in appveyor
|
||||
#ifndef OS_WIN
|
||||
TEST_F(DBTest2, MultiDBParallelOpenTest) {
|
||||
const int kNumDbs = 2;
|
||||
Options options = CurrentOptions();
|
||||
std::vector<std::string> dbnames;
|
||||
for (int i = 0; i < kNumDbs; ++i) {
|
||||
dbnames.emplace_back(test::TmpDir(env_) + "/db" + ToString(i));
|
||||
ASSERT_OK(DestroyDB(dbnames.back(), options));
|
||||
}
|
||||
|
||||
// Verify empty DBs can be created in parallel
|
||||
std::vector<std::thread> open_threads;
|
||||
std::vector<DB*> dbs{static_cast<unsigned int>(kNumDbs), nullptr};
|
||||
options.create_if_missing = true;
|
||||
for (int i = 0; i < kNumDbs; ++i) {
|
||||
open_threads.emplace_back(
|
||||
[&](int dbnum) {
|
||||
ASSERT_OK(DB::Open(options, dbnames[dbnum], &dbs[dbnum]));
|
||||
},
|
||||
i);
|
||||
}
|
||||
|
||||
// Now add some data and close, so next we can verify non-empty DBs can be
|
||||
// recovered in parallel
|
||||
for (int i = 0; i < kNumDbs; ++i) {
|
||||
open_threads[i].join();
|
||||
ASSERT_OK(dbs[i]->Put(WriteOptions(), "xi", "gua"));
|
||||
delete dbs[i];
|
||||
}
|
||||
|
||||
// Verify non-empty DBs can be recovered in parallel
|
||||
dbs.clear();
|
||||
open_threads.clear();
|
||||
for (int i = 0; i < kNumDbs; ++i) {
|
||||
open_threads.emplace_back(
|
||||
[&](int dbnum) {
|
||||
ASSERT_OK(DB::Open(options, dbnames[dbnum], &dbs[dbnum]));
|
||||
},
|
||||
i);
|
||||
}
|
||||
|
||||
// Wait and cleanup
|
||||
for (int i = 0; i < kNumDbs; ++i) {
|
||||
open_threads[i].join();
|
||||
delete dbs[i];
|
||||
ASSERT_OK(DestroyDB(dbnames[i], options));
|
||||
}
|
||||
}
|
||||
#endif // OS_WIN
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
+32
-6
@@ -604,6 +604,7 @@ Status DBTestBase::TryReopenWithColumnFamilies(
|
||||
column_families.push_back(ColumnFamilyDescriptor(cfs[i], options[i]));
|
||||
}
|
||||
DBOptions db_opts = DBOptions(options[0]);
|
||||
last_options_ = options[0];
|
||||
return DB::Open(db_opts, dbname_, column_families, &handles_, &db_);
|
||||
}
|
||||
|
||||
@@ -762,6 +763,31 @@ std::string DBTestBase::Get(int cf, const std::string& k,
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<std::string> DBTestBase::MultiGet(std::vector<int> cfs,
|
||||
const std::vector<std::string>& k,
|
||||
const Snapshot* snapshot) {
|
||||
ReadOptions options;
|
||||
options.verify_checksums = true;
|
||||
options.snapshot = snapshot;
|
||||
std::vector<ColumnFamilyHandle*> handles;
|
||||
std::vector<Slice> keys;
|
||||
std::vector<std::string> result;
|
||||
|
||||
for (unsigned int i = 0; i < cfs.size(); ++i) {
|
||||
handles.push_back(handles_[cfs[i]]);
|
||||
keys.push_back(k[i]);
|
||||
}
|
||||
std::vector<Status> s = db_->MultiGet(options, handles, keys, &result);
|
||||
for (unsigned int i = 0; i < s.size(); ++i) {
|
||||
if (s[i].IsNotFound()) {
|
||||
result[i] = "NOT_FOUND";
|
||||
} else if (!s[i].ok()) {
|
||||
result[i] = s[i].ToString();
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
Status DBTestBase::Get(const std::string& k, PinnableSlice* v) {
|
||||
ReadOptions options;
|
||||
options.verify_checksums = true;
|
||||
@@ -814,8 +840,8 @@ std::string DBTestBase::AllEntriesFor(const Slice& user_key, int cf) {
|
||||
Arena arena;
|
||||
auto options = CurrentOptions();
|
||||
InternalKeyComparator icmp(options.comparator);
|
||||
RangeDelAggregatorV2 range_del_agg(&icmp,
|
||||
kMaxSequenceNumber /* upper_bound */);
|
||||
ReadRangeDelAggregator range_del_agg(&icmp,
|
||||
kMaxSequenceNumber /* upper_bound */);
|
||||
ScopedArenaIterator iter;
|
||||
if (cf == 0) {
|
||||
iter.set(dbfull()->NewInternalIterator(&arena, &range_del_agg,
|
||||
@@ -1227,8 +1253,8 @@ void DBTestBase::validateNumberOfEntries(int numValues, int cf) {
|
||||
Arena arena;
|
||||
auto options = CurrentOptions();
|
||||
InternalKeyComparator icmp(options.comparator);
|
||||
RangeDelAggregatorV2 range_del_agg(&icmp,
|
||||
kMaxSequenceNumber /* upper_bound */);
|
||||
ReadRangeDelAggregator range_del_agg(&icmp,
|
||||
kMaxSequenceNumber /* upper_bound */);
|
||||
// This should be defined after range_del_agg so that it destructs the
|
||||
// assigned iterator before it range_del_agg is already destructed.
|
||||
ScopedArenaIterator iter;
|
||||
@@ -1437,8 +1463,8 @@ void DBTestBase::VerifyDBInternal(
|
||||
std::vector<std::pair<std::string, std::string>> true_data) {
|
||||
Arena arena;
|
||||
InternalKeyComparator icmp(last_options_.comparator);
|
||||
RangeDelAggregatorV2 range_del_agg(&icmp,
|
||||
kMaxSequenceNumber /* upper_bound */);
|
||||
ReadRangeDelAggregator range_del_agg(&icmp,
|
||||
kMaxSequenceNumber /* upper_bound */);
|
||||
auto iter =
|
||||
dbfull()->NewInternalIterator(&arena, &range_del_agg, kMaxSequenceNumber);
|
||||
iter->SeekToFirst();
|
||||
|
||||
@@ -503,6 +503,11 @@ class SpecialEnv : public EnvWrapper {
|
||||
return s;
|
||||
}
|
||||
|
||||
virtual uint64_t NowCPUNanos() override {
|
||||
now_cpu_count_.fetch_add(1);
|
||||
return target()->NowCPUNanos();
|
||||
}
|
||||
|
||||
virtual uint64_t NowNanos() override {
|
||||
return (time_elapse_only_sleep_ ? 0 : target()->NowNanos()) +
|
||||
addon_time_.load() * 1000;
|
||||
@@ -572,6 +577,8 @@ class SpecialEnv : public EnvWrapper {
|
||||
|
||||
std::atomic<int64_t> addon_time_;
|
||||
|
||||
std::atomic<int> now_cpu_count_;
|
||||
|
||||
std::atomic<int> delete_count_;
|
||||
|
||||
std::atomic<bool> time_elapse_only_sleep_;
|
||||
@@ -833,6 +840,10 @@ class DBTestBase : public testing::Test {
|
||||
|
||||
Status Get(const std::string& k, PinnableSlice* v);
|
||||
|
||||
std::vector<std::string> MultiGet(std::vector<int> cfs,
|
||||
const std::vector<std::string>& k,
|
||||
const Snapshot* snapshot = nullptr);
|
||||
|
||||
uint64_t GetNumSnapshots();
|
||||
|
||||
uint64_t GetTimeOldestSnapshots();
|
||||
|
||||
+28
-4
@@ -283,7 +283,31 @@ TEST_F(DBWALTest, RecoverWithTableHandle) {
|
||||
ASSERT_OK(Put(1, "bar", "v4"));
|
||||
ASSERT_OK(Flush(1));
|
||||
ASSERT_OK(Put(1, "big", std::string(100, 'a')));
|
||||
ReopenWithColumnFamilies({"default", "pikachu"}, CurrentOptions());
|
||||
|
||||
options = CurrentOptions();
|
||||
const int kSmallMaxOpenFiles = 13;
|
||||
if (option_config_ == kDBLogDir) {
|
||||
// Use this option to check not preloading files
|
||||
// Set the max open files to be small enough so no preload will
|
||||
// happen.
|
||||
options.max_open_files = kSmallMaxOpenFiles;
|
||||
// RocksDB sanitize max open files to at least 20. Modify it back.
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"SanitizeOptions::AfterChangeMaxOpenFiles", [&](void* arg) {
|
||||
int* max_open_files = static_cast<int*>(arg);
|
||||
*max_open_files = kSmallMaxOpenFiles;
|
||||
});
|
||||
|
||||
} else if (option_config_ == kWalDirAndMmapReads) {
|
||||
// Use this option to check always loading all files.
|
||||
options.max_open_files = 100;
|
||||
} else {
|
||||
options.max_open_files = -1;
|
||||
}
|
||||
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
||||
ReopenWithColumnFamilies({"default", "pikachu"}, options);
|
||||
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
||||
rocksdb::SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
|
||||
std::vector<std::vector<FileMetaData>> files;
|
||||
dbfull()->TEST_GetFilesMetaData(handles_[1], &files);
|
||||
@@ -294,10 +318,10 @@ TEST_F(DBWALTest, RecoverWithTableHandle) {
|
||||
ASSERT_EQ(total_files, 3);
|
||||
for (const auto& level : files) {
|
||||
for (const auto& file : level) {
|
||||
if (kInfiniteMaxOpenFiles == option_config_) {
|
||||
ASSERT_TRUE(file.table_reader_handle != nullptr);
|
||||
} else {
|
||||
if (options.max_open_files == kSmallMaxOpenFiles) {
|
||||
ASSERT_TRUE(file.table_reader_handle == nullptr);
|
||||
} else {
|
||||
ASSERT_TRUE(file.table_reader_handle != nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+238
-123
@@ -14,10 +14,11 @@
|
||||
namespace rocksdb {
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
class ExternalSSTFileBasicTest : public DBTestBase,
|
||||
public ::testing::WithParamInterface<bool> {
|
||||
class ExternalSSTFileBasicTest
|
||||
: public DBTestBase,
|
||||
public ::testing::WithParamInterface<std::tuple<bool, bool>> {
|
||||
public:
|
||||
ExternalSSTFileBasicTest() : DBTestBase("/external_sst_file_test") {
|
||||
ExternalSSTFileBasicTest() : DBTestBase("/external_sst_file_basic_test") {
|
||||
sst_files_dir_ = dbname_ + "/sst_files/";
|
||||
DestroyAndRecreateExternalSSTFilesDir();
|
||||
}
|
||||
@@ -42,7 +43,8 @@ class ExternalSSTFileBasicTest : public DBTestBase,
|
||||
const Options options, std::vector<int> keys,
|
||||
const std::vector<ValueType>& value_types,
|
||||
std::vector<std::pair<int, int>> range_deletions, int file_id,
|
||||
bool write_global_seqno, std::map<std::string, std::string>* true_data) {
|
||||
bool write_global_seqno, bool verify_checksums_before_ingest,
|
||||
std::map<std::string, std::string>* true_data) {
|
||||
assert(value_types.size() == 1 || keys.size() == value_types.size());
|
||||
std::string file_path = sst_files_dir_ + ToString(file_id);
|
||||
SstFileWriter sst_file_writer(EnvOptions(), options);
|
||||
@@ -107,6 +109,7 @@ class ExternalSSTFileBasicTest : public DBTestBase,
|
||||
IngestExternalFileOptions ifo;
|
||||
ifo.allow_global_seqno = true;
|
||||
ifo.write_global_seqno = write_global_seqno;
|
||||
ifo.verify_checksums_before_ingest = verify_checksums_before_ingest;
|
||||
s = db_->IngestExternalFile({file_path}, ifo);
|
||||
}
|
||||
return s;
|
||||
@@ -115,18 +118,20 @@ class ExternalSSTFileBasicTest : public DBTestBase,
|
||||
Status GenerateAndAddExternalFile(
|
||||
const Options options, std::vector<int> keys,
|
||||
const std::vector<ValueType>& value_types, int file_id,
|
||||
bool write_global_seqno, std::map<std::string, std::string>* true_data) {
|
||||
return GenerateAndAddExternalFile(options, keys, value_types, {}, file_id,
|
||||
write_global_seqno, true_data);
|
||||
bool write_global_seqno, bool verify_checksums_before_ingest,
|
||||
std::map<std::string, std::string>* true_data) {
|
||||
return GenerateAndAddExternalFile(
|
||||
options, keys, value_types, {}, file_id, write_global_seqno,
|
||||
verify_checksums_before_ingest, true_data);
|
||||
}
|
||||
|
||||
Status GenerateAndAddExternalFile(
|
||||
const Options options, std::vector<int> keys, const ValueType value_type,
|
||||
int file_id, bool write_global_seqno,
|
||||
int file_id, bool write_global_seqno, bool verify_checksums_before_ingest,
|
||||
std::map<std::string, std::string>* true_data) {
|
||||
return GenerateAndAddExternalFile(options, keys,
|
||||
std::vector<ValueType>(1, value_type),
|
||||
file_id, write_global_seqno, true_data);
|
||||
return GenerateAndAddExternalFile(
|
||||
options, keys, std::vector<ValueType>(1, value_type), file_id,
|
||||
write_global_seqno, verify_checksums_before_ingest, true_data);
|
||||
}
|
||||
|
||||
~ExternalSSTFileBasicTest() { test::DestroyDir(env_, sst_files_dir_); }
|
||||
@@ -249,7 +254,8 @@ TEST_F(ExternalSSTFileBasicTest, NoCopy) {
|
||||
}
|
||||
|
||||
TEST_P(ExternalSSTFileBasicTest, IngestFileWithGlobalSeqnoPickedSeqno) {
|
||||
bool write_global_seqno = GetParam();
|
||||
bool write_global_seqno = std::get<0>(GetParam());
|
||||
bool verify_checksums_before_ingest = std::get<1>(GetParam());
|
||||
do {
|
||||
Options options = CurrentOptions();
|
||||
DestroyAndReopen(options);
|
||||
@@ -257,39 +263,39 @@ TEST_P(ExternalSSTFileBasicTest, IngestFileWithGlobalSeqnoPickedSeqno) {
|
||||
|
||||
int file_id = 1;
|
||||
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, {1, 2, 3, 4, 5, 6},
|
||||
ValueType::kTypeValue, file_id++,
|
||||
write_global_seqno, &true_data));
|
||||
ASSERT_OK(GenerateAndAddExternalFile(
|
||||
options, {1, 2, 3, 4, 5, 6}, ValueType::kTypeValue, file_id++,
|
||||
write_global_seqno, verify_checksums_before_ingest, &true_data));
|
||||
// File doesn't overwrite any keys, no seqno needed
|
||||
ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 0);
|
||||
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, {10, 11, 12, 13},
|
||||
ValueType::kTypeValue, file_id++,
|
||||
write_global_seqno, &true_data));
|
||||
ASSERT_OK(GenerateAndAddExternalFile(
|
||||
options, {10, 11, 12, 13}, ValueType::kTypeValue, file_id++,
|
||||
write_global_seqno, verify_checksums_before_ingest, &true_data));
|
||||
// File doesn't overwrite any keys, no seqno needed
|
||||
ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 0);
|
||||
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, {1, 4, 6},
|
||||
ValueType::kTypeValue, file_id++,
|
||||
write_global_seqno, &true_data));
|
||||
ASSERT_OK(GenerateAndAddExternalFile(
|
||||
options, {1, 4, 6}, ValueType::kTypeValue, file_id++,
|
||||
write_global_seqno, verify_checksums_before_ingest, &true_data));
|
||||
// File overwrites some keys, a seqno will be assigned
|
||||
ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 1);
|
||||
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, {11, 15, 19},
|
||||
ValueType::kTypeValue, file_id++,
|
||||
write_global_seqno, &true_data));
|
||||
ASSERT_OK(GenerateAndAddExternalFile(
|
||||
options, {11, 15, 19}, ValueType::kTypeValue, file_id++,
|
||||
write_global_seqno, verify_checksums_before_ingest, &true_data));
|
||||
// File overwrites some keys, a seqno will be assigned
|
||||
ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 2);
|
||||
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, {120, 130},
|
||||
ValueType::kTypeValue, file_id++,
|
||||
write_global_seqno, &true_data));
|
||||
ASSERT_OK(GenerateAndAddExternalFile(
|
||||
options, {120, 130}, ValueType::kTypeValue, file_id++,
|
||||
write_global_seqno, verify_checksums_before_ingest, &true_data));
|
||||
// File doesn't overwrite any keys, no seqno needed
|
||||
ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 2);
|
||||
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, {1, 130},
|
||||
ValueType::kTypeValue, file_id++,
|
||||
write_global_seqno, &true_data));
|
||||
ASSERT_OK(GenerateAndAddExternalFile(
|
||||
options, {1, 130}, ValueType::kTypeValue, file_id++, write_global_seqno,
|
||||
verify_checksums_before_ingest, &true_data));
|
||||
// File overwrites some keys, a seqno will be assigned
|
||||
ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 3);
|
||||
|
||||
@@ -300,21 +306,21 @@ TEST_P(ExternalSSTFileBasicTest, IngestFileWithGlobalSeqnoPickedSeqno) {
|
||||
}
|
||||
SequenceNumber last_seqno = dbfull()->GetLatestSequenceNumber();
|
||||
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, {60, 61, 62},
|
||||
ValueType::kTypeValue, file_id++,
|
||||
write_global_seqno, &true_data));
|
||||
ASSERT_OK(GenerateAndAddExternalFile(
|
||||
options, {60, 61, 62}, ValueType::kTypeValue, file_id++,
|
||||
write_global_seqno, verify_checksums_before_ingest, &true_data));
|
||||
// File doesn't overwrite any keys, no seqno needed
|
||||
ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), last_seqno);
|
||||
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, {40, 41, 42},
|
||||
ValueType::kTypeValue, file_id++,
|
||||
write_global_seqno, &true_data));
|
||||
ASSERT_OK(GenerateAndAddExternalFile(
|
||||
options, {40, 41, 42}, ValueType::kTypeValue, file_id++,
|
||||
write_global_seqno, verify_checksums_before_ingest, &true_data));
|
||||
// File overwrites some keys, a seqno will be assigned
|
||||
ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), last_seqno + 1);
|
||||
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, {20, 30, 40},
|
||||
ValueType::kTypeValue, file_id++,
|
||||
write_global_seqno, &true_data));
|
||||
ASSERT_OK(GenerateAndAddExternalFile(
|
||||
options, {20, 30, 40}, ValueType::kTypeValue, file_id++,
|
||||
write_global_seqno, verify_checksums_before_ingest, &true_data));
|
||||
// File overwrites some keys, a seqno will be assigned
|
||||
ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), last_seqno + 2);
|
||||
|
||||
@@ -322,29 +328,29 @@ TEST_P(ExternalSSTFileBasicTest, IngestFileWithGlobalSeqnoPickedSeqno) {
|
||||
|
||||
// We will need a seqno for the file regardless if the file overwrite
|
||||
// keys in the DB or not because we have a snapshot
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, {1000, 1002},
|
||||
ValueType::kTypeValue, file_id++,
|
||||
write_global_seqno, &true_data));
|
||||
ASSERT_OK(GenerateAndAddExternalFile(
|
||||
options, {1000, 1002}, ValueType::kTypeValue, file_id++,
|
||||
write_global_seqno, verify_checksums_before_ingest, &true_data));
|
||||
// A global seqno will be assigned anyway because of the snapshot
|
||||
ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), last_seqno + 3);
|
||||
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, {2000, 3002},
|
||||
ValueType::kTypeValue, file_id++,
|
||||
write_global_seqno, &true_data));
|
||||
ASSERT_OK(GenerateAndAddExternalFile(
|
||||
options, {2000, 3002}, ValueType::kTypeValue, file_id++,
|
||||
write_global_seqno, verify_checksums_before_ingest, &true_data));
|
||||
// A global seqno will be assigned anyway because of the snapshot
|
||||
ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), last_seqno + 4);
|
||||
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, {1, 20, 40, 100, 150},
|
||||
ValueType::kTypeValue, file_id++,
|
||||
write_global_seqno, &true_data));
|
||||
ASSERT_OK(GenerateAndAddExternalFile(
|
||||
options, {1, 20, 40, 100, 150}, ValueType::kTypeValue, file_id++,
|
||||
write_global_seqno, verify_checksums_before_ingest, &true_data));
|
||||
// A global seqno will be assigned anyway because of the snapshot
|
||||
ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), last_seqno + 5);
|
||||
|
||||
db_->ReleaseSnapshot(snapshot);
|
||||
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, {5000, 5001},
|
||||
ValueType::kTypeValue, file_id++,
|
||||
write_global_seqno, &true_data));
|
||||
ASSERT_OK(GenerateAndAddExternalFile(
|
||||
options, {5000, 5001}, ValueType::kTypeValue, file_id++,
|
||||
write_global_seqno, verify_checksums_before_ingest, &true_data));
|
||||
// No snapshot anymore, no need to assign a seqno
|
||||
ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), last_seqno + 5);
|
||||
|
||||
@@ -354,7 +360,8 @@ TEST_P(ExternalSSTFileBasicTest, IngestFileWithGlobalSeqnoPickedSeqno) {
|
||||
}
|
||||
|
||||
TEST_P(ExternalSSTFileBasicTest, IngestFileWithMultipleValueType) {
|
||||
bool write_global_seqno = GetParam();
|
||||
bool write_global_seqno = std::get<0>(GetParam());
|
||||
bool verify_checksums_before_ingest = std::get<1>(GetParam());
|
||||
do {
|
||||
Options options = CurrentOptions();
|
||||
options.merge_operator.reset(new TestPutOperator());
|
||||
@@ -363,59 +370,59 @@ TEST_P(ExternalSSTFileBasicTest, IngestFileWithMultipleValueType) {
|
||||
|
||||
int file_id = 1;
|
||||
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, {1, 2, 3, 4, 5, 6},
|
||||
ValueType::kTypeValue, file_id++,
|
||||
write_global_seqno, &true_data));
|
||||
ASSERT_OK(GenerateAndAddExternalFile(
|
||||
options, {1, 2, 3, 4, 5, 6}, ValueType::kTypeValue, file_id++,
|
||||
write_global_seqno, verify_checksums_before_ingest, &true_data));
|
||||
// File doesn't overwrite any keys, no seqno needed
|
||||
ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 0);
|
||||
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, {10, 11, 12, 13},
|
||||
ValueType::kTypeValue, file_id++,
|
||||
write_global_seqno, &true_data));
|
||||
ASSERT_OK(GenerateAndAddExternalFile(
|
||||
options, {10, 11, 12, 13}, ValueType::kTypeValue, file_id++,
|
||||
write_global_seqno, verify_checksums_before_ingest, &true_data));
|
||||
// File doesn't overwrite any keys, no seqno needed
|
||||
ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 0);
|
||||
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, {1, 4, 6},
|
||||
ValueType::kTypeMerge, file_id++,
|
||||
write_global_seqno, &true_data));
|
||||
ASSERT_OK(GenerateAndAddExternalFile(
|
||||
options, {1, 4, 6}, ValueType::kTypeMerge, file_id++,
|
||||
write_global_seqno, verify_checksums_before_ingest, &true_data));
|
||||
// File overwrites some keys, a seqno will be assigned
|
||||
ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 1);
|
||||
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, {11, 15, 19},
|
||||
ValueType::kTypeDeletion, file_id++,
|
||||
write_global_seqno, &true_data));
|
||||
ASSERT_OK(GenerateAndAddExternalFile(
|
||||
options, {11, 15, 19}, ValueType::kTypeDeletion, file_id++,
|
||||
write_global_seqno, verify_checksums_before_ingest, &true_data));
|
||||
// File overwrites some keys, a seqno will be assigned
|
||||
ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 2);
|
||||
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, {120, 130},
|
||||
ValueType::kTypeMerge, file_id++,
|
||||
write_global_seqno, &true_data));
|
||||
ASSERT_OK(GenerateAndAddExternalFile(
|
||||
options, {120, 130}, ValueType::kTypeMerge, file_id++,
|
||||
write_global_seqno, verify_checksums_before_ingest, &true_data));
|
||||
// File doesn't overwrite any keys, no seqno needed
|
||||
ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 2);
|
||||
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, {1, 130},
|
||||
ValueType::kTypeDeletion, file_id++,
|
||||
write_global_seqno, &true_data));
|
||||
ASSERT_OK(GenerateAndAddExternalFile(
|
||||
options, {1, 130}, ValueType::kTypeDeletion, file_id++,
|
||||
write_global_seqno, verify_checksums_before_ingest, &true_data));
|
||||
// File overwrites some keys, a seqno will be assigned
|
||||
ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 3);
|
||||
|
||||
ASSERT_OK(GenerateAndAddExternalFile(
|
||||
options, {120}, {ValueType::kTypeValue}, {{120, 135}}, file_id++,
|
||||
write_global_seqno, &true_data));
|
||||
write_global_seqno, verify_checksums_before_ingest, &true_data));
|
||||
// File overwrites some keys, a seqno will be assigned
|
||||
ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 4);
|
||||
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, {}, {}, {{110, 120}},
|
||||
file_id++, write_global_seqno,
|
||||
&true_data));
|
||||
ASSERT_OK(GenerateAndAddExternalFile(
|
||||
options, {}, {}, {{110, 120}}, file_id++, write_global_seqno,
|
||||
verify_checksums_before_ingest, &true_data));
|
||||
// The range deletion ends on a key, but it doesn't actually delete
|
||||
// this key because the largest key in the range is exclusive. Still,
|
||||
// it counts as an overlap so a new seqno will be assigned.
|
||||
ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 5);
|
||||
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, {}, {}, {{100, 109}},
|
||||
file_id++, write_global_seqno,
|
||||
&true_data));
|
||||
ASSERT_OK(GenerateAndAddExternalFile(
|
||||
options, {}, {}, {{100, 109}}, file_id++, write_global_seqno,
|
||||
verify_checksums_before_ingest, &true_data));
|
||||
// File doesn't overwrite any keys, no seqno needed
|
||||
ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 5);
|
||||
|
||||
@@ -426,21 +433,21 @@ TEST_P(ExternalSSTFileBasicTest, IngestFileWithMultipleValueType) {
|
||||
}
|
||||
SequenceNumber last_seqno = dbfull()->GetLatestSequenceNumber();
|
||||
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, {60, 61, 62},
|
||||
ValueType::kTypeValue, file_id++,
|
||||
write_global_seqno, &true_data));
|
||||
ASSERT_OK(GenerateAndAddExternalFile(
|
||||
options, {60, 61, 62}, ValueType::kTypeValue, file_id++,
|
||||
write_global_seqno, verify_checksums_before_ingest, &true_data));
|
||||
// File doesn't overwrite any keys, no seqno needed
|
||||
ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), last_seqno);
|
||||
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, {40, 41, 42},
|
||||
ValueType::kTypeMerge, file_id++,
|
||||
write_global_seqno, &true_data));
|
||||
ASSERT_OK(GenerateAndAddExternalFile(
|
||||
options, {40, 41, 42}, ValueType::kTypeMerge, file_id++,
|
||||
write_global_seqno, verify_checksums_before_ingest, &true_data));
|
||||
// File overwrites some keys, a seqno will be assigned
|
||||
ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), last_seqno + 1);
|
||||
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, {20, 30, 40},
|
||||
ValueType::kTypeDeletion, file_id++,
|
||||
write_global_seqno, &true_data));
|
||||
ASSERT_OK(GenerateAndAddExternalFile(
|
||||
options, {20, 30, 40}, ValueType::kTypeDeletion, file_id++,
|
||||
write_global_seqno, verify_checksums_before_ingest, &true_data));
|
||||
// File overwrites some keys, a seqno will be assigned
|
||||
ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), last_seqno + 2);
|
||||
|
||||
@@ -448,29 +455,29 @@ TEST_P(ExternalSSTFileBasicTest, IngestFileWithMultipleValueType) {
|
||||
|
||||
// We will need a seqno for the file regardless if the file overwrite
|
||||
// keys in the DB or not because we have a snapshot
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, {1000, 1002},
|
||||
ValueType::kTypeMerge, file_id++,
|
||||
write_global_seqno, &true_data));
|
||||
ASSERT_OK(GenerateAndAddExternalFile(
|
||||
options, {1000, 1002}, ValueType::kTypeMerge, file_id++,
|
||||
write_global_seqno, verify_checksums_before_ingest, &true_data));
|
||||
// A global seqno will be assigned anyway because of the snapshot
|
||||
ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), last_seqno + 3);
|
||||
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, {2000, 3002},
|
||||
ValueType::kTypeMerge, file_id++,
|
||||
write_global_seqno, &true_data));
|
||||
ASSERT_OK(GenerateAndAddExternalFile(
|
||||
options, {2000, 3002}, ValueType::kTypeMerge, file_id++,
|
||||
write_global_seqno, verify_checksums_before_ingest, &true_data));
|
||||
// A global seqno will be assigned anyway because of the snapshot
|
||||
ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), last_seqno + 4);
|
||||
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, {1, 20, 40, 100, 150},
|
||||
ValueType::kTypeMerge, file_id++,
|
||||
write_global_seqno, &true_data));
|
||||
ASSERT_OK(GenerateAndAddExternalFile(
|
||||
options, {1, 20, 40, 100, 150}, ValueType::kTypeMerge, file_id++,
|
||||
write_global_seqno, verify_checksums_before_ingest, &true_data));
|
||||
// A global seqno will be assigned anyway because of the snapshot
|
||||
ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), last_seqno + 5);
|
||||
|
||||
db_->ReleaseSnapshot(snapshot);
|
||||
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, {5000, 5001},
|
||||
ValueType::kTypeValue, file_id++,
|
||||
write_global_seqno, &true_data));
|
||||
ASSERT_OK(GenerateAndAddExternalFile(
|
||||
options, {5000, 5001}, ValueType::kTypeValue, file_id++,
|
||||
write_global_seqno, verify_checksums_before_ingest, &true_data));
|
||||
// No snapshot anymore, no need to assign a seqno
|
||||
ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), last_seqno + 5);
|
||||
|
||||
@@ -480,7 +487,8 @@ TEST_P(ExternalSSTFileBasicTest, IngestFileWithMultipleValueType) {
|
||||
}
|
||||
|
||||
TEST_P(ExternalSSTFileBasicTest, IngestFileWithMixedValueType) {
|
||||
bool write_global_seqno = GetParam();
|
||||
bool write_global_seqno = std::get<0>(GetParam());
|
||||
bool verify_checksums_before_ingest = std::get<1>(GetParam());
|
||||
do {
|
||||
Options options = CurrentOptions();
|
||||
options.merge_operator.reset(new TestPutOperator());
|
||||
@@ -493,7 +501,8 @@ TEST_P(ExternalSSTFileBasicTest, IngestFileWithMixedValueType) {
|
||||
options, {1, 2, 3, 4, 5, 6},
|
||||
{ValueType::kTypeValue, ValueType::kTypeMerge, ValueType::kTypeValue,
|
||||
ValueType::kTypeMerge, ValueType::kTypeValue, ValueType::kTypeMerge},
|
||||
file_id++, write_global_seqno, &true_data));
|
||||
file_id++, write_global_seqno, verify_checksums_before_ingest,
|
||||
&true_data));
|
||||
// File doesn't overwrite any keys, no seqno needed
|
||||
ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 0);
|
||||
|
||||
@@ -501,7 +510,8 @@ TEST_P(ExternalSSTFileBasicTest, IngestFileWithMixedValueType) {
|
||||
options, {10, 11, 12, 13},
|
||||
{ValueType::kTypeValue, ValueType::kTypeMerge, ValueType::kTypeValue,
|
||||
ValueType::kTypeMerge},
|
||||
file_id++, write_global_seqno, &true_data));
|
||||
file_id++, write_global_seqno, verify_checksums_before_ingest,
|
||||
&true_data));
|
||||
// File doesn't overwrite any keys, no seqno needed
|
||||
ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 0);
|
||||
|
||||
@@ -509,7 +519,8 @@ TEST_P(ExternalSSTFileBasicTest, IngestFileWithMixedValueType) {
|
||||
options, {1, 4, 6},
|
||||
{ValueType::kTypeDeletion, ValueType::kTypeValue,
|
||||
ValueType::kTypeMerge},
|
||||
file_id++, write_global_seqno, &true_data));
|
||||
file_id++, write_global_seqno, verify_checksums_before_ingest,
|
||||
&true_data));
|
||||
// File overwrites some keys, a seqno will be assigned
|
||||
ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 1);
|
||||
|
||||
@@ -517,19 +528,22 @@ TEST_P(ExternalSSTFileBasicTest, IngestFileWithMixedValueType) {
|
||||
options, {11, 15, 19},
|
||||
{ValueType::kTypeDeletion, ValueType::kTypeMerge,
|
||||
ValueType::kTypeValue},
|
||||
file_id++, write_global_seqno, &true_data));
|
||||
file_id++, write_global_seqno, verify_checksums_before_ingest,
|
||||
&true_data));
|
||||
// File overwrites some keys, a seqno will be assigned
|
||||
ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 2);
|
||||
|
||||
ASSERT_OK(GenerateAndAddExternalFile(
|
||||
options, {120, 130}, {ValueType::kTypeValue, ValueType::kTypeMerge},
|
||||
file_id++, write_global_seqno, &true_data));
|
||||
file_id++, write_global_seqno, verify_checksums_before_ingest,
|
||||
&true_data));
|
||||
// File doesn't overwrite any keys, no seqno needed
|
||||
ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 2);
|
||||
|
||||
ASSERT_OK(GenerateAndAddExternalFile(
|
||||
options, {1, 130}, {ValueType::kTypeMerge, ValueType::kTypeDeletion},
|
||||
file_id++, write_global_seqno, &true_data));
|
||||
file_id++, write_global_seqno, verify_checksums_before_ingest,
|
||||
&true_data));
|
||||
// File overwrites some keys, a seqno will be assigned
|
||||
ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 3);
|
||||
|
||||
@@ -537,14 +551,16 @@ TEST_P(ExternalSSTFileBasicTest, IngestFileWithMixedValueType) {
|
||||
options, {150, 151, 152},
|
||||
{ValueType::kTypeValue, ValueType::kTypeMerge,
|
||||
ValueType::kTypeDeletion},
|
||||
{{150, 160}, {180, 190}}, file_id++, write_global_seqno, &true_data));
|
||||
{{150, 160}, {180, 190}}, file_id++, write_global_seqno,
|
||||
verify_checksums_before_ingest, &true_data));
|
||||
// File doesn't overwrite any keys, no seqno needed
|
||||
ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 3);
|
||||
|
||||
ASSERT_OK(GenerateAndAddExternalFile(
|
||||
options, {150, 151, 152},
|
||||
{ValueType::kTypeValue, ValueType::kTypeMerge, ValueType::kTypeValue},
|
||||
{{200, 250}}, file_id++, write_global_seqno, &true_data));
|
||||
{{200, 250}}, file_id++, write_global_seqno,
|
||||
verify_checksums_before_ingest, &true_data));
|
||||
// File overwrites some keys, a seqno will be assigned
|
||||
ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 4);
|
||||
|
||||
@@ -552,7 +568,8 @@ TEST_P(ExternalSSTFileBasicTest, IngestFileWithMixedValueType) {
|
||||
options, {300, 301, 302},
|
||||
{ValueType::kTypeValue, ValueType::kTypeMerge,
|
||||
ValueType::kTypeDeletion},
|
||||
{{1, 2}, {152, 154}}, file_id++, write_global_seqno, &true_data));
|
||||
{{1, 2}, {152, 154}}, file_id++, write_global_seqno,
|
||||
verify_checksums_before_ingest, &true_data));
|
||||
// File overwrites some keys, a seqno will be assigned
|
||||
ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 5);
|
||||
|
||||
@@ -566,7 +583,8 @@ TEST_P(ExternalSSTFileBasicTest, IngestFileWithMixedValueType) {
|
||||
ASSERT_OK(GenerateAndAddExternalFile(
|
||||
options, {60, 61, 62},
|
||||
{ValueType::kTypeValue, ValueType::kTypeMerge, ValueType::kTypeValue},
|
||||
file_id++, write_global_seqno, &true_data));
|
||||
file_id++, write_global_seqno, verify_checksums_before_ingest,
|
||||
&true_data));
|
||||
// File doesn't overwrite any keys, no seqno needed
|
||||
ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), last_seqno);
|
||||
|
||||
@@ -574,7 +592,8 @@ TEST_P(ExternalSSTFileBasicTest, IngestFileWithMixedValueType) {
|
||||
options, {40, 41, 42},
|
||||
{ValueType::kTypeValue, ValueType::kTypeDeletion,
|
||||
ValueType::kTypeDeletion},
|
||||
file_id++, write_global_seqno, &true_data));
|
||||
file_id++, write_global_seqno, verify_checksums_before_ingest,
|
||||
&true_data));
|
||||
// File overwrites some keys, a seqno will be assigned
|
||||
ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), last_seqno + 1);
|
||||
|
||||
@@ -582,7 +601,8 @@ TEST_P(ExternalSSTFileBasicTest, IngestFileWithMixedValueType) {
|
||||
options, {20, 30, 40},
|
||||
{ValueType::kTypeDeletion, ValueType::kTypeDeletion,
|
||||
ValueType::kTypeDeletion},
|
||||
file_id++, write_global_seqno, &true_data));
|
||||
file_id++, write_global_seqno, verify_checksums_before_ingest,
|
||||
&true_data));
|
||||
// File overwrites some keys, a seqno will be assigned
|
||||
ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), last_seqno + 2);
|
||||
|
||||
@@ -592,13 +612,15 @@ TEST_P(ExternalSSTFileBasicTest, IngestFileWithMixedValueType) {
|
||||
// keys in the DB or not because we have a snapshot
|
||||
ASSERT_OK(GenerateAndAddExternalFile(
|
||||
options, {1000, 1002}, {ValueType::kTypeValue, ValueType::kTypeMerge},
|
||||
file_id++, write_global_seqno, &true_data));
|
||||
file_id++, write_global_seqno, verify_checksums_before_ingest,
|
||||
&true_data));
|
||||
// A global seqno will be assigned anyway because of the snapshot
|
||||
ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), last_seqno + 3);
|
||||
|
||||
ASSERT_OK(GenerateAndAddExternalFile(
|
||||
options, {2000, 3002}, {ValueType::kTypeValue, ValueType::kTypeMerge},
|
||||
file_id++, write_global_seqno, &true_data));
|
||||
file_id++, write_global_seqno, verify_checksums_before_ingest,
|
||||
&true_data));
|
||||
// A global seqno will be assigned anyway because of the snapshot
|
||||
ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), last_seqno + 4);
|
||||
|
||||
@@ -606,7 +628,8 @@ TEST_P(ExternalSSTFileBasicTest, IngestFileWithMixedValueType) {
|
||||
options, {1, 20, 40, 100, 150},
|
||||
{ValueType::kTypeDeletion, ValueType::kTypeDeletion,
|
||||
ValueType::kTypeValue, ValueType::kTypeMerge, ValueType::kTypeMerge},
|
||||
file_id++, write_global_seqno, &true_data));
|
||||
file_id++, write_global_seqno, verify_checksums_before_ingest,
|
||||
&true_data));
|
||||
// A global seqno will be assigned anyway because of the snapshot
|
||||
ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), last_seqno + 5);
|
||||
|
||||
@@ -614,7 +637,8 @@ TEST_P(ExternalSSTFileBasicTest, IngestFileWithMixedValueType) {
|
||||
|
||||
ASSERT_OK(GenerateAndAddExternalFile(
|
||||
options, {5000, 5001}, {ValueType::kTypeValue, ValueType::kTypeMerge},
|
||||
file_id++, write_global_seqno, &true_data));
|
||||
file_id++, write_global_seqno, verify_checksums_before_ingest,
|
||||
&true_data));
|
||||
// No snapshot anymore, no need to assign a seqno
|
||||
ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), last_seqno + 5);
|
||||
|
||||
@@ -690,13 +714,15 @@ TEST_P(ExternalSSTFileBasicTest, IngestionWithRangeDeletions) {
|
||||
ASSERT_EQ(0, NumTableFilesAtLevel(kNumLevels - 2));
|
||||
ASSERT_EQ(1, NumTableFilesAtLevel(kNumLevels - 1));
|
||||
|
||||
bool write_global_seqno = GetParam();
|
||||
bool write_global_seqno = std::get<0>(GetParam());
|
||||
bool verify_checksums_before_ingest = std::get<1>(GetParam());
|
||||
// overlaps with L0 file but not memtable, so flush is skipped and file is
|
||||
// ingested into L0
|
||||
SequenceNumber last_seqno = dbfull()->GetLatestSequenceNumber();
|
||||
ASSERT_OK(GenerateAndAddExternalFile(
|
||||
options, {60, 90}, {ValueType::kTypeValue, ValueType::kTypeValue},
|
||||
{{65, 70}, {70, 85}}, file_id++, write_global_seqno, &true_data));
|
||||
{{65, 70}, {70, 85}}, file_id++, write_global_seqno,
|
||||
verify_checksums_before_ingest, &true_data));
|
||||
ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), ++last_seqno);
|
||||
ASSERT_EQ(2, NumTableFilesAtLevel(0));
|
||||
ASSERT_EQ(0, NumTableFilesAtLevel(kNumLevels - 2));
|
||||
@@ -706,7 +732,8 @@ TEST_P(ExternalSSTFileBasicTest, IngestionWithRangeDeletions) {
|
||||
// file is ingested into L5
|
||||
ASSERT_OK(GenerateAndAddExternalFile(
|
||||
options, {10, 40}, {ValueType::kTypeValue, ValueType::kTypeValue},
|
||||
file_id++, write_global_seqno, &true_data));
|
||||
file_id++, write_global_seqno, verify_checksums_before_ingest,
|
||||
&true_data));
|
||||
ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), ++last_seqno);
|
||||
ASSERT_EQ(2, NumTableFilesAtLevel(0));
|
||||
ASSERT_EQ(1, NumTableFilesAtLevel(kNumLevels - 2));
|
||||
@@ -714,8 +741,9 @@ TEST_P(ExternalSSTFileBasicTest, IngestionWithRangeDeletions) {
|
||||
|
||||
// overlaps with L5 file but not memtable or L0 file, so flush is skipped and
|
||||
// file is ingested into L4
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, {}, {}, {{5, 15}}, file_id++,
|
||||
write_global_seqno, &true_data));
|
||||
ASSERT_OK(GenerateAndAddExternalFile(
|
||||
options, {}, {}, {{5, 15}}, file_id++, write_global_seqno,
|
||||
verify_checksums_before_ingest, &true_data));
|
||||
ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), ++last_seqno);
|
||||
ASSERT_EQ(2, NumTableFilesAtLevel(0));
|
||||
ASSERT_EQ(1, NumTableFilesAtLevel(kNumLevels - 2));
|
||||
@@ -727,7 +755,8 @@ TEST_P(ExternalSSTFileBasicTest, IngestionWithRangeDeletions) {
|
||||
// count increases by two.
|
||||
ASSERT_OK(GenerateAndAddExternalFile(
|
||||
options, {100, 140}, {ValueType::kTypeValue, ValueType::kTypeValue},
|
||||
file_id++, write_global_seqno, &true_data));
|
||||
file_id++, write_global_seqno, verify_checksums_before_ingest,
|
||||
&true_data));
|
||||
ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), ++last_seqno);
|
||||
ASSERT_EQ(4, NumTableFilesAtLevel(0));
|
||||
ASSERT_EQ(1, NumTableFilesAtLevel(kNumLevels - 2));
|
||||
@@ -740,15 +769,101 @@ TEST_P(ExternalSSTFileBasicTest, IngestionWithRangeDeletions) {
|
||||
// seqnum.
|
||||
ASSERT_OK(GenerateAndAddExternalFile(
|
||||
options, {151, 175}, {ValueType::kTypeValue, ValueType::kTypeValue},
|
||||
{{160, 200}}, file_id++, write_global_seqno, &true_data));
|
||||
{{160, 200}}, file_id++, write_global_seqno,
|
||||
verify_checksums_before_ingest, &true_data));
|
||||
ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), last_seqno);
|
||||
ASSERT_EQ(4, NumTableFilesAtLevel(0));
|
||||
ASSERT_EQ(1, NumTableFilesAtLevel(kNumLevels - 2));
|
||||
ASSERT_EQ(2, NumTableFilesAtLevel(options.num_levels - 1));
|
||||
}
|
||||
|
||||
TEST_P(ExternalSSTFileBasicTest, IngestFileWithBadBlockChecksum) {
|
||||
bool change_checksum_called = false;
|
||||
const auto& change_checksum = [&](void* arg) {
|
||||
if (!change_checksum_called) {
|
||||
char* buf = reinterpret_cast<char*>(arg);
|
||||
assert(nullptr != buf);
|
||||
buf[0] ^= 0x1;
|
||||
change_checksum_called = true;
|
||||
}
|
||||
};
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"BlockBasedTableBuilder::WriteRawBlock:TamperWithChecksum",
|
||||
change_checksum);
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
int file_id = 0;
|
||||
bool write_global_seqno = std::get<0>(GetParam());
|
||||
bool verify_checksums_before_ingest = std::get<1>(GetParam());
|
||||
do {
|
||||
Options options = CurrentOptions();
|
||||
DestroyAndReopen(options);
|
||||
std::map<std::string, std::string> true_data;
|
||||
Status s = GenerateAndAddExternalFile(
|
||||
options, {1, 2, 3, 4, 5, 6}, ValueType::kTypeValue, file_id++,
|
||||
write_global_seqno, verify_checksums_before_ingest, &true_data);
|
||||
if (verify_checksums_before_ingest) {
|
||||
ASSERT_NOK(s);
|
||||
} else {
|
||||
ASSERT_OK(s);
|
||||
}
|
||||
change_checksum_called = false;
|
||||
} while (ChangeOptionsForFileIngestionTest());
|
||||
}
|
||||
|
||||
TEST_P(ExternalSSTFileBasicTest, IngestFileWithFirstByteTampered) {
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
int file_id = 0;
|
||||
EnvOptions env_options;
|
||||
do {
|
||||
Options options = CurrentOptions();
|
||||
std::string file_path = sst_files_dir_ + ToString(file_id++);
|
||||
SstFileWriter sst_file_writer(env_options, options);
|
||||
Status s = sst_file_writer.Open(file_path);
|
||||
ASSERT_OK(s);
|
||||
for (int i = 0; i != 100; ++i) {
|
||||
std::string key = Key(i);
|
||||
std::string value = Key(i) + ToString(0);
|
||||
ASSERT_OK(sst_file_writer.Put(key, value));
|
||||
}
|
||||
ASSERT_OK(sst_file_writer.Finish());
|
||||
{
|
||||
// Get file size
|
||||
uint64_t file_size = 0;
|
||||
ASSERT_OK(env_->GetFileSize(file_path, &file_size));
|
||||
ASSERT_GT(file_size, 8);
|
||||
std::unique_ptr<RandomRWFile> rwfile;
|
||||
ASSERT_OK(env_->NewRandomRWFile(file_path, &rwfile, EnvOptions()));
|
||||
// Manually corrupt the file
|
||||
// We deterministically corrupt the first byte because we currently
|
||||
// cannot choose a random offset. The reason for this limitation is that
|
||||
// we do not checksum property block at present.
|
||||
const uint64_t offset = 0;
|
||||
char scratch[8] = {0};
|
||||
Slice buf;
|
||||
ASSERT_OK(rwfile->Read(offset, sizeof(scratch), &buf, scratch));
|
||||
scratch[0] ^= 0xff; // flip one bit
|
||||
ASSERT_OK(rwfile->Write(offset, buf));
|
||||
}
|
||||
// Ingest file.
|
||||
IngestExternalFileOptions ifo;
|
||||
ifo.write_global_seqno = std::get<0>(GetParam());
|
||||
ifo.verify_checksums_before_ingest = std::get<1>(GetParam());
|
||||
s = db_->IngestExternalFile({file_path}, ifo);
|
||||
if (ifo.verify_checksums_before_ingest) {
|
||||
ASSERT_NOK(s);
|
||||
} else {
|
||||
ASSERT_OK(s);
|
||||
}
|
||||
} while (ChangeOptionsForFileIngestionTest());
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(ExternalSSTFileBasicTest, ExternalSSTFileBasicTest,
|
||||
testing::Bool());
|
||||
testing::Values(std::make_tuple(true, true),
|
||||
std::make_tuple(true, false),
|
||||
std::make_tuple(false, true),
|
||||
std::make_tuple(false, false)));
|
||||
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
|
||||
@@ -316,6 +316,13 @@ Status ExternalSstFileIngestionJob::GetIngestedFileInfo(
|
||||
return status;
|
||||
}
|
||||
|
||||
if (ingestion_options_.verify_checksums_before_ingest) {
|
||||
status = table_reader->VerifyChecksum();
|
||||
}
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
|
||||
// Get the external file properties
|
||||
auto props = table_reader->GetTableProperties();
|
||||
const auto& uprops = props->user_collected_properties;
|
||||
|
||||
@@ -15,8 +15,9 @@
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
class ExternalSSTFileTest : public DBTestBase,
|
||||
public ::testing::WithParamInterface<bool> {
|
||||
class ExternalSSTFileTest
|
||||
: public DBTestBase,
|
||||
public ::testing::WithParamInterface<std::tuple<bool, bool>> {
|
||||
public:
|
||||
ExternalSSTFileTest() : DBTestBase("/external_sst_file_test") {
|
||||
sst_files_dir_ = dbname_ + "/sst_files/";
|
||||
@@ -32,7 +33,7 @@ class ExternalSSTFileTest : public DBTestBase,
|
||||
const Options options,
|
||||
std::vector<std::pair<std::string, std::string>> data, int file_id = -1,
|
||||
bool allow_global_seqno = false, bool write_global_seqno = false,
|
||||
bool sort_data = false,
|
||||
bool verify_checksums_before_ingest = true, bool sort_data = false,
|
||||
std::map<std::string, std::string>* true_data = nullptr,
|
||||
ColumnFamilyHandle* cfh = nullptr) {
|
||||
// Generate a file id if not provided
|
||||
@@ -76,6 +77,7 @@ class ExternalSSTFileTest : public DBTestBase,
|
||||
IngestExternalFileOptions ifo;
|
||||
ifo.allow_global_seqno = allow_global_seqno;
|
||||
ifo.write_global_seqno = allow_global_seqno ? write_global_seqno : false;
|
||||
ifo.verify_checksums_before_ingest = verify_checksums_before_ingest;
|
||||
if (cfh) {
|
||||
s = db_->IngestExternalFile(cfh, {file_path}, ifo);
|
||||
} else {
|
||||
@@ -155,31 +157,32 @@ class ExternalSSTFileTest : public DBTestBase,
|
||||
Status GenerateAndAddExternalFile(
|
||||
const Options options, std::vector<std::pair<int, std::string>> data,
|
||||
int file_id = -1, bool allow_global_seqno = false,
|
||||
bool write_global_seqno = false, bool sort_data = false,
|
||||
bool write_global_seqno = false,
|
||||
bool verify_checksums_before_ingest = true, bool sort_data = false,
|
||||
std::map<std::string, std::string>* true_data = nullptr,
|
||||
ColumnFamilyHandle* cfh = nullptr) {
|
||||
std::vector<std::pair<std::string, std::string>> file_data;
|
||||
for (auto& entry : data) {
|
||||
file_data.emplace_back(Key(entry.first), entry.second);
|
||||
}
|
||||
return GenerateAndAddExternalFile(options, file_data, file_id,
|
||||
allow_global_seqno, write_global_seqno,
|
||||
sort_data, true_data, cfh);
|
||||
return GenerateAndAddExternalFile(
|
||||
options, file_data, file_id, allow_global_seqno, write_global_seqno,
|
||||
verify_checksums_before_ingest, sort_data, true_data, cfh);
|
||||
}
|
||||
|
||||
Status GenerateAndAddExternalFile(
|
||||
const Options options, std::vector<int> keys, int file_id = -1,
|
||||
bool allow_global_seqno = false, bool write_global_seqno = false,
|
||||
bool sort_data = false,
|
||||
bool verify_checksums_before_ingest = true, bool sort_data = false,
|
||||
std::map<std::string, std::string>* true_data = nullptr,
|
||||
ColumnFamilyHandle* cfh = nullptr) {
|
||||
std::vector<std::pair<std::string, std::string>> file_data;
|
||||
for (auto& k : keys) {
|
||||
file_data.emplace_back(Key(k), Key(k) + ToString(file_id));
|
||||
}
|
||||
return GenerateAndAddExternalFile(options, file_data, file_id,
|
||||
allow_global_seqno, write_global_seqno,
|
||||
sort_data, true_data, cfh);
|
||||
return GenerateAndAddExternalFile(
|
||||
options, file_data, file_id, allow_global_seqno, write_global_seqno,
|
||||
verify_checksums_before_ingest, sort_data, true_data, cfh);
|
||||
}
|
||||
|
||||
Status DeprecatedAddFile(const std::vector<std::string>& files,
|
||||
@@ -1157,13 +1160,13 @@ TEST_P(ExternalSSTFileTest, PickedLevel) {
|
||||
std::map<std::string, std::string> true_data;
|
||||
|
||||
// File 0 will go to last level (L3)
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, {1, 10}, -1, false, false,
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, {1, 10}, -1, false, false, true,
|
||||
false, &true_data));
|
||||
EXPECT_EQ(FilesPerLevel(), "0,0,0,1");
|
||||
|
||||
// File 1 will go to level L2 (since it overlap with file 0 in L3)
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, {2, 9}, -1, false, false, false,
|
||||
&true_data));
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, {2, 9}, -1, false, false, true,
|
||||
false, &true_data));
|
||||
EXPECT_EQ(FilesPerLevel(), "0,0,1,1");
|
||||
|
||||
rocksdb::SyncPoint::GetInstance()->LoadDependency({
|
||||
@@ -1192,13 +1195,13 @@ TEST_P(ExternalSSTFileTest, PickedLevel) {
|
||||
|
||||
// This file overlaps with file 0 (L3), file 1 (L2) and the
|
||||
// output of compaction going to L1
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, {4, 7}, -1, false, false, false,
|
||||
&true_data));
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, {4, 7}, -1, false, false, true,
|
||||
false, &true_data));
|
||||
EXPECT_EQ(FilesPerLevel(), "5,0,1,1");
|
||||
|
||||
// This file does not overlap with any file or with the running compaction
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, {9000, 9001}, -1, false, false,
|
||||
false, &true_data));
|
||||
true, false, &true_data));
|
||||
EXPECT_EQ(FilesPerLevel(), "5,0,1,2");
|
||||
|
||||
// Hold compaction from finishing
|
||||
@@ -1318,7 +1321,7 @@ TEST_F(ExternalSSTFileTest, IngestNonExistingFile) {
|
||||
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact(true));
|
||||
|
||||
|
||||
// After full compaction, there should be only 1 file.
|
||||
std::vector<std::string> files;
|
||||
env_->GetChildren(dbname_, &files);
|
||||
@@ -1429,12 +1432,12 @@ TEST_F(ExternalSSTFileTest, PickedLevelDynamic) {
|
||||
// This file overlaps with the output of the compaction (going to L3)
|
||||
// so the file will be added to L0 since L3 is the base level
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, {31, 32, 33, 34}, -1, false,
|
||||
false, false, &true_data));
|
||||
false, true, false, &true_data));
|
||||
EXPECT_EQ(FilesPerLevel(), "5");
|
||||
|
||||
// This file does not overlap with the current running compactiong
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, {9000, 9001}, -1, false, false,
|
||||
false, &true_data));
|
||||
true, false, &true_data));
|
||||
EXPECT_EQ(FilesPerLevel(), "5,0,0,1");
|
||||
|
||||
// Hold compaction from finishing
|
||||
@@ -1449,25 +1452,25 @@ TEST_F(ExternalSSTFileTest, PickedLevelDynamic) {
|
||||
Reopen(options);
|
||||
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, {1, 15, 19}, -1, false, false,
|
||||
false, &true_data));
|
||||
true, false, &true_data));
|
||||
ASSERT_EQ(FilesPerLevel(), "1,0,0,3");
|
||||
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, {1000, 1001, 1002}, -1, false,
|
||||
false, false, &true_data));
|
||||
false, true, false, &true_data));
|
||||
ASSERT_EQ(FilesPerLevel(), "1,0,0,4");
|
||||
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, {500, 600, 700}, -1, false,
|
||||
false, false, &true_data));
|
||||
false, true, false, &true_data));
|
||||
ASSERT_EQ(FilesPerLevel(), "1,0,0,5");
|
||||
|
||||
// File 5 overlaps with file 2 (L3 / base level)
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, {2, 10}, -1, false, false,
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, {2, 10}, -1, false, false, true,
|
||||
false, &true_data));
|
||||
ASSERT_EQ(FilesPerLevel(), "2,0,0,5");
|
||||
|
||||
// File 6 overlaps with file 2 (L3 / base level) and file 5 (L0)
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, {3, 9}, -1, false, false, false,
|
||||
&true_data));
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, {3, 9}, -1, false, false, true,
|
||||
false, &true_data));
|
||||
ASSERT_EQ(FilesPerLevel(), "3,0,0,5");
|
||||
|
||||
// Verify data in files
|
||||
@@ -1486,7 +1489,7 @@ TEST_F(ExternalSSTFileTest, PickedLevelDynamic) {
|
||||
|
||||
// File 7 overlaps with file 4 (L3)
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, {650, 651, 652}, -1, false,
|
||||
false, false, &true_data));
|
||||
false, true, false, &true_data));
|
||||
ASSERT_EQ(FilesPerLevel(), "5,0,0,5");
|
||||
|
||||
VerifyDBFromMap(true_data, &kcnt, false);
|
||||
@@ -1626,7 +1629,8 @@ TEST_P(ExternalSSTFileTest, IngestFileWithGlobalSeqnoRandomized) {
|
||||
options.level0_slowdown_writes_trigger = 256;
|
||||
options.level0_stop_writes_trigger = 256;
|
||||
|
||||
bool write_global_seqno = GetParam();
|
||||
bool write_global_seqno = std::get<0>(GetParam());
|
||||
bool verify_checksums_before_ingest = std::get<1>(GetParam());
|
||||
for (int iter = 0; iter < 2; iter++) {
|
||||
bool write_to_memtable = (iter == 0);
|
||||
DestroyAndReopen(options);
|
||||
@@ -1650,9 +1654,9 @@ TEST_P(ExternalSSTFileTest, IngestFileWithGlobalSeqnoRandomized) {
|
||||
true_data[entry.first] = entry.second;
|
||||
}
|
||||
} else {
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, random_data, -1, true,
|
||||
write_global_seqno, true,
|
||||
&true_data));
|
||||
ASSERT_OK(GenerateAndAddExternalFile(
|
||||
options, random_data, -1, true, write_global_seqno,
|
||||
verify_checksums_before_ingest, true, &true_data));
|
||||
}
|
||||
}
|
||||
size_t kcnt = 0;
|
||||
@@ -1681,9 +1685,11 @@ TEST_P(ExternalSSTFileTest, IngestFileWithGlobalSeqnoAssignedLevel) {
|
||||
for (int i = 0; i <= 20; i++) {
|
||||
file_data.emplace_back(Key(i), "L4");
|
||||
}
|
||||
bool write_global_seqno = GetParam();
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, file_data, -1, true,
|
||||
write_global_seqno, false, &true_data));
|
||||
bool write_global_seqno = std::get<0>(GetParam());
|
||||
bool verify_checksums_before_ingest = std::get<1>(GetParam());
|
||||
ASSERT_OK(GenerateAndAddExternalFile(
|
||||
options, file_data, -1, true, write_global_seqno,
|
||||
verify_checksums_before_ingest, false, &true_data));
|
||||
|
||||
// This file dont overlap with anything in the DB, will go to L4
|
||||
ASSERT_EQ("0,0,0,0,1", FilesPerLevel());
|
||||
@@ -1693,8 +1699,9 @@ TEST_P(ExternalSSTFileTest, IngestFileWithGlobalSeqnoAssignedLevel) {
|
||||
for (int i = 80; i <= 130; i++) {
|
||||
file_data.emplace_back(Key(i), "L0");
|
||||
}
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, file_data, -1, true,
|
||||
write_global_seqno, false, &true_data));
|
||||
ASSERT_OK(GenerateAndAddExternalFile(
|
||||
options, file_data, -1, true, write_global_seqno,
|
||||
verify_checksums_before_ingest, false, &true_data));
|
||||
|
||||
// This file overlap with the memtable, so it will flush it and add
|
||||
// it self to L0
|
||||
@@ -1705,8 +1712,9 @@ TEST_P(ExternalSSTFileTest, IngestFileWithGlobalSeqnoAssignedLevel) {
|
||||
for (int i = 30; i <= 50; i++) {
|
||||
file_data.emplace_back(Key(i), "L4");
|
||||
}
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, file_data, -1, true,
|
||||
write_global_seqno, false, &true_data));
|
||||
ASSERT_OK(GenerateAndAddExternalFile(
|
||||
options, file_data, -1, true, write_global_seqno,
|
||||
verify_checksums_before_ingest, false, &true_data));
|
||||
|
||||
// This file dont overlap with anything in the DB and fit in L4 as well
|
||||
ASSERT_EQ("2,0,0,0,2", FilesPerLevel());
|
||||
@@ -1716,8 +1724,9 @@ TEST_P(ExternalSSTFileTest, IngestFileWithGlobalSeqnoAssignedLevel) {
|
||||
for (int i = 10; i <= 40; i++) {
|
||||
file_data.emplace_back(Key(i), "L3");
|
||||
}
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, file_data, -1, true,
|
||||
write_global_seqno, false, &true_data));
|
||||
ASSERT_OK(GenerateAndAddExternalFile(
|
||||
options, file_data, -1, true, write_global_seqno,
|
||||
verify_checksums_before_ingest, false, &true_data));
|
||||
|
||||
// This file overlap with files in L4, we will ingest it in L3
|
||||
ASSERT_EQ("2,0,0,1,2", FilesPerLevel());
|
||||
@@ -1740,17 +1749,20 @@ TEST_P(ExternalSSTFileTest, IngestFileWithGlobalSeqnoMemtableFlush) {
|
||||
&entries_in_memtable);
|
||||
ASSERT_GE(entries_in_memtable, 1);
|
||||
|
||||
bool write_global_seqno = GetParam();
|
||||
bool write_global_seqno = std::get<0>(GetParam());
|
||||
bool verify_checksums_before_ingest = std::get<1>(GetParam());
|
||||
// No need for flush
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, {90, 100, 110}, -1, true,
|
||||
write_global_seqno, false, &true_data));
|
||||
ASSERT_OK(GenerateAndAddExternalFile(
|
||||
options, {90, 100, 110}, -1, true, write_global_seqno,
|
||||
verify_checksums_before_ingest, false, &true_data));
|
||||
db_->GetIntProperty(DB::Properties::kNumEntriesActiveMemTable,
|
||||
&entries_in_memtable);
|
||||
ASSERT_GE(entries_in_memtable, 1);
|
||||
|
||||
// This file will flush the memtable
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, {19, 20, 21}, -1, true,
|
||||
write_global_seqno, false, &true_data));
|
||||
ASSERT_OK(GenerateAndAddExternalFile(
|
||||
options, {19, 20, 21}, -1, true, write_global_seqno,
|
||||
verify_checksums_before_ingest, false, &true_data));
|
||||
db_->GetIntProperty(DB::Properties::kNumEntriesActiveMemTable,
|
||||
&entries_in_memtable);
|
||||
ASSERT_EQ(entries_in_memtable, 0);
|
||||
@@ -1764,15 +1776,17 @@ TEST_P(ExternalSSTFileTest, IngestFileWithGlobalSeqnoMemtableFlush) {
|
||||
ASSERT_GE(entries_in_memtable, 1);
|
||||
|
||||
// No need for flush, this file keys fit between the memtable keys
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, {202, 203, 204}, -1, true,
|
||||
write_global_seqno, false, &true_data));
|
||||
ASSERT_OK(GenerateAndAddExternalFile(
|
||||
options, {202, 203, 204}, -1, true, write_global_seqno,
|
||||
verify_checksums_before_ingest, false, &true_data));
|
||||
db_->GetIntProperty(DB::Properties::kNumEntriesActiveMemTable,
|
||||
&entries_in_memtable);
|
||||
ASSERT_GE(entries_in_memtable, 1);
|
||||
|
||||
// This file will flush the memtable
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, {206, 207}, -1, true, false,
|
||||
write_global_seqno, &true_data));
|
||||
ASSERT_OK(GenerateAndAddExternalFile(
|
||||
options, {206, 207}, -1, true, false, write_global_seqno,
|
||||
verify_checksums_before_ingest, &true_data));
|
||||
db_->GetIntProperty(DB::Properties::kNumEntriesActiveMemTable,
|
||||
&entries_in_memtable);
|
||||
ASSERT_EQ(entries_in_memtable, 0);
|
||||
@@ -1790,13 +1804,16 @@ TEST_P(ExternalSSTFileTest, L0SortingIssue) {
|
||||
ASSERT_OK(Put(Key(1), "memtable"));
|
||||
ASSERT_OK(Put(Key(10), "memtable"));
|
||||
|
||||
bool write_global_seqno = GetParam();
|
||||
bool write_global_seqno = std::get<0>(GetParam());
|
||||
bool verify_checksums_before_ingest = std::get<1>(GetParam());
|
||||
// No Flush needed, No global seqno needed, Ingest in L1
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, {7, 8}, -1, true,
|
||||
write_global_seqno, false));
|
||||
write_global_seqno,
|
||||
verify_checksums_before_ingest, false));
|
||||
// No Flush needed, but need a global seqno, Ingest in L0
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, {7, 8}, -1, true,
|
||||
write_global_seqno, false));
|
||||
write_global_seqno,
|
||||
verify_checksums_before_ingest, false));
|
||||
printf("%s\n", FilesPerLevel().c_str());
|
||||
|
||||
// Overwrite what we added using external files
|
||||
@@ -2032,11 +2049,12 @@ TEST_P(ExternalSSTFileTest, IngestionListener) {
|
||||
options.listeners.emplace_back(listener);
|
||||
CreateAndReopenWithCF({"koko", "toto"}, options);
|
||||
|
||||
bool write_global_seqno = GetParam();
|
||||
bool write_global_seqno = std::get<0>(GetParam());
|
||||
bool verify_checksums_before_ingest = std::get<1>(GetParam());
|
||||
// Ingest into default cf
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, {1, 2}, -1, true,
|
||||
write_global_seqno, true, nullptr,
|
||||
handles_[0]));
|
||||
ASSERT_OK(GenerateAndAddExternalFile(
|
||||
options, {1, 2}, -1, true, write_global_seqno,
|
||||
verify_checksums_before_ingest, true, nullptr, handles_[0]));
|
||||
ASSERT_EQ(listener->ingested_files.size(), 1);
|
||||
ASSERT_EQ(listener->ingested_files.back().cf_name, "default");
|
||||
ASSERT_EQ(listener->ingested_files.back().global_seqno, 0);
|
||||
@@ -2046,9 +2064,9 @@ TEST_P(ExternalSSTFileTest, IngestionListener) {
|
||||
"default");
|
||||
|
||||
// Ingest into cf1
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, {1, 2}, -1, true,
|
||||
write_global_seqno, true, nullptr,
|
||||
handles_[1]));
|
||||
ASSERT_OK(GenerateAndAddExternalFile(
|
||||
options, {1, 2}, -1, true, write_global_seqno,
|
||||
verify_checksums_before_ingest, true, nullptr, handles_[1]));
|
||||
ASSERT_EQ(listener->ingested_files.size(), 2);
|
||||
ASSERT_EQ(listener->ingested_files.back().cf_name, "koko");
|
||||
ASSERT_EQ(listener->ingested_files.back().global_seqno, 0);
|
||||
@@ -2058,9 +2076,9 @@ TEST_P(ExternalSSTFileTest, IngestionListener) {
|
||||
"koko");
|
||||
|
||||
// Ingest into cf2
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, {1, 2}, -1, true,
|
||||
write_global_seqno, true, nullptr,
|
||||
handles_[2]));
|
||||
ASSERT_OK(GenerateAndAddExternalFile(
|
||||
options, {1, 2}, -1, true, write_global_seqno,
|
||||
verify_checksums_before_ingest, true, nullptr, handles_[2]));
|
||||
ASSERT_EQ(listener->ingested_files.size(), 3);
|
||||
ASSERT_EQ(listener->ingested_files.back().cf_name, "toto");
|
||||
ASSERT_EQ(listener->ingested_files.back().global_seqno, 0);
|
||||
@@ -2126,7 +2144,8 @@ TEST_P(ExternalSSTFileTest, IngestBehind) {
|
||||
IngestExternalFileOptions ifo;
|
||||
ifo.allow_global_seqno = true;
|
||||
ifo.ingest_behind = true;
|
||||
ifo.write_global_seqno = GetParam();
|
||||
ifo.write_global_seqno = std::get<0>(GetParam());
|
||||
ifo.verify_checksums_before_ingest = std::get<1>(GetParam());
|
||||
|
||||
// Can't ingest behind since allow_ingest_behind isn't set to true
|
||||
ASSERT_NOK(GenerateAndAddExternalFileIngestBehind(options, ifo,
|
||||
@@ -2215,7 +2234,10 @@ TEST_F(ExternalSSTFileTest, SkipBloomFilter) {
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(ExternalSSTFileTest, ExternalSSTFileTest,
|
||||
testing::Bool());
|
||||
testing::Values(std::make_tuple(false, false),
|
||||
std::make_tuple(false, true),
|
||||
std::make_tuple(true, false),
|
||||
std::make_tuple(true, true)));
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
|
||||
+17
-9
@@ -24,14 +24,15 @@
|
||||
#include "db/event_helpers.h"
|
||||
#include "db/log_reader.h"
|
||||
#include "db/log_writer.h"
|
||||
#include "db/memtable.h"
|
||||
#include "db/memtable_list.h"
|
||||
#include "db/merge_context.h"
|
||||
#include "db/range_tombstone_fragmenter.h"
|
||||
#include "db/version_set.h"
|
||||
#include "monitoring/iostats_context_imp.h"
|
||||
#include "monitoring/perf_context_imp.h"
|
||||
#include "monitoring/thread_status_util.h"
|
||||
#include "port/port.h"
|
||||
#include "db/memtable.h"
|
||||
#include "rocksdb/db.h"
|
||||
#include "rocksdb/env.h"
|
||||
#include "rocksdb/statistics.h"
|
||||
@@ -210,6 +211,8 @@ Status FlushJob::Run(LogsWithPrepTracker* prep_tracker,
|
||||
uint64_t prev_fsync_nanos = 0;
|
||||
uint64_t prev_range_sync_nanos = 0;
|
||||
uint64_t prev_prepare_write_nanos = 0;
|
||||
uint64_t prev_cpu_write_nanos = 0;
|
||||
uint64_t prev_cpu_read_nanos = 0;
|
||||
if (measure_io_stats_) {
|
||||
prev_perf_level = GetPerfLevel();
|
||||
SetPerfLevel(PerfLevel::kEnableTime);
|
||||
@@ -217,6 +220,8 @@ Status FlushJob::Run(LogsWithPrepTracker* prep_tracker,
|
||||
prev_fsync_nanos = IOSTATS(fsync_nanos);
|
||||
prev_range_sync_nanos = IOSTATS(range_sync_nanos);
|
||||
prev_prepare_write_nanos = IOSTATS(prepare_write_nanos);
|
||||
prev_cpu_write_nanos = IOSTATS(cpu_write_nanos);
|
||||
prev_cpu_read_nanos = IOSTATS(cpu_read_nanos);
|
||||
}
|
||||
|
||||
// This will release and re-acquire the mutex.
|
||||
@@ -268,6 +273,10 @@ Status FlushJob::Run(LogsWithPrepTracker* prep_tracker,
|
||||
stream << "file_fsync_nanos" << (IOSTATS(fsync_nanos) - prev_fsync_nanos);
|
||||
stream << "file_prepare_write_nanos"
|
||||
<< (IOSTATS(prepare_write_nanos) - prev_prepare_write_nanos);
|
||||
stream << "file_cpu_write_nanos"
|
||||
<< (IOSTATS(cpu_write_nanos) - prev_cpu_write_nanos);
|
||||
stream << "file_cpu_read_nanos"
|
||||
<< (IOSTATS(cpu_read_nanos) - prev_cpu_read_nanos);
|
||||
}
|
||||
|
||||
return s;
|
||||
@@ -284,6 +293,7 @@ Status FlushJob::WriteLevel0Table() {
|
||||
ThreadStatus::STAGE_FLUSH_WRITE_L0);
|
||||
db_mutex_->AssertHeld();
|
||||
const uint64_t start_micros = db_options_.env->NowMicros();
|
||||
const uint64_t start_cpu_micros = db_options_.env->NowCPUNanos() / 1000;
|
||||
Status s;
|
||||
{
|
||||
auto write_hint = cfd_->CalculateSSTWriteHint(0);
|
||||
@@ -295,7 +305,8 @@ Status FlushJob::WriteLevel0Table() {
|
||||
// memtable and its associated range deletion memtable, respectively, at
|
||||
// corresponding indexes.
|
||||
std::vector<InternalIterator*> memtables;
|
||||
std::vector<InternalIterator*> range_del_iters;
|
||||
std::vector<std::unique_ptr<FragmentedRangeTombstoneIterator>>
|
||||
range_del_iters;
|
||||
ReadOptions ro;
|
||||
ro.total_order_seek = true;
|
||||
Arena arena;
|
||||
@@ -308,9 +319,9 @@ Status FlushJob::WriteLevel0Table() {
|
||||
cfd_->GetName().c_str(), job_context_->job_id, m->GetNextLogNumber());
|
||||
memtables.push_back(m->NewIterator(ro, &arena));
|
||||
auto* range_del_iter =
|
||||
m->NewRangeTombstoneIterator(ro, versions_->LastSequence());
|
||||
m->NewRangeTombstoneIterator(ro, kMaxSequenceNumber);
|
||||
if (range_del_iter != nullptr) {
|
||||
range_del_iters.push_back(range_del_iter);
|
||||
range_del_iters.emplace_back(range_del_iter);
|
||||
}
|
||||
total_num_entries += m->num_entries();
|
||||
total_num_deletes += m->num_deletes();
|
||||
@@ -329,10 +340,6 @@ Status FlushJob::WriteLevel0Table() {
|
||||
ScopedArenaIterator iter(
|
||||
NewMergingIterator(&cfd_->internal_comparator(), &memtables[0],
|
||||
static_cast<int>(memtables.size()), &arena));
|
||||
std::unique_ptr<InternalIterator> range_del_iter(NewMergingIterator(
|
||||
&cfd_->internal_comparator(),
|
||||
range_del_iters.empty() ? nullptr : &range_del_iters[0],
|
||||
static_cast<int>(range_del_iters.size())));
|
||||
ROCKS_LOG_INFO(db_options_.info_log,
|
||||
"[%s] [JOB %d] Level-0 flush table #%" PRIu64 ": started",
|
||||
cfd_->GetName().c_str(), job_context_->job_id,
|
||||
@@ -358,7 +365,7 @@ Status FlushJob::WriteLevel0Table() {
|
||||
s = BuildTable(
|
||||
dbname_, db_options_.env, *cfd_->ioptions(), mutable_cf_options_,
|
||||
env_options_, cfd_->table_cache(), iter.get(),
|
||||
std::move(range_del_iter), &meta_, cfd_->internal_comparator(),
|
||||
std::move(range_del_iters), &meta_, cfd_->internal_comparator(),
|
||||
cfd_->int_tbl_prop_collector_factories(), cfd_->GetID(),
|
||||
cfd_->GetName(), existing_snapshots_,
|
||||
earliest_write_conflict_snapshot_, snapshot_checker_,
|
||||
@@ -403,6 +410,7 @@ Status FlushJob::WriteLevel0Table() {
|
||||
// Note that here we treat flush as level 0 compaction in internal stats
|
||||
InternalStats::CompactionStats stats(CompactionReason::kFlush, 1);
|
||||
stats.micros = db_options_.env->NowMicros() - start_micros;
|
||||
stats.cpu_micros = db_options_.env->NowCPUNanos() / 1000 - start_cpu_micros;
|
||||
stats.bytes_written = meta_.fd.GetFileSize();
|
||||
MeasureTime(stats_, FLUSH_TIME, stats.micros);
|
||||
cfd_->internal_stats()->AddCompactionStats(0 /* level */, stats);
|
||||
|
||||
+6
-11
@@ -279,7 +279,6 @@ TEST_F(FlushJobTest, FlushMemtablesMultipleColumnFamilies) {
|
||||
*cfd->GetLatestMutableCFOptions(), kMaxSequenceNumber);
|
||||
mem->SetID(i);
|
||||
mem->Ref();
|
||||
mem->TEST_AtomicFlushSequenceNumber() = 123;
|
||||
|
||||
for (size_t j = 0; j != num_keys_per_memtable; ++j) {
|
||||
std::string key(ToString(j + i * num_keys_per_memtable));
|
||||
@@ -325,17 +324,13 @@ TEST_F(FlushJobTest, FlushMemtablesMultipleColumnFamilies) {
|
||||
const auto& mems = flush_jobs[i].GetMemTables();
|
||||
mems_list.push_back(&mems);
|
||||
}
|
||||
autovector<MemTableList*> imm_lists;
|
||||
autovector<const MutableCFOptions*> mutable_cf_options_list;
|
||||
for (auto cfd : all_cfds) {
|
||||
imm_lists.push_back(cfd->imm());
|
||||
mutable_cf_options_list.push_back(cfd->GetLatestMutableCFOptions());
|
||||
}
|
||||
|
||||
bool atomic_flush_commit_in_progress = false;
|
||||
Status s = MemTableList::TryInstallMemtableFlushResults(
|
||||
imm_lists, all_cfds, mutable_cf_options_list, mems_list,
|
||||
&atomic_flush_commit_in_progress, nullptr /* logs_prep_tracker */,
|
||||
Status s = InstallMemtableAtomicFlushResults(
|
||||
nullptr /* imm_lists */, all_cfds, mutable_cf_options_list, mems_list,
|
||||
versions_.get(), &mutex_, file_metas, &job_context.memtables_to_free,
|
||||
nullptr /* db_directory */, nullptr /* log_buffer */);
|
||||
ASSERT_OK(s);
|
||||
@@ -370,17 +365,17 @@ TEST_F(FlushJobTest, Snapshots) {
|
||||
auto new_mem = cfd->ConstructNewMemtable(*cfd->GetLatestMutableCFOptions(),
|
||||
kMaxSequenceNumber);
|
||||
|
||||
std::vector<SequenceNumber> snapshots;
|
||||
std::set<SequenceNumber> snapshots_set;
|
||||
int keys = 10000;
|
||||
int max_inserts_per_keys = 8;
|
||||
|
||||
Random rnd(301);
|
||||
for (int i = 0; i < keys / 2; ++i) {
|
||||
snapshots.push_back(rnd.Uniform(keys * (max_inserts_per_keys / 2)) + 1);
|
||||
snapshots_set.insert(snapshots.back());
|
||||
snapshots_set.insert(rnd.Uniform(keys * (max_inserts_per_keys / 2)) + 1);
|
||||
}
|
||||
std::sort(snapshots.begin(), snapshots.end());
|
||||
// set has already removed the duplicate snapshots
|
||||
std::vector<SequenceNumber> snapshots(snapshots_set.begin(),
|
||||
snapshots_set.end());
|
||||
|
||||
new_mem->Ref();
|
||||
SequenceNumber current_seqno = 0;
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
#include "db/db_iter.h"
|
||||
#include "db/dbformat.h"
|
||||
#include "db/job_context.h"
|
||||
#include "db/range_del_aggregator_v2.h"
|
||||
#include "db/range_del_aggregator.h"
|
||||
#include "db/range_tombstone_fragmenter.h"
|
||||
#include "rocksdb/env.h"
|
||||
#include "rocksdb/slice.h"
|
||||
@@ -73,8 +73,8 @@ class ForwardLevelIterator : public InternalIterator {
|
||||
delete file_iter_;
|
||||
}
|
||||
|
||||
RangeDelAggregatorV2 range_del_agg(&cfd_->internal_comparator(),
|
||||
kMaxSequenceNumber /* upper_bound */);
|
||||
ReadRangeDelAggregator range_del_agg(&cfd_->internal_comparator(),
|
||||
kMaxSequenceNumber /* upper_bound */);
|
||||
file_iter_ = cfd_->table_cache()->NewIterator(
|
||||
read_options_, *(cfd_->soptions()), cfd_->internal_comparator(),
|
||||
*files_[file_index_],
|
||||
@@ -610,8 +610,8 @@ void ForwardIterator::RebuildIterators(bool refresh_sv) {
|
||||
// New
|
||||
sv_ = cfd_->GetReferencedSuperVersion(&(db_->mutex_));
|
||||
}
|
||||
RangeDelAggregatorV2 range_del_agg(&cfd_->internal_comparator(),
|
||||
kMaxSequenceNumber /* upper_bound */);
|
||||
ReadRangeDelAggregator range_del_agg(&cfd_->internal_comparator(),
|
||||
kMaxSequenceNumber /* upper_bound */);
|
||||
mutable_iter_ = sv_->mem->NewIterator(read_options_, &arena_);
|
||||
sv_->imm->AddIterators(read_options_, &imm_iters_, &arena_);
|
||||
if (!read_options_.ignore_range_deletions) {
|
||||
@@ -669,8 +669,8 @@ void ForwardIterator::RenewIterators() {
|
||||
|
||||
mutable_iter_ = svnew->mem->NewIterator(read_options_, &arena_);
|
||||
svnew->imm->AddIterators(read_options_, &imm_iters_, &arena_);
|
||||
RangeDelAggregatorV2 range_del_agg(&cfd_->internal_comparator(),
|
||||
kMaxSequenceNumber /* upper_bound */);
|
||||
ReadRangeDelAggregator range_del_agg(&cfd_->internal_comparator(),
|
||||
kMaxSequenceNumber /* upper_bound */);
|
||||
if (!read_options_.ignore_range_deletions) {
|
||||
std::unique_ptr<FragmentedRangeTombstoneIterator> range_del_iter(
|
||||
svnew->mem->NewRangeTombstoneIterator(
|
||||
|
||||
+10
-5
@@ -45,6 +45,8 @@ const std::map<LevelStatType, LevelStat> InternalStats::compaction_level_stats =
|
||||
{LevelStatType::READ_MBPS, LevelStat{"ReadMBps", "Rd(MB/s)"}},
|
||||
{LevelStatType::WRITE_MBPS, LevelStat{"WriteMBps", "Wr(MB/s)"}},
|
||||
{LevelStatType::COMP_SEC, LevelStat{"CompSec", "Comp(sec)"}},
|
||||
{LevelStatType::COMP_CPU_SEC,
|
||||
LevelStat{"CompMergeCPU", "CompMergeCPU(sec)"}},
|
||||
{LevelStatType::COMP_COUNT, LevelStat{"CompCount", "Comp(cnt)"}},
|
||||
{LevelStatType::AVG_SEC, LevelStat{"AvgSec", "Avg(sec)"}},
|
||||
{LevelStatType::KEY_IN, LevelStat{"KeyIn", "KeyIn"}},
|
||||
@@ -64,7 +66,7 @@ void PrintLevelStatsHeader(char* buf, size_t len, const std::string& cf_name) {
|
||||
};
|
||||
int line_size = snprintf(
|
||||
buf + written_size, len - written_size,
|
||||
"Level %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s\n",
|
||||
"Level %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s\n",
|
||||
// Note that we skip COMPACTED_FILES and merge it with Files column
|
||||
hdr(LevelStatType::NUM_FILES), hdr(LevelStatType::SIZE_BYTES),
|
||||
hdr(LevelStatType::SCORE), hdr(LevelStatType::READ_GB),
|
||||
@@ -72,9 +74,9 @@ void PrintLevelStatsHeader(char* buf, size_t len, const std::string& cf_name) {
|
||||
hdr(LevelStatType::WRITE_GB), hdr(LevelStatType::W_NEW_GB),
|
||||
hdr(LevelStatType::MOVED_GB), hdr(LevelStatType::WRITE_AMP),
|
||||
hdr(LevelStatType::READ_MBPS), hdr(LevelStatType::WRITE_MBPS),
|
||||
hdr(LevelStatType::COMP_SEC), hdr(LevelStatType::COMP_COUNT),
|
||||
hdr(LevelStatType::AVG_SEC), hdr(LevelStatType::KEY_IN),
|
||||
hdr(LevelStatType::KEY_DROP));
|
||||
hdr(LevelStatType::COMP_SEC), hdr(LevelStatType::COMP_CPU_SEC),
|
||||
hdr(LevelStatType::COMP_COUNT), hdr(LevelStatType::AVG_SEC),
|
||||
hdr(LevelStatType::KEY_IN), hdr(LevelStatType::KEY_DROP));
|
||||
|
||||
written_size += line_size;
|
||||
snprintf(buf + written_size, len - written_size, "%s\n",
|
||||
@@ -106,6 +108,7 @@ void PrepareLevelStats(std::map<LevelStatType, double>* level_stats,
|
||||
(*level_stats)[LevelStatType::WRITE_MBPS] =
|
||||
stats.bytes_written / kMB / elapsed;
|
||||
(*level_stats)[LevelStatType::COMP_SEC] = stats.micros / kMicrosInSec;
|
||||
(*level_stats)[LevelStatType::COMP_CPU_SEC] = stats.cpu_micros / kMicrosInSec;
|
||||
(*level_stats)[LevelStatType::COMP_COUNT] = stats.count;
|
||||
(*level_stats)[LevelStatType::AVG_SEC] =
|
||||
stats.count == 0 ? 0 : stats.micros / kMicrosInSec / stats.count;
|
||||
@@ -132,7 +135,8 @@ void PrintLevelStats(char* buf, size_t len, const std::string& name,
|
||||
"%5.1f " /* W-Amp */
|
||||
"%8.1f " /* Rd(MB/s) */
|
||||
"%8.1f " /* Wr(MB/s) */
|
||||
"%9.0f " /* Comp(sec) */
|
||||
"%9.2f " /* Comp(sec) */
|
||||
"%17.2f " /* CompMergeCPU(sec) */
|
||||
"%9d " /* Comp(cnt) */
|
||||
"%8.3f " /* Avg(sec) */
|
||||
"%7s " /* KeyIn */
|
||||
@@ -153,6 +157,7 @@ void PrintLevelStats(char* buf, size_t len, const std::string& name,
|
||||
stat_value.at(LevelStatType::READ_MBPS),
|
||||
stat_value.at(LevelStatType::WRITE_MBPS),
|
||||
stat_value.at(LevelStatType::COMP_SEC),
|
||||
stat_value.at(LevelStatType::COMP_CPU_SEC),
|
||||
static_cast<int>(stat_value.at(LevelStatType::COMP_COUNT)),
|
||||
stat_value.at(LevelStatType::AVG_SEC),
|
||||
NumberToHumanString(
|
||||
|
||||
+10
-2
@@ -71,6 +71,7 @@ enum class LevelStatType {
|
||||
READ_MBPS,
|
||||
WRITE_MBPS,
|
||||
COMP_SEC,
|
||||
COMP_CPU_SEC,
|
||||
COMP_COUNT,
|
||||
AVG_SEC,
|
||||
KEY_IN,
|
||||
@@ -135,6 +136,7 @@ class InternalStats {
|
||||
// compactions that produced data for the specified "level".
|
||||
struct CompactionStats {
|
||||
uint64_t micros;
|
||||
uint64_t cpu_micros;
|
||||
|
||||
// The number of bytes read from all non-output levels
|
||||
uint64_t bytes_read_non_output_levels;
|
||||
@@ -172,6 +174,7 @@ class InternalStats {
|
||||
|
||||
explicit CompactionStats()
|
||||
: micros(0),
|
||||
cpu_micros(0),
|
||||
bytes_read_non_output_levels(0),
|
||||
bytes_read_output_level(0),
|
||||
bytes_written(0),
|
||||
@@ -190,6 +193,7 @@ class InternalStats {
|
||||
|
||||
explicit CompactionStats(CompactionReason reason, int c)
|
||||
: micros(0),
|
||||
cpu_micros(0),
|
||||
bytes_read_non_output_levels(0),
|
||||
bytes_read_output_level(0),
|
||||
bytes_written(0),
|
||||
@@ -214,14 +218,14 @@ class InternalStats {
|
||||
|
||||
explicit CompactionStats(const CompactionStats& c)
|
||||
: micros(c.micros),
|
||||
cpu_micros(c.cpu_micros),
|
||||
bytes_read_non_output_levels(c.bytes_read_non_output_levels),
|
||||
bytes_read_output_level(c.bytes_read_output_level),
|
||||
bytes_written(c.bytes_written),
|
||||
bytes_moved(c.bytes_moved),
|
||||
num_input_files_in_non_output_levels(
|
||||
c.num_input_files_in_non_output_levels),
|
||||
num_input_files_in_output_level(
|
||||
c.num_input_files_in_output_level),
|
||||
num_input_files_in_output_level(c.num_input_files_in_output_level),
|
||||
num_output_files(c.num_output_files),
|
||||
num_input_records(c.num_input_records),
|
||||
num_dropped_records(c.num_dropped_records),
|
||||
@@ -234,6 +238,7 @@ class InternalStats {
|
||||
|
||||
void Clear() {
|
||||
this->micros = 0;
|
||||
this->cpu_micros = 0;
|
||||
this->bytes_read_non_output_levels = 0;
|
||||
this->bytes_read_output_level = 0;
|
||||
this->bytes_written = 0;
|
||||
@@ -252,6 +257,7 @@ class InternalStats {
|
||||
|
||||
void Add(const CompactionStats& c) {
|
||||
this->micros += c.micros;
|
||||
this->cpu_micros += c.cpu_micros;
|
||||
this->bytes_read_non_output_levels += c.bytes_read_non_output_levels;
|
||||
this->bytes_read_output_level += c.bytes_read_output_level;
|
||||
this->bytes_written += c.bytes_written;
|
||||
@@ -272,6 +278,7 @@ class InternalStats {
|
||||
|
||||
void Subtract(const CompactionStats& c) {
|
||||
this->micros -= c.micros;
|
||||
this->cpu_micros -= c.cpu_micros;
|
||||
this->bytes_read_non_output_levels -= c.bytes_read_non_output_levels;
|
||||
this->bytes_read_output_level -= c.bytes_read_output_level;
|
||||
this->bytes_written -= c.bytes_written;
|
||||
@@ -595,6 +602,7 @@ class InternalStats {
|
||||
|
||||
struct CompactionStats {
|
||||
uint64_t micros;
|
||||
uint64_t cpu_micros;
|
||||
uint64_t bytes_read_non_output_levels;
|
||||
uint64_t bytes_read_output_level;
|
||||
uint64_t bytes_written;
|
||||
|
||||
@@ -175,6 +175,9 @@ struct JobContext {
|
||||
size_t num_alive_log_files = 0;
|
||||
uint64_t size_log_to_delete = 0;
|
||||
|
||||
// Snapshot taken before flush/compaction job.
|
||||
std::unique_ptr<ManagedSnapshot> job_snapshot;
|
||||
|
||||
explicit JobContext(int _job_id, bool create_superversion = false) {
|
||||
job_id = _job_id;
|
||||
manifest_file_number = 0;
|
||||
@@ -204,6 +207,7 @@ struct JobContext {
|
||||
|
||||
memtables_to_free.clear();
|
||||
logs_to_free.clear();
|
||||
job_snapshot.reset();
|
||||
}
|
||||
|
||||
~JobContext() {
|
||||
|
||||
@@ -905,6 +905,7 @@ class TestFileOperationListener : public EventListener {
|
||||
if (info.status.ok()) {
|
||||
++file_reads_success_;
|
||||
}
|
||||
ReportDuration(info);
|
||||
}
|
||||
|
||||
void OnFileWriteFinish(const FileOperationInfo& info) override {
|
||||
@@ -912,6 +913,7 @@ class TestFileOperationListener : public EventListener {
|
||||
if (info.status.ok()) {
|
||||
++file_writes_success_;
|
||||
}
|
||||
ReportDuration(info);
|
||||
}
|
||||
|
||||
bool ShouldBeNotifiedOnFileIO() override { return true; }
|
||||
@@ -920,6 +922,13 @@ class TestFileOperationListener : public EventListener {
|
||||
std::atomic<size_t> file_reads_success_;
|
||||
std::atomic<size_t> file_writes_;
|
||||
std::atomic<size_t> file_writes_success_;
|
||||
|
||||
private:
|
||||
void ReportDuration(const FileOperationInfo& info) const {
|
||||
auto duration = std::chrono::duration_cast<std::chrono::nanoseconds>(
|
||||
info.finish_timestamp - info.start_timestamp);
|
||||
ASSERT_GT(duration.count(), 0);
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(EventListenerTest, OnFileOperationTest) {
|
||||
|
||||
+8
-9
@@ -13,17 +13,16 @@
|
||||
#include <memory>
|
||||
#include <string.h>
|
||||
|
||||
#include "port/jemalloc_helper.h"
|
||||
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
#ifdef ROCKSDB_JEMALLOC
|
||||
#ifdef __FreeBSD__
|
||||
#include <malloc_np.h>
|
||||
#else
|
||||
#include "jemalloc/jemalloc.h"
|
||||
|
||||
#ifdef JEMALLOC_NO_RENAME
|
||||
#define malloc_stats_print je_malloc_stats_print
|
||||
#endif
|
||||
#endif
|
||||
|
||||
typedef struct {
|
||||
char* cur;
|
||||
@@ -41,10 +40,10 @@ static void GetJemallocStatus(void* mstat_arg, const char* status) {
|
||||
snprintf(mstat->cur, buf_size, "%s", status);
|
||||
mstat->cur += status_len;
|
||||
}
|
||||
#endif // ROCKSDB_JEMALLOC
|
||||
|
||||
#ifdef ROCKSDB_JEMALLOC
|
||||
void DumpMallocStats(std::string* stats) {
|
||||
if (!HasJemalloc()) {
|
||||
return;
|
||||
}
|
||||
MallocStatus mstat;
|
||||
const unsigned int kMallocStatusLen = 1000000;
|
||||
std::unique_ptr<char[]> buf{new char[kMallocStatusLen + 1]};
|
||||
@@ -56,5 +55,5 @@ void DumpMallocStats(std::string* stats) {
|
||||
#else
|
||||
void DumpMallocStats(std::string*) {}
|
||||
#endif // ROCKSDB_JEMALLOC
|
||||
}
|
||||
} // namespace rocksdb
|
||||
#endif // !ROCKSDB_LITE
|
||||
|
||||
+9
-8
@@ -110,10 +110,10 @@ MemTable::MemTable(const InternalKeyComparator& cmp,
|
||||
assert(!ShouldScheduleFlush());
|
||||
|
||||
if (prefix_extractor_ && moptions_.memtable_prefix_bloom_bits > 0) {
|
||||
prefix_bloom_.reset(new DynamicBloom(
|
||||
&arena_, moptions_.memtable_prefix_bloom_bits, ioptions.bloom_locality,
|
||||
6 /* hard coded 6 probes */, nullptr, moptions_.memtable_huge_page_size,
|
||||
ioptions.info_log));
|
||||
prefix_bloom_.reset(
|
||||
new DynamicBloom(&arena_, moptions_.memtable_prefix_bloom_bits,
|
||||
ioptions.bloom_locality, 6 /* hard coded 6 probes */,
|
||||
moptions_.memtable_huge_page_size, ioptions.info_log));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -414,7 +414,8 @@ InternalIterator* MemTable::NewIterator(const ReadOptions& read_options,
|
||||
|
||||
FragmentedRangeTombstoneIterator* MemTable::NewRangeTombstoneIterator(
|
||||
const ReadOptions& read_options, SequenceNumber read_seq) {
|
||||
if (read_options.ignore_range_deletions || is_range_del_table_empty_) {
|
||||
if (read_options.ignore_range_deletions ||
|
||||
is_range_del_table_empty_.load(std::memory_order_relaxed)) {
|
||||
return nullptr;
|
||||
}
|
||||
auto* unfragmented_iter = new MemTableIterator(
|
||||
@@ -428,7 +429,7 @@ FragmentedRangeTombstoneIterator* MemTable::NewRangeTombstoneIterator(
|
||||
comparator_.comparator);
|
||||
|
||||
auto* fragmented_iter = new FragmentedRangeTombstoneIterator(
|
||||
fragmented_tombstone_list, read_seq, comparator_.comparator);
|
||||
fragmented_tombstone_list, comparator_.comparator, read_seq);
|
||||
return fragmented_iter;
|
||||
}
|
||||
|
||||
@@ -563,8 +564,8 @@ bool MemTable::Add(SequenceNumber s, ValueType type,
|
||||
!first_seqno_.compare_exchange_weak(cur_earliest_seqno, s)) {
|
||||
}
|
||||
}
|
||||
if (is_range_del_table_empty_ && type == kTypeRangeDeletion) {
|
||||
is_range_del_table_empty_ = false;
|
||||
if (type == kTypeRangeDeletion) {
|
||||
is_range_del_table_empty_.store(false, std::memory_order_relaxed);
|
||||
}
|
||||
UpdateOldestKeyTime();
|
||||
return true;
|
||||
|
||||
+9
-7
@@ -386,14 +386,16 @@ class MemTable {
|
||||
|
||||
uint64_t GetID() const { return id_; }
|
||||
|
||||
SequenceNumber& TEST_AtomicFlushSequenceNumber() {
|
||||
return atomic_flush_seqno_;
|
||||
void SetFlushCompleted(bool completed) { flush_completed_ = completed; }
|
||||
|
||||
uint64_t GetFileNumber() const { return file_number_; }
|
||||
|
||||
void SetFileNumber(uint64_t file_num) { file_number_ = file_num; }
|
||||
|
||||
void SetFlushInProgress(bool in_progress) {
|
||||
flush_in_progress_ = in_progress;
|
||||
}
|
||||
|
||||
void TEST_SetFlushCompleted(bool completed) { flush_completed_ = completed; }
|
||||
|
||||
void TEST_SetFileNumber(uint64_t file_num) { file_number_ = file_num; }
|
||||
|
||||
private:
|
||||
enum FlushStateEnum { FLUSH_NOT_REQUESTED, FLUSH_REQUESTED, FLUSH_SCHEDULED };
|
||||
|
||||
@@ -409,7 +411,7 @@ class MemTable {
|
||||
ConcurrentArena arena_;
|
||||
std::unique_ptr<MemTableRep> table_;
|
||||
std::unique_ptr<MemTableRep> range_del_table_;
|
||||
bool is_range_del_table_empty_;
|
||||
std::atomic_bool is_range_del_table_empty_;
|
||||
|
||||
// Total data size of all data inserted
|
||||
std::atomic<uint64_t> data_size_;
|
||||
|
||||
+102
-223
@@ -159,7 +159,7 @@ bool MemTableListVersion::GetFromList(
|
||||
|
||||
Status MemTableListVersion::AddRangeTombstoneIterators(
|
||||
const ReadOptions& read_opts, Arena* /*arena*/,
|
||||
RangeDelAggregatorV2* range_del_agg) {
|
||||
RangeDelAggregator* range_del_agg) {
|
||||
assert(range_del_agg != nullptr);
|
||||
for (auto& m : memlist_) {
|
||||
// Using kMaxSequenceNumber is OK because these are immutable memtables.
|
||||
@@ -260,228 +260,6 @@ void MemTableListVersion::TrimHistory(autovector<MemTable*>* to_delete) {
|
||||
}
|
||||
}
|
||||
|
||||
// Try to record multiple successful flush to the MANIFEST as an atomic unit.
|
||||
// This function may just return Status::OK if there has already been
|
||||
// a concurrent thread performing actual recording.
|
||||
Status MemTableList::TryInstallMemtableFlushResults(
|
||||
autovector<MemTableList*>& imm_lists,
|
||||
const autovector<ColumnFamilyData*>& cfds,
|
||||
const autovector<const MutableCFOptions*>& mutable_cf_options_list,
|
||||
const autovector<const autovector<MemTable*>*>& mems_list,
|
||||
bool* atomic_flush_commit_in_progress, LogsWithPrepTracker* prep_tracker,
|
||||
VersionSet* vset, InstrumentedMutex* mu,
|
||||
const autovector<FileMetaData>& file_metas,
|
||||
autovector<MemTable*>* to_delete, Directory* db_directory,
|
||||
LogBuffer* log_buffer) {
|
||||
AutoThreadOperationStageUpdater stage_updater(
|
||||
ThreadStatus::STAGE_MEMTABLE_INSTALL_FLUSH_RESULTS);
|
||||
mu->AssertHeld();
|
||||
|
||||
for (size_t k = 0; k != mems_list.size(); ++k) {
|
||||
for (size_t i = 0; i != mems_list[k]->size(); ++i) {
|
||||
assert(i == 0 || (*mems_list[k])[i]->GetEdits()->NumEntries() == 0);
|
||||
(*mems_list[k])[i]->flush_completed_ = true;
|
||||
(*mems_list[k])[i]->file_number_ = file_metas[k].fd.GetNumber();
|
||||
}
|
||||
}
|
||||
|
||||
assert(atomic_flush_commit_in_progress != nullptr);
|
||||
Status s;
|
||||
if (*atomic_flush_commit_in_progress) {
|
||||
// If the function reaches here, there must be a concurrent thread that
|
||||
// have already started recording to MANIFEST. Therefore we should just
|
||||
// return Status::OK and let the othe thread finish writing to MANIFEST on
|
||||
// our behalf.
|
||||
return s;
|
||||
}
|
||||
|
||||
// If the function reaches here, the current thread will start writing to
|
||||
// MANIFEST. It may record to MANIFEST the flush results of other flushes.
|
||||
*atomic_flush_commit_in_progress = true;
|
||||
|
||||
auto comp = [&imm_lists](size_t lh, size_t rh) {
|
||||
const auto& memlist1 = imm_lists[lh]->current_->memlist_;
|
||||
const auto& memlist2 = imm_lists[rh]->current_->memlist_;
|
||||
auto it1 = memlist1.rbegin();
|
||||
auto it2 = memlist2.rbegin();
|
||||
return (*it1)->atomic_flush_seqno_ > (*it2)->atomic_flush_seqno_;
|
||||
};
|
||||
// The top of the heap is the memtable with smallest atomic_flush_seqno_.
|
||||
std::priority_queue<size_t, std::vector<size_t>, decltype(comp)> heap(comp);
|
||||
// Sequence number of the oldest unfinished atomic flush.
|
||||
SequenceNumber min_unfinished_seqno = kMaxSequenceNumber;
|
||||
// Populate the heap with first element of each imm iff. it has been
|
||||
// flushed to storage, i.e. flush_completed_ is true.
|
||||
size_t num = imm_lists.size();
|
||||
assert(num == cfds.size());
|
||||
for (size_t i = 0; i != num; ++i) {
|
||||
std::list<MemTable*>& memlist = imm_lists[i]->current_->memlist_;
|
||||
if (memlist.empty()) {
|
||||
continue;
|
||||
}
|
||||
auto it = memlist.rbegin();
|
||||
if ((*it)->flush_completed_) {
|
||||
heap.emplace(i);
|
||||
} else if (min_unfinished_seqno > (*it)->atomic_flush_seqno_) {
|
||||
min_unfinished_seqno = (*it)->atomic_flush_seqno_;
|
||||
}
|
||||
}
|
||||
|
||||
while (s.ok() && !heap.empty()) {
|
||||
autovector<size_t> batch;
|
||||
SequenceNumber seqno = kMaxSequenceNumber;
|
||||
// Pop from the heap the memtables that belong to the same atomic flush,
|
||||
// namely their atomic_flush_seqno_ are equal.
|
||||
do {
|
||||
size_t pos = heap.top();
|
||||
const auto& memlist = imm_lists[pos]->current_->memlist_;
|
||||
MemTable* mem = *(memlist.rbegin());
|
||||
if (seqno == kMaxSequenceNumber) {
|
||||
// First mem in this batch.
|
||||
seqno = mem->atomic_flush_seqno_;
|
||||
batch.emplace_back(pos);
|
||||
heap.pop();
|
||||
} else if (mem->atomic_flush_seqno_ == seqno) {
|
||||
// mem has the same atomic_flush_seqno_, thus in the same atomic flush.
|
||||
batch.emplace_back(pos);
|
||||
heap.pop();
|
||||
} else if (mem->atomic_flush_seqno_ > seqno) {
|
||||
// mem belongs to another atomic flush with higher seqno, break the
|
||||
// loop.
|
||||
break;
|
||||
}
|
||||
} while (!heap.empty());
|
||||
if (seqno >= min_unfinished_seqno) {
|
||||
// If there is an older, unfinished atomic flush, then we should not
|
||||
// proceed.
|
||||
TEST_SYNC_POINT_CALLBACK(
|
||||
"MemTableList::TryInstallMemtableFlushResults:"
|
||||
"HasOlderUnfinishedAtomicFlush:0",
|
||||
nullptr);
|
||||
break;
|
||||
}
|
||||
|
||||
// Found the earliest, complete atomic flush. No earlier atomic flush is
|
||||
// pending. Therefore ready to record it to the MANIFEST.
|
||||
uint32_t num_entries = 0;
|
||||
autovector<ColumnFamilyData*> tmp_cfds;
|
||||
autovector<const MutableCFOptions*> tmp_mutable_cf_options_list;
|
||||
std::vector<autovector<MemTable*>> memtables_to_flush;
|
||||
autovector<autovector<VersionEdit*>> edit_lists;
|
||||
for (auto pos : batch) {
|
||||
tmp_cfds.emplace_back(cfds[pos]);
|
||||
tmp_mutable_cf_options_list.emplace_back(mutable_cf_options_list[pos]);
|
||||
const auto& memlist = imm_lists[pos]->current_->memlist_;
|
||||
uint64_t batch_file_number = 0;
|
||||
autovector<MemTable*> tmp_mems;
|
||||
autovector<VersionEdit*> edits;
|
||||
for (auto it = memlist.rbegin(); it != memlist.rend(); ++it) {
|
||||
MemTable* m = *it;
|
||||
if (!m->flush_completed_ ||
|
||||
(it != memlist.rbegin() && m->file_number_ != batch_file_number)) {
|
||||
break;
|
||||
}
|
||||
if (it == memlist.rbegin()) {
|
||||
batch_file_number = m->file_number_;
|
||||
edits.push_back(m->GetEdits());
|
||||
++num_entries;
|
||||
}
|
||||
tmp_mems.push_back(m);
|
||||
}
|
||||
edit_lists.push_back(edits);
|
||||
memtables_to_flush.push_back(tmp_mems);
|
||||
}
|
||||
TEST_SYNC_POINT_CALLBACK(
|
||||
"MemTableList::TryInstallMemtableFlushResults:FoundBatchToCommit:0",
|
||||
&num_entries);
|
||||
|
||||
// Mark the version edits as an atomic group
|
||||
uint32_t remaining = num_entries;
|
||||
for (auto& edit_list : edit_lists) {
|
||||
assert(edit_list.size() == 1);
|
||||
edit_list[0]->MarkAtomicGroup(--remaining);
|
||||
}
|
||||
assert(remaining == 0);
|
||||
|
||||
size_t batch_sz = batch.size();
|
||||
assert(batch_sz > 0);
|
||||
assert(batch_sz == memtables_to_flush.size());
|
||||
assert(batch_sz == tmp_cfds.size());
|
||||
assert(batch_sz == edit_lists.size());
|
||||
|
||||
if (vset->db_options()->allow_2pc) {
|
||||
for (size_t i = 0; i != batch_sz; ++i) {
|
||||
auto& edit_list = edit_lists[i];
|
||||
assert(!edit_list.empty());
|
||||
edit_list.back()->SetMinLogNumberToKeep(
|
||||
PrecomputeMinLogNumberToKeep(vset, *tmp_cfds[i], edit_list,
|
||||
memtables_to_flush[i], prep_tracker));
|
||||
}
|
||||
}
|
||||
// this can release and reacquire the mutex.
|
||||
s = vset->LogAndApply(tmp_cfds, tmp_mutable_cf_options_list, edit_lists, mu,
|
||||
db_directory);
|
||||
|
||||
for (const auto pos : batch) {
|
||||
imm_lists[pos]->InstallNewVersion();
|
||||
}
|
||||
|
||||
if (s.ok()) {
|
||||
for (size_t i = 0; i != batch_sz; ++i) {
|
||||
if (tmp_cfds[i]->IsDropped()) {
|
||||
continue;
|
||||
}
|
||||
size_t pos = batch[i];
|
||||
for (auto m : memtables_to_flush[i]) {
|
||||
assert(m->file_number_ > 0);
|
||||
uint64_t mem_id = m->GetID();
|
||||
ROCKS_LOG_BUFFER(log_buffer,
|
||||
"[%s] Level-0 commit table #%" PRIu64
|
||||
": memtable #%" PRIu64 " done",
|
||||
tmp_cfds[i]->GetName().c_str(), m->file_number_,
|
||||
mem_id);
|
||||
imm_lists[pos]->current_->Remove(m, to_delete);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (size_t i = 0; i != batch_sz; ++i) {
|
||||
size_t pos = batch[i];
|
||||
for (auto m : memtables_to_flush[i]) {
|
||||
uint64_t mem_id = m->GetID();
|
||||
ROCKS_LOG_BUFFER(log_buffer,
|
||||
"[%s] Level-0 commit table #%" PRIu64
|
||||
": memtable #%" PRIu64 " failed",
|
||||
tmp_cfds[i]->GetName().c_str(), m->file_number_,
|
||||
mem_id);
|
||||
m->flush_completed_ = false;
|
||||
m->flush_in_progress_ = false;
|
||||
m->edit_.Clear();
|
||||
m->file_number_ = 0;
|
||||
imm_lists[pos]->num_flush_not_started_++;
|
||||
}
|
||||
imm_lists[pos]->imm_flush_needed.store(true, std::memory_order_release);
|
||||
}
|
||||
}
|
||||
// Adjust the heap AFTER installing new MemTableListVersions because the
|
||||
// compare function 'comp' needs to capture the most up-to-date state of
|
||||
// imm_lists.
|
||||
for (auto pos : batch) {
|
||||
const auto& memlist = imm_lists[pos]->current_->memlist_;
|
||||
if (!memlist.empty()) {
|
||||
MemTable* mem = *(memlist.rbegin());
|
||||
if (mem->flush_completed_) {
|
||||
heap.emplace(pos);
|
||||
} else if (min_unfinished_seqno > mem->atomic_flush_seqno_) {
|
||||
min_unfinished_seqno = mem->atomic_flush_seqno_;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
*atomic_flush_commit_in_progress = false;
|
||||
return s;
|
||||
}
|
||||
|
||||
// Returns true if there is at least one memtable on which flush has
|
||||
// not yet started.
|
||||
bool MemTableList::IsFlushPending() const {
|
||||
@@ -749,4 +527,105 @@ uint64_t MemTableList::PrecomputeMinLogContainingPrepSection(
|
||||
return min_log;
|
||||
}
|
||||
|
||||
// Commit a successful atomic flush in the manifest file.
|
||||
Status InstallMemtableAtomicFlushResults(
|
||||
const autovector<MemTableList*>* imm_lists,
|
||||
const autovector<ColumnFamilyData*>& cfds,
|
||||
const autovector<const MutableCFOptions*>& mutable_cf_options_list,
|
||||
const autovector<const autovector<MemTable*>*>& mems_list, VersionSet* vset,
|
||||
InstrumentedMutex* mu, const autovector<FileMetaData>& file_metas,
|
||||
autovector<MemTable*>* to_delete, Directory* db_directory,
|
||||
LogBuffer* log_buffer) {
|
||||
AutoThreadOperationStageUpdater stage_updater(
|
||||
ThreadStatus::STAGE_MEMTABLE_INSTALL_FLUSH_RESULTS);
|
||||
mu->AssertHeld();
|
||||
|
||||
size_t num = mems_list.size();
|
||||
assert(cfds.size() == num);
|
||||
if (imm_lists != nullptr) {
|
||||
assert(imm_lists->size() == num);
|
||||
}
|
||||
for (size_t k = 0; k != num; ++k) {
|
||||
#ifndef NDEBUG
|
||||
const auto* imm =
|
||||
(imm_lists == nullptr) ? cfds[k]->imm() : imm_lists->at(k);
|
||||
if (!mems_list[k]->empty()) {
|
||||
assert((*mems_list[k])[0]->GetID() == imm->GetEarliestMemTableID());
|
||||
}
|
||||
#endif
|
||||
for (size_t i = 0; i != mems_list[k]->size(); ++i) {
|
||||
assert(i == 0 || (*mems_list[k])[i]->GetEdits()->NumEntries() == 0);
|
||||
(*mems_list[k])[i]->SetFlushCompleted(true);
|
||||
(*mems_list[k])[i]->SetFileNumber(file_metas[k].fd.GetNumber());
|
||||
}
|
||||
}
|
||||
|
||||
Status s;
|
||||
|
||||
autovector<autovector<VersionEdit*>> edit_lists;
|
||||
uint32_t num_entries = 0;
|
||||
for (const auto mems : mems_list) {
|
||||
assert(mems != nullptr);
|
||||
autovector<VersionEdit*> edits;
|
||||
assert(!mems->empty());
|
||||
edits.emplace_back((*mems)[0]->GetEdits());
|
||||
++num_entries;
|
||||
edit_lists.emplace_back(edits);
|
||||
}
|
||||
// Mark the version edits as an atomic group
|
||||
for (auto& edits : edit_lists) {
|
||||
assert(edits.size() == 1);
|
||||
edits[0]->MarkAtomicGroup(--num_entries);
|
||||
}
|
||||
assert(0 == num_entries);
|
||||
|
||||
// this can release and reacquire the mutex.
|
||||
s = vset->LogAndApply(cfds, mutable_cf_options_list, edit_lists, mu,
|
||||
db_directory);
|
||||
|
||||
for (size_t k = 0; k != cfds.size(); ++k) {
|
||||
auto* imm = (imm_lists == nullptr) ? cfds[k]->imm() : imm_lists->at(k);
|
||||
imm->InstallNewVersion();
|
||||
}
|
||||
|
||||
if (s.ok() || s.IsShutdownInProgress()) {
|
||||
for (size_t i = 0; i != cfds.size(); ++i) {
|
||||
if (cfds[i]->IsDropped()) {
|
||||
continue;
|
||||
}
|
||||
auto* imm = (imm_lists == nullptr) ? cfds[i]->imm() : imm_lists->at(i);
|
||||
for (auto m : *mems_list[i]) {
|
||||
assert(m->GetFileNumber() > 0);
|
||||
uint64_t mem_id = m->GetID();
|
||||
ROCKS_LOG_BUFFER(log_buffer,
|
||||
"[%s] Level-0 commit table #%" PRIu64
|
||||
": memtable #%" PRIu64 " done",
|
||||
cfds[i]->GetName().c_str(), m->GetFileNumber(),
|
||||
mem_id);
|
||||
imm->current_->Remove(m, to_delete);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (size_t i = 0; i != cfds.size(); ++i) {
|
||||
auto* imm = (imm_lists == nullptr) ? cfds[i]->imm() : imm_lists->at(i);
|
||||
for (auto m : *mems_list[i]) {
|
||||
uint64_t mem_id = m->GetID();
|
||||
ROCKS_LOG_BUFFER(log_buffer,
|
||||
"[%s] Level-0 commit table #%" PRIu64
|
||||
": memtable #%" PRIu64 " failed",
|
||||
cfds[i]->GetName().c_str(), m->GetFileNumber(),
|
||||
mem_id);
|
||||
m->SetFlushCompleted(false);
|
||||
m->SetFlushInProgress(false);
|
||||
m->GetEdits()->Clear();
|
||||
m->SetFileNumber(0);
|
||||
imm->num_flush_not_started_++;
|
||||
}
|
||||
imm->imm_flush_needed.store(true, std::memory_order_release);
|
||||
}
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
+39
-16
@@ -15,7 +15,7 @@
|
||||
#include "db/dbformat.h"
|
||||
#include "db/logs_with_prep_tracker.h"
|
||||
#include "db/memtable.h"
|
||||
#include "db/range_del_aggregator_v2.h"
|
||||
#include "db/range_del_aggregator.h"
|
||||
#include "monitoring/instrumented_mutex.h"
|
||||
#include "rocksdb/db.h"
|
||||
#include "rocksdb/iterator.h"
|
||||
@@ -31,6 +31,7 @@ class ColumnFamilyData;
|
||||
class InternalKeyComparator;
|
||||
class InstrumentedMutex;
|
||||
class MergeIteratorBuilder;
|
||||
class MemTableList;
|
||||
|
||||
// keeps a list of immutable memtables in a vector. the list is immutable
|
||||
// if refcount is bigger than one. It is used as a state for Get() and
|
||||
@@ -91,7 +92,7 @@ class MemTableListVersion {
|
||||
}
|
||||
|
||||
Status AddRangeTombstoneIterators(const ReadOptions& read_opts, Arena* arena,
|
||||
RangeDelAggregatorV2* range_del_agg);
|
||||
RangeDelAggregator* range_del_agg);
|
||||
|
||||
void AddIterators(const ReadOptions& options,
|
||||
std::vector<InternalIterator*>* iterator_list,
|
||||
@@ -114,6 +115,18 @@ class MemTableListVersion {
|
||||
SequenceNumber GetEarliestSequenceNumber(bool include_history = false) const;
|
||||
|
||||
private:
|
||||
friend class MemTableList;
|
||||
|
||||
friend Status InstallMemtableAtomicFlushResults(
|
||||
const autovector<MemTableList*>* imm_lists,
|
||||
const autovector<ColumnFamilyData*>& cfds,
|
||||
const autovector<const MutableCFOptions*>& mutable_cf_options_list,
|
||||
const autovector<const autovector<MemTable*>*>& mems_list,
|
||||
VersionSet* vset, InstrumentedMutex* mu,
|
||||
const autovector<FileMetaData>& file_meta,
|
||||
autovector<MemTable*>* to_delete, Directory* db_directory,
|
||||
LogBuffer* log_buffer);
|
||||
|
||||
// REQUIRE: m is an immutable memtable
|
||||
void Add(MemTable* m, autovector<MemTable*>* to_delete);
|
||||
// REQUIRE: m is an immutable memtable
|
||||
@@ -132,8 +145,6 @@ class MemTableListVersion {
|
||||
|
||||
void UnrefMemTable(autovector<MemTable*>* to_delete, MemTable* m);
|
||||
|
||||
friend class MemTableList;
|
||||
|
||||
// Immutable MemTables that have not yet been flushed.
|
||||
std::list<MemTable*> memlist_;
|
||||
|
||||
@@ -163,18 +174,6 @@ class MemTableListVersion {
|
||||
// write thread.)
|
||||
class MemTableList {
|
||||
public:
|
||||
// Commit a successful atomic flush in the manifest file
|
||||
static Status TryInstallMemtableFlushResults(
|
||||
autovector<MemTableList*>& imm_lists,
|
||||
const autovector<ColumnFamilyData*>& cfds,
|
||||
const autovector<const MutableCFOptions*>& mutable_cf_options_list,
|
||||
const autovector<const autovector<MemTable*>*>& mems_list,
|
||||
bool* atomic_flush_commit_in_progress, LogsWithPrepTracker* prep_tracker,
|
||||
VersionSet* vset, InstrumentedMutex* mu,
|
||||
const autovector<FileMetaData>& file_meta,
|
||||
autovector<MemTable*>* to_delete, Directory* db_directory,
|
||||
LogBuffer* log_buffer);
|
||||
|
||||
// A list of memtables.
|
||||
explicit MemTableList(int min_write_buffer_number_to_merge,
|
||||
int max_write_buffer_number_to_maintain)
|
||||
@@ -296,6 +295,16 @@ class MemTableList {
|
||||
}
|
||||
|
||||
private:
|
||||
friend Status InstallMemtableAtomicFlushResults(
|
||||
const autovector<MemTableList*>* imm_lists,
|
||||
const autovector<ColumnFamilyData*>& cfds,
|
||||
const autovector<const MutableCFOptions*>& mutable_cf_options_list,
|
||||
const autovector<const autovector<MemTable*>*>& mems_list,
|
||||
VersionSet* vset, InstrumentedMutex* mu,
|
||||
const autovector<FileMetaData>& file_meta,
|
||||
autovector<MemTable*>* to_delete, Directory* db_directory,
|
||||
LogBuffer* log_buffer);
|
||||
|
||||
// DB mutex held
|
||||
void InstallNewVersion();
|
||||
|
||||
@@ -317,4 +326,18 @@ class MemTableList {
|
||||
size_t current_memory_usage_;
|
||||
};
|
||||
|
||||
// Installs memtable atomic flush results.
|
||||
// In most cases, imm_lists is nullptr, and the function simply uses the
|
||||
// immutable memtable lists associated with the cfds. There are unit tests that
|
||||
// installs flush results for external immutable memtable lists other than the
|
||||
// cfds' own immutable memtable lists, e.g. MemTableLIstTest. In this case,
|
||||
// imm_lists parameter is not nullptr.
|
||||
extern Status InstallMemtableAtomicFlushResults(
|
||||
const autovector<MemTableList*>* imm_lists,
|
||||
const autovector<ColumnFamilyData*>& cfds,
|
||||
const autovector<const MutableCFOptions*>& mutable_cf_options_list,
|
||||
const autovector<const autovector<MemTable*>*>& mems_list, VersionSet* vset,
|
||||
InstrumentedMutex* mu, const autovector<FileMetaData>& file_meta,
|
||||
autovector<MemTable*>* to_delete, Directory* db_directory,
|
||||
LogBuffer* log_buffer);
|
||||
} // namespace rocksdb
|
||||
|
||||
+99
-265
@@ -8,7 +8,6 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include "db/merge_context.h"
|
||||
#include "db/range_del_aggregator.h"
|
||||
#include "db/version_set.h"
|
||||
#include "db/write_controller.h"
|
||||
#include "rocksdb/db.h"
|
||||
@@ -86,17 +85,46 @@ class MemTableListTest : public testing::Test {
|
||||
Status Mock_InstallMemtableFlushResults(
|
||||
MemTableList* list, const MutableCFOptions& mutable_cf_options,
|
||||
const autovector<MemTable*>& m, autovector<MemTable*>* to_delete) {
|
||||
autovector<MemTableList*> lists;
|
||||
lists.emplace_back(list);
|
||||
autovector<const autovector<MemTable*>*> mems_list;
|
||||
mems_list.emplace_back(&m);
|
||||
return Mock_InstallMemtableFlushResults(
|
||||
lists, {0} /* cf_ids */, {&mutable_cf_options}, mems_list, to_delete);
|
||||
// Create a mock Logger
|
||||
test::NullLogger logger;
|
||||
LogBuffer log_buffer(DEBUG_LEVEL, &logger);
|
||||
|
||||
CreateDB();
|
||||
// Create a mock VersionSet
|
||||
DBOptions db_options;
|
||||
ImmutableDBOptions immutable_db_options(db_options);
|
||||
EnvOptions env_options;
|
||||
std::shared_ptr<Cache> table_cache(NewLRUCache(50000, 16));
|
||||
WriteBufferManager write_buffer_manager(db_options.db_write_buffer_size);
|
||||
WriteController write_controller(10000000u);
|
||||
|
||||
VersionSet versions(dbname, &immutable_db_options, env_options,
|
||||
table_cache.get(), &write_buffer_manager,
|
||||
&write_controller);
|
||||
std::vector<ColumnFamilyDescriptor> cf_descs;
|
||||
cf_descs.emplace_back(kDefaultColumnFamilyName, ColumnFamilyOptions());
|
||||
cf_descs.emplace_back("one", ColumnFamilyOptions());
|
||||
cf_descs.emplace_back("two", ColumnFamilyOptions());
|
||||
|
||||
EXPECT_OK(versions.Recover(cf_descs, false));
|
||||
|
||||
// Create mock default ColumnFamilyData
|
||||
auto column_family_set = versions.GetColumnFamilySet();
|
||||
LogsWithPrepTracker dummy_prep_tracker;
|
||||
auto cfd = column_family_set->GetDefault();
|
||||
EXPECT_TRUE(nullptr != cfd);
|
||||
uint64_t file_num = file_number.fetch_add(1);
|
||||
// Create dummy mutex.
|
||||
InstrumentedMutex mutex;
|
||||
InstrumentedMutexLock l(&mutex);
|
||||
return list->TryInstallMemtableFlushResults(
|
||||
cfd, mutable_cf_options, m, &dummy_prep_tracker, &versions, &mutex,
|
||||
file_num, to_delete, nullptr, &log_buffer);
|
||||
}
|
||||
|
||||
// Calls MemTableList::InstallMemtableFlushResults() and sets up all
|
||||
// structures needed to call this function.
|
||||
Status Mock_InstallMemtableFlushResults(
|
||||
Status Mock_InstallMemtableAtomicFlushResults(
|
||||
autovector<MemTableList*>& lists, const autovector<uint32_t>& cf_ids,
|
||||
const autovector<const MutableCFOptions*>& mutable_cf_options_list,
|
||||
const autovector<const autovector<MemTable*>*>& mems_list,
|
||||
@@ -128,25 +156,6 @@ class MemTableListTest : public testing::Test {
|
||||
auto column_family_set = versions.GetColumnFamilySet();
|
||||
|
||||
LogsWithPrepTracker dummy_prep_tracker;
|
||||
if (1 == cf_ids.size()) {
|
||||
auto cfd = column_family_set->GetColumnFamily(cf_ids[0]);
|
||||
EXPECT_TRUE(nullptr != cfd);
|
||||
EXPECT_EQ(1, lists.size());
|
||||
MemTableList* list = lists[0];
|
||||
EXPECT_EQ(1, mutable_cf_options_list.size());
|
||||
const MutableCFOptions& mutable_cf_options =
|
||||
*(mutable_cf_options_list.at(0));
|
||||
const autovector<MemTable*>* mems = mems_list.at(0);
|
||||
EXPECT_TRUE(nullptr != mems);
|
||||
|
||||
uint64_t file_num = file_number.fetch_add(1);
|
||||
// Create dummy mutex.
|
||||
InstrumentedMutex mutex;
|
||||
InstrumentedMutexLock l(&mutex);
|
||||
return list->TryInstallMemtableFlushResults(
|
||||
cfd, mutable_cf_options, *mems, &dummy_prep_tracker, &versions,
|
||||
&mutex, file_num, to_delete, nullptr, &log_buffer);
|
||||
}
|
||||
autovector<ColumnFamilyData*> cfds;
|
||||
for (int i = 0; i != static_cast<int>(cf_ids.size()); ++i) {
|
||||
cfds.emplace_back(column_family_set->GetColumnFamily(cf_ids[i]));
|
||||
@@ -159,13 +168,11 @@ class MemTableListTest : public testing::Test {
|
||||
meta.fd = FileDescriptor(file_num, 0, 0);
|
||||
file_metas.emplace_back(meta);
|
||||
}
|
||||
bool atomic_flush_commit_in_progress = false;
|
||||
InstrumentedMutex mutex;
|
||||
InstrumentedMutexLock l(&mutex);
|
||||
return MemTableList::TryInstallMemtableFlushResults(
|
||||
lists, cfds, mutable_cf_options_list, mems_list,
|
||||
&atomic_flush_commit_in_progress, &dummy_prep_tracker, &versions,
|
||||
&mutex, file_metas, to_delete, nullptr, &log_buffer);
|
||||
return InstallMemtableAtomicFlushResults(
|
||||
&lists, cfds, mutable_cf_options_list, mems_list, &versions, &mutex,
|
||||
file_metas, to_delete, nullptr, &log_buffer);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -731,18 +738,28 @@ TEST_F(MemTableListTest, FlushPendingTest) {
|
||||
to_delete.clear();
|
||||
}
|
||||
|
||||
TEST_F(MemTableListTest, FlushMultipleCFsTest) {
|
||||
TEST_F(MemTableListTest, EmptyAtomicFlusTest) {
|
||||
autovector<MemTableList*> lists;
|
||||
autovector<uint32_t> cf_ids;
|
||||
autovector<const MutableCFOptions*> options_list;
|
||||
autovector<const autovector<MemTable*>*> to_flush;
|
||||
autovector<MemTable*> to_delete;
|
||||
Status s = Mock_InstallMemtableAtomicFlushResults(lists, cf_ids, options_list,
|
||||
to_flush, &to_delete);
|
||||
ASSERT_OK(s);
|
||||
ASSERT_TRUE(to_delete.empty());
|
||||
}
|
||||
|
||||
TEST_F(MemTableListTest, AtomicFlusTest) {
|
||||
const int num_cfs = 3;
|
||||
const int num_tables_per_cf = 5;
|
||||
const int num_tables_per_cf = 2;
|
||||
SequenceNumber seq = 1;
|
||||
Status s;
|
||||
|
||||
auto factory = std::make_shared<SkipListFactory>();
|
||||
options.memtable_factory = factory;
|
||||
ImmutableCFOptions ioptions(options);
|
||||
InternalKeyComparator cmp(BytewiseComparator());
|
||||
WriteBufferManager wb(options.db_write_buffer_size);
|
||||
autovector<MemTable*> to_delete;
|
||||
|
||||
// Create MemTableLists
|
||||
int min_write_buffer_number_to_merge = 3;
|
||||
@@ -783,135 +800,72 @@ TEST_F(MemTableListTest, FlushMultipleCFsTest) {
|
||||
std::vector<autovector<MemTable*>> flush_candidates(num_cfs);
|
||||
|
||||
// Nothing to flush
|
||||
for (int i = 0; i != num_cfs; ++i) {
|
||||
auto list = lists[i];
|
||||
for (auto i = 0; i != num_cfs; ++i) {
|
||||
auto* list = lists[i];
|
||||
ASSERT_FALSE(list->IsFlushPending());
|
||||
ASSERT_FALSE(list->imm_flush_needed.load(std::memory_order_acquire));
|
||||
list->PickMemtablesToFlush(nullptr /* memtable_id */, &flush_candidates[i]);
|
||||
ASSERT_EQ(0, static_cast<int>(flush_candidates[i].size()));
|
||||
ASSERT_EQ(0, flush_candidates[i].size());
|
||||
}
|
||||
|
||||
// Request flush even though there is nothing to flush
|
||||
for (int i = 0; i != num_cfs; ++i) {
|
||||
auto list = lists[i];
|
||||
for (auto i = 0; i != num_cfs; ++i) {
|
||||
auto* list = lists[i];
|
||||
list->FlushRequested();
|
||||
ASSERT_FALSE(list->IsFlushPending());
|
||||
ASSERT_FALSE(list->imm_flush_needed.load(std::memory_order_acquire));
|
||||
}
|
||||
|
||||
// Add tables to column families
|
||||
for (int i = 0; i != num_cfs; ++i) {
|
||||
for (int j = 0; j != num_tables_per_cf; ++j) {
|
||||
autovector<MemTable*> to_delete;
|
||||
// Add tables to the immutable memtalbe lists associated with column families
|
||||
for (auto i = 0; i != num_cfs; ++i) {
|
||||
for (auto j = 0; j != num_tables_per_cf; ++j) {
|
||||
lists[i]->Add(tables[i][j], &to_delete);
|
||||
}
|
||||
ASSERT_EQ(num_tables_per_cf, lists[i]->NumNotFlushed());
|
||||
ASSERT_TRUE(lists[i]->IsFlushPending());
|
||||
ASSERT_TRUE(lists[i]->imm_flush_needed.load(std::memory_order_acquire));
|
||||
}
|
||||
|
||||
std::vector<uint64_t> flush_memtable_ids = {1, 1, 0};
|
||||
// +----+
|
||||
// list[0]: |0 1|
|
||||
// list[1]: |0 1|
|
||||
// | +--+
|
||||
// list[2]: |0| 1
|
||||
// +-+
|
||||
// Pick memtables to flush
|
||||
for (auto i = 0; i != num_cfs; ++i) {
|
||||
flush_candidates[i].clear();
|
||||
lists[i]->PickMemtablesToFlush(&flush_memtable_ids[i],
|
||||
&flush_candidates[i]);
|
||||
ASSERT_EQ(flush_memtable_ids[i] - 0 + 1,
|
||||
static_cast<uint64_t>(flush_candidates[i].size()));
|
||||
}
|
||||
autovector<MemTableList*> tmp_lists;
|
||||
autovector<uint32_t> tmp_cf_ids;
|
||||
autovector<const MutableCFOptions*> tmp_options_list;
|
||||
autovector<const autovector<MemTable*>*> to_flush;
|
||||
std::vector<uint64_t> prev_memtable_ids;
|
||||
// For each column family, determine the memtables to flush
|
||||
for (int k = 0; k != 4; ++k) {
|
||||
std::vector<uint64_t> flush_memtable_ids;
|
||||
if (0 == k) {
|
||||
// +----+
|
||||
// list[0]: |0 1| 2 3 4
|
||||
// list[1]: |0 1| 2 3 4
|
||||
// | +--+
|
||||
// list[2]: |0| 1 2 3 4
|
||||
// +-+
|
||||
flush_memtable_ids = {1, 1, 0};
|
||||
} else if (1 == k) {
|
||||
// +----+ +---+
|
||||
// list[0]: |0 1| |2 3| 4
|
||||
// list[1]: |0 1| |2 3| 4
|
||||
// | +--+ +---+
|
||||
// list[2]: |0| 1 2 3 4
|
||||
// +-+
|
||||
flush_memtable_ids = {3, 3, 0};
|
||||
} else if (2 == k) {
|
||||
// +-----+ +---+
|
||||
// list[0]: |0 1| |2 3| 4
|
||||
// list[1]: |0 1| |2 3| 4
|
||||
// | +---+ +---+
|
||||
// | | +-------+
|
||||
// list[2]: |0| |1 2 3| 4
|
||||
// +-+ +-------+
|
||||
flush_memtable_ids = {3, 3, 3};
|
||||
} else {
|
||||
// +-----+ +---+ +-+
|
||||
// list[0]: |0 1| |2 3| |4|
|
||||
// list[1]: |0 1| |2 3| |4|
|
||||
// | +---+ +---+ | |
|
||||
// | | +-------+ | |
|
||||
// list[2]: |0| |1 2 3| |4|
|
||||
// +-+ +-------+ +-+
|
||||
flush_memtable_ids = {4, 4, 4};
|
||||
}
|
||||
assert(num_cfs == static_cast<int>(flush_memtable_ids.size()));
|
||||
|
||||
// Pick memtables to flush
|
||||
for (int i = 0; i != num_cfs; ++i) {
|
||||
flush_candidates[i].clear();
|
||||
lists[i]->PickMemtablesToFlush(&flush_memtable_ids[i],
|
||||
&flush_candidates[i]);
|
||||
for (auto mem : flush_candidates[i]) {
|
||||
mem->TEST_AtomicFlushSequenceNumber() = SequenceNumber(k);
|
||||
}
|
||||
if (prev_memtable_ids.empty()) {
|
||||
ASSERT_EQ(flush_memtable_ids[i] - 0 + 1, flush_candidates[i].size());
|
||||
} else {
|
||||
ASSERT_EQ(flush_memtable_ids[i] - prev_memtable_ids[i],
|
||||
flush_candidates[i].size());
|
||||
}
|
||||
ASSERT_EQ(num_tables_per_cf, lists[i]->NumNotFlushed());
|
||||
ASSERT_FALSE(lists[i]->HasFlushRequested());
|
||||
if (flush_memtable_ids[i] == num_tables_per_cf - 1) {
|
||||
ASSERT_FALSE(
|
||||
lists[i]->imm_flush_needed.load(std::memory_order_acquire));
|
||||
} else {
|
||||
ASSERT_TRUE(lists[i]->imm_flush_needed.load(std::memory_order_acquire));
|
||||
}
|
||||
}
|
||||
prev_memtable_ids = flush_memtable_ids;
|
||||
|
||||
if (k < 3) {
|
||||
for (const auto& mems : flush_candidates) {
|
||||
uint64_t file_num = file_number.fetch_add(1);
|
||||
for (auto m : mems) {
|
||||
m->TEST_SetFlushCompleted(true);
|
||||
m->TEST_SetFileNumber(file_num);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (k == 0) {
|
||||
// Rollback first pick of tables
|
||||
for (int i = 0; i != num_cfs; ++i) {
|
||||
auto list = lists[i];
|
||||
const auto& mems = flush_candidates[i];
|
||||
for (auto m : mems) {
|
||||
m->TEST_SetFileNumber(0);
|
||||
}
|
||||
list->RollbackMemtableFlush(flush_candidates[i], 0);
|
||||
ASSERT_TRUE(list->IsFlushPending());
|
||||
ASSERT_TRUE(list->imm_flush_needed.load(std::memory_order_acquire));
|
||||
}
|
||||
prev_memtable_ids.clear();
|
||||
}
|
||||
|
||||
if (k == 3) {
|
||||
for (int i = 0; i != num_cfs; ++i) {
|
||||
to_flush.emplace_back(&flush_candidates[i]);
|
||||
}
|
||||
for (auto i = 0; i != num_cfs; ++i) {
|
||||
if (!flush_candidates[i].empty()) {
|
||||
to_flush.push_back(&flush_candidates[i]);
|
||||
tmp_lists.push_back(lists[i]);
|
||||
tmp_cf_ids.push_back(i);
|
||||
tmp_options_list.push_back(mutable_cf_options_list[i]);
|
||||
}
|
||||
}
|
||||
|
||||
s = Mock_InstallMemtableFlushResults(lists, cf_ids, mutable_cf_options_list,
|
||||
to_flush, &to_delete);
|
||||
Status s = Mock_InstallMemtableAtomicFlushResults(
|
||||
tmp_lists, tmp_cf_ids, tmp_options_list, to_flush, &to_delete);
|
||||
ASSERT_OK(s);
|
||||
|
||||
for (auto i = 0; i != num_cfs; ++i) {
|
||||
for (auto j = 0; j != num_tables_per_cf; ++j) {
|
||||
if (static_cast<uint64_t>(j) <= flush_memtable_ids[i]) {
|
||||
ASSERT_LT(0, tables[i][j]->GetFileNumber());
|
||||
}
|
||||
}
|
||||
ASSERT_EQ(
|
||||
static_cast<size_t>(num_tables_per_cf) - flush_candidates[i].size(),
|
||||
lists[i]->NumNotFlushed());
|
||||
}
|
||||
|
||||
to_delete.clear();
|
||||
for (auto list : lists) {
|
||||
list->current()->Unref(&to_delete);
|
||||
@@ -933,126 +887,6 @@ TEST_F(MemTableListTest, FlushMultipleCFsTest) {
|
||||
ASSERT_EQ(m, m->Unref());
|
||||
delete m;
|
||||
}
|
||||
to_delete.clear();
|
||||
}
|
||||
|
||||
TEST_F(MemTableListTest, HasOlderAtomicFlush) {
|
||||
const size_t num_cfs = 3;
|
||||
const size_t num_memtables_per_cf = 2;
|
||||
SequenceNumber seq = 1;
|
||||
Status s;
|
||||
|
||||
auto factory = std::make_shared<SkipListFactory>();
|
||||
options.memtable_factory = factory;
|
||||
ImmutableCFOptions ioptions(options);
|
||||
InternalKeyComparator cmp(BytewiseComparator());
|
||||
WriteBufferManager wb(options.db_write_buffer_size);
|
||||
autovector<MemTable*> to_delete;
|
||||
|
||||
// Create MemTableLists
|
||||
int min_write_buffer_number_to_merge = 3;
|
||||
int max_write_buffer_number_to_maintain = 7;
|
||||
autovector<MemTableList*> lists;
|
||||
for (size_t i = 0; i != num_cfs; ++i) {
|
||||
lists.emplace_back(new MemTableList(min_write_buffer_number_to_merge,
|
||||
max_write_buffer_number_to_maintain));
|
||||
}
|
||||
|
||||
autovector<uint32_t> cf_ids;
|
||||
std::vector<std::vector<MemTable*>> tables;
|
||||
autovector<const MutableCFOptions*> mutable_cf_options_list;
|
||||
uint32_t cf_id = 0;
|
||||
for (size_t k = 0; k != num_cfs; ++k) {
|
||||
std::vector<MemTable*> elem;
|
||||
mutable_cf_options_list.emplace_back(new MutableCFOptions(options));
|
||||
uint64_t memtable_id = 0;
|
||||
for (int i = 0; i != num_memtables_per_cf; ++i) {
|
||||
MemTable* mem =
|
||||
new MemTable(cmp, ioptions, *(mutable_cf_options_list.back()), &wb,
|
||||
kMaxSequenceNumber, cf_id);
|
||||
mem->SetID(memtable_id++);
|
||||
mem->Ref();
|
||||
|
||||
std::string value;
|
||||
|
||||
mem->Add(++seq, kTypeValue, "key1", ToString(i));
|
||||
mem->Add(++seq, kTypeValue, "keyN" + ToString(i), "valueN");
|
||||
mem->Add(++seq, kTypeValue, "keyX" + ToString(i), "value");
|
||||
mem->Add(++seq, kTypeValue, "keyM" + ToString(i), "valueM");
|
||||
mem->Add(++seq, kTypeDeletion, "keyX" + ToString(i), "");
|
||||
|
||||
elem.push_back(mem);
|
||||
}
|
||||
tables.emplace_back(elem);
|
||||
cf_ids.push_back(cf_id++);
|
||||
}
|
||||
|
||||
// Add tables to column families' immutable memtable lists
|
||||
for (size_t i = 0; i != num_cfs; ++i) {
|
||||
for (size_t j = 0; j != num_memtables_per_cf; ++j) {
|
||||
lists[i]->Add(tables[i][j], &to_delete);
|
||||
}
|
||||
lists[i]->FlushRequested();
|
||||
ASSERT_EQ(num_memtables_per_cf, lists[i]->NumNotFlushed());
|
||||
ASSERT_TRUE(lists[i]->IsFlushPending());
|
||||
ASSERT_TRUE(lists[i]->imm_flush_needed.load(std::memory_order_acquire));
|
||||
}
|
||||
std::vector<autovector<MemTable*>> flush_candidates(num_cfs);
|
||||
for (size_t i = 0; i != num_cfs; ++i) {
|
||||
lists[i]->PickMemtablesToFlush(nullptr, &flush_candidates[i]);
|
||||
for (auto m : flush_candidates[i]) {
|
||||
m->TEST_AtomicFlushSequenceNumber() = 123;
|
||||
}
|
||||
lists[i]->RollbackMemtableFlush(flush_candidates[i], 0);
|
||||
}
|
||||
uint64_t memtable_id = num_memtables_per_cf - 1;
|
||||
autovector<MemTable*> other_flush_candidates;
|
||||
lists[0]->PickMemtablesToFlush(&memtable_id, &other_flush_candidates);
|
||||
for (auto m : other_flush_candidates) {
|
||||
m->TEST_AtomicFlushSequenceNumber() = 124;
|
||||
m->TEST_SetFlushCompleted(true);
|
||||
m->TEST_SetFileNumber(1);
|
||||
}
|
||||
autovector<const autovector<MemTable*>*> to_flush;
|
||||
to_flush.emplace_back(&other_flush_candidates);
|
||||
bool has_older_unfinished_atomic_flush = false;
|
||||
bool found_batch_to_commit = false;
|
||||
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"MemTableList::TryInstallMemtableFlushResults:"
|
||||
"HasOlderUnfinishedAtomicFlush:0",
|
||||
[&](void* /*arg*/) { has_older_unfinished_atomic_flush = true; });
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"MemTableList::TryInstallMemtableFlushResults:FoundBatchToCommit:0",
|
||||
[&](void* /*arg*/) { found_batch_to_commit = true; });
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
s = Mock_InstallMemtableFlushResults(lists, cf_ids, mutable_cf_options_list,
|
||||
to_flush, &to_delete);
|
||||
ASSERT_OK(s);
|
||||
ASSERT_TRUE(has_older_unfinished_atomic_flush);
|
||||
ASSERT_FALSE(found_batch_to_commit);
|
||||
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
|
||||
ASSERT_TRUE(to_delete.empty());
|
||||
for (auto list : lists) {
|
||||
list->current()->Unref(&to_delete);
|
||||
delete list;
|
||||
}
|
||||
lists.clear();
|
||||
ASSERT_EQ(num_cfs * num_memtables_per_cf, to_delete.size());
|
||||
for (auto m : to_delete) {
|
||||
m->Ref();
|
||||
ASSERT_EQ(m, m->Unref());
|
||||
delete m;
|
||||
}
|
||||
to_delete.clear();
|
||||
for (auto& opts : mutable_cf_options_list) {
|
||||
delete opts;
|
||||
opts = nullptr;
|
||||
}
|
||||
mutable_cf_options_list.clear();
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
+4
-2
@@ -79,7 +79,8 @@ class MergeContext {
|
||||
return GetOperandsDirectionForward();
|
||||
}
|
||||
|
||||
// Return all the operands in the order as they were merged (passed to FullMerge or FullMergeV2)
|
||||
// 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;
|
||||
@@ -89,7 +90,8 @@ class MergeContext {
|
||||
return *operand_list_;
|
||||
}
|
||||
|
||||
// Return all the operands in the reversed order relative to how they were merged (passed to FullMerge or FullMergeV2)
|
||||
// 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;
|
||||
|
||||
+23
-16
@@ -110,8 +110,11 @@ Status MergeHelper::TimedFullMerge(const MergeOperator* merge_operator,
|
||||
// keys_ stores the list of keys encountered while merging.
|
||||
// operands_ stores the list of merge operands encountered while merging.
|
||||
// keys_[i] corresponds to operands_[i] for each i.
|
||||
//
|
||||
// TODO: Avoid the snapshot stripe map lookup in CompactionRangeDelAggregator
|
||||
// and just pass the StripeRep corresponding to the stripe being merged.
|
||||
Status MergeHelper::MergeUntil(InternalIterator* iter,
|
||||
RangeDelAggregator* range_del_agg,
|
||||
CompactionRangeDelAggregator* range_del_agg,
|
||||
const SequenceNumber stop_before,
|
||||
const bool at_bottom) {
|
||||
// Get a copy of the internal key, before it's invalidated by iter->Next()
|
||||
@@ -163,9 +166,11 @@ Status MergeHelper::MergeUntil(InternalIterator* iter,
|
||||
break;
|
||||
} else if (stop_before > 0 && ikey.sequence <= stop_before &&
|
||||
LIKELY(snapshot_checker_ == nullptr ||
|
||||
snapshot_checker_->IsInSnapshot(ikey.sequence,
|
||||
stop_before))) {
|
||||
// hit an entry that's visible by the previous snapshot, can't touch that
|
||||
snapshot_checker_->CheckInSnapshot(ikey.sequence,
|
||||
stop_before) !=
|
||||
SnapshotCheckerResult::kNotInSnapshot)) {
|
||||
// hit an entry that's possibly visible by the previous snapshot, can't
|
||||
// touch that
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -277,22 +282,24 @@ Status MergeHelper::MergeUntil(InternalIterator* iter,
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
// We are sure we have seen this key's entire history if we are at the
|
||||
// last level and exhausted all internal keys of this user key.
|
||||
// NOTE: !iter->Valid() does not necessarily mean we hit the
|
||||
// beginning of a user key, as versions of a user key might be
|
||||
// split into multiple files (even files on the same level)
|
||||
// and some files might not be included in the compaction/merge.
|
||||
// We are sure we have seen this key's entire history if:
|
||||
// at_bottom == true (this does not necessarily mean it is the bottommost
|
||||
// layer, but rather that we are confident the key does not appear on any of
|
||||
// the lower layers, at_bottom == false doesn't mean it does appear, just
|
||||
// that we can't be sure, see Compaction::IsBottommostLevel for details)
|
||||
// AND
|
||||
// we have either encountered another key or end of key history on this
|
||||
// layer.
|
||||
//
|
||||
// There are also cases where we have seen the root of history of this
|
||||
// key without being sure of it. Then, we simply miss the opportunity
|
||||
// When these conditions are true we are able to merge all the keys
|
||||
// using full merge.
|
||||
//
|
||||
// For these cases we are not sure about, we simply miss the opportunity
|
||||
// to combine the keys. Since VersionSet::SetupOtherInputs() always makes
|
||||
// sure that all merge-operands on the same level get compacted together,
|
||||
// this will simply lead to these merge operands moving to the next level.
|
||||
//
|
||||
// So, we only perform the following logic (to merge all operands together
|
||||
// without a Put/Delete) if we are certain that we have seen the end of key.
|
||||
bool surely_seen_the_beginning = hit_the_next_user_key && at_bottom;
|
||||
bool surely_seen_the_beginning =
|
||||
(hit_the_next_user_key || !iter->Valid()) && at_bottom;
|
||||
if (surely_seen_the_beginning) {
|
||||
// do a final merge with nullptr as the existing value and say
|
||||
// bye to the merge type (it's now converted to a Put)
|
||||
|
||||
+1
-1
@@ -78,7 +78,7 @@ class MergeHelper {
|
||||
//
|
||||
// REQUIRED: The first key in the input is not corrupted.
|
||||
Status MergeUntil(InternalIterator* iter,
|
||||
RangeDelAggregator* range_del_agg = nullptr,
|
||||
CompactionRangeDelAggregator* range_del_agg = nullptr,
|
||||
const SequenceNumber stop_before = 0,
|
||||
const bool at_bottom = false);
|
||||
|
||||
|
||||
@@ -130,7 +130,7 @@ TEST_F(MergeHelperTest, SingleOperand) {
|
||||
|
||||
AddKeyVal("a", 50, kTypeMerge, test::EncodeInt(1U));
|
||||
|
||||
ASSERT_TRUE(Run(31, true).IsMergeInProgress());
|
||||
ASSERT_TRUE(Run(31, false).IsMergeInProgress());
|
||||
ASSERT_FALSE(iter_->Valid());
|
||||
ASSERT_EQ(test::KeyStr("a", 50, kTypeMerge), merge_helper_->keys()[0]);
|
||||
ASSERT_EQ(test::EncodeInt(1U), merge_helper_->values()[0]);
|
||||
|
||||
@@ -696,6 +696,10 @@ TEST_F(PerfContextTest, PerfContextByLevelGetSet) {
|
||||
PERF_COUNTER_BY_LEVEL_ADD(bloom_filter_useful, 1, 7);
|
||||
PERF_COUNTER_BY_LEVEL_ADD(bloom_filter_useful, 1, 7);
|
||||
PERF_COUNTER_BY_LEVEL_ADD(bloom_filter_full_true_positive, 1, 2);
|
||||
PERF_COUNTER_BY_LEVEL_ADD(block_cache_hit_count, 1, 0);
|
||||
PERF_COUNTER_BY_LEVEL_ADD(block_cache_hit_count, 5, 2);
|
||||
PERF_COUNTER_BY_LEVEL_ADD(block_cache_miss_count, 2, 3);
|
||||
PERF_COUNTER_BY_LEVEL_ADD(block_cache_miss_count, 4, 1);
|
||||
ASSERT_EQ(
|
||||
0, (*(get_perf_context()->level_to_perf_context))[0].bloom_filter_useful);
|
||||
ASSERT_EQ(
|
||||
@@ -706,6 +710,14 @@ TEST_F(PerfContextTest, PerfContextByLevelGetSet) {
|
||||
.bloom_filter_full_positive);
|
||||
ASSERT_EQ(1, (*(get_perf_context()->level_to_perf_context))[2]
|
||||
.bloom_filter_full_true_positive);
|
||||
ASSERT_EQ(1, (*(get_perf_context()->level_to_perf_context))[0]
|
||||
.block_cache_hit_count);
|
||||
ASSERT_EQ(5, (*(get_perf_context()->level_to_perf_context))[2]
|
||||
.block_cache_hit_count);
|
||||
ASSERT_EQ(2, (*(get_perf_context()->level_to_perf_context))[3]
|
||||
.block_cache_miss_count);
|
||||
ASSERT_EQ(4, (*(get_perf_context()->level_to_perf_context))[1]
|
||||
.block_cache_miss_count);
|
||||
std::string zero_excluded = get_perf_context()->ToString(true);
|
||||
ASSERT_NE(std::string::npos,
|
||||
zero_excluded.find("bloom_filter_useful = 1@level5, 2@level7"));
|
||||
@@ -713,6 +725,10 @@ TEST_F(PerfContextTest, PerfContextByLevelGetSet) {
|
||||
zero_excluded.find("bloom_filter_full_positive = 1@level0"));
|
||||
ASSERT_NE(std::string::npos,
|
||||
zero_excluded.find("bloom_filter_full_true_positive = 1@level2"));
|
||||
ASSERT_NE(std::string::npos,
|
||||
zero_excluded.find("block_cache_hit_count = 1@level0, 5@level2"));
|
||||
ASSERT_NE(std::string::npos,
|
||||
zero_excluded.find("block_cache_miss_count = 4@level1, 2@level3"));
|
||||
}
|
||||
} // namespace rocksdb
|
||||
|
||||
|
||||
+64
-64
@@ -134,7 +134,6 @@ class PlainTableDBTest : public testing::Test,
|
||||
plain_table_options.huge_page_tlb_size = 0;
|
||||
plain_table_options.encoding_type = kPrefix;
|
||||
plain_table_options.full_scan_mode = false;
|
||||
plain_table_options.store_index_in_file = false;
|
||||
|
||||
options.table_factory.reset(NewPlainTableFactory(plain_table_options));
|
||||
options.memtable_factory.reset(NewHashLinkListRepFactory(4, 0, 3, true));
|
||||
@@ -158,6 +157,8 @@ class PlainTableDBTest : public testing::Test,
|
||||
db_ = nullptr;
|
||||
}
|
||||
|
||||
bool mmap_mode() const { return mmap_mode_; }
|
||||
|
||||
void DestroyAndReopen(Options* options = nullptr) {
|
||||
//Destroy using last options
|
||||
Destroy(&last_options_);
|
||||
@@ -174,6 +175,12 @@ class PlainTableDBTest : public testing::Test,
|
||||
return DB::Open(*options, dbname_, db);
|
||||
}
|
||||
|
||||
Status ReopenForReadOnly(Options* options) {
|
||||
delete db_;
|
||||
db_ = nullptr;
|
||||
return DB::OpenForReadOnly(*options, dbname_, &db_);
|
||||
}
|
||||
|
||||
Status TryReopen(Options* options = nullptr) {
|
||||
delete db_;
|
||||
db_ = nullptr;
|
||||
@@ -264,8 +271,7 @@ class TestPlainTableReader : public PlainTableReader {
|
||||
std::unique_ptr<RandomAccessFileReader>&& file,
|
||||
const ImmutableCFOptions& ioptions,
|
||||
const SliceTransform* prefix_extractor,
|
||||
bool* expect_bloom_not_match, bool store_index_in_file,
|
||||
uint32_t column_family_id,
|
||||
bool* expect_bloom_not_match, uint32_t column_family_id,
|
||||
const std::string& column_family_name)
|
||||
: PlainTableReader(ioptions, std::move(file), env_options, icomparator,
|
||||
encoding_type, file_size, table_properties,
|
||||
@@ -282,17 +288,6 @@ class TestPlainTableReader : public PlainTableReader {
|
||||
TableProperties* props = const_cast<TableProperties*>(table_properties);
|
||||
EXPECT_EQ(column_family_id, static_cast<uint32_t>(props->column_family_id));
|
||||
EXPECT_EQ(column_family_name, props->column_family_name);
|
||||
if (store_index_in_file) {
|
||||
auto bloom_version_ptr = props->user_collected_properties.find(
|
||||
PlainTablePropertyNames::kBloomVersion);
|
||||
EXPECT_TRUE(bloom_version_ptr != props->user_collected_properties.end());
|
||||
EXPECT_EQ(bloom_version_ptr->second, std::string("1"));
|
||||
if (ioptions.bloom_locality > 0) {
|
||||
auto num_blocks_ptr = props->user_collected_properties.find(
|
||||
PlainTablePropertyNames::kNumBloomBlocks);
|
||||
EXPECT_TRUE(num_blocks_ptr != props->user_collected_properties.end());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
virtual ~TestPlainTableReader() {}
|
||||
@@ -321,7 +316,6 @@ class TestPlainTableFactory : public PlainTableFactory {
|
||||
bloom_bits_per_key_(options.bloom_bits_per_key),
|
||||
hash_table_ratio_(options.hash_table_ratio),
|
||||
index_sparseness_(options.index_sparseness),
|
||||
store_index_in_file_(options.store_index_in_file),
|
||||
expect_bloom_not_match_(expect_bloom_not_match),
|
||||
column_family_id_(column_family_id),
|
||||
column_family_name_(std::move(column_family_name)) {}
|
||||
@@ -338,22 +332,6 @@ class TestPlainTableFactory : public PlainTableFactory {
|
||||
true /* compression_type_missing */);
|
||||
EXPECT_TRUE(s.ok());
|
||||
|
||||
if (store_index_in_file_) {
|
||||
BlockHandle bloom_block_handle;
|
||||
s = FindMetaBlock(file.get(), file_size, kPlainTableMagicNumber,
|
||||
table_reader_options.ioptions,
|
||||
BloomBlockBuilder::kBloomBlock, &bloom_block_handle,
|
||||
/* compression_type_missing */ true);
|
||||
EXPECT_TRUE(s.ok());
|
||||
|
||||
BlockHandle index_block_handle;
|
||||
s = FindMetaBlock(file.get(), file_size, kPlainTableMagicNumber,
|
||||
table_reader_options.ioptions,
|
||||
PlainTableIndexBuilder::kPlainTableIndexBlock,
|
||||
&index_block_handle, /* compression_type_missing */ true);
|
||||
EXPECT_TRUE(s.ok());
|
||||
}
|
||||
|
||||
auto& user_props = props->user_collected_properties;
|
||||
auto encoding_type_prop =
|
||||
user_props.find(PlainTablePropertyNames::kEncodingType);
|
||||
@@ -367,7 +345,7 @@ class TestPlainTableFactory : public PlainTableFactory {
|
||||
bloom_bits_per_key_, hash_table_ratio_, index_sparseness_, props,
|
||||
std::move(file), table_reader_options.ioptions,
|
||||
table_reader_options.prefix_extractor, expect_bloom_not_match_,
|
||||
store_index_in_file_, column_family_id_, column_family_name_));
|
||||
column_family_id_, column_family_name_));
|
||||
|
||||
*table = std::move(new_reader);
|
||||
return s;
|
||||
@@ -377,7 +355,6 @@ class TestPlainTableFactory : public PlainTableFactory {
|
||||
int bloom_bits_per_key_;
|
||||
double hash_table_ratio_;
|
||||
size_t index_sparseness_;
|
||||
bool store_index_in_file_;
|
||||
bool* expect_bloom_not_match_;
|
||||
const uint32_t column_family_id_;
|
||||
const std::string column_family_name_;
|
||||
@@ -387,10 +364,8 @@ TEST_P(PlainTableDBTest, Flush) {
|
||||
for (size_t huge_page_tlb_size = 0; huge_page_tlb_size <= 2 * 1024 * 1024;
|
||||
huge_page_tlb_size += 2 * 1024 * 1024) {
|
||||
for (EncodingType encoding_type : {kPlain, kPrefix}) {
|
||||
for (int bloom_bits = 0; bloom_bits <= 117; bloom_bits += 117) {
|
||||
for (int total_order = 0; total_order <= 1; total_order++) {
|
||||
for (int store_index_in_file = 0; store_index_in_file <= 1;
|
||||
++store_index_in_file) {
|
||||
for (int bloom_bits = 0; bloom_bits <= 117; bloom_bits += 117) {
|
||||
for (int total_order = 0; total_order <= 1; total_order++) {
|
||||
Options options = CurrentOptions();
|
||||
options.create_if_missing = true;
|
||||
// Set only one bucket to force bucket conflict.
|
||||
@@ -406,7 +381,6 @@ TEST_P(PlainTableDBTest, Flush) {
|
||||
plain_table_options.huge_page_tlb_size = huge_page_tlb_size;
|
||||
plain_table_options.encoding_type = encoding_type;
|
||||
plain_table_options.full_scan_mode = false;
|
||||
plain_table_options.store_index_in_file = store_index_in_file;
|
||||
|
||||
options.table_factory.reset(
|
||||
NewPlainTableFactory(plain_table_options));
|
||||
@@ -419,7 +393,6 @@ TEST_P(PlainTableDBTest, Flush) {
|
||||
plain_table_options.huge_page_tlb_size = huge_page_tlb_size;
|
||||
plain_table_options.encoding_type = encoding_type;
|
||||
plain_table_options.full_scan_mode = false;
|
||||
plain_table_options.store_index_in_file = store_index_in_file;
|
||||
|
||||
options.table_factory.reset(
|
||||
NewPlainTableFactory(plain_table_options));
|
||||
@@ -445,22 +418,15 @@ TEST_P(PlainTableDBTest, Flush) {
|
||||
auto row = ptc.begin();
|
||||
auto tp = row->second;
|
||||
|
||||
if (!store_index_in_file) {
|
||||
ASSERT_EQ(total_order ? "4" : "12",
|
||||
(tp->user_collected_properties)
|
||||
.at("plain_table_hash_table_size"));
|
||||
ASSERT_EQ("0", (tp->user_collected_properties)
|
||||
.at("plain_table_sub_index_size"));
|
||||
} else {
|
||||
ASSERT_EQ("0", (tp->user_collected_properties)
|
||||
.at("plain_table_hash_table_size"));
|
||||
ASSERT_EQ("0", (tp->user_collected_properties)
|
||||
.at("plain_table_sub_index_size"));
|
||||
}
|
||||
ASSERT_EQ(total_order ? "4" : "12",
|
||||
(tp->user_collected_properties)
|
||||
.at("plain_table_hash_table_size"));
|
||||
ASSERT_EQ(
|
||||
"0",
|
||||
(tp->user_collected_properties).at("plain_table_sub_index_size"));
|
||||
ASSERT_EQ("v3", Get("1000000000000foo"));
|
||||
ASSERT_EQ("v2", Get("0000000000000bar"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -470,19 +436,11 @@ TEST_P(PlainTableDBTest, Flush2) {
|
||||
for (size_t huge_page_tlb_size = 0; huge_page_tlb_size <= 2 * 1024 * 1024;
|
||||
huge_page_tlb_size += 2 * 1024 * 1024) {
|
||||
for (EncodingType encoding_type : {kPlain, kPrefix}) {
|
||||
for (int bloom_bits = 0; bloom_bits <= 117; bloom_bits += 117) {
|
||||
for (int total_order = 0; total_order <= 1; total_order++) {
|
||||
for (int store_index_in_file = 0; store_index_in_file <= 1;
|
||||
++store_index_in_file) {
|
||||
for (int bloom_bits = 0; bloom_bits <= 117; bloom_bits += 117) {
|
||||
for (int total_order = 0; total_order <= 1; total_order++) {
|
||||
if (encoding_type == kPrefix && total_order) {
|
||||
continue;
|
||||
}
|
||||
if (!bloom_bits && store_index_in_file) {
|
||||
continue;
|
||||
}
|
||||
if (total_order && store_index_in_file) {
|
||||
continue;
|
||||
}
|
||||
bool expect_bloom_not_match = false;
|
||||
Options options = CurrentOptions();
|
||||
options.create_if_missing = true;
|
||||
@@ -501,7 +459,6 @@ TEST_P(PlainTableDBTest, Flush2) {
|
||||
plain_table_options.bloom_bits_per_key = bloom_bits;
|
||||
plain_table_options.huge_page_tlb_size = huge_page_tlb_size;
|
||||
plain_table_options.encoding_type = encoding_type;
|
||||
plain_table_options.store_index_in_file = store_index_in_file;
|
||||
options.table_factory.reset(new TestPlainTableFactory(
|
||||
&expect_bloom_not_match, plain_table_options,
|
||||
0 /* column_family_id */, kDefaultColumnFamilyName));
|
||||
@@ -540,10 +497,53 @@ TEST_P(PlainTableDBTest, Flush2) {
|
||||
}
|
||||
expect_bloom_not_match = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(PlainTableDBTest, Immortal) {
|
||||
for (EncodingType encoding_type : {kPlain, kPrefix}) {
|
||||
Options options = CurrentOptions();
|
||||
options.create_if_missing = true;
|
||||
options.max_open_files = -1;
|
||||
// Set only one bucket to force bucket conflict.
|
||||
// Test index interval for the same prefix to be 1, 2 and 4
|
||||
PlainTableOptions plain_table_options;
|
||||
plain_table_options.hash_table_ratio = 0.75;
|
||||
plain_table_options.index_sparseness = 16;
|
||||
plain_table_options.user_key_len = kPlainTableVariableLength;
|
||||
plain_table_options.bloom_bits_per_key = 10;
|
||||
plain_table_options.encoding_type = encoding_type;
|
||||
options.table_factory.reset(NewPlainTableFactory(plain_table_options));
|
||||
|
||||
DestroyAndReopen(&options);
|
||||
ASSERT_OK(Put("0000000000000bar", "b"));
|
||||
ASSERT_OK(Put("1000000000000foo", "v1"));
|
||||
dbfull()->TEST_FlushMemTable();
|
||||
|
||||
int copied = 0;
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"GetContext::SaveValue::PinSelf", [&](void* /*arg*/) { copied++; });
|
||||
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
||||
ASSERT_EQ("b", Get("0000000000000bar"));
|
||||
ASSERT_EQ("v1", Get("1000000000000foo"));
|
||||
ASSERT_EQ(2, copied);
|
||||
copied = 0;
|
||||
|
||||
Close();
|
||||
ASSERT_OK(ReopenForReadOnly(&options));
|
||||
|
||||
ASSERT_EQ("b", Get("0000000000000bar"));
|
||||
ASSERT_EQ("v1", Get("1000000000000foo"));
|
||||
ASSERT_EQ("NOT_FOUND", Get("1000000000000bar"));
|
||||
if (mmap_mode()) {
|
||||
ASSERT_EQ(0, copied);
|
||||
} else {
|
||||
ASSERT_EQ(2, copied);
|
||||
}
|
||||
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+419
-644
File diff suppressed because it is too large
Load Diff
+388
-190
@@ -1,10 +1,12 @@
|
||||
// Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
|
||||
// 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).
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <iterator>
|
||||
#include <list>
|
||||
#include <map>
|
||||
#include <set>
|
||||
@@ -14,220 +16,416 @@
|
||||
#include "db/compaction_iteration_stats.h"
|
||||
#include "db/dbformat.h"
|
||||
#include "db/pinned_iterators_manager.h"
|
||||
#include "db/range_del_aggregator.h"
|
||||
#include "db/range_tombstone_fragmenter.h"
|
||||
#include "db/version_edit.h"
|
||||
#include "include/rocksdb/comparator.h"
|
||||
#include "include/rocksdb/types.h"
|
||||
#include "table/internal_iterator.h"
|
||||
#include "table/scoped_arena_iterator.h"
|
||||
#include "table/table_builder.h"
|
||||
#include "util/heap.h"
|
||||
#include "util/kv_map.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
// RangeDelMaps maintain position across calls to ShouldDelete. The caller may
|
||||
// wish to specify a mode to optimize positioning the iterator during the next
|
||||
// call to ShouldDelete. The non-kFullScan modes are only available when
|
||||
// deletion collapsing is enabled.
|
||||
//
|
||||
// For example, if we invoke Next() on an iterator, kForwardTraversal should be
|
||||
// specified to advance one-by-one through deletions until one is found with its
|
||||
// interval containing the key. This will typically be faster than doing a full
|
||||
// binary search (kBinarySearch).
|
||||
enum class RangeDelPositioningMode {
|
||||
kFullScan, // used iff collapse_deletions_ == false
|
||||
kForwardTraversal,
|
||||
kBackwardTraversal,
|
||||
kBinarySearch,
|
||||
};
|
||||
class TruncatedRangeDelIterator {
|
||||
public:
|
||||
TruncatedRangeDelIterator(
|
||||
std::unique_ptr<FragmentedRangeTombstoneIterator> iter,
|
||||
const InternalKeyComparator* icmp, const InternalKey* smallest,
|
||||
const InternalKey* largest);
|
||||
|
||||
// TruncatedRangeTombstones are a slight generalization of regular
|
||||
// RangeTombstones that can represent truncations caused by SST boundaries.
|
||||
// Instead of using user keys to represent the start and end keys, they instead
|
||||
// use internal keys, whose sequence number indicates the sequence number of
|
||||
// the smallest/largest SST key (in the case where a tombstone is untruncated,
|
||||
// the sequence numbers will be kMaxSequenceNumber for both start and end
|
||||
// keys). Like RangeTombstones, TruncatedRangeTombstone are also
|
||||
// end-key-exclusive.
|
||||
struct TruncatedRangeTombstone {
|
||||
TruncatedRangeTombstone(const ParsedInternalKey& sk,
|
||||
const ParsedInternalKey& ek, SequenceNumber s)
|
||||
: start_key_(sk), end_key_(ek), seq_(s) {}
|
||||
bool Valid() const;
|
||||
|
||||
RangeTombstone Tombstone() const {
|
||||
// The RangeTombstone returned here can cover less than the
|
||||
// TruncatedRangeTombstone when its end key has a seqnum that is not
|
||||
// kMaxSequenceNumber. Since this method is only used by RangeDelIterators
|
||||
// (which in turn are only used during flush/compaction), we avoid this
|
||||
// problem by using truncation boundaries spanning multiple SSTs, which
|
||||
// are selected in a way that guarantee a clean break at the end key.
|
||||
assert(end_key_.sequence == kMaxSequenceNumber);
|
||||
return RangeTombstone(start_key_.user_key, end_key_.user_key, seq_);
|
||||
void Next();
|
||||
void Prev();
|
||||
|
||||
void InternalNext();
|
||||
|
||||
// Seeks to the tombstone with the highest viisble sequence number that covers
|
||||
// target (a user key). If no such tombstone exists, the position will be at
|
||||
// the earliest tombstone that ends after target.
|
||||
void Seek(const Slice& target);
|
||||
|
||||
// Seeks to the tombstone with the highest viisble sequence number that covers
|
||||
// target (a user key). If no such tombstone exists, the position will be at
|
||||
// the latest tombstone that starts before target.
|
||||
void SeekForPrev(const Slice& target);
|
||||
|
||||
void SeekToFirst();
|
||||
void SeekToLast();
|
||||
|
||||
ParsedInternalKey start_key() const {
|
||||
return (smallest_ == nullptr ||
|
||||
icmp_->Compare(*smallest_, iter_->parsed_start_key()) <= 0)
|
||||
? iter_->parsed_start_key()
|
||||
: *smallest_;
|
||||
}
|
||||
|
||||
ParsedInternalKey start_key_;
|
||||
ParsedInternalKey end_key_;
|
||||
SequenceNumber seq_;
|
||||
};
|
||||
|
||||
// A RangeDelIterator iterates over range deletion tombstones.
|
||||
class RangeDelIterator {
|
||||
public:
|
||||
virtual ~RangeDelIterator() = default;
|
||||
|
||||
virtual bool Valid() const = 0;
|
||||
virtual void Next() = 0;
|
||||
// NOTE: the Slice passed to this method must be a user key.
|
||||
virtual void Seek(const Slice& target) = 0;
|
||||
virtual void Seek(const ParsedInternalKey& target) = 0;
|
||||
virtual RangeTombstone Tombstone() const = 0;
|
||||
};
|
||||
|
||||
// A RangeDelMap keeps track of range deletion tombstones within a snapshot
|
||||
// stripe.
|
||||
//
|
||||
// RangeDelMaps are used internally by RangeDelAggregator. They are not intended
|
||||
// to be used directly.
|
||||
class RangeDelMap {
|
||||
public:
|
||||
virtual ~RangeDelMap() = default;
|
||||
|
||||
virtual bool ShouldDelete(const ParsedInternalKey& parsed,
|
||||
RangeDelPositioningMode mode) = 0;
|
||||
virtual bool IsRangeOverlapped(const ParsedInternalKey& start,
|
||||
const ParsedInternalKey& end) = 0;
|
||||
virtual void InvalidatePosition() = 0;
|
||||
|
||||
virtual size_t Size() const = 0;
|
||||
bool IsEmpty() const { return Size() == 0; }
|
||||
|
||||
virtual void AddTombstone(TruncatedRangeTombstone tombstone) = 0;
|
||||
virtual std::unique_ptr<RangeDelIterator> NewIterator() = 0;
|
||||
};
|
||||
|
||||
// A RangeDelAggregator aggregates range deletion tombstones as they are
|
||||
// encountered in memtables/SST files. It provides methods that check whether a
|
||||
// key is covered by range tombstones or write the relevant tombstones to a new
|
||||
// SST file.
|
||||
class RangeDelAggregator {
|
||||
public:
|
||||
// @param snapshots These are used to organize the tombstones into snapshot
|
||||
// stripes, which is the seqnum range between consecutive snapshots,
|
||||
// including the higher snapshot and excluding the lower one. Currently,
|
||||
// this is used by ShouldDelete() to prevent deletion of keys that are
|
||||
// covered by range tombstones in other snapshot stripes. This constructor
|
||||
// is used for writes (flush/compaction). All DB snapshots are provided
|
||||
// such that no keys are removed that are uncovered according to any DB
|
||||
// snapshot.
|
||||
// Note this overload does not lazily initialize Rep.
|
||||
RangeDelAggregator(const InternalKeyComparator& icmp,
|
||||
const std::vector<SequenceNumber>& snapshots,
|
||||
bool collapse_deletions = true);
|
||||
|
||||
// @param upper_bound Similar to snapshots above, except with a single
|
||||
// snapshot, which allows us to store the snapshot on the stack and defer
|
||||
// initialization of heap-allocating members (in Rep) until the first range
|
||||
// deletion is encountered. This constructor is used in case of reads (get/
|
||||
// iterator), for which only the user snapshot (upper_bound) is provided
|
||||
// such that the seqnum space is divided into two stripes. Only the older
|
||||
// stripe will be used by ShouldDelete().
|
||||
RangeDelAggregator(const InternalKeyComparator& icmp,
|
||||
SequenceNumber upper_bound,
|
||||
bool collapse_deletions = false);
|
||||
|
||||
// Returns whether the key should be deleted, which is the case when it is
|
||||
// covered by a range tombstone residing in the same snapshot stripe.
|
||||
// @param mode If collapse_deletions_ is true, this dictates how we will find
|
||||
// the deletion whose interval contains this key. Otherwise, its
|
||||
// value must be kFullScan indicating linear scan from beginning.
|
||||
bool ShouldDelete(
|
||||
const ParsedInternalKey& parsed,
|
||||
RangeDelPositioningMode mode = RangeDelPositioningMode::kFullScan) {
|
||||
if (rep_ == nullptr) {
|
||||
return false;
|
||||
}
|
||||
return ShouldDeleteImpl(parsed, mode);
|
||||
ParsedInternalKey end_key() const {
|
||||
return (largest_ == nullptr ||
|
||||
icmp_->Compare(iter_->parsed_end_key(), *largest_) <= 0)
|
||||
? iter_->parsed_end_key()
|
||||
: *largest_;
|
||||
}
|
||||
bool ShouldDelete(
|
||||
const Slice& internal_key,
|
||||
RangeDelPositioningMode mode = RangeDelPositioningMode::kFullScan) {
|
||||
if (rep_ == nullptr) {
|
||||
return false;
|
||||
}
|
||||
return ShouldDeleteImpl(internal_key, mode);
|
||||
}
|
||||
bool ShouldDeleteImpl(const ParsedInternalKey& parsed,
|
||||
RangeDelPositioningMode mode);
|
||||
bool ShouldDeleteImpl(const Slice& internal_key,
|
||||
RangeDelPositioningMode mode);
|
||||
|
||||
// Checks whether range deletions cover any keys between `start` and `end`,
|
||||
// inclusive.
|
||||
//
|
||||
// @param start User key representing beginning of range to check for overlap.
|
||||
// @param end User key representing end of range to check for overlap. This
|
||||
// argument is inclusive, so the existence of a range deletion covering
|
||||
// `end` causes this to return true.
|
||||
bool IsRangeOverlapped(const Slice& start, const Slice& end);
|
||||
SequenceNumber seq() const { return iter_->seq(); }
|
||||
|
||||
// Adds tombstones to the tombstone aggregation structure maintained by this
|
||||
// object. Tombstones are truncated to smallest and largest. If smallest (or
|
||||
// largest) is null, it is not used for truncation. When adding range
|
||||
// tombstones present in an sstable, smallest and largest should be set to
|
||||
// the smallest and largest keys from the sstable file metadata. Note that
|
||||
// tombstones end keys are exclusive while largest is inclusive.
|
||||
// @return non-OK status if any of the tombstone keys are corrupted.
|
||||
Status AddTombstones(std::unique_ptr<InternalIterator> input,
|
||||
const InternalKey* smallest = nullptr,
|
||||
const InternalKey* largest = nullptr);
|
||||
std::map<SequenceNumber, std::unique_ptr<TruncatedRangeDelIterator>>
|
||||
SplitBySnapshot(const std::vector<SequenceNumber>& snapshots);
|
||||
|
||||
// Resets iterators maintained across calls to ShouldDelete(). This may be
|
||||
// called when the tombstones change, or the owner may call explicitly, e.g.,
|
||||
// if it's an iterator that just seeked to an arbitrary position. The effect
|
||||
// of invalidation is that the following call to ShouldDelete() will binary
|
||||
// search for its tombstone.
|
||||
void InvalidateRangeDelMapPositions();
|
||||
SequenceNumber upper_bound() const { return iter_->upper_bound(); }
|
||||
|
||||
bool IsEmpty();
|
||||
bool AddFile(uint64_t file_number);
|
||||
|
||||
// Create a new iterator over the range deletion tombstones in all of the
|
||||
// snapshot stripes in this aggregator. Tombstones are presented in start key
|
||||
// order. Tombstones with the same start key are presented in arbitrary order.
|
||||
//
|
||||
// The iterator is invalidated after any call to AddTombstones. It is the
|
||||
// caller's responsibility to avoid using invalid iterators.
|
||||
std::unique_ptr<RangeDelIterator> NewIterator();
|
||||
SequenceNumber lower_bound() const { return iter_->lower_bound(); }
|
||||
|
||||
private:
|
||||
// Maps snapshot seqnum -> map of tombstones that fall in that stripe, i.e.,
|
||||
// their seqnums are greater than the next smaller snapshot's seqnum, and the
|
||||
// corresponding index into the list of snapshots. Each entry is lazily
|
||||
// initialized.
|
||||
typedef std::map<SequenceNumber,
|
||||
std::pair<std::unique_ptr<RangeDelMap>, size_t>>
|
||||
StripeMap;
|
||||
std::unique_ptr<FragmentedRangeTombstoneIterator> iter_;
|
||||
const InternalKeyComparator* icmp_;
|
||||
const ParsedInternalKey* smallest_ = nullptr;
|
||||
const ParsedInternalKey* largest_ = nullptr;
|
||||
std::list<ParsedInternalKey> pinned_bounds_;
|
||||
|
||||
struct Rep {
|
||||
std::vector<SequenceNumber> snapshots_;
|
||||
StripeMap stripe_map_;
|
||||
PinnedIteratorsManager pinned_iters_mgr_;
|
||||
std::list<std::string> pinned_slices_;
|
||||
std::set<uint64_t> added_files_;
|
||||
const InternalKey* smallest_ikey_;
|
||||
const InternalKey* largest_ikey_;
|
||||
};
|
||||
|
||||
struct SeqMaxComparator {
|
||||
bool operator()(const TruncatedRangeDelIterator* a,
|
||||
const TruncatedRangeDelIterator* b) const {
|
||||
return a->seq() > b->seq();
|
||||
}
|
||||
};
|
||||
|
||||
struct StartKeyMinComparator {
|
||||
explicit StartKeyMinComparator(const InternalKeyComparator* c) : icmp(c) {}
|
||||
|
||||
bool operator()(const TruncatedRangeDelIterator* a,
|
||||
const TruncatedRangeDelIterator* b) const {
|
||||
return icmp->Compare(a->start_key(), b->start_key()) > 0;
|
||||
}
|
||||
|
||||
const InternalKeyComparator* icmp;
|
||||
};
|
||||
|
||||
class ForwardRangeDelIterator {
|
||||
public:
|
||||
explicit ForwardRangeDelIterator(const InternalKeyComparator* icmp);
|
||||
|
||||
bool ShouldDelete(const ParsedInternalKey& parsed);
|
||||
void Invalidate();
|
||||
|
||||
void AddNewIter(TruncatedRangeDelIterator* iter,
|
||||
const ParsedInternalKey& parsed) {
|
||||
iter->Seek(parsed.user_key);
|
||||
PushIter(iter, parsed);
|
||||
assert(active_iters_.size() == active_seqnums_.size());
|
||||
}
|
||||
|
||||
size_t UnusedIdx() const { return unused_idx_; }
|
||||
void IncUnusedIdx() { unused_idx_++; }
|
||||
|
||||
private:
|
||||
using ActiveSeqSet =
|
||||
std::multiset<TruncatedRangeDelIterator*, SeqMaxComparator>;
|
||||
|
||||
struct EndKeyMinComparator {
|
||||
explicit EndKeyMinComparator(const InternalKeyComparator* c) : icmp(c) {}
|
||||
|
||||
bool operator()(const ActiveSeqSet::const_iterator& a,
|
||||
const ActiveSeqSet::const_iterator& b) const {
|
||||
return icmp->Compare((*a)->end_key(), (*b)->end_key()) > 0;
|
||||
}
|
||||
|
||||
const InternalKeyComparator* icmp;
|
||||
};
|
||||
// Initializes rep_ lazily. This aggregator object is constructed for every
|
||||
// read, so expensive members should only be created when necessary, i.e.,
|
||||
// once the first range deletion is encountered.
|
||||
void InitRep(const std::vector<SequenceNumber>& snapshots);
|
||||
|
||||
std::unique_ptr<RangeDelMap> NewRangeDelMap();
|
||||
RangeDelMap* GetRangeDelMapIfExists(SequenceNumber seq);
|
||||
RangeDelMap& GetRangeDelMap(SequenceNumber seq);
|
||||
void PushIter(TruncatedRangeDelIterator* iter,
|
||||
const ParsedInternalKey& parsed) {
|
||||
if (!iter->Valid()) {
|
||||
// The iterator has been fully consumed, so we don't need to add it to
|
||||
// either of the heaps.
|
||||
return;
|
||||
}
|
||||
int cmp = icmp_->Compare(parsed, iter->start_key());
|
||||
if (cmp < 0) {
|
||||
PushInactiveIter(iter);
|
||||
} else {
|
||||
PushActiveIter(iter);
|
||||
}
|
||||
}
|
||||
|
||||
SequenceNumber upper_bound_;
|
||||
std::unique_ptr<Rep> rep_;
|
||||
const InternalKeyComparator& icmp_;
|
||||
// collapse range deletions so they're binary searchable
|
||||
const bool collapse_deletions_;
|
||||
void PushActiveIter(TruncatedRangeDelIterator* iter) {
|
||||
auto seq_pos = active_seqnums_.insert(iter);
|
||||
active_iters_.push(seq_pos);
|
||||
}
|
||||
|
||||
TruncatedRangeDelIterator* PopActiveIter() {
|
||||
auto active_top = active_iters_.top();
|
||||
auto iter = *active_top;
|
||||
active_iters_.pop();
|
||||
active_seqnums_.erase(active_top);
|
||||
return iter;
|
||||
}
|
||||
|
||||
void PushInactiveIter(TruncatedRangeDelIterator* iter) {
|
||||
inactive_iters_.push(iter);
|
||||
}
|
||||
|
||||
TruncatedRangeDelIterator* PopInactiveIter() {
|
||||
auto* iter = inactive_iters_.top();
|
||||
inactive_iters_.pop();
|
||||
return iter;
|
||||
}
|
||||
|
||||
const InternalKeyComparator* icmp_;
|
||||
size_t unused_idx_;
|
||||
ActiveSeqSet active_seqnums_;
|
||||
BinaryHeap<ActiveSeqSet::const_iterator, EndKeyMinComparator> active_iters_;
|
||||
BinaryHeap<TruncatedRangeDelIterator*, StartKeyMinComparator> inactive_iters_;
|
||||
};
|
||||
|
||||
class ReverseRangeDelIterator {
|
||||
public:
|
||||
explicit ReverseRangeDelIterator(const InternalKeyComparator* icmp);
|
||||
|
||||
bool ShouldDelete(const ParsedInternalKey& parsed);
|
||||
void Invalidate();
|
||||
|
||||
void AddNewIter(TruncatedRangeDelIterator* iter,
|
||||
const ParsedInternalKey& parsed) {
|
||||
iter->SeekForPrev(parsed.user_key);
|
||||
PushIter(iter, parsed);
|
||||
assert(active_iters_.size() == active_seqnums_.size());
|
||||
}
|
||||
|
||||
size_t UnusedIdx() const { return unused_idx_; }
|
||||
void IncUnusedIdx() { unused_idx_++; }
|
||||
|
||||
private:
|
||||
using ActiveSeqSet =
|
||||
std::multiset<TruncatedRangeDelIterator*, SeqMaxComparator>;
|
||||
|
||||
struct EndKeyMaxComparator {
|
||||
explicit EndKeyMaxComparator(const InternalKeyComparator* c) : icmp(c) {}
|
||||
|
||||
bool operator()(const TruncatedRangeDelIterator* a,
|
||||
const TruncatedRangeDelIterator* b) const {
|
||||
return icmp->Compare(a->end_key(), b->end_key()) < 0;
|
||||
}
|
||||
|
||||
const InternalKeyComparator* icmp;
|
||||
};
|
||||
struct StartKeyMaxComparator {
|
||||
explicit StartKeyMaxComparator(const InternalKeyComparator* c) : icmp(c) {}
|
||||
|
||||
bool operator()(const ActiveSeqSet::const_iterator& a,
|
||||
const ActiveSeqSet::const_iterator& b) const {
|
||||
return icmp->Compare((*a)->start_key(), (*b)->start_key()) < 0;
|
||||
}
|
||||
|
||||
const InternalKeyComparator* icmp;
|
||||
};
|
||||
|
||||
void PushIter(TruncatedRangeDelIterator* iter,
|
||||
const ParsedInternalKey& parsed) {
|
||||
if (!iter->Valid()) {
|
||||
// The iterator has been fully consumed, so we don't need to add it to
|
||||
// either of the heaps.
|
||||
} else if (icmp_->Compare(iter->end_key(), parsed) <= 0) {
|
||||
PushInactiveIter(iter);
|
||||
} else {
|
||||
PushActiveIter(iter);
|
||||
}
|
||||
}
|
||||
|
||||
void PushActiveIter(TruncatedRangeDelIterator* iter) {
|
||||
auto seq_pos = active_seqnums_.insert(iter);
|
||||
active_iters_.push(seq_pos);
|
||||
}
|
||||
|
||||
TruncatedRangeDelIterator* PopActiveIter() {
|
||||
auto active_top = active_iters_.top();
|
||||
auto iter = *active_top;
|
||||
active_iters_.pop();
|
||||
active_seqnums_.erase(active_top);
|
||||
return iter;
|
||||
}
|
||||
|
||||
void PushInactiveIter(TruncatedRangeDelIterator* iter) {
|
||||
inactive_iters_.push(iter);
|
||||
}
|
||||
|
||||
TruncatedRangeDelIterator* PopInactiveIter() {
|
||||
auto* iter = inactive_iters_.top();
|
||||
inactive_iters_.pop();
|
||||
return iter;
|
||||
}
|
||||
|
||||
const InternalKeyComparator* icmp_;
|
||||
size_t unused_idx_;
|
||||
ActiveSeqSet active_seqnums_;
|
||||
BinaryHeap<ActiveSeqSet::const_iterator, StartKeyMaxComparator> active_iters_;
|
||||
BinaryHeap<TruncatedRangeDelIterator*, EndKeyMaxComparator> inactive_iters_;
|
||||
};
|
||||
|
||||
enum class RangeDelPositioningMode { kForwardTraversal, kBackwardTraversal };
|
||||
class RangeDelAggregator {
|
||||
public:
|
||||
explicit RangeDelAggregator(const InternalKeyComparator* icmp)
|
||||
: icmp_(icmp) {}
|
||||
virtual ~RangeDelAggregator() {}
|
||||
|
||||
virtual void AddTombstones(
|
||||
std::unique_ptr<FragmentedRangeTombstoneIterator> input_iter,
|
||||
const InternalKey* smallest = nullptr,
|
||||
const InternalKey* largest = nullptr) = 0;
|
||||
|
||||
bool ShouldDelete(const Slice& key, RangeDelPositioningMode mode) {
|
||||
ParsedInternalKey parsed;
|
||||
if (!ParseInternalKey(key, &parsed)) {
|
||||
return false;
|
||||
}
|
||||
return ShouldDelete(parsed, mode);
|
||||
}
|
||||
virtual bool ShouldDelete(const ParsedInternalKey& parsed,
|
||||
RangeDelPositioningMode mode) = 0;
|
||||
|
||||
virtual void InvalidateRangeDelMapPositions() = 0;
|
||||
|
||||
virtual bool IsEmpty() const = 0;
|
||||
|
||||
bool AddFile(uint64_t file_number) {
|
||||
return files_seen_.insert(file_number).second;
|
||||
}
|
||||
|
||||
protected:
|
||||
class StripeRep {
|
||||
public:
|
||||
StripeRep(const InternalKeyComparator* icmp, SequenceNumber upper_bound,
|
||||
SequenceNumber lower_bound)
|
||||
: icmp_(icmp),
|
||||
forward_iter_(icmp),
|
||||
reverse_iter_(icmp),
|
||||
upper_bound_(upper_bound),
|
||||
lower_bound_(lower_bound) {}
|
||||
|
||||
void AddTombstones(std::unique_ptr<TruncatedRangeDelIterator> input_iter) {
|
||||
iters_.push_back(std::move(input_iter));
|
||||
}
|
||||
|
||||
bool IsEmpty() const { return iters_.empty(); }
|
||||
|
||||
bool ShouldDelete(const ParsedInternalKey& parsed,
|
||||
RangeDelPositioningMode mode);
|
||||
|
||||
void Invalidate() {
|
||||
InvalidateForwardIter();
|
||||
InvalidateReverseIter();
|
||||
}
|
||||
|
||||
bool IsRangeOverlapped(const Slice& start, const Slice& end);
|
||||
|
||||
private:
|
||||
bool InStripe(SequenceNumber seq) const {
|
||||
return lower_bound_ <= seq && seq <= upper_bound_;
|
||||
}
|
||||
|
||||
void InvalidateForwardIter() { forward_iter_.Invalidate(); }
|
||||
|
||||
void InvalidateReverseIter() { reverse_iter_.Invalidate(); }
|
||||
|
||||
const InternalKeyComparator* icmp_;
|
||||
std::vector<std::unique_ptr<TruncatedRangeDelIterator>> iters_;
|
||||
ForwardRangeDelIterator forward_iter_;
|
||||
ReverseRangeDelIterator reverse_iter_;
|
||||
SequenceNumber upper_bound_;
|
||||
SequenceNumber lower_bound_;
|
||||
};
|
||||
|
||||
const InternalKeyComparator* icmp_;
|
||||
|
||||
private:
|
||||
std::set<uint64_t> files_seen_;
|
||||
};
|
||||
|
||||
class ReadRangeDelAggregator : public RangeDelAggregator {
|
||||
public:
|
||||
ReadRangeDelAggregator(const InternalKeyComparator* icmp,
|
||||
SequenceNumber upper_bound)
|
||||
: RangeDelAggregator(icmp),
|
||||
rep_(icmp, upper_bound, 0 /* lower_bound */) {}
|
||||
~ReadRangeDelAggregator() override {}
|
||||
|
||||
using RangeDelAggregator::ShouldDelete;
|
||||
void AddTombstones(
|
||||
std::unique_ptr<FragmentedRangeTombstoneIterator> input_iter,
|
||||
const InternalKey* smallest = nullptr,
|
||||
const InternalKey* largest = nullptr) override;
|
||||
|
||||
bool ShouldDelete(const ParsedInternalKey& parsed,
|
||||
RangeDelPositioningMode mode) override;
|
||||
|
||||
bool IsRangeOverlapped(const Slice& start, const Slice& end);
|
||||
|
||||
void InvalidateRangeDelMapPositions() override { rep_.Invalidate(); }
|
||||
|
||||
bool IsEmpty() const override { return rep_.IsEmpty(); }
|
||||
|
||||
private:
|
||||
StripeRep rep_;
|
||||
};
|
||||
|
||||
class CompactionRangeDelAggregator : public RangeDelAggregator {
|
||||
public:
|
||||
CompactionRangeDelAggregator(const InternalKeyComparator* icmp,
|
||||
const std::vector<SequenceNumber>& snapshots)
|
||||
: RangeDelAggregator(icmp), snapshots_(&snapshots) {}
|
||||
~CompactionRangeDelAggregator() override {}
|
||||
|
||||
void AddTombstones(
|
||||
std::unique_ptr<FragmentedRangeTombstoneIterator> input_iter,
|
||||
const InternalKey* smallest = nullptr,
|
||||
const InternalKey* largest = nullptr) override;
|
||||
|
||||
using RangeDelAggregator::ShouldDelete;
|
||||
bool ShouldDelete(const ParsedInternalKey& parsed,
|
||||
RangeDelPositioningMode mode) override;
|
||||
|
||||
bool IsRangeOverlapped(const Slice& start, const Slice& end);
|
||||
|
||||
void InvalidateRangeDelMapPositions() override {
|
||||
for (auto& rep : reps_) {
|
||||
rep.second.Invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
bool IsEmpty() const override {
|
||||
for (const auto& rep : reps_) {
|
||||
if (!rep.second.IsEmpty()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Creates an iterator over all the range tombstones in the aggregator, for
|
||||
// use in compaction. Nullptr arguments indicate that the iterator range is
|
||||
// unbounded.
|
||||
// NOTE: the boundaries are used for optimization purposes to reduce the
|
||||
// number of tombstones that are passed to the fragmenter; they do not
|
||||
// guarantee that the resulting iterator only contains range tombstones that
|
||||
// cover keys in the provided range. If required, these bounds must be
|
||||
// enforced during iteration.
|
||||
std::unique_ptr<FragmentedRangeTombstoneIterator> NewIterator(
|
||||
const Slice* lower_bound = nullptr, const Slice* upper_bound = nullptr,
|
||||
bool upper_bound_inclusive = false);
|
||||
|
||||
private:
|
||||
std::vector<std::unique_ptr<TruncatedRangeDelIterator>> parent_iters_;
|
||||
std::map<SequenceNumber, StripeRep> reps_;
|
||||
|
||||
const std::vector<SequenceNumber>* snapshots_;
|
||||
};
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
@@ -20,7 +20,6 @@ int main() {
|
||||
#include <vector>
|
||||
|
||||
#include "db/range_del_aggregator.h"
|
||||
#include "db/range_del_aggregator_v2.h"
|
||||
#include "db/range_tombstone_fragmenter.h"
|
||||
#include "rocksdb/comparator.h"
|
||||
#include "rocksdb/env.h"
|
||||
@@ -48,8 +47,6 @@ 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");
|
||||
@@ -57,8 +54,6 @@ 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");
|
||||
|
||||
DEFINE_bool(use_v2_aggregator, false, "benchmark RangeDelAggregatorV2");
|
||||
|
||||
namespace {
|
||||
|
||||
struct Stats {
|
||||
@@ -187,14 +182,10 @@ int main(int argc, char** argv) {
|
||||
std::vector<rocksdb::PersistentRangeTombstone>(
|
||||
FLAGS_num_range_tombstones);
|
||||
}
|
||||
auto mode = FLAGS_use_collapsed
|
||||
? rocksdb::RangeDelPositioningMode::kForwardTraversal
|
||||
: rocksdb::RangeDelPositioningMode::kFullScan;
|
||||
auto mode = rocksdb::RangeDelPositioningMode::kForwardTraversal;
|
||||
|
||||
for (int i = 0; i < FLAGS_num_runs; i++) {
|
||||
rocksdb::RangeDelAggregator range_del_agg(icmp, {} /* snapshots */,
|
||||
FLAGS_use_collapsed);
|
||||
rocksdb::RangeDelAggregatorV2 range_del_agg_v2(
|
||||
rocksdb::ReadRangeDelAggregator range_del_agg(
|
||||
&icmp, rocksdb::kMaxSequenceNumber /* upper_bound */);
|
||||
|
||||
std::vector<std::unique_ptr<rocksdb::FragmentedRangeTombstoneList> >
|
||||
@@ -206,7 +197,8 @@ int main(int argc, char** argv) {
|
||||
// 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));
|
||||
uint64_t end = static_cast<uint64_t>(
|
||||
std::round(start + std::max(1.0, normal_dist(random_gen))));
|
||||
persistent_range_tombstones[j] = rocksdb::PersistentRangeTombstone(
|
||||
rocksdb::Key(start), rocksdb::Key(end), j);
|
||||
}
|
||||
@@ -220,20 +212,13 @@ int main(int argc, char** argv) {
|
||||
std::unique_ptr<rocksdb::FragmentedRangeTombstoneIterator>
|
||||
fragmented_range_del_iter(
|
||||
new rocksdb::FragmentedRangeTombstoneIterator(
|
||||
fragmented_range_tombstone_lists.back().get(),
|
||||
rocksdb::kMaxSequenceNumber, icmp));
|
||||
fragmented_range_tombstone_lists.back().get(), icmp,
|
||||
rocksdb::kMaxSequenceNumber));
|
||||
|
||||
if (FLAGS_use_v2_aggregator) {
|
||||
rocksdb::StopWatchNano stop_watch_add_tombstones(
|
||||
rocksdb::Env::Default(), true /* auto_start */);
|
||||
range_del_agg_v2.AddTombstones(std::move(fragmented_range_del_iter));
|
||||
stats.time_add_tombstones += stop_watch_add_tombstones.ElapsedNanos();
|
||||
} else {
|
||||
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::StopWatchNano stop_watch_add_tombstones(rocksdb::Env::Default(),
|
||||
true /* auto_start */);
|
||||
range_del_agg.AddTombstones(std::move(fragmented_range_del_iter));
|
||||
stats.time_add_tombstones += stop_watch_add_tombstones.ElapsedNanos();
|
||||
}
|
||||
|
||||
rocksdb::ParsedInternalKey parsed_key;
|
||||
@@ -247,18 +232,10 @@ int main(int argc, char** argv) {
|
||||
std::string key_string = rocksdb::Key(first_key + j);
|
||||
parsed_key.user_key = key_string;
|
||||
|
||||
uint64_t call_time;
|
||||
if (FLAGS_use_v2_aggregator) {
|
||||
rocksdb::StopWatchNano stop_watch_should_delete(rocksdb::Env::Default(),
|
||||
true /* auto_start */);
|
||||
range_del_agg_v2.ShouldDelete(parsed_key, mode);
|
||||
call_time = stop_watch_should_delete.ElapsedNanos();
|
||||
} else {
|
||||
rocksdb::StopWatchNano stop_watch_should_delete(rocksdb::Env::Default(),
|
||||
true /* auto_start */);
|
||||
range_del_agg.ShouldDelete(parsed_key, mode);
|
||||
call_time = stop_watch_should_delete.ElapsedNanos();
|
||||
}
|
||||
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;
|
||||
|
||||
+637
-400
File diff suppressed because it is too large
Load Diff
@@ -1,295 +0,0 @@
|
||||
// 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).
|
||||
|
||||
#include "db/range_del_aggregator_v2.h"
|
||||
|
||||
#include "db/compaction_iteration_stats.h"
|
||||
#include "db/dbformat.h"
|
||||
#include "db/pinned_iterators_manager.h"
|
||||
#include "db/range_del_aggregator.h"
|
||||
#include "db/range_tombstone_fragmenter.h"
|
||||
#include "db/version_edit.h"
|
||||
#include "include/rocksdb/comparator.h"
|
||||
#include "include/rocksdb/types.h"
|
||||
#include "table/internal_iterator.h"
|
||||
#include "table/scoped_arena_iterator.h"
|
||||
#include "table/table_builder.h"
|
||||
#include "util/heap.h"
|
||||
#include "util/kv_map.h"
|
||||
#include "util/vector_iterator.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
TruncatedRangeDelIterator::TruncatedRangeDelIterator(
|
||||
std::unique_ptr<FragmentedRangeTombstoneIterator> iter,
|
||||
const InternalKeyComparator* icmp, const InternalKey* smallest,
|
||||
const InternalKey* largest)
|
||||
: iter_(std::move(iter)), icmp_(icmp) {
|
||||
if (smallest != nullptr) {
|
||||
pinned_bounds_.emplace_back();
|
||||
auto& parsed_smallest = pinned_bounds_.back();
|
||||
if (!ParseInternalKey(smallest->Encode(), &parsed_smallest)) {
|
||||
assert(false);
|
||||
}
|
||||
smallest_ = &parsed_smallest;
|
||||
}
|
||||
if (largest != nullptr) {
|
||||
pinned_bounds_.emplace_back();
|
||||
auto& parsed_largest = pinned_bounds_.back();
|
||||
if (!ParseInternalKey(largest->Encode(), &parsed_largest)) {
|
||||
assert(false);
|
||||
}
|
||||
if (parsed_largest.type == kTypeRangeDeletion &&
|
||||
parsed_largest.sequence == kMaxSequenceNumber) {
|
||||
// The file boundary has been artificially extended by a range tombstone.
|
||||
// We do not need to adjust largest to properly truncate range
|
||||
// tombstones that extend past the boundary.
|
||||
} else if (parsed_largest.sequence == 0) {
|
||||
// The largest key in the sstable has a sequence number of 0. Since we
|
||||
// guarantee that no internal keys with the same user key and sequence
|
||||
// number can exist in a DB, we know that the largest key in this sstable
|
||||
// cannot exist as the smallest key in the next sstable. This further
|
||||
// implies that no range tombstone in this sstable covers largest;
|
||||
// otherwise, the file boundary would have been artificially extended.
|
||||
//
|
||||
// Therefore, we will never truncate a range tombstone at largest, so we
|
||||
// can leave it unchanged.
|
||||
} else {
|
||||
// The same user key may straddle two sstable boundaries. To ensure that
|
||||
// the truncated end key can cover the largest key in this sstable, reduce
|
||||
// its sequence number by 1.
|
||||
parsed_largest.sequence -= 1;
|
||||
}
|
||||
largest_ = &parsed_largest;
|
||||
}
|
||||
}
|
||||
|
||||
bool TruncatedRangeDelIterator::Valid() const {
|
||||
return iter_->Valid() &&
|
||||
(smallest_ == nullptr ||
|
||||
icmp_->Compare(*smallest_, iter_->parsed_end_key()) < 0) &&
|
||||
(largest_ == nullptr ||
|
||||
icmp_->Compare(iter_->parsed_start_key(), *largest_) < 0);
|
||||
}
|
||||
|
||||
void TruncatedRangeDelIterator::Next() { iter_->TopNext(); }
|
||||
|
||||
void TruncatedRangeDelIterator::Prev() { iter_->TopPrev(); }
|
||||
|
||||
// NOTE: target is a user key
|
||||
void TruncatedRangeDelIterator::Seek(const Slice& target) {
|
||||
if (largest_ != nullptr &&
|
||||
icmp_->Compare(*largest_, ParsedInternalKey(target, kMaxSequenceNumber,
|
||||
kTypeRangeDeletion)) <= 0) {
|
||||
iter_->Invalidate();
|
||||
return;
|
||||
}
|
||||
iter_->Seek(target);
|
||||
}
|
||||
|
||||
// NOTE: target is a user key
|
||||
void TruncatedRangeDelIterator::SeekForPrev(const Slice& target) {
|
||||
if (smallest_ != nullptr &&
|
||||
icmp_->Compare(ParsedInternalKey(target, 0, kTypeRangeDeletion),
|
||||
*smallest_) < 0) {
|
||||
iter_->Invalidate();
|
||||
return;
|
||||
}
|
||||
iter_->SeekForPrev(target);
|
||||
}
|
||||
|
||||
void TruncatedRangeDelIterator::SeekToFirst() { iter_->SeekToTopFirst(); }
|
||||
|
||||
void TruncatedRangeDelIterator::SeekToLast() { iter_->SeekToTopLast(); }
|
||||
|
||||
ForwardRangeDelIterator::ForwardRangeDelIterator(
|
||||
const InternalKeyComparator* icmp,
|
||||
const std::vector<std::unique_ptr<TruncatedRangeDelIterator>>* iters)
|
||||
: icmp_(icmp),
|
||||
iters_(iters),
|
||||
unused_idx_(0),
|
||||
active_seqnums_(SeqMaxComparator()),
|
||||
active_iters_(EndKeyMinComparator(icmp)),
|
||||
inactive_iters_(StartKeyMinComparator(icmp)) {}
|
||||
|
||||
bool ForwardRangeDelIterator::ShouldDelete(const ParsedInternalKey& parsed) {
|
||||
assert(iters_ != nullptr);
|
||||
// Pick up previously unseen iterators.
|
||||
for (auto it = std::next(iters_->begin(), unused_idx_); it != iters_->end();
|
||||
++it, ++unused_idx_) {
|
||||
auto& iter = *it;
|
||||
iter->Seek(parsed.user_key);
|
||||
PushIter(iter.get(), parsed);
|
||||
assert(active_iters_.size() == active_seqnums_.size());
|
||||
}
|
||||
|
||||
// Move active iterators that end before parsed.
|
||||
while (!active_iters_.empty() &&
|
||||
icmp_->Compare((*active_iters_.top())->end_key(), parsed) <= 0) {
|
||||
TruncatedRangeDelIterator* iter = PopActiveIter();
|
||||
do {
|
||||
iter->Next();
|
||||
} while (iter->Valid() && icmp_->Compare(iter->end_key(), parsed) <= 0);
|
||||
PushIter(iter, parsed);
|
||||
assert(active_iters_.size() == active_seqnums_.size());
|
||||
}
|
||||
|
||||
// Move inactive iterators that start before parsed.
|
||||
while (!inactive_iters_.empty() &&
|
||||
icmp_->Compare(inactive_iters_.top()->start_key(), parsed) <= 0) {
|
||||
TruncatedRangeDelIterator* iter = PopInactiveIter();
|
||||
while (iter->Valid() && icmp_->Compare(iter->end_key(), parsed) <= 0) {
|
||||
iter->Next();
|
||||
}
|
||||
PushIter(iter, parsed);
|
||||
assert(active_iters_.size() == active_seqnums_.size());
|
||||
}
|
||||
|
||||
return active_seqnums_.empty()
|
||||
? false
|
||||
: (*active_seqnums_.begin())->seq() > parsed.sequence;
|
||||
}
|
||||
|
||||
void ForwardRangeDelIterator::Invalidate() {
|
||||
unused_idx_ = 0;
|
||||
active_iters_.clear();
|
||||
active_seqnums_.clear();
|
||||
inactive_iters_.clear();
|
||||
}
|
||||
|
||||
ReverseRangeDelIterator::ReverseRangeDelIterator(
|
||||
const InternalKeyComparator* icmp,
|
||||
const std::vector<std::unique_ptr<TruncatedRangeDelIterator>>* iters)
|
||||
: icmp_(icmp),
|
||||
iters_(iters),
|
||||
unused_idx_(0),
|
||||
active_seqnums_(SeqMaxComparator()),
|
||||
active_iters_(StartKeyMaxComparator(icmp)),
|
||||
inactive_iters_(EndKeyMaxComparator(icmp)) {}
|
||||
|
||||
bool ReverseRangeDelIterator::ShouldDelete(const ParsedInternalKey& parsed) {
|
||||
assert(iters_ != nullptr);
|
||||
// Pick up previously unseen iterators.
|
||||
for (auto it = std::next(iters_->begin(), unused_idx_); it != iters_->end();
|
||||
++it, ++unused_idx_) {
|
||||
auto& iter = *it;
|
||||
iter->SeekForPrev(parsed.user_key);
|
||||
PushIter(iter.get(), parsed);
|
||||
assert(active_iters_.size() == active_seqnums_.size());
|
||||
}
|
||||
|
||||
// Move active iterators that start after parsed.
|
||||
while (!active_iters_.empty() &&
|
||||
icmp_->Compare(parsed, (*active_iters_.top())->start_key()) < 0) {
|
||||
TruncatedRangeDelIterator* iter = PopActiveIter();
|
||||
do {
|
||||
iter->Prev();
|
||||
} while (iter->Valid() && icmp_->Compare(parsed, iter->start_key()) < 0);
|
||||
PushIter(iter, parsed);
|
||||
assert(active_iters_.size() == active_seqnums_.size());
|
||||
}
|
||||
|
||||
// Move inactive iterators that end after parsed.
|
||||
while (!inactive_iters_.empty() &&
|
||||
icmp_->Compare(parsed, inactive_iters_.top()->end_key()) < 0) {
|
||||
TruncatedRangeDelIterator* iter = PopInactiveIter();
|
||||
while (iter->Valid() && icmp_->Compare(parsed, iter->start_key()) < 0) {
|
||||
iter->Prev();
|
||||
}
|
||||
PushIter(iter, parsed);
|
||||
assert(active_iters_.size() == active_seqnums_.size());
|
||||
}
|
||||
|
||||
return active_seqnums_.empty()
|
||||
? false
|
||||
: (*active_seqnums_.begin())->seq() > parsed.sequence;
|
||||
}
|
||||
|
||||
void ReverseRangeDelIterator::Invalidate() {
|
||||
unused_idx_ = 0;
|
||||
active_iters_.clear();
|
||||
active_seqnums_.clear();
|
||||
inactive_iters_.clear();
|
||||
}
|
||||
|
||||
RangeDelAggregatorV2::RangeDelAggregatorV2(const InternalKeyComparator* icmp,
|
||||
SequenceNumber /* upper_bound */)
|
||||
: icmp_(icmp), forward_iter_(icmp, &iters_), reverse_iter_(icmp, &iters_) {}
|
||||
|
||||
void RangeDelAggregatorV2::AddTombstones(
|
||||
std::unique_ptr<FragmentedRangeTombstoneIterator> input_iter,
|
||||
const InternalKey* smallest, const InternalKey* largest) {
|
||||
if (input_iter == nullptr || input_iter->empty()) {
|
||||
return;
|
||||
}
|
||||
if (wrapped_range_del_agg != nullptr) {
|
||||
wrapped_range_del_agg->AddTombstones(std::move(input_iter), smallest,
|
||||
largest);
|
||||
// TODO: this eats the status of the wrapped call; may want to propagate it
|
||||
return;
|
||||
}
|
||||
iters_.emplace_back(new TruncatedRangeDelIterator(std::move(input_iter),
|
||||
icmp_, smallest, largest));
|
||||
}
|
||||
|
||||
bool RangeDelAggregatorV2::ShouldDelete(const ParsedInternalKey& parsed,
|
||||
RangeDelPositioningMode mode) {
|
||||
if (wrapped_range_del_agg != nullptr) {
|
||||
return wrapped_range_del_agg->ShouldDelete(parsed, mode);
|
||||
}
|
||||
|
||||
switch (mode) {
|
||||
case RangeDelPositioningMode::kForwardTraversal:
|
||||
reverse_iter_.Invalidate();
|
||||
return forward_iter_.ShouldDelete(parsed);
|
||||
case RangeDelPositioningMode::kBackwardTraversal:
|
||||
forward_iter_.Invalidate();
|
||||
return reverse_iter_.ShouldDelete(parsed);
|
||||
default:
|
||||
assert(false);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool RangeDelAggregatorV2::IsRangeOverlapped(const Slice& start,
|
||||
const Slice& end) {
|
||||
assert(wrapped_range_del_agg == nullptr);
|
||||
InvalidateRangeDelMapPositions();
|
||||
|
||||
// Set the internal start/end keys so that:
|
||||
// - if start_ikey has the same user key and sequence number as the current
|
||||
// end key, start_ikey will be considered greater; and
|
||||
// - if end_ikey has the same user key and sequence number as the current
|
||||
// start key, end_ikey will be considered greater.
|
||||
ParsedInternalKey start_ikey(start, kMaxSequenceNumber,
|
||||
static_cast<ValueType>(0));
|
||||
ParsedInternalKey end_ikey(end, 0, static_cast<ValueType>(0));
|
||||
for (auto& iter : iters_) {
|
||||
bool checked_candidate_tombstones = false;
|
||||
for (iter->SeekForPrev(start);
|
||||
iter->Valid() && icmp_->Compare(iter->start_key(), end_ikey) <= 0;
|
||||
iter->Next()) {
|
||||
checked_candidate_tombstones = true;
|
||||
if (icmp_->Compare(start_ikey, iter->end_key()) < 0 &&
|
||||
icmp_->Compare(iter->start_key(), end_ikey) <= 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!checked_candidate_tombstones) {
|
||||
// Do an additional check for when the end of the range is the begin key
|
||||
// of a tombstone, which we missed earlier since SeekForPrev'ing to the
|
||||
// start was invalid.
|
||||
iter->SeekForPrev(end);
|
||||
if (iter->Valid() && icmp_->Compare(start_ikey, iter->end_key()) < 0 &&
|
||||
icmp_->Compare(iter->start_key(), end_ikey) <= 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
@@ -1,295 +0,0 @@
|
||||
// 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).
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <list>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "db/compaction_iteration_stats.h"
|
||||
#include "db/dbformat.h"
|
||||
#include "db/pinned_iterators_manager.h"
|
||||
#include "db/range_del_aggregator.h"
|
||||
#include "db/range_tombstone_fragmenter.h"
|
||||
#include "db/version_edit.h"
|
||||
#include "include/rocksdb/comparator.h"
|
||||
#include "include/rocksdb/types.h"
|
||||
#include "table/internal_iterator.h"
|
||||
#include "table/scoped_arena_iterator.h"
|
||||
#include "table/table_builder.h"
|
||||
#include "util/heap.h"
|
||||
#include "util/kv_map.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
class RangeDelAggregatorV2;
|
||||
|
||||
class TruncatedRangeDelIterator {
|
||||
public:
|
||||
TruncatedRangeDelIterator(
|
||||
std::unique_ptr<FragmentedRangeTombstoneIterator> iter,
|
||||
const InternalKeyComparator* icmp, const InternalKey* smallest,
|
||||
const InternalKey* largest);
|
||||
|
||||
bool Valid() const;
|
||||
|
||||
void Next();
|
||||
void Prev();
|
||||
|
||||
// Seeks to the tombstone with the highest viisble sequence number that covers
|
||||
// target (a user key). If no such tombstone exists, the position will be at
|
||||
// the earliest tombstone that ends after target.
|
||||
void Seek(const Slice& target);
|
||||
|
||||
// Seeks to the tombstone with the highest viisble sequence number that covers
|
||||
// target (a user key). If no such tombstone exists, the position will be at
|
||||
// the latest tombstone that starts before target.
|
||||
void SeekForPrev(const Slice& target);
|
||||
|
||||
void SeekToFirst();
|
||||
void SeekToLast();
|
||||
|
||||
ParsedInternalKey start_key() const {
|
||||
return (smallest_ == nullptr ||
|
||||
icmp_->Compare(*smallest_, iter_->parsed_start_key()) <= 0)
|
||||
? iter_->parsed_start_key()
|
||||
: *smallest_;
|
||||
}
|
||||
|
||||
ParsedInternalKey end_key() const {
|
||||
return (largest_ == nullptr ||
|
||||
icmp_->Compare(iter_->parsed_end_key(), *largest_) <= 0)
|
||||
? iter_->parsed_end_key()
|
||||
: *largest_;
|
||||
}
|
||||
|
||||
SequenceNumber seq() const { return iter_->seq(); }
|
||||
|
||||
private:
|
||||
std::unique_ptr<FragmentedRangeTombstoneIterator> iter_;
|
||||
const InternalKeyComparator* icmp_;
|
||||
const ParsedInternalKey* smallest_ = nullptr;
|
||||
const ParsedInternalKey* largest_ = nullptr;
|
||||
std::list<ParsedInternalKey> pinned_bounds_;
|
||||
};
|
||||
|
||||
struct SeqMaxComparator {
|
||||
bool operator()(const TruncatedRangeDelIterator* a,
|
||||
const TruncatedRangeDelIterator* b) const {
|
||||
return a->seq() > b->seq();
|
||||
}
|
||||
};
|
||||
|
||||
class ForwardRangeDelIterator {
|
||||
public:
|
||||
ForwardRangeDelIterator(
|
||||
const InternalKeyComparator* icmp,
|
||||
const std::vector<std::unique_ptr<TruncatedRangeDelIterator>>* iters);
|
||||
|
||||
bool ShouldDelete(const ParsedInternalKey& parsed);
|
||||
void Invalidate();
|
||||
|
||||
private:
|
||||
using ActiveSeqSet =
|
||||
std::multiset<TruncatedRangeDelIterator*, SeqMaxComparator>;
|
||||
|
||||
struct StartKeyMinComparator {
|
||||
explicit StartKeyMinComparator(const InternalKeyComparator* c) : icmp(c) {}
|
||||
|
||||
bool operator()(const TruncatedRangeDelIterator* a,
|
||||
const TruncatedRangeDelIterator* b) const {
|
||||
return icmp->Compare(a->start_key(), b->start_key()) > 0;
|
||||
}
|
||||
|
||||
const InternalKeyComparator* icmp;
|
||||
};
|
||||
struct EndKeyMinComparator {
|
||||
explicit EndKeyMinComparator(const InternalKeyComparator* c) : icmp(c) {}
|
||||
|
||||
bool operator()(const ActiveSeqSet::const_iterator& a,
|
||||
const ActiveSeqSet::const_iterator& b) const {
|
||||
return icmp->Compare((*a)->end_key(), (*b)->end_key()) > 0;
|
||||
}
|
||||
|
||||
const InternalKeyComparator* icmp;
|
||||
};
|
||||
|
||||
void PushIter(TruncatedRangeDelIterator* iter,
|
||||
const ParsedInternalKey& parsed) {
|
||||
if (!iter->Valid()) {
|
||||
// The iterator has been fully consumed, so we don't need to add it to
|
||||
// either of the heaps.
|
||||
} else if (icmp_->Compare(parsed, iter->start_key()) < 0) {
|
||||
PushInactiveIter(iter);
|
||||
} else {
|
||||
PushActiveIter(iter);
|
||||
}
|
||||
}
|
||||
|
||||
void PushActiveIter(TruncatedRangeDelIterator* iter) {
|
||||
auto seq_pos = active_seqnums_.insert(iter);
|
||||
active_iters_.push(seq_pos);
|
||||
}
|
||||
|
||||
TruncatedRangeDelIterator* PopActiveIter() {
|
||||
auto active_top = active_iters_.top();
|
||||
auto iter = *active_top;
|
||||
active_iters_.pop();
|
||||
active_seqnums_.erase(active_top);
|
||||
return iter;
|
||||
}
|
||||
|
||||
void PushInactiveIter(TruncatedRangeDelIterator* iter) {
|
||||
inactive_iters_.push(iter);
|
||||
}
|
||||
|
||||
TruncatedRangeDelIterator* PopInactiveIter() {
|
||||
auto* iter = inactive_iters_.top();
|
||||
inactive_iters_.pop();
|
||||
return iter;
|
||||
}
|
||||
|
||||
const InternalKeyComparator* icmp_;
|
||||
const std::vector<std::unique_ptr<TruncatedRangeDelIterator>>* iters_;
|
||||
size_t unused_idx_;
|
||||
ActiveSeqSet active_seqnums_;
|
||||
BinaryHeap<ActiveSeqSet::const_iterator, EndKeyMinComparator> active_iters_;
|
||||
BinaryHeap<TruncatedRangeDelIterator*, StartKeyMinComparator> inactive_iters_;
|
||||
};
|
||||
|
||||
class ReverseRangeDelIterator {
|
||||
public:
|
||||
ReverseRangeDelIterator(
|
||||
const InternalKeyComparator* icmp,
|
||||
const std::vector<std::unique_ptr<TruncatedRangeDelIterator>>* iters);
|
||||
|
||||
bool ShouldDelete(const ParsedInternalKey& parsed);
|
||||
void Invalidate();
|
||||
|
||||
private:
|
||||
using ActiveSeqSet =
|
||||
std::multiset<TruncatedRangeDelIterator*, SeqMaxComparator>;
|
||||
|
||||
struct EndKeyMaxComparator {
|
||||
explicit EndKeyMaxComparator(const InternalKeyComparator* c) : icmp(c) {}
|
||||
|
||||
bool operator()(const TruncatedRangeDelIterator* a,
|
||||
const TruncatedRangeDelIterator* b) const {
|
||||
return icmp->Compare(a->end_key(), b->end_key()) < 0;
|
||||
}
|
||||
|
||||
const InternalKeyComparator* icmp;
|
||||
};
|
||||
struct StartKeyMaxComparator {
|
||||
explicit StartKeyMaxComparator(const InternalKeyComparator* c) : icmp(c) {}
|
||||
|
||||
bool operator()(const ActiveSeqSet::const_iterator& a,
|
||||
const ActiveSeqSet::const_iterator& b) const {
|
||||
return icmp->Compare((*a)->start_key(), (*b)->start_key()) < 0;
|
||||
}
|
||||
|
||||
const InternalKeyComparator* icmp;
|
||||
};
|
||||
|
||||
void PushIter(TruncatedRangeDelIterator* iter,
|
||||
const ParsedInternalKey& parsed) {
|
||||
if (!iter->Valid()) {
|
||||
// The iterator has been fully consumed, so we don't need to add it to
|
||||
// either of the heaps.
|
||||
} else if (icmp_->Compare(iter->end_key(), parsed) <= 0) {
|
||||
PushInactiveIter(iter);
|
||||
} else {
|
||||
PushActiveIter(iter);
|
||||
}
|
||||
}
|
||||
|
||||
void PushActiveIter(TruncatedRangeDelIterator* iter) {
|
||||
auto seq_pos = active_seqnums_.insert(iter);
|
||||
active_iters_.push(seq_pos);
|
||||
}
|
||||
|
||||
TruncatedRangeDelIterator* PopActiveIter() {
|
||||
auto active_top = active_iters_.top();
|
||||
auto iter = *active_top;
|
||||
active_iters_.pop();
|
||||
active_seqnums_.erase(active_top);
|
||||
return iter;
|
||||
}
|
||||
|
||||
void PushInactiveIter(TruncatedRangeDelIterator* iter) {
|
||||
inactive_iters_.push(iter);
|
||||
}
|
||||
|
||||
TruncatedRangeDelIterator* PopInactiveIter() {
|
||||
auto* iter = inactive_iters_.top();
|
||||
inactive_iters_.pop();
|
||||
return iter;
|
||||
}
|
||||
|
||||
const InternalKeyComparator* icmp_;
|
||||
const std::vector<std::unique_ptr<TruncatedRangeDelIterator>>* iters_;
|
||||
size_t unused_idx_;
|
||||
ActiveSeqSet active_seqnums_;
|
||||
BinaryHeap<ActiveSeqSet::const_iterator, StartKeyMaxComparator> active_iters_;
|
||||
BinaryHeap<TruncatedRangeDelIterator*, EndKeyMaxComparator> inactive_iters_;
|
||||
};
|
||||
|
||||
class RangeDelAggregatorV2 {
|
||||
public:
|
||||
RangeDelAggregatorV2(const InternalKeyComparator* icmp,
|
||||
SequenceNumber upper_bound);
|
||||
|
||||
void AddTombstones(
|
||||
std::unique_ptr<FragmentedRangeTombstoneIterator> input_iter,
|
||||
const InternalKey* smallest = nullptr,
|
||||
const InternalKey* largest = nullptr);
|
||||
|
||||
bool ShouldDelete(const ParsedInternalKey& parsed,
|
||||
RangeDelPositioningMode mode);
|
||||
|
||||
bool IsRangeOverlapped(const Slice& start, const Slice& end);
|
||||
|
||||
void InvalidateRangeDelMapPositions() {
|
||||
forward_iter_.Invalidate();
|
||||
reverse_iter_.Invalidate();
|
||||
}
|
||||
|
||||
bool IsEmpty() const { return iters_.empty(); }
|
||||
bool AddFile(uint64_t file_number) {
|
||||
return files_seen_.insert(file_number).second;
|
||||
}
|
||||
|
||||
// Adaptor method to pass calls through to an old-style RangeDelAggregator.
|
||||
// Will be removed once this new version supports an iterator that can be used
|
||||
// during flush/compaction.
|
||||
RangeDelAggregator* DelegateToRangeDelAggregator(
|
||||
const std::vector<SequenceNumber>& snapshots) {
|
||||
wrapped_range_del_agg.reset(new RangeDelAggregator(
|
||||
*icmp_, snapshots, true /* collapse_deletions */));
|
||||
return wrapped_range_del_agg.get();
|
||||
}
|
||||
|
||||
std::unique_ptr<RangeDelIterator> NewIterator() {
|
||||
assert(wrapped_range_del_agg != nullptr);
|
||||
return wrapped_range_del_agg->NewIterator();
|
||||
}
|
||||
|
||||
private:
|
||||
const InternalKeyComparator* icmp_;
|
||||
|
||||
std::vector<std::unique_ptr<TruncatedRangeDelIterator>> iters_;
|
||||
std::set<uint64_t> files_seen_;
|
||||
|
||||
ForwardRangeDelIterator forward_iter_;
|
||||
ReverseRangeDelIterator reverse_iter_;
|
||||
|
||||
// TODO: remove once V2 supports exposing tombstone iterators
|
||||
std::unique_ptr<RangeDelAggregator> wrapped_range_del_agg;
|
||||
};
|
||||
|
||||
} // namespace rocksdb
|
||||
@@ -1,469 +0,0 @@
|
||||
// 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).
|
||||
|
||||
#include "db/range_del_aggregator_v2.h"
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "db/db_test_util.h"
|
||||
#include "db/dbformat.h"
|
||||
#include "db/range_tombstone_fragmenter.h"
|
||||
#include "util/testutil.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
class RangeDelAggregatorV2Test : public testing::Test {};
|
||||
|
||||
namespace {
|
||||
|
||||
static auto bytewise_icmp = InternalKeyComparator(BytewiseComparator());
|
||||
|
||||
std::unique_ptr<InternalIterator> MakeRangeDelIter(
|
||||
const std::vector<RangeTombstone>& range_dels) {
|
||||
std::vector<std::string> keys, values;
|
||||
for (const auto& range_del : range_dels) {
|
||||
auto key_and_value = range_del.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));
|
||||
}
|
||||
|
||||
std::vector<std::unique_ptr<FragmentedRangeTombstoneList>>
|
||||
MakeFragmentedTombstoneLists(
|
||||
const std::vector<std::vector<RangeTombstone>>& range_dels_list) {
|
||||
std::vector<std::unique_ptr<FragmentedRangeTombstoneList>> fragment_lists;
|
||||
for (const auto& range_dels : range_dels_list) {
|
||||
auto range_del_iter = MakeRangeDelIter(range_dels);
|
||||
fragment_lists.emplace_back(new FragmentedRangeTombstoneList(
|
||||
std::move(range_del_iter), bytewise_icmp));
|
||||
}
|
||||
return fragment_lists;
|
||||
}
|
||||
|
||||
struct TruncatedIterScanTestCase {
|
||||
ParsedInternalKey start;
|
||||
ParsedInternalKey end;
|
||||
SequenceNumber seq;
|
||||
};
|
||||
|
||||
struct TruncatedIterSeekTestCase {
|
||||
Slice target;
|
||||
ParsedInternalKey start;
|
||||
ParsedInternalKey end;
|
||||
SequenceNumber seq;
|
||||
bool invalid;
|
||||
};
|
||||
|
||||
struct ShouldDeleteTestCase {
|
||||
ParsedInternalKey lookup_key;
|
||||
bool result;
|
||||
};
|
||||
|
||||
struct IsRangeOverlappedTestCase {
|
||||
Slice start;
|
||||
Slice end;
|
||||
bool result;
|
||||
};
|
||||
|
||||
ParsedInternalKey UncutEndpoint(const Slice& s) {
|
||||
return ParsedInternalKey(s, kMaxSequenceNumber, kTypeRangeDeletion);
|
||||
}
|
||||
|
||||
ParsedInternalKey InternalValue(const Slice& key, SequenceNumber seq) {
|
||||
return ParsedInternalKey(key, seq, kTypeValue);
|
||||
}
|
||||
|
||||
void VerifyIterator(
|
||||
TruncatedRangeDelIterator* iter, const InternalKeyComparator& icmp,
|
||||
const std::vector<TruncatedIterScanTestCase>& expected_range_dels) {
|
||||
// Test forward iteration.
|
||||
iter->SeekToFirst();
|
||||
for (size_t i = 0; i < expected_range_dels.size(); i++, iter->Next()) {
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
EXPECT_EQ(0, icmp.Compare(iter->start_key(), expected_range_dels[i].start));
|
||||
EXPECT_EQ(0, icmp.Compare(iter->end_key(), expected_range_dels[i].end));
|
||||
EXPECT_EQ(expected_range_dels[i].seq, iter->seq());
|
||||
}
|
||||
EXPECT_FALSE(iter->Valid());
|
||||
|
||||
// Test reverse iteration.
|
||||
iter->SeekToLast();
|
||||
std::vector<TruncatedIterScanTestCase> reverse_expected_range_dels(
|
||||
expected_range_dels.rbegin(), expected_range_dels.rend());
|
||||
for (size_t i = 0; i < reverse_expected_range_dels.size();
|
||||
i++, iter->Prev()) {
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
EXPECT_EQ(0, icmp.Compare(iter->start_key(),
|
||||
reverse_expected_range_dels[i].start));
|
||||
EXPECT_EQ(
|
||||
0, icmp.Compare(iter->end_key(), reverse_expected_range_dels[i].end));
|
||||
EXPECT_EQ(reverse_expected_range_dels[i].seq, iter->seq());
|
||||
}
|
||||
EXPECT_FALSE(iter->Valid());
|
||||
}
|
||||
|
||||
void VerifySeek(TruncatedRangeDelIterator* iter,
|
||||
const InternalKeyComparator& icmp,
|
||||
const std::vector<TruncatedIterSeekTestCase>& test_cases) {
|
||||
for (const auto& test_case : test_cases) {
|
||||
iter->Seek(test_case.target);
|
||||
if (test_case.invalid) {
|
||||
ASSERT_FALSE(iter->Valid());
|
||||
} else {
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
EXPECT_EQ(0, icmp.Compare(iter->start_key(), test_case.start));
|
||||
EXPECT_EQ(0, icmp.Compare(iter->end_key(), test_case.end));
|
||||
EXPECT_EQ(test_case.seq, iter->seq());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void VerifySeekForPrev(
|
||||
TruncatedRangeDelIterator* iter, const InternalKeyComparator& icmp,
|
||||
const std::vector<TruncatedIterSeekTestCase>& test_cases) {
|
||||
for (const auto& test_case : test_cases) {
|
||||
iter->SeekForPrev(test_case.target);
|
||||
if (test_case.invalid) {
|
||||
ASSERT_FALSE(iter->Valid());
|
||||
} else {
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
EXPECT_EQ(0, icmp.Compare(iter->start_key(), test_case.start));
|
||||
EXPECT_EQ(0, icmp.Compare(iter->end_key(), test_case.end));
|
||||
EXPECT_EQ(test_case.seq, iter->seq());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void VerifyShouldDelete(RangeDelAggregatorV2* range_del_agg,
|
||||
const std::vector<ShouldDeleteTestCase>& test_cases) {
|
||||
for (const auto& test_case : test_cases) {
|
||||
EXPECT_EQ(
|
||||
test_case.result,
|
||||
range_del_agg->ShouldDelete(
|
||||
test_case.lookup_key, RangeDelPositioningMode::kForwardTraversal));
|
||||
}
|
||||
for (auto it = test_cases.rbegin(); it != test_cases.rend(); ++it) {
|
||||
const auto& test_case = *it;
|
||||
EXPECT_EQ(
|
||||
test_case.result,
|
||||
range_del_agg->ShouldDelete(
|
||||
test_case.lookup_key, RangeDelPositioningMode::kBackwardTraversal));
|
||||
}
|
||||
}
|
||||
|
||||
void VerifyIsRangeOverlapped(
|
||||
RangeDelAggregatorV2* range_del_agg,
|
||||
const std::vector<IsRangeOverlappedTestCase>& test_cases) {
|
||||
for (const auto& test_case : test_cases) {
|
||||
EXPECT_EQ(test_case.result,
|
||||
range_del_agg->IsRangeOverlapped(test_case.start, test_case.end));
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST_F(RangeDelAggregatorV2Test, EmptyTruncatedIter) {
|
||||
auto range_del_iter = MakeRangeDelIter({});
|
||||
FragmentedRangeTombstoneList fragment_list(std::move(range_del_iter),
|
||||
bytewise_icmp);
|
||||
std::unique_ptr<FragmentedRangeTombstoneIterator> input_iter(
|
||||
new FragmentedRangeTombstoneIterator(&fragment_list, kMaxSequenceNumber,
|
||||
bytewise_icmp));
|
||||
|
||||
TruncatedRangeDelIterator iter(std::move(input_iter), &bytewise_icmp, nullptr,
|
||||
nullptr);
|
||||
|
||||
iter.SeekToFirst();
|
||||
ASSERT_FALSE(iter.Valid());
|
||||
|
||||
iter.SeekToLast();
|
||||
ASSERT_FALSE(iter.Valid());
|
||||
}
|
||||
|
||||
TEST_F(RangeDelAggregatorV2Test, UntruncatedIter) {
|
||||
auto range_del_iter =
|
||||
MakeRangeDelIter({{"a", "e", 10}, {"e", "g", 8}, {"j", "n", 4}});
|
||||
FragmentedRangeTombstoneList fragment_list(std::move(range_del_iter),
|
||||
bytewise_icmp);
|
||||
std::unique_ptr<FragmentedRangeTombstoneIterator> input_iter(
|
||||
new FragmentedRangeTombstoneIterator(&fragment_list, kMaxSequenceNumber,
|
||||
bytewise_icmp));
|
||||
|
||||
TruncatedRangeDelIterator iter(std::move(input_iter), &bytewise_icmp, nullptr,
|
||||
nullptr);
|
||||
|
||||
VerifyIterator(&iter, bytewise_icmp,
|
||||
{{UncutEndpoint("a"), UncutEndpoint("e"), 10},
|
||||
{UncutEndpoint("e"), UncutEndpoint("g"), 8},
|
||||
{UncutEndpoint("j"), UncutEndpoint("n"), 4}});
|
||||
|
||||
VerifySeek(
|
||||
&iter, bytewise_icmp,
|
||||
{{"d", UncutEndpoint("a"), UncutEndpoint("e"), 10},
|
||||
{"e", UncutEndpoint("e"), UncutEndpoint("g"), 8},
|
||||
{"ia", UncutEndpoint("j"), UncutEndpoint("n"), 4},
|
||||
{"n", UncutEndpoint(""), UncutEndpoint(""), 0, true /* invalid */},
|
||||
{"", UncutEndpoint("a"), UncutEndpoint("e"), 10}});
|
||||
|
||||
VerifySeekForPrev(
|
||||
&iter, bytewise_icmp,
|
||||
{{"d", UncutEndpoint("a"), UncutEndpoint("e"), 10},
|
||||
{"e", UncutEndpoint("e"), UncutEndpoint("g"), 8},
|
||||
{"ia", UncutEndpoint("e"), UncutEndpoint("g"), 8},
|
||||
{"n", UncutEndpoint("j"), UncutEndpoint("n"), 4},
|
||||
{"", UncutEndpoint(""), UncutEndpoint(""), 0, true /* invalid */}});
|
||||
}
|
||||
|
||||
TEST_F(RangeDelAggregatorV2Test, UntruncatedIterWithSnapshot) {
|
||||
auto range_del_iter =
|
||||
MakeRangeDelIter({{"a", "e", 10}, {"e", "g", 8}, {"j", "n", 4}});
|
||||
FragmentedRangeTombstoneList fragment_list(std::move(range_del_iter),
|
||||
bytewise_icmp);
|
||||
std::unique_ptr<FragmentedRangeTombstoneIterator> input_iter(
|
||||
new FragmentedRangeTombstoneIterator(&fragment_list, 9 /* snapshot */,
|
||||
bytewise_icmp));
|
||||
|
||||
TruncatedRangeDelIterator iter(std::move(input_iter), &bytewise_icmp, nullptr,
|
||||
nullptr);
|
||||
|
||||
VerifyIterator(&iter, bytewise_icmp,
|
||||
{{UncutEndpoint("e"), UncutEndpoint("g"), 8},
|
||||
{UncutEndpoint("j"), UncutEndpoint("n"), 4}});
|
||||
|
||||
VerifySeek(
|
||||
&iter, bytewise_icmp,
|
||||
{{"d", UncutEndpoint("e"), UncutEndpoint("g"), 8},
|
||||
{"e", UncutEndpoint("e"), UncutEndpoint("g"), 8},
|
||||
{"ia", UncutEndpoint("j"), UncutEndpoint("n"), 4},
|
||||
{"n", UncutEndpoint(""), UncutEndpoint(""), 0, true /* invalid */},
|
||||
{"", UncutEndpoint("e"), UncutEndpoint("g"), 8}});
|
||||
|
||||
VerifySeekForPrev(
|
||||
&iter, bytewise_icmp,
|
||||
{{"d", UncutEndpoint(""), UncutEndpoint(""), 0, true /* invalid */},
|
||||
{"e", UncutEndpoint("e"), UncutEndpoint("g"), 8},
|
||||
{"ia", UncutEndpoint("e"), UncutEndpoint("g"), 8},
|
||||
{"n", UncutEndpoint("j"), UncutEndpoint("n"), 4},
|
||||
{"", UncutEndpoint(""), UncutEndpoint(""), 0, true /* invalid */}});
|
||||
}
|
||||
|
||||
TEST_F(RangeDelAggregatorV2Test, TruncatedIter) {
|
||||
auto range_del_iter =
|
||||
MakeRangeDelIter({{"a", "e", 10}, {"e", "g", 8}, {"j", "n", 4}});
|
||||
FragmentedRangeTombstoneList fragment_list(std::move(range_del_iter),
|
||||
bytewise_icmp);
|
||||
std::unique_ptr<FragmentedRangeTombstoneIterator> input_iter(
|
||||
new FragmentedRangeTombstoneIterator(&fragment_list, kMaxSequenceNumber,
|
||||
bytewise_icmp));
|
||||
|
||||
InternalKey smallest("d", 7, kTypeValue);
|
||||
InternalKey largest("m", 9, kTypeValue);
|
||||
TruncatedRangeDelIterator iter(std::move(input_iter), &bytewise_icmp,
|
||||
&smallest, &largest);
|
||||
|
||||
VerifyIterator(&iter, bytewise_icmp,
|
||||
{{InternalValue("d", 7), UncutEndpoint("e"), 10},
|
||||
{UncutEndpoint("e"), UncutEndpoint("g"), 8},
|
||||
{UncutEndpoint("j"), InternalValue("m", 8), 4}});
|
||||
|
||||
VerifySeek(
|
||||
&iter, bytewise_icmp,
|
||||
{{"d", InternalValue("d", 7), UncutEndpoint("e"), 10},
|
||||
{"e", UncutEndpoint("e"), UncutEndpoint("g"), 8},
|
||||
{"ia", UncutEndpoint("j"), InternalValue("m", 8), 4},
|
||||
{"n", UncutEndpoint(""), UncutEndpoint(""), 0, true /* invalid */},
|
||||
{"", InternalValue("d", 7), UncutEndpoint("e"), 10}});
|
||||
|
||||
VerifySeekForPrev(
|
||||
&iter, bytewise_icmp,
|
||||
{{"d", InternalValue("d", 7), UncutEndpoint("e"), 10},
|
||||
{"e", UncutEndpoint("e"), UncutEndpoint("g"), 8},
|
||||
{"ia", UncutEndpoint("e"), UncutEndpoint("g"), 8},
|
||||
{"n", UncutEndpoint("j"), InternalValue("m", 8), 4},
|
||||
{"", UncutEndpoint(""), UncutEndpoint(""), 0, true /* invalid */}});
|
||||
}
|
||||
|
||||
TEST_F(RangeDelAggregatorV2Test, SingleIterInAggregator) {
|
||||
auto range_del_iter = MakeRangeDelIter({{"a", "e", 10}, {"c", "g", 8}});
|
||||
FragmentedRangeTombstoneList fragment_list(std::move(range_del_iter),
|
||||
bytewise_icmp);
|
||||
std::unique_ptr<FragmentedRangeTombstoneIterator> input_iter(
|
||||
new FragmentedRangeTombstoneIterator(&fragment_list, kMaxSequenceNumber,
|
||||
bytewise_icmp));
|
||||
|
||||
RangeDelAggregatorV2 range_del_agg(&bytewise_icmp, kMaxSequenceNumber);
|
||||
range_del_agg.AddTombstones(std::move(input_iter));
|
||||
|
||||
VerifyShouldDelete(&range_del_agg, {{InternalValue("a", 19), false},
|
||||
{InternalValue("b", 9), true},
|
||||
{InternalValue("d", 9), true},
|
||||
{InternalValue("e", 7), true},
|
||||
{InternalValue("g", 7), false}});
|
||||
|
||||
VerifyIsRangeOverlapped(&range_del_agg, {{"", "_", false},
|
||||
{"_", "a", true},
|
||||
{"a", "c", true},
|
||||
{"d", "f", true},
|
||||
{"g", "l", false}});
|
||||
}
|
||||
|
||||
TEST_F(RangeDelAggregatorV2Test, MultipleItersInAggregator) {
|
||||
auto fragment_lists = MakeFragmentedTombstoneLists(
|
||||
{{{"a", "e", 10}, {"c", "g", 8}},
|
||||
{{"a", "b", 20}, {"h", "i", 25}, {"ii", "j", 15}}});
|
||||
|
||||
RangeDelAggregatorV2 range_del_agg(&bytewise_icmp, kMaxSequenceNumber);
|
||||
for (const auto& fragment_list : fragment_lists) {
|
||||
std::unique_ptr<FragmentedRangeTombstoneIterator> input_iter(
|
||||
new FragmentedRangeTombstoneIterator(
|
||||
fragment_list.get(), kMaxSequenceNumber, bytewise_icmp));
|
||||
range_del_agg.AddTombstones(std::move(input_iter));
|
||||
}
|
||||
|
||||
VerifyShouldDelete(&range_del_agg, {{InternalValue("a", 19), true},
|
||||
{InternalValue("b", 19), false},
|
||||
{InternalValue("b", 9), true},
|
||||
{InternalValue("d", 9), true},
|
||||
{InternalValue("e", 7), true},
|
||||
{InternalValue("g", 7), false},
|
||||
{InternalValue("h", 24), true},
|
||||
{InternalValue("i", 24), false},
|
||||
{InternalValue("ii", 14), true},
|
||||
{InternalValue("j", 14), false}});
|
||||
|
||||
VerifyIsRangeOverlapped(&range_del_agg, {{"", "_", false},
|
||||
{"_", "a", true},
|
||||
{"a", "c", true},
|
||||
{"d", "f", true},
|
||||
{"g", "l", true},
|
||||
{"x", "y", false}});
|
||||
}
|
||||
|
||||
TEST_F(RangeDelAggregatorV2Test, MultipleItersInAggregatorWithUpperBound) {
|
||||
auto fragment_lists = MakeFragmentedTombstoneLists(
|
||||
{{{"a", "e", 10}, {"c", "g", 8}},
|
||||
{{"a", "b", 20}, {"h", "i", 25}, {"ii", "j", 15}}});
|
||||
|
||||
RangeDelAggregatorV2 range_del_agg(&bytewise_icmp, 19);
|
||||
for (const auto& fragment_list : fragment_lists) {
|
||||
std::unique_ptr<FragmentedRangeTombstoneIterator> input_iter(
|
||||
new FragmentedRangeTombstoneIterator(fragment_list.get(),
|
||||
19 /* snapshot */, bytewise_icmp));
|
||||
range_del_agg.AddTombstones(std::move(input_iter));
|
||||
}
|
||||
|
||||
VerifyShouldDelete(&range_del_agg, {{InternalValue("a", 19), false},
|
||||
{InternalValue("a", 9), true},
|
||||
{InternalValue("b", 9), true},
|
||||
{InternalValue("d", 9), true},
|
||||
{InternalValue("e", 7), true},
|
||||
{InternalValue("g", 7), false},
|
||||
{InternalValue("h", 24), false},
|
||||
{InternalValue("i", 24), false},
|
||||
{InternalValue("ii", 14), true},
|
||||
{InternalValue("j", 14), false}});
|
||||
|
||||
VerifyIsRangeOverlapped(&range_del_agg, {{"", "_", false},
|
||||
{"_", "a", true},
|
||||
{"a", "c", true},
|
||||
{"d", "f", true},
|
||||
{"g", "l", true},
|
||||
{"x", "y", false}});
|
||||
}
|
||||
|
||||
TEST_F(RangeDelAggregatorV2Test, MultipleTruncatedItersInAggregator) {
|
||||
auto fragment_lists = MakeFragmentedTombstoneLists(
|
||||
{{{"a", "z", 10}}, {{"a", "z", 10}}, {{"a", "z", 10}}});
|
||||
std::vector<std::pair<InternalKey, InternalKey>> iter_bounds = {
|
||||
{InternalKey("a", 4, kTypeValue),
|
||||
InternalKey("m", kMaxSequenceNumber, kTypeRangeDeletion)},
|
||||
{InternalKey("m", 20, kTypeValue),
|
||||
InternalKey("x", kMaxSequenceNumber, kTypeRangeDeletion)},
|
||||
{InternalKey("x", 5, kTypeValue), InternalKey("zz", 30, kTypeValue)}};
|
||||
|
||||
RangeDelAggregatorV2 range_del_agg(&bytewise_icmp, 19);
|
||||
for (size_t i = 0; i < fragment_lists.size(); i++) {
|
||||
const auto& fragment_list = fragment_lists[i];
|
||||
const auto& bounds = iter_bounds[i];
|
||||
std::unique_ptr<FragmentedRangeTombstoneIterator> input_iter(
|
||||
new FragmentedRangeTombstoneIterator(fragment_list.get(),
|
||||
19 /* snapshot */, bytewise_icmp));
|
||||
range_del_agg.AddTombstones(std::move(input_iter), &bounds.first,
|
||||
&bounds.second);
|
||||
}
|
||||
|
||||
VerifyShouldDelete(&range_del_agg, {{InternalValue("a", 10), false},
|
||||
{InternalValue("a", 9), false},
|
||||
{InternalValue("a", 4), true},
|
||||
{InternalValue("m", 10), false},
|
||||
{InternalValue("m", 9), true},
|
||||
{InternalValue("x", 10), false},
|
||||
{InternalValue("x", 9), false},
|
||||
{InternalValue("x", 5), true},
|
||||
{InternalValue("z", 9), false}});
|
||||
|
||||
VerifyIsRangeOverlapped(&range_del_agg, {{"", "_", false},
|
||||
{"_", "a", true},
|
||||
{"a", "n", true},
|
||||
{"l", "x", true},
|
||||
{"w", "z", true},
|
||||
{"zzz", "zz", false},
|
||||
{"zz", "zzz", false}});
|
||||
}
|
||||
|
||||
TEST_F(RangeDelAggregatorV2Test, MultipleTruncatedItersInAggregatorSameLevel) {
|
||||
auto fragment_lists = MakeFragmentedTombstoneLists(
|
||||
{{{"a", "z", 10}}, {{"a", "z", 10}}, {{"a", "z", 10}}});
|
||||
std::vector<std::pair<InternalKey, InternalKey>> iter_bounds = {
|
||||
{InternalKey("a", 4, kTypeValue),
|
||||
InternalKey("m", kMaxSequenceNumber, kTypeRangeDeletion)},
|
||||
{InternalKey("m", 20, kTypeValue),
|
||||
InternalKey("x", kMaxSequenceNumber, kTypeRangeDeletion)},
|
||||
{InternalKey("x", 5, kTypeValue), InternalKey("zz", 30, kTypeValue)}};
|
||||
|
||||
RangeDelAggregatorV2 range_del_agg(&bytewise_icmp, 19);
|
||||
|
||||
auto add_iter_to_agg = [&](size_t i) {
|
||||
std::unique_ptr<FragmentedRangeTombstoneIterator> input_iter(
|
||||
new FragmentedRangeTombstoneIterator(fragment_lists[i].get(),
|
||||
19 /* snapshot */, bytewise_icmp));
|
||||
range_del_agg.AddTombstones(std::move(input_iter), &iter_bounds[i].first,
|
||||
&iter_bounds[i].second);
|
||||
};
|
||||
|
||||
add_iter_to_agg(0);
|
||||
VerifyShouldDelete(&range_del_agg, {{InternalValue("a", 10), false},
|
||||
{InternalValue("a", 9), false},
|
||||
{InternalValue("a", 4), true}});
|
||||
|
||||
add_iter_to_agg(1);
|
||||
VerifyShouldDelete(&range_del_agg, {{InternalValue("m", 10), false},
|
||||
{InternalValue("m", 9), true}});
|
||||
|
||||
add_iter_to_agg(2);
|
||||
VerifyShouldDelete(&range_del_agg, {{InternalValue("x", 10), false},
|
||||
{InternalValue("x", 9), false},
|
||||
{InternalValue("x", 5), true},
|
||||
{InternalValue("z", 9), false}});
|
||||
|
||||
VerifyIsRangeOverlapped(&range_del_agg, {{"", "_", false},
|
||||
{"_", "a", true},
|
||||
{"a", "n", true},
|
||||
{"l", "x", true},
|
||||
{"w", "z", true},
|
||||
{"zzz", "zz", false},
|
||||
{"zz", "zzz", false}});
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
@@ -20,7 +20,8 @@ namespace rocksdb {
|
||||
|
||||
FragmentedRangeTombstoneList::FragmentedRangeTombstoneList(
|
||||
std::unique_ptr<InternalIterator> unfragmented_tombstones,
|
||||
const InternalKeyComparator& icmp) {
|
||||
const InternalKeyComparator& icmp, bool for_compaction,
|
||||
const std::vector<SequenceNumber>& snapshots) {
|
||||
if (unfragmented_tombstones == nullptr) {
|
||||
return;
|
||||
}
|
||||
@@ -43,7 +44,8 @@ FragmentedRangeTombstoneList::FragmentedRangeTombstoneList(
|
||||
}
|
||||
}
|
||||
if (is_sorted) {
|
||||
FragmentTombstones(std::move(unfragmented_tombstones), icmp);
|
||||
FragmentTombstones(std::move(unfragmented_tombstones), icmp, for_compaction,
|
||||
snapshots);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -61,12 +63,13 @@ FragmentedRangeTombstoneList::FragmentedRangeTombstoneList(
|
||||
// VectorIterator implicitly sorts by key during construction.
|
||||
auto iter = std::unique_ptr<VectorIterator>(
|
||||
new VectorIterator(std::move(keys), std::move(values), &icmp));
|
||||
FragmentTombstones(std::move(iter), icmp);
|
||||
FragmentTombstones(std::move(iter), icmp, for_compaction, snapshots);
|
||||
}
|
||||
|
||||
void FragmentedRangeTombstoneList::FragmentTombstones(
|
||||
std::unique_ptr<InternalIterator> unfragmented_tombstones,
|
||||
const InternalKeyComparator& icmp) {
|
||||
const InternalKeyComparator& icmp, bool for_compaction,
|
||||
const std::vector<SequenceNumber>& snapshots) {
|
||||
Slice cur_start_key(nullptr, 0);
|
||||
auto cmp = ParsedInternalKeyComparator(&icmp);
|
||||
|
||||
@@ -117,10 +120,38 @@ void FragmentedRangeTombstoneList::FragmentTombstones(
|
||||
}
|
||||
std::sort(seqnums_to_flush.begin(), seqnums_to_flush.end(),
|
||||
std::greater<SequenceNumber>());
|
||||
|
||||
size_t start_idx = tombstone_seqs_.size();
|
||||
size_t end_idx = start_idx + seqnums_to_flush.size();
|
||||
tombstone_seqs_.insert(tombstone_seqs_.end(), seqnums_to_flush.begin(),
|
||||
seqnums_to_flush.end());
|
||||
|
||||
if (for_compaction) {
|
||||
// Drop all tombstone seqnums that are not preserved by a snapshot.
|
||||
SequenceNumber next_snapshot = kMaxSequenceNumber;
|
||||
for (auto seq : seqnums_to_flush) {
|
||||
if (seq <= next_snapshot) {
|
||||
// This seqnum is visible by a lower snapshot.
|
||||
tombstone_seqs_.push_back(seq);
|
||||
seq_set_.insert(seq);
|
||||
auto upper_bound_it =
|
||||
std::lower_bound(snapshots.begin(), snapshots.end(), seq);
|
||||
if (upper_bound_it == snapshots.begin()) {
|
||||
// This seqnum is the topmost one visible by the earliest
|
||||
// snapshot. None of the seqnums below it will be visible, so we
|
||||
// can skip them.
|
||||
break;
|
||||
}
|
||||
next_snapshot = *std::prev(upper_bound_it);
|
||||
}
|
||||
}
|
||||
end_idx = tombstone_seqs_.size();
|
||||
} else {
|
||||
// The fragmentation is being done for reads, so preserve all seqnums.
|
||||
tombstone_seqs_.insert(tombstone_seqs_.end(), seqnums_to_flush.begin(),
|
||||
seqnums_to_flush.end());
|
||||
seq_set_.insert(seqnums_to_flush.begin(), seqnums_to_flush.end());
|
||||
}
|
||||
|
||||
assert(start_idx < end_idx);
|
||||
tombstones_.emplace_back(cur_start_key, cur_end_key, start_idx, end_idx);
|
||||
|
||||
cur_start_key = cur_end_key;
|
||||
@@ -143,6 +174,11 @@ void FragmentedRangeTombstoneList::FragmentTombstones(
|
||||
const Slice& ikey = unfragmented_tombstones->key();
|
||||
Slice tombstone_start_key = ExtractUserKey(ikey);
|
||||
SequenceNumber tombstone_seq = GetInternalKeySeqno(ikey);
|
||||
if (!unfragmented_tombstones->IsKeyPinned()) {
|
||||
pinned_slices_.emplace_back(tombstone_start_key.data(),
|
||||
tombstone_start_key.size());
|
||||
tombstone_start_key = pinned_slices_.back();
|
||||
}
|
||||
no_tombstones = false;
|
||||
|
||||
Slice tombstone_end_key = unfragmented_tombstones->value();
|
||||
@@ -157,13 +193,7 @@ void FragmentedRangeTombstoneList::FragmentTombstones(
|
||||
// this new start key.
|
||||
flush_current_tombstones(tombstone_start_key);
|
||||
}
|
||||
if (unfragmented_tombstones->IsKeyPinned()) {
|
||||
cur_start_key = tombstone_start_key;
|
||||
} else {
|
||||
pinned_slices_.emplace_back(tombstone_start_key.data(),
|
||||
tombstone_start_key.size());
|
||||
cur_start_key = pinned_slices_.back();
|
||||
}
|
||||
cur_start_key = tombstone_start_key;
|
||||
|
||||
cur_end_keys.emplace(tombstone_end_key, tombstone_seq, kTypeRangeDeletion);
|
||||
}
|
||||
@@ -178,33 +208,41 @@ void FragmentedRangeTombstoneList::FragmentTombstones(
|
||||
}
|
||||
}
|
||||
|
||||
bool FragmentedRangeTombstoneList::ContainsRange(SequenceNumber lower,
|
||||
SequenceNumber upper) const {
|
||||
auto seq_it = seq_set_.lower_bound(lower);
|
||||
return seq_it != seq_set_.end() && *seq_it <= upper;
|
||||
}
|
||||
|
||||
FragmentedRangeTombstoneIterator::FragmentedRangeTombstoneIterator(
|
||||
const FragmentedRangeTombstoneList* tombstones, SequenceNumber snapshot,
|
||||
const InternalKeyComparator& icmp)
|
||||
const FragmentedRangeTombstoneList* tombstones,
|
||||
const InternalKeyComparator& icmp, SequenceNumber _upper_bound,
|
||||
SequenceNumber _lower_bound)
|
||||
: tombstone_start_cmp_(icmp.user_comparator()),
|
||||
tombstone_end_cmp_(icmp.user_comparator()),
|
||||
icmp_(&icmp),
|
||||
ucmp_(icmp.user_comparator()),
|
||||
tombstones_(tombstones),
|
||||
snapshot_(snapshot) {
|
||||
upper_bound_(_upper_bound),
|
||||
lower_bound_(_lower_bound) {
|
||||
assert(tombstones_ != nullptr);
|
||||
pos_ = tombstones_->end();
|
||||
pinned_pos_ = tombstones_->end();
|
||||
Invalidate();
|
||||
}
|
||||
|
||||
FragmentedRangeTombstoneIterator::FragmentedRangeTombstoneIterator(
|
||||
const std::shared_ptr<const FragmentedRangeTombstoneList>& tombstones,
|
||||
SequenceNumber snapshot, const InternalKeyComparator& icmp)
|
||||
const InternalKeyComparator& icmp, SequenceNumber _upper_bound,
|
||||
SequenceNumber _lower_bound)
|
||||
: tombstone_start_cmp_(icmp.user_comparator()),
|
||||
tombstone_end_cmp_(icmp.user_comparator()),
|
||||
icmp_(&icmp),
|
||||
ucmp_(icmp.user_comparator()),
|
||||
tombstones_ref_(tombstones),
|
||||
tombstones_(tombstones_ref_.get()),
|
||||
snapshot_(snapshot) {
|
||||
upper_bound_(_upper_bound),
|
||||
lower_bound_(_lower_bound) {
|
||||
assert(tombstones_ != nullptr);
|
||||
pos_ = tombstones_->end();
|
||||
seq_pos_ = tombstones_->seq_end();
|
||||
pinned_pos_ = tombstones_->end();
|
||||
pinned_seq_pos_ = tombstones_->seq_end();
|
||||
Invalidate();
|
||||
}
|
||||
|
||||
void FragmentedRangeTombstoneIterator::SeekToFirst() {
|
||||
@@ -220,7 +258,7 @@ void FragmentedRangeTombstoneIterator::SeekToTopFirst() {
|
||||
pos_ = tombstones_->begin();
|
||||
seq_pos_ = std::lower_bound(tombstones_->seq_iter(pos_->seq_start_idx),
|
||||
tombstones_->seq_iter(pos_->seq_end_idx),
|
||||
snapshot_, std::greater<SequenceNumber>());
|
||||
upper_bound_, std::greater<SequenceNumber>());
|
||||
ScanForwardToVisibleTombstone();
|
||||
}
|
||||
|
||||
@@ -237,7 +275,7 @@ void FragmentedRangeTombstoneIterator::SeekToTopLast() {
|
||||
pos_ = std::prev(tombstones_->end());
|
||||
seq_pos_ = std::lower_bound(tombstones_->seq_iter(pos_->seq_start_idx),
|
||||
tombstones_->seq_iter(pos_->seq_end_idx),
|
||||
snapshot_, std::greater<SequenceNumber>());
|
||||
upper_bound_, std::greater<SequenceNumber>());
|
||||
ScanBackwardToVisibleTombstone();
|
||||
}
|
||||
|
||||
@@ -270,7 +308,7 @@ void FragmentedRangeTombstoneIterator::SeekToCoveringTombstone(
|
||||
}
|
||||
seq_pos_ = std::lower_bound(tombstones_->seq_iter(pos_->seq_start_idx),
|
||||
tombstones_->seq_iter(pos_->seq_end_idx),
|
||||
snapshot_, std::greater<SequenceNumber>());
|
||||
upper_bound_, std::greater<SequenceNumber>());
|
||||
}
|
||||
|
||||
void FragmentedRangeTombstoneIterator::SeekForPrevToCoveringTombstone(
|
||||
@@ -289,25 +327,28 @@ void FragmentedRangeTombstoneIterator::SeekForPrevToCoveringTombstone(
|
||||
--pos_;
|
||||
seq_pos_ = std::lower_bound(tombstones_->seq_iter(pos_->seq_start_idx),
|
||||
tombstones_->seq_iter(pos_->seq_end_idx),
|
||||
snapshot_, std::greater<SequenceNumber>());
|
||||
upper_bound_, std::greater<SequenceNumber>());
|
||||
}
|
||||
|
||||
void FragmentedRangeTombstoneIterator::ScanForwardToVisibleTombstone() {
|
||||
while (pos_ != tombstones_->end() &&
|
||||
seq_pos_ == tombstones_->seq_iter(pos_->seq_end_idx)) {
|
||||
(seq_pos_ == tombstones_->seq_iter(pos_->seq_end_idx) ||
|
||||
*seq_pos_ < lower_bound_)) {
|
||||
++pos_;
|
||||
if (pos_ == tombstones_->end()) {
|
||||
Invalidate();
|
||||
return;
|
||||
}
|
||||
seq_pos_ = std::lower_bound(tombstones_->seq_iter(pos_->seq_start_idx),
|
||||
tombstones_->seq_iter(pos_->seq_end_idx),
|
||||
snapshot_, std::greater<SequenceNumber>());
|
||||
upper_bound_, std::greater<SequenceNumber>());
|
||||
}
|
||||
}
|
||||
|
||||
void FragmentedRangeTombstoneIterator::ScanBackwardToVisibleTombstone() {
|
||||
while (pos_ != tombstones_->end() &&
|
||||
seq_pos_ == tombstones_->seq_iter(pos_->seq_end_idx)) {
|
||||
(seq_pos_ == tombstones_->seq_iter(pos_->seq_end_idx) ||
|
||||
*seq_pos_ < lower_bound_)) {
|
||||
if (pos_ == tombstones_->begin()) {
|
||||
Invalidate();
|
||||
return;
|
||||
@@ -315,7 +356,7 @@ void FragmentedRangeTombstoneIterator::ScanBackwardToVisibleTombstone() {
|
||||
--pos_;
|
||||
seq_pos_ = std::lower_bound(tombstones_->seq_iter(pos_->seq_start_idx),
|
||||
tombstones_->seq_iter(pos_->seq_end_idx),
|
||||
snapshot_, std::greater<SequenceNumber>());
|
||||
upper_bound_, std::greater<SequenceNumber>());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -333,14 +374,13 @@ void FragmentedRangeTombstoneIterator::TopNext() {
|
||||
}
|
||||
seq_pos_ = std::lower_bound(tombstones_->seq_iter(pos_->seq_start_idx),
|
||||
tombstones_->seq_iter(pos_->seq_end_idx),
|
||||
snapshot_, std::greater<SequenceNumber>());
|
||||
upper_bound_, std::greater<SequenceNumber>());
|
||||
ScanForwardToVisibleTombstone();
|
||||
}
|
||||
|
||||
void FragmentedRangeTombstoneIterator::Prev() {
|
||||
if (seq_pos_ == tombstones_->seq_begin()) {
|
||||
pos_ = tombstones_->end();
|
||||
seq_pos_ = tombstones_->seq_end();
|
||||
Invalidate();
|
||||
return;
|
||||
}
|
||||
--seq_pos_;
|
||||
@@ -358,7 +398,7 @@ void FragmentedRangeTombstoneIterator::TopPrev() {
|
||||
--pos_;
|
||||
seq_pos_ = std::lower_bound(tombstones_->seq_iter(pos_->seq_start_idx),
|
||||
tombstones_->seq_iter(pos_->seq_end_idx),
|
||||
snapshot_, std::greater<SequenceNumber>());
|
||||
upper_bound_, std::greater<SequenceNumber>());
|
||||
ScanBackwardToVisibleTombstone();
|
||||
}
|
||||
|
||||
@@ -372,4 +412,27 @@ SequenceNumber FragmentedRangeTombstoneIterator::MaxCoveringTombstoneSeqnum(
|
||||
return ValidPos() && ucmp_->Compare(start_key(), user_key) <= 0 ? seq() : 0;
|
||||
}
|
||||
|
||||
std::map<SequenceNumber, std::unique_ptr<FragmentedRangeTombstoneIterator>>
|
||||
FragmentedRangeTombstoneIterator::SplitBySnapshot(
|
||||
const std::vector<SequenceNumber>& snapshots) {
|
||||
std::map<SequenceNumber, std::unique_ptr<FragmentedRangeTombstoneIterator>>
|
||||
splits;
|
||||
SequenceNumber lower = 0;
|
||||
SequenceNumber upper;
|
||||
for (size_t i = 0; i <= snapshots.size(); i++) {
|
||||
if (i >= snapshots.size()) {
|
||||
upper = kMaxSequenceNumber;
|
||||
} else {
|
||||
upper = snapshots[i];
|
||||
}
|
||||
if (tombstones_->ContainsRange(lower, upper)) {
|
||||
splits.emplace(upper, std::unique_ptr<FragmentedRangeTombstoneIterator>(
|
||||
new FragmentedRangeTombstoneIterator(
|
||||
tombstones_, *icmp_, upper, lower)));
|
||||
}
|
||||
lower = upper + 1;
|
||||
}
|
||||
return splits;
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
#include <list>
|
||||
#include <memory>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
@@ -38,7 +39,8 @@ struct FragmentedRangeTombstoneList {
|
||||
};
|
||||
FragmentedRangeTombstoneList(
|
||||
std::unique_ptr<InternalIterator> unfragmented_tombstones,
|
||||
const InternalKeyComparator& icmp);
|
||||
const InternalKeyComparator& icmp, bool for_compaction = false,
|
||||
const std::vector<SequenceNumber>& snapshots = {});
|
||||
|
||||
std::vector<RangeTombstoneStack>::const_iterator begin() const {
|
||||
return tombstones_.begin();
|
||||
@@ -60,7 +62,11 @@ struct FragmentedRangeTombstoneList {
|
||||
return tombstone_seqs_.end();
|
||||
}
|
||||
|
||||
bool empty() const { return tombstones_.size() == 0; }
|
||||
bool empty() const { return tombstones_.empty(); }
|
||||
|
||||
// Returns true if the stored tombstones contain with one with a sequence
|
||||
// number in [lower, upper].
|
||||
bool ContainsRange(SequenceNumber lower, SequenceNumber upper) const;
|
||||
|
||||
private:
|
||||
// Given an ordered range tombstone iterator unfragmented_tombstones,
|
||||
@@ -68,10 +74,12 @@ struct FragmentedRangeTombstoneList {
|
||||
// tombstones_ and tombstone_seqs_.
|
||||
void FragmentTombstones(
|
||||
std::unique_ptr<InternalIterator> unfragmented_tombstones,
|
||||
const InternalKeyComparator& icmp);
|
||||
const InternalKeyComparator& icmp, bool for_compaction,
|
||||
const std::vector<SequenceNumber>& snapshots);
|
||||
|
||||
std::vector<RangeTombstoneStack> tombstones_;
|
||||
std::vector<SequenceNumber> tombstone_seqs_;
|
||||
std::set<SequenceNumber> seq_set_;
|
||||
std::list<std::string> pinned_slices_;
|
||||
PinnedIteratorsManager pinned_iters_mgr_;
|
||||
};
|
||||
@@ -88,11 +96,13 @@ struct FragmentedRangeTombstoneList {
|
||||
class FragmentedRangeTombstoneIterator : public InternalIterator {
|
||||
public:
|
||||
FragmentedRangeTombstoneIterator(
|
||||
const FragmentedRangeTombstoneList* tombstones, SequenceNumber snapshot,
|
||||
const InternalKeyComparator& icmp);
|
||||
const FragmentedRangeTombstoneList* tombstones,
|
||||
const InternalKeyComparator& icmp, SequenceNumber upper_bound,
|
||||
SequenceNumber lower_bound = 0);
|
||||
FragmentedRangeTombstoneIterator(
|
||||
const std::shared_ptr<const FragmentedRangeTombstoneList>& tombstones,
|
||||
SequenceNumber snapshot, const InternalKeyComparator& icmp);
|
||||
const InternalKeyComparator& icmp, SequenceNumber upper_bound,
|
||||
SequenceNumber lower_bound = 0);
|
||||
|
||||
void SeekToFirst() override;
|
||||
void SeekToLast() override;
|
||||
@@ -136,8 +146,7 @@ class FragmentedRangeTombstoneIterator : public InternalIterator {
|
||||
seq_pos_ = tombstones_->seq_end();
|
||||
}
|
||||
|
||||
// TODO: implement properly
|
||||
RangeTombstone tombstone() const {
|
||||
RangeTombstone Tombstone() const {
|
||||
return RangeTombstone(start_key(), end_key(), seq());
|
||||
}
|
||||
Slice start_key() const { return pos_->start_key; }
|
||||
@@ -151,12 +160,24 @@ class FragmentedRangeTombstoneIterator : public InternalIterator {
|
||||
return ParsedInternalKey(pos_->end_key, kMaxSequenceNumber,
|
||||
kTypeRangeDeletion);
|
||||
}
|
||||
ParsedInternalKey internal_key() const {
|
||||
return ParsedInternalKey(pos_->start_key, *seq_pos_, kTypeRangeDeletion);
|
||||
}
|
||||
|
||||
SequenceNumber MaxCoveringTombstoneSeqnum(const Slice& user_key);
|
||||
|
||||
// Splits the iterator into n+1 iterators (where n is the number of
|
||||
// snapshots), each providing a view over a "stripe" of sequence numbers. The
|
||||
// iterators are keyed by the upper bound of their ranges (the provided
|
||||
// snapshots + kMaxSequenceNumber).
|
||||
//
|
||||
// NOTE: the iterators in the returned map are no longer valid if their
|
||||
// parent iterator is deleted, since they do not modify the refcount of the
|
||||
// underlying tombstone list. Therefore, this map should be deleted before
|
||||
// the parent iterator.
|
||||
std::map<SequenceNumber, std::unique_ptr<FragmentedRangeTombstoneIterator>>
|
||||
SplitBySnapshot(const std::vector<SequenceNumber>& snapshots);
|
||||
|
||||
SequenceNumber upper_bound() const { return upper_bound_; }
|
||||
SequenceNumber lower_bound() const { return lower_bound_; }
|
||||
|
||||
private:
|
||||
using RangeTombstoneStack = FragmentedRangeTombstoneList::RangeTombstoneStack;
|
||||
|
||||
@@ -217,10 +238,12 @@ class FragmentedRangeTombstoneIterator : public InternalIterator {
|
||||
|
||||
const RangeTombstoneStackStartComparator tombstone_start_cmp_;
|
||||
const RangeTombstoneStackEndComparator tombstone_end_cmp_;
|
||||
const InternalKeyComparator* icmp_;
|
||||
const Comparator* ucmp_;
|
||||
std::shared_ptr<const FragmentedRangeTombstoneList> tombstones_ref_;
|
||||
const FragmentedRangeTombstoneList* tombstones_;
|
||||
SequenceNumber snapshot_;
|
||||
SequenceNumber upper_bound_;
|
||||
SequenceNumber lower_bound_;
|
||||
std::vector<RangeTombstoneStack>::const_iterator pos_;
|
||||
std::vector<SequenceNumber>::const_iterator seq_pos_;
|
||||
mutable std::vector<RangeTombstoneStack>::const_iterator pinned_pos_;
|
||||
|
||||
@@ -29,15 +29,26 @@ std::unique_ptr<InternalIterator> MakeRangeDelIter(
|
||||
new test::VectorIterator(keys, values));
|
||||
}
|
||||
|
||||
void CheckIterPosition(const RangeTombstone& tombstone,
|
||||
const FragmentedRangeTombstoneIterator* iter) {
|
||||
// Test InternalIterator interface.
|
||||
EXPECT_EQ(tombstone.start_key_, ExtractUserKey(iter->key()));
|
||||
EXPECT_EQ(tombstone.end_key_, iter->value());
|
||||
EXPECT_EQ(tombstone.seq_, iter->seq());
|
||||
|
||||
// Test FragmentedRangeTombstoneIterator interface.
|
||||
EXPECT_EQ(tombstone.start_key_, iter->start_key());
|
||||
EXPECT_EQ(tombstone.end_key_, iter->end_key());
|
||||
EXPECT_EQ(tombstone.seq_, GetInternalKeySeqno(iter->key()));
|
||||
}
|
||||
|
||||
void VerifyFragmentedRangeDels(
|
||||
FragmentedRangeTombstoneIterator* iter,
|
||||
const std::vector<RangeTombstone>& expected_tombstones) {
|
||||
iter->SeekToFirst();
|
||||
for (size_t i = 0; i < expected_tombstones.size() && iter->Valid();
|
||||
i++, iter->Next()) {
|
||||
EXPECT_EQ(iter->start_key(), expected_tombstones[i].start_key_);
|
||||
EXPECT_EQ(iter->value(), expected_tombstones[i].end_key_);
|
||||
EXPECT_EQ(iter->seq(), expected_tombstones[i].seq_);
|
||||
for (size_t i = 0; i < expected_tombstones.size(); i++, iter->Next()) {
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
CheckIterPosition(expected_tombstones[i], iter);
|
||||
}
|
||||
EXPECT_FALSE(iter->Valid());
|
||||
}
|
||||
@@ -46,11 +57,9 @@ void VerifyVisibleTombstones(
|
||||
FragmentedRangeTombstoneIterator* iter,
|
||||
const std::vector<RangeTombstone>& expected_tombstones) {
|
||||
iter->SeekToTopFirst();
|
||||
for (size_t i = 0; i < expected_tombstones.size() && iter->Valid();
|
||||
i++, iter->TopNext()) {
|
||||
EXPECT_EQ(iter->start_key(), expected_tombstones[i].start_key_);
|
||||
EXPECT_EQ(iter->value(), expected_tombstones[i].end_key_);
|
||||
EXPECT_EQ(iter->seq(), expected_tombstones[i].seq_);
|
||||
for (size_t i = 0; i < expected_tombstones.size(); i++, iter->TopNext()) {
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
CheckIterPosition(expected_tombstones[i], iter);
|
||||
}
|
||||
EXPECT_FALSE(iter->Valid());
|
||||
}
|
||||
@@ -69,9 +78,7 @@ void VerifySeek(FragmentedRangeTombstoneIterator* iter,
|
||||
ASSERT_FALSE(iter->Valid());
|
||||
} else {
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
EXPECT_EQ(testcase.expected_position.start_key_, iter->start_key());
|
||||
EXPECT_EQ(testcase.expected_position.end_key_, iter->value());
|
||||
EXPECT_EQ(testcase.expected_position.seq_, iter->seq());
|
||||
CheckIterPosition(testcase.expected_position, iter);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -84,9 +91,7 @@ void VerifySeekForPrev(FragmentedRangeTombstoneIterator* iter,
|
||||
ASSERT_FALSE(iter->Valid());
|
||||
} else {
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
EXPECT_EQ(testcase.expected_position.start_key_, iter->start_key());
|
||||
EXPECT_EQ(testcase.expected_position.end_key_, iter->value());
|
||||
EXPECT_EQ(testcase.expected_position.seq_, iter->seq());
|
||||
CheckIterPosition(testcase.expected_position, iter);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -112,8 +117,10 @@ TEST_F(RangeTombstoneFragmenterTest, NonOverlappingTombstones) {
|
||||
|
||||
FragmentedRangeTombstoneList fragment_list(std::move(range_del_iter),
|
||||
bytewise_icmp);
|
||||
FragmentedRangeTombstoneIterator iter(&fragment_list, kMaxSequenceNumber,
|
||||
bytewise_icmp);
|
||||
FragmentedRangeTombstoneIterator iter(&fragment_list, bytewise_icmp,
|
||||
kMaxSequenceNumber);
|
||||
ASSERT_EQ(0, iter.lower_bound());
|
||||
ASSERT_EQ(kMaxSequenceNumber, iter.upper_bound());
|
||||
VerifyFragmentedRangeDels(&iter, {{"a", "b", 10}, {"c", "d", 5}});
|
||||
VerifyMaxCoveringTombstoneSeqnum(&iter,
|
||||
{{"", 0}, {"a", 10}, {"b", 0}, {"c", 5}});
|
||||
@@ -124,8 +131,10 @@ TEST_F(RangeTombstoneFragmenterTest, OverlappingTombstones) {
|
||||
|
||||
FragmentedRangeTombstoneList fragment_list(std::move(range_del_iter),
|
||||
bytewise_icmp);
|
||||
FragmentedRangeTombstoneIterator iter(&fragment_list, kMaxSequenceNumber,
|
||||
bytewise_icmp);
|
||||
FragmentedRangeTombstoneIterator iter(&fragment_list, bytewise_icmp,
|
||||
kMaxSequenceNumber);
|
||||
ASSERT_EQ(0, iter.lower_bound());
|
||||
ASSERT_EQ(kMaxSequenceNumber, iter.upper_bound());
|
||||
VerifyFragmentedRangeDels(
|
||||
&iter, {{"a", "c", 10}, {"c", "e", 15}, {"c", "e", 10}, {"e", "g", 15}});
|
||||
VerifyMaxCoveringTombstoneSeqnum(&iter,
|
||||
@@ -138,8 +147,10 @@ TEST_F(RangeTombstoneFragmenterTest, ContiguousTombstones) {
|
||||
|
||||
FragmentedRangeTombstoneList fragment_list(std::move(range_del_iter),
|
||||
bytewise_icmp);
|
||||
FragmentedRangeTombstoneIterator iter(&fragment_list, kMaxSequenceNumber,
|
||||
bytewise_icmp);
|
||||
FragmentedRangeTombstoneIterator iter(&fragment_list, bytewise_icmp,
|
||||
kMaxSequenceNumber);
|
||||
ASSERT_EQ(0, iter.lower_bound());
|
||||
ASSERT_EQ(kMaxSequenceNumber, iter.upper_bound());
|
||||
VerifyFragmentedRangeDels(
|
||||
&iter, {{"a", "c", 10}, {"c", "e", 20}, {"c", "e", 5}, {"e", "g", 15}});
|
||||
VerifyMaxCoveringTombstoneSeqnum(&iter,
|
||||
@@ -152,8 +163,10 @@ TEST_F(RangeTombstoneFragmenterTest, RepeatedStartAndEndKey) {
|
||||
|
||||
FragmentedRangeTombstoneList fragment_list(std::move(range_del_iter),
|
||||
bytewise_icmp);
|
||||
FragmentedRangeTombstoneIterator iter(&fragment_list, kMaxSequenceNumber,
|
||||
bytewise_icmp);
|
||||
FragmentedRangeTombstoneIterator iter(&fragment_list, bytewise_icmp,
|
||||
kMaxSequenceNumber);
|
||||
ASSERT_EQ(0, iter.lower_bound());
|
||||
ASSERT_EQ(kMaxSequenceNumber, iter.upper_bound());
|
||||
VerifyFragmentedRangeDels(&iter,
|
||||
{{"a", "c", 10}, {"a", "c", 7}, {"a", "c", 3}});
|
||||
VerifyMaxCoveringTombstoneSeqnum(&iter, {{"a", 10}, {"b", 10}, {"c", 0}});
|
||||
@@ -165,8 +178,10 @@ TEST_F(RangeTombstoneFragmenterTest, RepeatedStartKeyDifferentEndKeys) {
|
||||
|
||||
FragmentedRangeTombstoneList fragment_list(std::move(range_del_iter),
|
||||
bytewise_icmp);
|
||||
FragmentedRangeTombstoneIterator iter(&fragment_list, kMaxSequenceNumber,
|
||||
bytewise_icmp);
|
||||
FragmentedRangeTombstoneIterator iter(&fragment_list, bytewise_icmp,
|
||||
kMaxSequenceNumber);
|
||||
ASSERT_EQ(0, iter.lower_bound());
|
||||
ASSERT_EQ(kMaxSequenceNumber, iter.upper_bound());
|
||||
VerifyFragmentedRangeDels(&iter, {{"a", "c", 10},
|
||||
{"a", "c", 7},
|
||||
{"a", "c", 3},
|
||||
@@ -186,8 +201,10 @@ TEST_F(RangeTombstoneFragmenterTest, RepeatedStartKeyMixedEndKeys) {
|
||||
|
||||
FragmentedRangeTombstoneList fragment_list(std::move(range_del_iter),
|
||||
bytewise_icmp);
|
||||
FragmentedRangeTombstoneIterator iter(&fragment_list, kMaxSequenceNumber,
|
||||
bytewise_icmp);
|
||||
FragmentedRangeTombstoneIterator iter(&fragment_list, bytewise_icmp,
|
||||
kMaxSequenceNumber);
|
||||
ASSERT_EQ(0, iter.lower_bound());
|
||||
ASSERT_EQ(kMaxSequenceNumber, iter.upper_bound());
|
||||
VerifyFragmentedRangeDels(&iter, {{"a", "c", 30},
|
||||
{"a", "c", 20},
|
||||
{"a", "c", 10},
|
||||
@@ -211,16 +228,16 @@ TEST_F(RangeTombstoneFragmenterTest, OverlapAndRepeatedStartKey) {
|
||||
|
||||
FragmentedRangeTombstoneList fragment_list(std::move(range_del_iter),
|
||||
bytewise_icmp);
|
||||
FragmentedRangeTombstoneIterator iter1(&fragment_list, kMaxSequenceNumber,
|
||||
bytewise_icmp);
|
||||
FragmentedRangeTombstoneIterator iter2(&fragment_list, 9 /* snapshot */,
|
||||
bytewise_icmp);
|
||||
FragmentedRangeTombstoneIterator iter3(&fragment_list, 7 /* snapshot */,
|
||||
bytewise_icmp);
|
||||
FragmentedRangeTombstoneIterator iter4(&fragment_list, 5 /* snapshot */,
|
||||
bytewise_icmp);
|
||||
FragmentedRangeTombstoneIterator iter5(&fragment_list, 3 /* snapshot */,
|
||||
bytewise_icmp);
|
||||
FragmentedRangeTombstoneIterator iter1(&fragment_list, bytewise_icmp,
|
||||
kMaxSequenceNumber);
|
||||
FragmentedRangeTombstoneIterator iter2(&fragment_list, bytewise_icmp,
|
||||
9 /* upper_bound */);
|
||||
FragmentedRangeTombstoneIterator iter3(&fragment_list, bytewise_icmp,
|
||||
7 /* upper_bound */);
|
||||
FragmentedRangeTombstoneIterator iter4(&fragment_list, bytewise_icmp,
|
||||
5 /* upper_bound */);
|
||||
FragmentedRangeTombstoneIterator iter5(&fragment_list, bytewise_icmp,
|
||||
3 /* upper_bound */);
|
||||
for (auto* iter : {&iter1, &iter2, &iter3, &iter4, &iter5}) {
|
||||
VerifyFragmentedRangeDels(iter, {{"a", "c", 10},
|
||||
{"c", "e", 10},
|
||||
@@ -234,6 +251,8 @@ TEST_F(RangeTombstoneFragmenterTest, OverlapAndRepeatedStartKey) {
|
||||
{"l", "n", 4}});
|
||||
}
|
||||
|
||||
ASSERT_EQ(0, iter1.lower_bound());
|
||||
ASSERT_EQ(kMaxSequenceNumber, iter1.upper_bound());
|
||||
VerifyVisibleTombstones(&iter1, {{"a", "c", 10},
|
||||
{"c", "e", 10},
|
||||
{"e", "g", 8},
|
||||
@@ -243,6 +262,8 @@ TEST_F(RangeTombstoneFragmenterTest, OverlapAndRepeatedStartKey) {
|
||||
VerifyMaxCoveringTombstoneSeqnum(
|
||||
&iter1, {{"a", 10}, {"c", 10}, {"e", 8}, {"i", 0}, {"j", 4}, {"m", 4}});
|
||||
|
||||
ASSERT_EQ(0, iter2.lower_bound());
|
||||
ASSERT_EQ(9, iter2.upper_bound());
|
||||
VerifyVisibleTombstones(&iter2, {{"c", "e", 8},
|
||||
{"e", "g", 8},
|
||||
{"g", "i", 6},
|
||||
@@ -251,6 +272,8 @@ TEST_F(RangeTombstoneFragmenterTest, OverlapAndRepeatedStartKey) {
|
||||
VerifyMaxCoveringTombstoneSeqnum(
|
||||
&iter2, {{"a", 0}, {"c", 8}, {"e", 8}, {"i", 0}, {"j", 4}, {"m", 4}});
|
||||
|
||||
ASSERT_EQ(0, iter3.lower_bound());
|
||||
ASSERT_EQ(7, iter3.upper_bound());
|
||||
VerifyVisibleTombstones(&iter3, {{"c", "e", 6},
|
||||
{"e", "g", 6},
|
||||
{"g", "i", 6},
|
||||
@@ -259,10 +282,14 @@ TEST_F(RangeTombstoneFragmenterTest, OverlapAndRepeatedStartKey) {
|
||||
VerifyMaxCoveringTombstoneSeqnum(
|
||||
&iter3, {{"a", 0}, {"c", 6}, {"e", 6}, {"i", 0}, {"j", 4}, {"m", 4}});
|
||||
|
||||
ASSERT_EQ(0, iter4.lower_bound());
|
||||
ASSERT_EQ(5, iter4.upper_bound());
|
||||
VerifyVisibleTombstones(&iter4, {{"j", "l", 4}, {"l", "n", 4}});
|
||||
VerifyMaxCoveringTombstoneSeqnum(
|
||||
&iter4, {{"a", 0}, {"c", 0}, {"e", 0}, {"i", 0}, {"j", 4}, {"m", 4}});
|
||||
|
||||
ASSERT_EQ(0, iter5.lower_bound());
|
||||
ASSERT_EQ(3, iter5.upper_bound());
|
||||
VerifyVisibleTombstones(&iter5, {{"j", "l", 2}});
|
||||
VerifyMaxCoveringTombstoneSeqnum(
|
||||
&iter5, {{"a", 0}, {"c", 0}, {"e", 0}, {"i", 0}, {"j", 2}, {"m", 0}});
|
||||
@@ -277,8 +304,10 @@ TEST_F(RangeTombstoneFragmenterTest, OverlapAndRepeatedStartKeyUnordered) {
|
||||
|
||||
FragmentedRangeTombstoneList fragment_list(std::move(range_del_iter),
|
||||
bytewise_icmp);
|
||||
FragmentedRangeTombstoneIterator iter(&fragment_list, 9 /* snapshot */,
|
||||
bytewise_icmp);
|
||||
FragmentedRangeTombstoneIterator iter(&fragment_list, bytewise_icmp,
|
||||
9 /* upper_bound */);
|
||||
ASSERT_EQ(0, iter.lower_bound());
|
||||
ASSERT_EQ(9, iter.upper_bound());
|
||||
VerifyFragmentedRangeDels(&iter, {{"a", "c", 10},
|
||||
{"c", "e", 10},
|
||||
{"c", "e", 8},
|
||||
@@ -293,6 +322,116 @@ TEST_F(RangeTombstoneFragmenterTest, OverlapAndRepeatedStartKeyUnordered) {
|
||||
&iter, {{"a", 0}, {"c", 8}, {"e", 8}, {"i", 0}, {"j", 4}, {"m", 4}});
|
||||
}
|
||||
|
||||
TEST_F(RangeTombstoneFragmenterTest, OverlapAndRepeatedStartKeyForCompaction) {
|
||||
auto range_del_iter = MakeRangeDelIter({{"a", "e", 10},
|
||||
{"j", "n", 4},
|
||||
{"c", "i", 6},
|
||||
{"c", "g", 8},
|
||||
{"j", "l", 2}});
|
||||
|
||||
FragmentedRangeTombstoneList fragment_list(
|
||||
std::move(range_del_iter), bytewise_icmp, true /* for_compaction */,
|
||||
{} /* snapshots */);
|
||||
FragmentedRangeTombstoneIterator iter(&fragment_list, bytewise_icmp,
|
||||
kMaxSequenceNumber /* upper_bound */);
|
||||
VerifyFragmentedRangeDels(&iter, {{"a", "c", 10},
|
||||
{"c", "e", 10},
|
||||
{"e", "g", 8},
|
||||
{"g", "i", 6},
|
||||
{"j", "l", 4},
|
||||
{"l", "n", 4}});
|
||||
}
|
||||
|
||||
TEST_F(RangeTombstoneFragmenterTest,
|
||||
OverlapAndRepeatedStartKeyForCompactionWithSnapshot) {
|
||||
auto range_del_iter = MakeRangeDelIter({{"a", "e", 10},
|
||||
{"j", "n", 4},
|
||||
{"c", "i", 6},
|
||||
{"c", "g", 8},
|
||||
{"j", "l", 2}});
|
||||
|
||||
FragmentedRangeTombstoneList fragment_list(
|
||||
std::move(range_del_iter), bytewise_icmp, true /* for_compaction */,
|
||||
{20, 9} /* upper_bounds */);
|
||||
FragmentedRangeTombstoneIterator iter(&fragment_list, bytewise_icmp,
|
||||
kMaxSequenceNumber /* upper_bound */);
|
||||
VerifyFragmentedRangeDels(&iter, {{"a", "c", 10},
|
||||
{"c", "e", 10},
|
||||
{"c", "e", 8},
|
||||
{"e", "g", 8},
|
||||
{"g", "i", 6},
|
||||
{"j", "l", 4},
|
||||
{"l", "n", 4}});
|
||||
}
|
||||
|
||||
TEST_F(RangeTombstoneFragmenterTest, IteratorSplitNoSnapshots) {
|
||||
auto range_del_iter = MakeRangeDelIter({{"a", "e", 10},
|
||||
{"j", "n", 4},
|
||||
{"c", "i", 6},
|
||||
{"c", "g", 8},
|
||||
{"j", "l", 2}});
|
||||
|
||||
FragmentedRangeTombstoneList fragment_list(std::move(range_del_iter),
|
||||
bytewise_icmp);
|
||||
FragmentedRangeTombstoneIterator iter(&fragment_list, bytewise_icmp,
|
||||
kMaxSequenceNumber /* upper_bound */);
|
||||
|
||||
auto split_iters = iter.SplitBySnapshot({} /* snapshots */);
|
||||
ASSERT_EQ(1, split_iters.size());
|
||||
|
||||
auto* split_iter = split_iters[kMaxSequenceNumber].get();
|
||||
ASSERT_EQ(0, split_iter->lower_bound());
|
||||
ASSERT_EQ(kMaxSequenceNumber, split_iter->upper_bound());
|
||||
VerifyVisibleTombstones(split_iter, {{"a", "c", 10},
|
||||
{"c", "e", 10},
|
||||
{"e", "g", 8},
|
||||
{"g", "i", 6},
|
||||
{"j", "l", 4},
|
||||
{"l", "n", 4}});
|
||||
}
|
||||
|
||||
TEST_F(RangeTombstoneFragmenterTest, IteratorSplitWithSnapshots) {
|
||||
auto range_del_iter = MakeRangeDelIter({{"a", "e", 10},
|
||||
{"j", "n", 4},
|
||||
{"c", "i", 6},
|
||||
{"c", "g", 8},
|
||||
{"j", "l", 2}});
|
||||
|
||||
FragmentedRangeTombstoneList fragment_list(std::move(range_del_iter),
|
||||
bytewise_icmp);
|
||||
FragmentedRangeTombstoneIterator iter(&fragment_list, bytewise_icmp,
|
||||
kMaxSequenceNumber /* upper_bound */);
|
||||
|
||||
auto split_iters = iter.SplitBySnapshot({3, 5, 7, 9} /* snapshots */);
|
||||
ASSERT_EQ(5, split_iters.size());
|
||||
|
||||
auto* split_iter1 = split_iters[3].get();
|
||||
ASSERT_EQ(0, split_iter1->lower_bound());
|
||||
ASSERT_EQ(3, split_iter1->upper_bound());
|
||||
VerifyVisibleTombstones(split_iter1, {{"j", "l", 2}});
|
||||
|
||||
auto* split_iter2 = split_iters[5].get();
|
||||
ASSERT_EQ(4, split_iter2->lower_bound());
|
||||
ASSERT_EQ(5, split_iter2->upper_bound());
|
||||
VerifyVisibleTombstones(split_iter2, {{"j", "l", 4}, {"l", "n", 4}});
|
||||
|
||||
auto* split_iter3 = split_iters[7].get();
|
||||
ASSERT_EQ(6, split_iter3->lower_bound());
|
||||
ASSERT_EQ(7, split_iter3->upper_bound());
|
||||
VerifyVisibleTombstones(split_iter3,
|
||||
{{"c", "e", 6}, {"e", "g", 6}, {"g", "i", 6}});
|
||||
|
||||
auto* split_iter4 = split_iters[9].get();
|
||||
ASSERT_EQ(8, split_iter4->lower_bound());
|
||||
ASSERT_EQ(9, split_iter4->upper_bound());
|
||||
VerifyVisibleTombstones(split_iter4, {{"c", "e", 8}, {"e", "g", 8}});
|
||||
|
||||
auto* split_iter5 = split_iters[kMaxSequenceNumber].get();
|
||||
ASSERT_EQ(10, split_iter5->lower_bound());
|
||||
ASSERT_EQ(kMaxSequenceNumber, split_iter5->upper_bound());
|
||||
VerifyVisibleTombstones(split_iter5, {{"a", "c", 10}, {"c", "e", 10}});
|
||||
}
|
||||
|
||||
TEST_F(RangeTombstoneFragmenterTest, SeekStartKey) {
|
||||
// Same tombstones as OverlapAndRepeatedStartKey.
|
||||
auto range_del_iter = MakeRangeDelIter({{"a", "e", 10},
|
||||
@@ -304,8 +443,8 @@ TEST_F(RangeTombstoneFragmenterTest, SeekStartKey) {
|
||||
FragmentedRangeTombstoneList fragment_list(std::move(range_del_iter),
|
||||
bytewise_icmp);
|
||||
|
||||
FragmentedRangeTombstoneIterator iter1(&fragment_list, kMaxSequenceNumber,
|
||||
bytewise_icmp);
|
||||
FragmentedRangeTombstoneIterator iter1(&fragment_list, bytewise_icmp,
|
||||
kMaxSequenceNumber);
|
||||
VerifySeek(
|
||||
&iter1,
|
||||
{{"a", {"a", "c", 10}}, {"e", {"e", "g", 8}}, {"l", {"l", "n", 4}}});
|
||||
@@ -313,8 +452,8 @@ TEST_F(RangeTombstoneFragmenterTest, SeekStartKey) {
|
||||
&iter1,
|
||||
{{"a", {"a", "c", 10}}, {"e", {"e", "g", 8}}, {"l", {"l", "n", 4}}});
|
||||
|
||||
FragmentedRangeTombstoneIterator iter2(&fragment_list, 3 /* snapshot */,
|
||||
bytewise_icmp);
|
||||
FragmentedRangeTombstoneIterator iter2(&fragment_list, bytewise_icmp,
|
||||
3 /* upper_bound */);
|
||||
VerifySeek(&iter2, {{"a", {"j", "l", 2}},
|
||||
{"e", {"j", "l", 2}},
|
||||
{"l", {}, true /* out of range */}});
|
||||
@@ -334,8 +473,8 @@ TEST_F(RangeTombstoneFragmenterTest, SeekCovered) {
|
||||
FragmentedRangeTombstoneList fragment_list(std::move(range_del_iter),
|
||||
bytewise_icmp);
|
||||
|
||||
FragmentedRangeTombstoneIterator iter1(&fragment_list, kMaxSequenceNumber,
|
||||
bytewise_icmp);
|
||||
FragmentedRangeTombstoneIterator iter1(&fragment_list, bytewise_icmp,
|
||||
kMaxSequenceNumber);
|
||||
VerifySeek(
|
||||
&iter1,
|
||||
{{"b", {"a", "c", 10}}, {"f", {"e", "g", 8}}, {"m", {"l", "n", 4}}});
|
||||
@@ -343,8 +482,8 @@ TEST_F(RangeTombstoneFragmenterTest, SeekCovered) {
|
||||
&iter1,
|
||||
{{"b", {"a", "c", 10}}, {"f", {"e", "g", 8}}, {"m", {"l", "n", 4}}});
|
||||
|
||||
FragmentedRangeTombstoneIterator iter2(&fragment_list, 3 /* snapshot */,
|
||||
bytewise_icmp);
|
||||
FragmentedRangeTombstoneIterator iter2(&fragment_list, bytewise_icmp,
|
||||
3 /* upper_bound */);
|
||||
VerifySeek(&iter2, {{"b", {"j", "l", 2}},
|
||||
{"f", {"j", "l", 2}},
|
||||
{"m", {}, true /* out of range */}});
|
||||
@@ -364,8 +503,8 @@ TEST_F(RangeTombstoneFragmenterTest, SeekEndKey) {
|
||||
FragmentedRangeTombstoneList fragment_list(std::move(range_del_iter),
|
||||
bytewise_icmp);
|
||||
|
||||
FragmentedRangeTombstoneIterator iter1(&fragment_list, kMaxSequenceNumber,
|
||||
bytewise_icmp);
|
||||
FragmentedRangeTombstoneIterator iter1(&fragment_list, bytewise_icmp,
|
||||
kMaxSequenceNumber);
|
||||
VerifySeek(&iter1, {{"c", {"c", "e", 10}},
|
||||
{"g", {"g", "i", 6}},
|
||||
{"i", {"j", "l", 4}},
|
||||
@@ -375,8 +514,8 @@ TEST_F(RangeTombstoneFragmenterTest, SeekEndKey) {
|
||||
{"i", {"g", "i", 6}},
|
||||
{"n", {"l", "n", 4}}});
|
||||
|
||||
FragmentedRangeTombstoneIterator iter2(&fragment_list, 3 /* snapshot */,
|
||||
bytewise_icmp);
|
||||
FragmentedRangeTombstoneIterator iter2(&fragment_list, bytewise_icmp,
|
||||
3 /* upper_bound */);
|
||||
VerifySeek(&iter2, {{"c", {"j", "l", 2}},
|
||||
{"g", {"j", "l", 2}},
|
||||
{"i", {"j", "l", 2}},
|
||||
@@ -398,8 +537,8 @@ TEST_F(RangeTombstoneFragmenterTest, SeekOutOfBounds) {
|
||||
FragmentedRangeTombstoneList fragment_list(std::move(range_del_iter),
|
||||
bytewise_icmp);
|
||||
|
||||
FragmentedRangeTombstoneIterator iter(&fragment_list, kMaxSequenceNumber,
|
||||
bytewise_icmp);
|
||||
FragmentedRangeTombstoneIterator iter(&fragment_list, bytewise_icmp,
|
||||
kMaxSequenceNumber);
|
||||
VerifySeek(&iter, {{"", {"a", "c", 10}}, {"z", {}, true /* out of range */}});
|
||||
VerifySeekForPrev(&iter,
|
||||
{{"", {}, true /* out of range */}, {"z", {"l", "n", 4}}});
|
||||
|
||||
+8
-3
@@ -417,11 +417,16 @@ class Repairer {
|
||||
SnapshotChecker* snapshot_checker = DisableGCSnapshotChecker::Instance();
|
||||
|
||||
auto write_hint = cfd->CalculateSSTWriteHint(0);
|
||||
std::vector<std::unique_ptr<FragmentedRangeTombstoneIterator>>
|
||||
range_del_iters;
|
||||
auto range_del_iter =
|
||||
mem->NewRangeTombstoneIterator(ro, kMaxSequenceNumber);
|
||||
if (range_del_iter != nullptr) {
|
||||
range_del_iters.emplace_back(range_del_iter);
|
||||
}
|
||||
status = BuildTable(
|
||||
dbname_, env_, *cfd->ioptions(), *cfd->GetLatestMutableCFOptions(),
|
||||
env_options_, table_cache_, iter.get(),
|
||||
std::unique_ptr<InternalIterator>(
|
||||
mem->NewRangeTombstoneIterator(ro, vset_.LastSequence())),
|
||||
env_options_, table_cache_, iter.get(), std::move(range_del_iters),
|
||||
&meta, cfd->internal_comparator(),
|
||||
cfd->int_tbl_prop_collector_factories(), cfd->GetID(), cfd->GetName(),
|
||||
{}, kMaxSequenceNumber, snapshot_checker, kNoCompression,
|
||||
|
||||
+16
-8
@@ -8,22 +8,30 @@
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
// Callback class that control GC of duplicate keys in flush/compaction
|
||||
enum class SnapshotCheckerResult : int {
|
||||
kInSnapshot = 0,
|
||||
kNotInSnapshot = 1,
|
||||
// In case snapshot is released and the checker has no clue whether
|
||||
// the given sequence is visible to the snapshot.
|
||||
kSnapshotReleased = 2,
|
||||
};
|
||||
|
||||
// Callback class that control GC of duplicate keys in flush/compaction.
|
||||
class SnapshotChecker {
|
||||
public:
|
||||
virtual ~SnapshotChecker() {}
|
||||
virtual bool IsInSnapshot(SequenceNumber sequence,
|
||||
SequenceNumber snapshot_sequence) const = 0;
|
||||
virtual SnapshotCheckerResult CheckInSnapshot(
|
||||
SequenceNumber sequence, SequenceNumber snapshot_sequence) const = 0;
|
||||
};
|
||||
|
||||
class DisableGCSnapshotChecker : public SnapshotChecker {
|
||||
public:
|
||||
virtual ~DisableGCSnapshotChecker() {}
|
||||
virtual bool IsInSnapshot(
|
||||
virtual SnapshotCheckerResult CheckInSnapshot(
|
||||
SequenceNumber /*sequence*/,
|
||||
SequenceNumber /*snapshot_sequence*/) const override {
|
||||
// By returning false, we prevent all the values from being GCed
|
||||
return false;
|
||||
// By returning kNotInSnapshot, we prevent all the values from being GCed
|
||||
return SnapshotCheckerResult::kNotInSnapshot;
|
||||
}
|
||||
static DisableGCSnapshotChecker* Instance() { return &instance_; }
|
||||
|
||||
@@ -41,8 +49,8 @@ class WritePreparedSnapshotChecker : public SnapshotChecker {
|
||||
explicit WritePreparedSnapshotChecker(WritePreparedTxnDB* txn_db);
|
||||
virtual ~WritePreparedSnapshotChecker() {}
|
||||
|
||||
virtual bool IsInSnapshot(SequenceNumber sequence,
|
||||
SequenceNumber snapshot_sequence) const override;
|
||||
virtual SnapshotCheckerResult CheckInSnapshot(
|
||||
SequenceNumber sequence, SequenceNumber snapshot_sequence) const override;
|
||||
|
||||
private:
|
||||
#ifndef ROCKSDB_LITE
|
||||
|
||||
+5
-2
@@ -86,7 +86,7 @@ class SnapshotList {
|
||||
}
|
||||
|
||||
// retrieve all snapshot numbers up until max_seq. They are sorted in
|
||||
// ascending order.
|
||||
// ascending order (with no duplicates).
|
||||
std::vector<SequenceNumber> GetAll(
|
||||
SequenceNumber* oldest_write_conflict_snapshot = nullptr,
|
||||
const SequenceNumber& max_seq = kMaxSequenceNumber) const {
|
||||
@@ -104,7 +104,10 @@ class SnapshotList {
|
||||
if (s->next_->number_ > max_seq) {
|
||||
break;
|
||||
}
|
||||
ret.push_back(s->next_->number_);
|
||||
// Avoid duplicates
|
||||
if (ret.empty() || ret.back() != s->next_->number_) {
|
||||
ret.push_back(s->next_->number_);
|
||||
}
|
||||
|
||||
if (oldest_write_conflict_snapshot != nullptr &&
|
||||
*oldest_write_conflict_snapshot == kMaxSequenceNumber &&
|
||||
|
||||
+2
-2
@@ -147,7 +147,7 @@ Status TableCache::FindTable(const EnvOptions& env_options,
|
||||
HistogramImpl* file_read_hist, bool skip_filters,
|
||||
int level,
|
||||
bool prefetch_index_and_filter_in_cache) {
|
||||
PERF_TIMER_GUARD(find_table_nanos);
|
||||
PERF_TIMER_GUARD_WITH_ENV(find_table_nanos, ioptions_.env);
|
||||
Status s;
|
||||
uint64_t number = fd.GetNumber();
|
||||
Slice key = GetSliceForFileNumber(&number);
|
||||
@@ -185,7 +185,7 @@ Status TableCache::FindTable(const EnvOptions& env_options,
|
||||
InternalIterator* TableCache::NewIterator(
|
||||
const ReadOptions& options, const EnvOptions& env_options,
|
||||
const InternalKeyComparator& icomparator, const FileMetaData& file_meta,
|
||||
RangeDelAggregatorV2* range_del_agg, const SliceTransform* prefix_extractor,
|
||||
RangeDelAggregator* range_del_agg, const SliceTransform* prefix_extractor,
|
||||
TableReader** table_reader_ptr, HistogramImpl* file_read_hist,
|
||||
bool for_compaction, Arena* arena, bool skip_filters, int level,
|
||||
const InternalKey* smallest_compaction_key,
|
||||
|
||||
+4
-2
@@ -15,7 +15,7 @@
|
||||
#include <stdint.h>
|
||||
|
||||
#include "db/dbformat.h"
|
||||
#include "db/range_del_aggregator_v2.h"
|
||||
#include "db/range_del_aggregator.h"
|
||||
#include "options/cf_options.h"
|
||||
#include "port/port.h"
|
||||
#include "rocksdb/cache.h"
|
||||
@@ -52,7 +52,7 @@ class TableCache {
|
||||
InternalIterator* NewIterator(
|
||||
const ReadOptions& options, const EnvOptions& toptions,
|
||||
const InternalKeyComparator& internal_comparator,
|
||||
const FileMetaData& file_meta, RangeDelAggregatorV2* range_del_agg,
|
||||
const FileMetaData& file_meta, RangeDelAggregator* range_del_agg,
|
||||
const SliceTransform* prefix_extractor = nullptr,
|
||||
TableReader** table_reader_ptr = nullptr,
|
||||
HistogramImpl* file_read_hist = nullptr, bool for_compaction = false,
|
||||
@@ -122,6 +122,8 @@ class TableCache {
|
||||
// Release the handle from a cache
|
||||
void ReleaseHandle(Cache::Handle* handle);
|
||||
|
||||
Cache* get_cache() const { return cache_; }
|
||||
|
||||
// Capacity of the backing Cache that indicates inifinite TableCache capacity.
|
||||
// For example when max_open_files is -1 we set the backing Cache to this.
|
||||
static const int kInfiniteCapacity = 0x400000;
|
||||
|
||||
+41
-1
@@ -366,8 +366,40 @@ class VersionBuilder::Rep {
|
||||
|
||||
void LoadTableHandlers(InternalStats* internal_stats, int max_threads,
|
||||
bool prefetch_index_and_filter_in_cache,
|
||||
bool is_initial_load,
|
||||
const SliceTransform* prefix_extractor) {
|
||||
assert(table_cache_ != nullptr);
|
||||
|
||||
size_t table_cache_capacity = table_cache_->get_cache()->GetCapacity();
|
||||
bool always_load = (table_cache_capacity == TableCache::kInfiniteCapacity);
|
||||
size_t max_load = port::kMaxSizet;
|
||||
|
||||
if (!always_load) {
|
||||
// If it is initial loading and not set to always laoding all the
|
||||
// files, we only load up to kInitialLoadLimit files, to limit the
|
||||
// time reopening the DB.
|
||||
const size_t kInitialLoadLimit = 16;
|
||||
size_t load_limit;
|
||||
// If the table cache is not 1/4 full, we pin the table handle to
|
||||
// file metadata to avoid the cache read costs when reading the file.
|
||||
// The downside of pinning those files is that LRU won't be followed
|
||||
// for those files. This doesn't matter much because if number of files
|
||||
// of the DB excceeds table cache capacity, eventually no table reader
|
||||
// will be pinned and LRU will be followed.
|
||||
if (is_initial_load) {
|
||||
load_limit = std::min(kInitialLoadLimit, table_cache_capacity / 4);
|
||||
} else {
|
||||
load_limit = table_cache_capacity / 4;
|
||||
}
|
||||
|
||||
size_t table_cache_usage = table_cache_->get_cache()->GetUsage();
|
||||
if (table_cache_usage >= load_limit) {
|
||||
return;
|
||||
} else {
|
||||
max_load = load_limit - table_cache_usage;
|
||||
}
|
||||
}
|
||||
|
||||
// <file metadata, level>
|
||||
std::vector<std::pair<FileMetaData*, int>> files_meta;
|
||||
for (int level = 0; level < num_levels_; level++) {
|
||||
@@ -375,6 +407,12 @@ class VersionBuilder::Rep {
|
||||
auto* file_meta = file_meta_pair.second;
|
||||
assert(!file_meta->table_reader_handle);
|
||||
files_meta.emplace_back(file_meta, level);
|
||||
if (files_meta.size() >= max_load) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (files_meta.size() >= max_load) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -452,9 +490,11 @@ void VersionBuilder::SaveTo(VersionStorageInfo* vstorage) {
|
||||
void VersionBuilder::LoadTableHandlers(InternalStats* internal_stats,
|
||||
int max_threads,
|
||||
bool prefetch_index_and_filter_in_cache,
|
||||
bool is_initial_load,
|
||||
const SliceTransform* prefix_extractor) {
|
||||
rep_->LoadTableHandlers(internal_stats, max_threads,
|
||||
prefetch_index_and_filter_in_cache, prefix_extractor);
|
||||
prefetch_index_and_filter_in_cache, is_initial_load,
|
||||
prefix_extractor);
|
||||
}
|
||||
|
||||
void VersionBuilder::MaybeAddFile(VersionStorageInfo* vstorage, int level,
|
||||
|
||||
@@ -35,6 +35,7 @@ class VersionBuilder {
|
||||
void SaveTo(VersionStorageInfo* vstorage);
|
||||
void LoadTableHandlers(InternalStats* internal_stats, int max_threads,
|
||||
bool prefetch_index_and_filter_in_cache,
|
||||
bool is_initial_load,
|
||||
const SliceTransform* prefix_extractor);
|
||||
void MaybeAddFile(VersionStorageInfo* vstorage, int level, FileMetaData* f);
|
||||
|
||||
|
||||
+2
-2
@@ -263,7 +263,7 @@ const char* VersionEdit::DecodeNewFile4From(Slice* input) {
|
||||
break;
|
||||
}
|
||||
if (!GetLengthPrefixedSlice(input, &field)) {
|
||||
return "new-file4 custom field lenth prefixed slice error";
|
||||
return "new-file4 custom field length prefixed slice error";
|
||||
}
|
||||
switch (custom_tag) {
|
||||
case kPathId:
|
||||
@@ -579,7 +579,7 @@ std::string VersionEdit::DebugString(bool hex_key) const {
|
||||
AppendNumberTo(&r, max_column_family_);
|
||||
}
|
||||
if (is_in_atomic_group_) {
|
||||
r.append("\n AtomicGroup: ");
|
||||
r.append("\n AtomicGroup: ");
|
||||
AppendNumberTo(&r, remaining_entries_);
|
||||
r.append(" entries remains");
|
||||
}
|
||||
|
||||
+112
-29
@@ -301,17 +301,28 @@ class FilePicker {
|
||||
// On Level-n (n>=1), files are sorted. Binary search to find the
|
||||
// earliest file whose largest key >= ikey. Search left bound and
|
||||
// right bound are used to narrow the range.
|
||||
if (search_left_bound_ == search_right_bound_) {
|
||||
start_index = search_left_bound_;
|
||||
} else if (search_left_bound_ < search_right_bound_) {
|
||||
if (search_left_bound_ <= search_right_bound_) {
|
||||
if (search_right_bound_ == FileIndexer::kLevelMaxIndex) {
|
||||
search_right_bound_ =
|
||||
static_cast<int32_t>(curr_file_level_->num_files) - 1;
|
||||
}
|
||||
// `search_right_bound_` is an inclusive upper-bound, but since it was
|
||||
// determined based on user key, it is still possible the lookup key
|
||||
// falls to the right of `search_right_bound_`'s corresponding file.
|
||||
// So, pass a limit one higher, which allows us to detect this case.
|
||||
start_index =
|
||||
FindFileInRange(*internal_comparator_, *curr_file_level_, ikey_,
|
||||
static_cast<uint32_t>(search_left_bound_),
|
||||
static_cast<uint32_t>(search_right_bound_));
|
||||
static_cast<uint32_t>(search_right_bound_) + 1);
|
||||
if (start_index == search_right_bound_ + 1) {
|
||||
// `ikey_` comes after `search_right_bound_`. The lookup key does
|
||||
// not exist on this level, so let's skip this level and do a full
|
||||
// binary search on the next level.
|
||||
search_left_bound_ = 0;
|
||||
search_right_bound_ = FileIndexer::kLevelMaxIndex;
|
||||
curr_level_++;
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
// search_left_bound > search_right_bound, key does not exist in
|
||||
// this level. Since no comparison is done in this level, it will
|
||||
@@ -459,7 +470,7 @@ class LevelIterator final : public InternalIterator {
|
||||
const EnvOptions& env_options, const InternalKeyComparator& icomparator,
|
||||
const LevelFilesBrief* flevel, const SliceTransform* prefix_extractor,
|
||||
bool should_sample, HistogramImpl* file_read_hist, bool for_compaction,
|
||||
bool skip_filters, int level, RangeDelAggregatorV2* range_del_agg,
|
||||
bool skip_filters, int level, RangeDelAggregator* range_del_agg,
|
||||
const std::vector<AtomicCompactionUnitBoundary>* compaction_boundaries =
|
||||
nullptr)
|
||||
: table_cache_(table_cache),
|
||||
@@ -571,7 +582,7 @@ class LevelIterator final : public InternalIterator {
|
||||
bool skip_filters_;
|
||||
size_t file_index_;
|
||||
int level_;
|
||||
RangeDelAggregatorV2* range_del_agg_;
|
||||
RangeDelAggregator* range_del_agg_;
|
||||
IteratorWrapper file_iter_; // May be nullptr
|
||||
PinnedIteratorsManager* pinned_iters_mgr_;
|
||||
|
||||
@@ -985,7 +996,7 @@ double VersionStorageInfo::GetEstimatedCompressionRatioAtLevel(
|
||||
void Version::AddIterators(const ReadOptions& read_options,
|
||||
const EnvOptions& soptions,
|
||||
MergeIteratorBuilder* merge_iter_builder,
|
||||
RangeDelAggregatorV2* range_del_agg) {
|
||||
RangeDelAggregator* range_del_agg) {
|
||||
assert(storage_info_.finalized_);
|
||||
|
||||
for (int level = 0; level < storage_info_.num_non_empty_levels(); level++) {
|
||||
@@ -998,7 +1009,7 @@ void Version::AddIteratorsForLevel(const ReadOptions& read_options,
|
||||
const EnvOptions& soptions,
|
||||
MergeIteratorBuilder* merge_iter_builder,
|
||||
int level,
|
||||
RangeDelAggregatorV2* range_del_agg) {
|
||||
RangeDelAggregator* range_del_agg) {
|
||||
assert(storage_info_.finalized_);
|
||||
if (level >= storage_info_.num_non_empty_levels()) {
|
||||
// This is an empty level
|
||||
@@ -1057,8 +1068,8 @@ Status Version::OverlapWithLevelIterator(const ReadOptions& read_options,
|
||||
|
||||
Arena arena;
|
||||
Status status;
|
||||
RangeDelAggregatorV2 range_del_agg(&icmp,
|
||||
kMaxSequenceNumber /* upper_bound */);
|
||||
ReadRangeDelAggregator range_del_agg(&icmp,
|
||||
kMaxSequenceNumber /* upper_bound */);
|
||||
|
||||
*overlap = false;
|
||||
|
||||
@@ -1876,9 +1887,9 @@ void SortFileByOverlappingRatio(
|
||||
next_level_it++;
|
||||
}
|
||||
|
||||
assert(file->fd.file_size != 0);
|
||||
assert(file->compensated_file_size != 0);
|
||||
file_to_order[file->fd.GetNumber()] =
|
||||
overlapping_bytes * 1024u / file->fd.file_size;
|
||||
overlapping_bytes * 1024u / file->compensated_file_size;
|
||||
}
|
||||
|
||||
std::sort(temp->begin(), temp->end(),
|
||||
@@ -2849,6 +2860,7 @@ Status VersionSet::ProcessManifestWrites(
|
||||
batch_edits.push_back(first_writer.edit_list.front());
|
||||
} else {
|
||||
auto it = manifest_writers_.cbegin();
|
||||
size_t group_start = std::numeric_limits<size_t>::max();
|
||||
while (it != manifest_writers_.cend()) {
|
||||
if ((*it)->edit_list.front()->IsColumnFamilyManipulation()) {
|
||||
// no group commits for column family add or drop
|
||||
@@ -2857,7 +2869,36 @@ Status VersionSet::ProcessManifestWrites(
|
||||
last_writer = *(it++);
|
||||
assert(last_writer != nullptr);
|
||||
assert(last_writer->cfd != nullptr);
|
||||
if (last_writer->cfd != nullptr && last_writer->cfd->IsDropped()) {
|
||||
if (last_writer->cfd->IsDropped()) {
|
||||
// If we detect a dropped CF at this point, and the corresponding
|
||||
// version edits belong to an atomic group, then we need to find out
|
||||
// the preceding version edits in the same atomic group, and update
|
||||
// their `remaining_entries_` member variable because we are NOT going
|
||||
// to write the version edits' of dropped CF to the MANIFEST. If we
|
||||
// don't update, then Recover can report corrupted atomic group because
|
||||
// the `remaining_entries_` do not match.
|
||||
if (!batch_edits.empty()) {
|
||||
if (batch_edits.back()->is_in_atomic_group_ &&
|
||||
batch_edits.back()->remaining_entries_ > 0) {
|
||||
assert(group_start < batch_edits.size());
|
||||
const auto& edit_list = last_writer->edit_list;
|
||||
size_t k = 0;
|
||||
while (k < edit_list.size()) {
|
||||
if (!edit_list[k]->is_in_atomic_group_) {
|
||||
break;
|
||||
} else if (edit_list[k]->remaining_entries_ == 0) {
|
||||
++k;
|
||||
break;
|
||||
}
|
||||
++k;
|
||||
}
|
||||
for (auto i = group_start; i < batch_edits.size(); ++i) {
|
||||
assert(static_cast<uint32_t>(k) <=
|
||||
batch_edits.back()->remaining_entries_);
|
||||
batch_edits[i]->remaining_entries_ -= static_cast<uint32_t>(k);
|
||||
}
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
// We do a linear search on versions because versions is small.
|
||||
@@ -2888,6 +2929,15 @@ Status VersionSet::ProcessManifestWrites(
|
||||
}
|
||||
assert(builder != nullptr); // make checker happy
|
||||
for (const auto& e : last_writer->edit_list) {
|
||||
if (e->is_in_atomic_group_) {
|
||||
if (batch_edits.empty() || !batch_edits.back()->is_in_atomic_group_ ||
|
||||
(batch_edits.back()->is_in_atomic_group_ &&
|
||||
batch_edits.back()->remaining_entries_ == 0)) {
|
||||
group_start = batch_edits.size();
|
||||
}
|
||||
} else if (group_start != std::numeric_limits<size_t>::max()) {
|
||||
group_start = std::numeric_limits<size_t>::max();
|
||||
}
|
||||
LogAndApplyHelper(last_writer->cfd, builder, version, e, mu);
|
||||
batch_edits.push_back(e);
|
||||
}
|
||||
@@ -2900,6 +2950,42 @@ Status VersionSet::ProcessManifestWrites(
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
// Verify that version edits of atomic groups have correct
|
||||
// remaining_entries_.
|
||||
size_t k = 0;
|
||||
while (k < batch_edits.size()) {
|
||||
while (k < batch_edits.size() && !batch_edits[k]->is_in_atomic_group_) {
|
||||
++k;
|
||||
}
|
||||
if (k == batch_edits.size()) {
|
||||
break;
|
||||
}
|
||||
size_t i = k;
|
||||
while (i < batch_edits.size()) {
|
||||
if (!batch_edits[i]->is_in_atomic_group_) {
|
||||
break;
|
||||
}
|
||||
assert(i - k + batch_edits[i]->remaining_entries_ ==
|
||||
batch_edits[k]->remaining_entries_);
|
||||
if (batch_edits[i]->remaining_entries_ == 0) {
|
||||
++i;
|
||||
break;
|
||||
}
|
||||
++i;
|
||||
}
|
||||
assert(batch_edits[i - 1]->is_in_atomic_group_);
|
||||
assert(0 == batch_edits[i - 1]->remaining_entries_);
|
||||
std::vector<VersionEdit*> tmp;
|
||||
for (size_t j = k; j != i; ++j) {
|
||||
tmp.emplace_back(batch_edits[j]);
|
||||
}
|
||||
TEST_SYNC_POINT_CALLBACK(
|
||||
"VersionSet::ProcessManifestWrites:CheckOneAtomicGroup", &tmp);
|
||||
k = i;
|
||||
}
|
||||
#endif // NDEBUG
|
||||
|
||||
uint64_t new_manifest_file_size = 0;
|
||||
Status s;
|
||||
|
||||
@@ -2927,9 +3013,7 @@ Status VersionSet::ProcessManifestWrites(
|
||||
mu->Unlock();
|
||||
|
||||
TEST_SYNC_POINT("VersionSet::LogAndApply:WriteManifest");
|
||||
if (!first_writer.edit_list.front()->IsColumnFamilyManipulation() &&
|
||||
column_family_set_->get_table_cache()->GetCapacity() ==
|
||||
TableCache::kInfiniteCapacity) {
|
||||
if (!first_writer.edit_list.front()->IsColumnFamilyManipulation()) {
|
||||
for (int i = 0; i < static_cast<int>(versions.size()); ++i) {
|
||||
assert(!builder_guards.empty() &&
|
||||
builder_guards.size() == versions.size());
|
||||
@@ -2939,6 +3023,7 @@ Status VersionSet::ProcessManifestWrites(
|
||||
builder_guards[i]->version_builder()->LoadTableHandlers(
|
||||
cfd->internal_stats(), cfd->ioptions()->optimize_filters_for_hits,
|
||||
true /* prefetch_index_and_filter_in_cache */,
|
||||
false /* is_initial_load */,
|
||||
mutable_cf_options_ptrs[i]->prefix_extractor.get());
|
||||
}
|
||||
}
|
||||
@@ -2959,8 +3044,8 @@ Status VersionSet::ProcessManifestWrites(
|
||||
db_options_->manifest_preallocation_size);
|
||||
|
||||
std::unique_ptr<WritableFileWriter> file_writer(new WritableFileWriter(
|
||||
std::move(descriptor_file), descriptor_fname, opt_env_opts, nullptr,
|
||||
db_options_->listeners));
|
||||
std::move(descriptor_file), descriptor_fname, opt_env_opts, env_,
|
||||
nullptr, db_options_->listeners));
|
||||
descriptor_log_.reset(
|
||||
new log::Writer(std::move(file_writer), 0, false));
|
||||
s = WriteSnapshot(descriptor_log_.get());
|
||||
@@ -3205,7 +3290,7 @@ Status VersionSet::LogAndApply(
|
||||
if (!manifest_writers_.empty()) {
|
||||
manifest_writers_.front()->cv.Signal();
|
||||
}
|
||||
return Status::OK();
|
||||
return Status::ShutdownInProgress();
|
||||
}
|
||||
|
||||
return ProcessManifestWrites(writers, mu, db_directory, new_descriptor_log,
|
||||
@@ -3596,15 +3681,13 @@ Status VersionSet::Recover(
|
||||
assert(builders_iter != builders.end());
|
||||
auto* builder = builders_iter->second->version_builder();
|
||||
|
||||
if (GetColumnFamilySet()->get_table_cache()->GetCapacity() ==
|
||||
TableCache::kInfiniteCapacity) {
|
||||
// unlimited table cache. Pre-load table handle now.
|
||||
// Need to do it out of the mutex.
|
||||
builder->LoadTableHandlers(
|
||||
cfd->internal_stats(), db_options_->max_file_opening_threads,
|
||||
false /* prefetch_index_and_filter_in_cache */,
|
||||
cfd->GetLatestMutableCFOptions()->prefix_extractor.get());
|
||||
}
|
||||
// unlimited table cache. Pre-load table handle now.
|
||||
// Need to do it out of the mutex.
|
||||
builder->LoadTableHandlers(
|
||||
cfd->internal_stats(), db_options_->max_file_opening_threads,
|
||||
false /* prefetch_index_and_filter_in_cache */,
|
||||
true /* is_initial_load */,
|
||||
cfd->GetLatestMutableCFOptions()->prefix_extractor.get());
|
||||
|
||||
Version* v = new Version(cfd, this, env_options_,
|
||||
*cfd->GetLatestMutableCFOptions(),
|
||||
@@ -4253,7 +4336,7 @@ void VersionSet::AddLiveFiles(std::vector<FileDescriptor>* live_list) {
|
||||
}
|
||||
|
||||
InternalIterator* VersionSet::MakeInputIterator(
|
||||
const Compaction* c, RangeDelAggregatorV2* range_del_agg,
|
||||
const Compaction* c, RangeDelAggregator* range_del_agg,
|
||||
const EnvOptions& env_options_compactions) {
|
||||
auto cfd = c->column_family_data();
|
||||
ReadOptions read_options;
|
||||
|
||||
+4
-4
@@ -34,7 +34,7 @@
|
||||
#include "db/dbformat.h"
|
||||
#include "db/file_indexer.h"
|
||||
#include "db/log_reader.h"
|
||||
#include "db/range_del_aggregator_v2.h"
|
||||
#include "db/range_del_aggregator.h"
|
||||
#include "db/read_callback.h"
|
||||
#include "db/table_cache.h"
|
||||
#include "db/version_builder.h"
|
||||
@@ -538,11 +538,11 @@ class Version {
|
||||
// REQUIRES: This version has been saved (see VersionSet::SaveTo)
|
||||
void AddIterators(const ReadOptions&, const EnvOptions& soptions,
|
||||
MergeIteratorBuilder* merger_iter_builder,
|
||||
RangeDelAggregatorV2* range_del_agg);
|
||||
RangeDelAggregator* range_del_agg);
|
||||
|
||||
void AddIteratorsForLevel(const ReadOptions&, const EnvOptions& soptions,
|
||||
MergeIteratorBuilder* merger_iter_builder,
|
||||
int level, RangeDelAggregatorV2* range_del_agg);
|
||||
int level, RangeDelAggregator* range_del_agg);
|
||||
|
||||
Status OverlapWithLevelIterator(const ReadOptions&, const EnvOptions&,
|
||||
const Slice& smallest_user_key,
|
||||
@@ -935,7 +935,7 @@ class VersionSet {
|
||||
// Create an iterator that reads over the compaction inputs for "*c".
|
||||
// The caller should delete the iterator when no longer needed.
|
||||
InternalIterator* MakeInputIterator(
|
||||
const Compaction* c, RangeDelAggregatorV2* range_del_agg,
|
||||
const Compaction* c, RangeDelAggregator* range_del_agg,
|
||||
const EnvOptions& env_options_compactions);
|
||||
|
||||
// Add all files listed in any live version to *live.
|
||||
|
||||
+138
-4
@@ -605,9 +605,13 @@ TEST_F(FindLevelFileTest, LevelOverlappingFiles) {
|
||||
ASSERT_TRUE(Overlaps("600", "700"));
|
||||
}
|
||||
|
||||
class VersionSetTest : public testing::Test {
|
||||
class VersionSetTestBase {
|
||||
public:
|
||||
VersionSetTest()
|
||||
const static std::string kColumnFamilyName1;
|
||||
const static std::string kColumnFamilyName2;
|
||||
const static std::string kColumnFamilyName3;
|
||||
|
||||
VersionSetTestBase()
|
||||
: env_(Env::Default()),
|
||||
dbname_(test::PerThreadDBPath("version_set_test")),
|
||||
db_options_(),
|
||||
@@ -635,8 +639,9 @@ class VersionSetTest : public testing::Test {
|
||||
new_db.SetNextFile(2);
|
||||
new_db.SetLastSequence(0);
|
||||
|
||||
const std::vector<std::string> cf_names = {kDefaultColumnFamilyName,
|
||||
"alice", "bob"};
|
||||
const std::vector<std::string> cf_names = {
|
||||
kDefaultColumnFamilyName, kColumnFamilyName1, kColumnFamilyName2,
|
||||
kColumnFamilyName3};
|
||||
const int kInitialNumOfCfs = static_cast<int>(cf_names.size());
|
||||
autovector<VersionEdit> new_cfs;
|
||||
uint64_t last_seq = 1;
|
||||
@@ -711,6 +716,15 @@ class VersionSetTest : public testing::Test {
|
||||
std::shared_ptr<mock::MockTableFactory> mock_table_factory_;
|
||||
};
|
||||
|
||||
const std::string VersionSetTestBase::kColumnFamilyName1 = "alice";
|
||||
const std::string VersionSetTestBase::kColumnFamilyName2 = "bob";
|
||||
const std::string VersionSetTestBase::kColumnFamilyName3 = "charles";
|
||||
|
||||
class VersionSetTest : public VersionSetTestBase, public testing::Test {
|
||||
public:
|
||||
VersionSetTest() : VersionSetTestBase() {}
|
||||
};
|
||||
|
||||
TEST_F(VersionSetTest, SameColumnFamilyGroupCommit) {
|
||||
NewDB();
|
||||
const int kGroupSize = 5;
|
||||
@@ -958,6 +972,126 @@ TEST_F(VersionSetTest, HandleIncorrectAtomicGroupSize) {
|
||||
versions_->GetColumnFamilySet()->NumberOfColumnFamilies());
|
||||
EXPECT_TRUE(incorrect_group_size);
|
||||
}
|
||||
|
||||
class VersionSetTestDropOneCF : public VersionSetTestBase,
|
||||
public testing::TestWithParam<std::string> {
|
||||
public:
|
||||
VersionSetTestDropOneCF() : VersionSetTestBase() {}
|
||||
};
|
||||
|
||||
// This test simulates the following execution sequence
|
||||
// Time thread1 bg_flush_thr
|
||||
// | Prepare version edits (e1,e2,e3) for atomic
|
||||
// | flush cf1, cf2, cf3
|
||||
// | Enqueue e to drop cfi
|
||||
// | to manifest_writers_
|
||||
// | Enqueue (e1,e2,e3) to manifest_writers_
|
||||
// |
|
||||
// | Apply e,
|
||||
// | cfi.IsDropped() is true
|
||||
// | Apply (e1,e2,e3),
|
||||
// | since cfi.IsDropped() == true, we need to
|
||||
// | drop ei and write the rest to MANIFEST.
|
||||
// V
|
||||
//
|
||||
// Repeat the test for i = 1, 2, 3 to simulate dropping the first, middle and
|
||||
// last column family in an atomic group.
|
||||
TEST_P(VersionSetTestDropOneCF, HandleDroppedColumnFamilyInAtomicGroup) {
|
||||
std::vector<ColumnFamilyDescriptor> column_families;
|
||||
SequenceNumber last_seqno;
|
||||
std::unique_ptr<log::Writer> log_writer;
|
||||
PrepareManifest(&column_families, &last_seqno, &log_writer);
|
||||
Status s = SetCurrentFile(env_, dbname_, 1, nullptr);
|
||||
ASSERT_OK(s);
|
||||
|
||||
EXPECT_OK(versions_->Recover(column_families, false /* read_only */));
|
||||
EXPECT_EQ(column_families.size(),
|
||||
versions_->GetColumnFamilySet()->NumberOfColumnFamilies());
|
||||
|
||||
const int kAtomicGroupSize = 3;
|
||||
const std::vector<std::string> non_default_cf_names = {
|
||||
kColumnFamilyName1, kColumnFamilyName2, kColumnFamilyName3};
|
||||
|
||||
// Drop one column family
|
||||
VersionEdit drop_cf_edit;
|
||||
drop_cf_edit.DropColumnFamily();
|
||||
const std::string cf_to_drop_name(GetParam());
|
||||
auto cfd_to_drop =
|
||||
versions_->GetColumnFamilySet()->GetColumnFamily(cf_to_drop_name);
|
||||
ASSERT_NE(nullptr, cfd_to_drop);
|
||||
// Increase its refcount because cfd_to_drop is used later, and we need to
|
||||
// prevent it from being deleted.
|
||||
cfd_to_drop->Ref();
|
||||
drop_cf_edit.SetColumnFamily(cfd_to_drop->GetID());
|
||||
mutex_.Lock();
|
||||
s = versions_->LogAndApply(cfd_to_drop,
|
||||
*cfd_to_drop->GetLatestMutableCFOptions(),
|
||||
&drop_cf_edit, &mutex_);
|
||||
mutex_.Unlock();
|
||||
ASSERT_OK(s);
|
||||
|
||||
std::vector<VersionEdit> edits(kAtomicGroupSize);
|
||||
uint32_t remaining = kAtomicGroupSize;
|
||||
size_t i = 0;
|
||||
autovector<ColumnFamilyData*> cfds;
|
||||
autovector<const MutableCFOptions*> mutable_cf_options_list;
|
||||
autovector<autovector<VersionEdit*>> edit_lists;
|
||||
for (const auto& cf_name : non_default_cf_names) {
|
||||
auto cfd = (cf_name != cf_to_drop_name)
|
||||
? versions_->GetColumnFamilySet()->GetColumnFamily(cf_name)
|
||||
: cfd_to_drop;
|
||||
ASSERT_NE(nullptr, cfd);
|
||||
cfds.push_back(cfd);
|
||||
mutable_cf_options_list.emplace_back(cfd->GetLatestMutableCFOptions());
|
||||
edits[i].SetColumnFamily(cfd->GetID());
|
||||
edits[i].SetLogNumber(0);
|
||||
edits[i].SetNextFile(2);
|
||||
edits[i].MarkAtomicGroup(--remaining);
|
||||
edits[i].SetLastSequence(last_seqno++);
|
||||
autovector<VersionEdit*> tmp_edits;
|
||||
tmp_edits.push_back(&edits[i]);
|
||||
edit_lists.emplace_back(tmp_edits);
|
||||
++i;
|
||||
}
|
||||
int called = 0;
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"VersionSet::ProcessManifestWrites:CheckOneAtomicGroup", [&](void* arg) {
|
||||
std::vector<VersionEdit*>* tmp_edits =
|
||||
reinterpret_cast<std::vector<VersionEdit*>*>(arg);
|
||||
EXPECT_EQ(kAtomicGroupSize - 1, tmp_edits->size());
|
||||
for (const auto e : *tmp_edits) {
|
||||
bool found = false;
|
||||
for (const auto& e2 : edits) {
|
||||
if (&e2 == e) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
ASSERT_TRUE(found);
|
||||
}
|
||||
++called;
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
mutex_.Lock();
|
||||
s = versions_->LogAndApply(cfds, mutable_cf_options_list, edit_lists,
|
||||
&mutex_);
|
||||
mutex_.Unlock();
|
||||
ASSERT_OK(s);
|
||||
ASSERT_EQ(1, called);
|
||||
if (cfd_to_drop->Unref()) {
|
||||
delete cfd_to_drop;
|
||||
cfd_to_drop = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(
|
||||
AtomicGroup, VersionSetTestDropOneCF,
|
||||
testing::Values(VersionSetTestBase::kColumnFamilyName1,
|
||||
VersionSetTestBase::kColumnFamilyName2,
|
||||
VersionSetTestBase::kColumnFamilyName3));
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user