mirror of
https://github.com/facebook/rocksdb.git
synced 2026-11-06 11:02:36 -05:00
Compare commits
110
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6ad033ce2b | ||
|
|
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 |
No files matched your search
+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})
|
||||
|
||||
+20
@@ -1,11 +1,31 @@
|
||||
# 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.
|
||||
|
||||
### 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.
|
||||
|
||||
### 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),)
|
||||
@@ -1154,9 +1146,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 +1284,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 +1304,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 +1430,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 +1478,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 +1517,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 +1556,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 +1630,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 +1676,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 +1690,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 +1704,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 +1733,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 +1904,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 +1942,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",
|
||||
|
||||
@@ -368,6 +368,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
|
||||
|
||||
@@ -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/"
|
||||
|
||||
@@ -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)
|
||||
+12
-10
@@ -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(),
|
||||
@@ -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_;
|
||||
|
||||
+57
-23
@@ -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"
|
||||
@@ -805,15 +805,13 @@ Status CompactionJob::Install(const MutableCFOptions& mutable_cf_options) {
|
||||
void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
|
||||
assert(sub_compact != nullptr);
|
||||
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);
|
||||
@@ -902,8 +900,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 +1039,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 +1090,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 +1099,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;
|
||||
@@ -1168,7 +1165,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 +1189,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 +1219,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 +1226,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 +1262,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 +1272,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 +1311,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();
|
||||
}
|
||||
|
||||
+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
|
||||
|
||||
+63
-31
@@ -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);
|
||||
|
||||
@@ -1053,7 +1064,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 +1118,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 +1147,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 +1378,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 +1445,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 +1486,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 +1555,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 +1580,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 +1644,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,
|
||||
|
||||
+322
-150
@@ -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();
|
||||
@@ -115,14 +137,12 @@ Status DBImpl::FlushMemTableToOutputFile(
|
||||
assert(cfd->imm()->NumNotFlushed() != 0);
|
||||
assert(cfd->imm()->IsFlushPending());
|
||||
|
||||
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();
|
||||
}
|
||||
FlushJob flush_job(
|
||||
dbname_, cfd, immutable_db_options_, mutable_cf_options,
|
||||
nullptr /* memtable_id */, env_options_for_compaction_, versions_.get(),
|
||||
@@ -219,20 +239,24 @@ Status DBImpl::FlushMemTablesToOutputFiles(
|
||||
return AtomicFlushMemTablesToOutputFiles(bg_flush_args, made_progress,
|
||||
job_context, log_buffer);
|
||||
}
|
||||
Status s;
|
||||
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,
|
||||
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 +285,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 +314,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 +355,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 +364,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 +371,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 +401,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 +483,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 +505,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 +515,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 +550,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 +602,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 +776,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 +785,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 +817,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 +857,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 +932,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 +952,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 +973,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 +989,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 +1136,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 +1392,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 +1431,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 +1444,7 @@ Status DBImpl::RunManualCompaction(ColumnFamilyData* cfd, int input_level,
|
||||
}
|
||||
}
|
||||
|
||||
log_buffer.FlushBufferToLog();
|
||||
assert(!manual.in_progress);
|
||||
assert(HasPendingManualCompaction());
|
||||
RemoveManualCompaction(&manual);
|
||||
@@ -1407,6 +1455,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 +1489,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 +1572,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 +1596,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 +1880,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 +2130,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 +2164,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 +2304,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 +2357,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 +2443,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 +2461,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 +2515,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 +2539,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 +2558,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 +2598,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 +2785,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 +2838,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 +2905,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
|
||||
+55
-28
@@ -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);
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
+9
-9
@@ -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;
|
||||
}
|
||||
|
||||
+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_EQ(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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ namespace rocksdb {
|
||||
class ExternalSSTFileBasicTest : public DBTestBase,
|
||||
public ::testing::WithParamInterface<bool> {
|
||||
public:
|
||||
ExternalSSTFileBasicTest() : DBTestBase("/external_sst_file_test") {
|
||||
ExternalSSTFileBasicTest() : DBTestBase("/external_sst_file_basic_test") {
|
||||
sst_files_dir_ = dbname_ + "/sst_files/";
|
||||
DestroyAndRecreateExternalSSTFilesDir();
|
||||
}
|
||||
|
||||
+7
-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"
|
||||
@@ -295,7 +296,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 +310,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 +331,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 +356,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_,
|
||||
|
||||
+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(
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -158,6 +158,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 +176,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;
|
||||
@@ -547,6 +555,50 @@ TEST_P(PlainTableDBTest, Flush2) {
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(PlainTableDBTest, Iterator) {
|
||||
for (size_t huge_page_tlb_size = 0; huge_page_tlb_size <= 2 * 1024 * 1024;
|
||||
huge_page_tlb_size += 2 * 1024 * 1024) {
|
||||
|
||||
+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");
|
||||
}
|
||||
|
||||
+110
-27
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
+1
-1
@@ -127,7 +127,7 @@ class WriteThread {
|
||||
std::atomic<uint8_t> state; // write under StateMutex() or pre-link
|
||||
WriteGroup* write_group;
|
||||
SequenceNumber sequence; // the sequence number to use for the first key
|
||||
Status status; // status of memtable inserter
|
||||
Status status;
|
||||
Status callback_status; // status returned by callback->Callback()
|
||||
|
||||
std::aligned_storage<sizeof(std::mutex)>::type state_mutex_bytes;
|
||||
|
||||
@@ -31,4 +31,11 @@
|
||||
}(document, 'script', 'facebook-jssdk'));
|
||||
</script>
|
||||
|
||||
<script>!function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0],p=/^http:/.test(d.location)?'http':'https';if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src=p+'://platform.twitter.com/widgets.js';fjs.parentNode.insertBefore(js,fjs);}}(document, 'script', 'twitter-wjs');</script>
|
||||
<script>!function(d,s,id){
|
||||
var js,fjs=d.getElementsByTagName(s)[0],p=/^http:/.test(d.location)?'http':'https';
|
||||
if(!d.getElementById(id)){
|
||||
js=d.createElement(s);js.id=id;js.src=p+'://platform.twitter.com/widgets.js';
|
||||
fjs.parentNode.insertBefore(js,fjs);
|
||||
}
|
||||
}(document, 'script', 'twitter-wjs');
|
||||
</script>
|
||||
@@ -2,4 +2,11 @@
|
||||
<a href="https://twitter.com/{{ include.href }}" class="twitter-follow-button pluginBlock" data-show-count="false">Follow @{{ include.href }}</a>
|
||||
</div>
|
||||
|
||||
<script>!function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0],p=/^http:/.test(d.location)?'http':'https';if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src=p+'://platform.twitter.com/widgets.js';fjs.parentNode.insertBefore(js,fjs);}}(document, 'script', 'twitter-wjs');</script>
|
||||
<script>!function(d,s,id){
|
||||
var js,fjs=d.getElementsByTagName(s)[0],p=/^http:/.test(d.location)?'http':'https';
|
||||
if(!d.getElementById(id)){
|
||||
js=d.createElement(s);js.id=id;js.src=p+'://platform.twitter.com/widgets.js';
|
||||
fjs.parentNode.insertBefore(js,fjs);
|
||||
}
|
||||
}(document, 'script', 'twitter-wjs');
|
||||
</script>
|
||||
@@ -1,4 +1,11 @@
|
||||
<div class="pluginWrapper twitterSharePlugin">
|
||||
<a href="https://twitter.com/share" class="twitter-share-button" data-hashtags="{{ site.title| replace: ' ', '' }}">Tweet</a>
|
||||
</div>
|
||||
<script>!function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0],p=/^http:/.test(d.location)?'http':'https';if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src=p+'://platform.twitter.com/widgets.js';fjs.parentNode.insertBefore(js,fjs);}}(document, 'script', 'twitter-wjs');</script>
|
||||
<script>!function(d,s,id){
|
||||
var js,fjs=d.getElementsByTagName(s)[0],p=/^http:/.test(d.location)?'http':'https';
|
||||
if(!d.getElementById(id)){js=d.createElement(s);
|
||||
js.id=id;js.src=p+'://platform.twitter.com/widgets.js';
|
||||
fjs.parentNode.insertBefore(js,fjs);
|
||||
}
|
||||
}(document, 'script', 'twitter-wjs');
|
||||
</script>
|
||||
@@ -21,4 +21,11 @@
|
||||
fjs.parentNode.insertBefore(js, fjs);
|
||||
}(document, 'script', 'facebook-jssdk'));</script>
|
||||
|
||||
<script>!function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0],p=/^http:/.test(d.location)?'http':'https';if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src=p+'://platform.twitter.com/widgets.js';fjs.parentNode.insertBefore(js,fjs);}}(document, 'script', 'twitter-wjs');</script>
|
||||
<script>!function(d,s,id){
|
||||
var js,fjs=d.getElementsByTagName(s)[0],p=/^http:/.test(d.location)?'http':'https';
|
||||
if(!d.getElementById(id)){
|
||||
js=d.createElement(s);js.id=id;js.src=p+'://platform.twitter.com/widgets.js';
|
||||
fjs.parentNode.insertBefore(js,fjs);
|
||||
}
|
||||
}(document, 'script', 'twitter-wjs');
|
||||
</script>
|
||||
Vendored
+9
@@ -843,6 +843,15 @@ class PosixEnv : public Env {
|
||||
#endif
|
||||
}
|
||||
|
||||
virtual uint64_t NowCPUNanos() override {
|
||||
#if defined(OS_LINUX) || defined(OS_FREEBSD) || defined(OS_AIX)
|
||||
struct timespec ts;
|
||||
clock_gettime(CLOCK_THREAD_CPUTIME_ID, &ts);
|
||||
return static_cast<uint64_t>(ts.tv_sec) * 1000000000 + ts.tv_nsec;
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
virtual void SleepForMicroseconds(int micros) override { usleep(micros); }
|
||||
|
||||
virtual Status GetHostName(char* name, uint64_t len) override {
|
||||
|
||||
@@ -528,8 +528,8 @@ struct AdvancedColumnFamilyOptions {
|
||||
|
||||
// If level compaction_style = kCompactionStyleLevel, for each level,
|
||||
// which files are prioritized to be picked to compact.
|
||||
// Default: kByCompensatedSize
|
||||
CompactionPri compaction_pri = kByCompensatedSize;
|
||||
// Default: kMinOverlappingRatio
|
||||
CompactionPri compaction_pri = kMinOverlappingRatio;
|
||||
|
||||
// The options needed to support Universal Style compactions
|
||||
//
|
||||
|
||||
+3
-1
@@ -637,7 +637,6 @@ extern ROCKSDB_LIBRARY_API rocksdb_iterator_t* rocksdb_writebatch_wi_create_iter
|
||||
rocksdb_iterator_t* base_iterator,
|
||||
rocksdb_column_family_handle_t* cf);
|
||||
|
||||
|
||||
/* Block based table options */
|
||||
|
||||
extern ROCKSDB_LIBRARY_API rocksdb_block_based_table_options_t*
|
||||
@@ -729,6 +728,9 @@ extern ROCKSDB_LIBRARY_API void rocksdb_options_set_cuckoo_table_factory(
|
||||
extern ROCKSDB_LIBRARY_API void rocksdb_set_options(
|
||||
rocksdb_t* db, int count, const char* const keys[], const char* const values[], char** errptr);
|
||||
|
||||
extern ROCKSDB_LIBRARY_API 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);
|
||||
|
||||
extern ROCKSDB_LIBRARY_API rocksdb_options_t* rocksdb_options_create();
|
||||
extern ROCKSDB_LIBRARY_API void rocksdb_options_destroy(rocksdb_options_t*);
|
||||
extern ROCKSDB_LIBRARY_API void rocksdb_options_increase_parallelism(
|
||||
|
||||
Loaded 100 of 254 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user