mirror of
https://github.com/facebook/rocksdb.git
synced 2026-11-06 11:02:36 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b8c78da6ed | ||
|
|
5dac6ba2dc | ||
|
|
61d6cab888 | ||
|
|
26a2fb1cea | ||
|
|
41eb08a960 | ||
|
|
e7708343f3 | ||
|
|
dbffb828fa | ||
|
|
2ed9b9206b | ||
|
|
4860bfdeab | ||
|
|
74bcf18772 | ||
|
|
18a4f6a1de | ||
|
|
f300da40d0 | ||
|
|
b8f656767a | ||
|
|
fab086daa1 | ||
|
|
f9a3440b76 | ||
|
|
3bbf0e1e4f | ||
|
|
0be02649d8 | ||
|
|
f7ebc2f346 | ||
|
|
addd9545f8 | ||
|
|
ac50fd3a71 | ||
|
|
b54d4dd435 | ||
|
|
48a8667c30 | ||
|
|
d9bca1e14c | ||
|
|
45d794068c | ||
|
|
20d7902df9 | ||
|
|
1477dcb37d | ||
|
|
c7cb1076a6 | ||
|
|
df7e3b6229 | ||
|
|
235b162be1 | ||
|
|
9a8e3f73ed | ||
|
|
7ece10ecb6 | ||
|
|
94d9df2482 | ||
|
|
2f01e10fa9 | ||
|
|
601f1306a1 | ||
|
|
f3fb39814d | ||
|
|
55b37efa15 | ||
|
|
8c71eb5afc | ||
|
|
73c31377bb | ||
|
|
57605d7ef3 | ||
|
|
6935eb24e0 | ||
|
|
9760c842ce | ||
|
|
b1a3b4c0d0 | ||
|
|
ba83447363 | ||
|
|
da032495d3 | ||
|
|
260c29762a | ||
|
|
e541dcc8fa | ||
|
|
51adc5457a | ||
|
|
a2422f0533 | ||
|
|
1627c4b1b3 | ||
|
|
92d0850f1c | ||
|
|
7699439b7c | ||
|
|
4041903ecd | ||
|
|
3993432050 | ||
|
|
d74c9f0a57 | ||
|
|
1dec5b8f5d | ||
|
|
12fa27b4f6 | ||
|
|
0fde291abe | ||
|
|
7e81dba5cf | ||
|
|
bae5b0a1d3 | ||
|
|
ac16663bd6 | ||
|
|
ab5a9a66d2 | ||
|
|
eb5a13904f | ||
|
|
a41f68ac22 | ||
|
|
7238be090e | ||
|
|
c9e2490bc6 | ||
|
|
63ddb783db | ||
|
|
d8677a8d2c | ||
|
|
edf1cd497f | ||
|
|
9eb4f95967 | ||
|
|
22c0ed8a5f | ||
|
|
fcafac053f | ||
|
|
b99d4276f2 | ||
|
|
11672df19a | ||
|
|
7fafd52dce | ||
|
|
2b7c810db8 | ||
|
|
b79ccbd573 | ||
|
|
7d87f02799 | ||
|
|
5b2587b5cb | ||
|
|
b4aa823661 | ||
|
|
4dfdd1d928 | ||
|
|
298ba27ae2 | ||
|
|
7810aa802a | ||
|
|
4c5560d70a | ||
|
|
d43da8ae0d | ||
|
|
3280ae9a29 | ||
|
|
ec2664fefd | ||
|
|
4fd23fb130 | ||
|
|
9c176ef90d | ||
|
|
15b8902264 | ||
|
|
73b175a773 | ||
|
|
b9f77ba12b | ||
|
|
445d5b8c5c | ||
|
|
e089db40f9 | ||
|
|
06c05495e8 | ||
|
|
aa515823bc | ||
|
|
2ba03196d8 | ||
|
|
dbb8260f7e | ||
|
|
2bf9b968ca | ||
|
|
d005c66faf | ||
|
|
728f944f0d | ||
|
|
8ac7fb8377 | ||
|
|
e53e8219ad | ||
|
|
e0abec1580 | ||
|
|
33e09c0e19 | ||
|
|
22c6b50ee8 | ||
|
|
24c7dae130 | ||
|
|
eff309867e | ||
|
|
97ea8afaaf | ||
|
|
521da3abb3 | ||
|
|
a48382399d | ||
|
|
bf8ffc1d60 | ||
|
|
131f7ddf63 | ||
|
|
c37729a6a6 | ||
|
|
7b12ae97d4 | ||
|
|
d72b31774e | ||
|
|
6b2a3ac92c | ||
|
|
167fb919a5 | ||
|
|
32ff05e971 | ||
|
|
aececc209e | ||
|
|
e6e505a4d9 | ||
|
|
aa29cc1289 | ||
|
|
97265f5f14 | ||
|
|
b68dc0f83e | ||
|
|
b6d19adcf7 | ||
|
|
963660eb55 | ||
|
|
99ae549d37 | ||
|
|
636cd3c714 | ||
|
|
030215bf01 | ||
|
|
d26a4ea621 | ||
|
|
768a61486c | ||
|
|
aca403d2b5 | ||
|
|
a6fbdd64e0 | ||
|
|
3fa68af316 | ||
|
|
236fe21c92 | ||
|
|
84f98792d6 | ||
|
|
3bfd3d39a3 | ||
|
|
362d819a14 | ||
|
|
00d6edf6a0 | ||
|
|
64fa43843b | ||
|
|
2074ddd625 | ||
|
|
a3ba5915c8 | ||
|
|
f0a8e5a2d8 | ||
|
|
9e44629061 | ||
|
|
c5af8bffbf | ||
|
|
c30b499541 | ||
|
|
56e77f0967 | ||
|
|
d6e1035a1f | ||
|
|
de6958b2e2 | ||
|
|
0991cee6cd | ||
|
|
49957f9a98 | ||
|
|
0836d265c9 | ||
|
|
c6fedf2bf8 | ||
|
|
ac8e56f050 | ||
|
|
9c227923c6 | ||
|
|
fa3dbf203f | ||
|
|
ad6aaf4fab | ||
|
|
56bbecc316 | ||
|
|
188170fb4e | ||
|
|
758dbec7f0 | ||
|
|
774b80e99e | ||
|
|
afc84731f4 | ||
|
|
e5c5f23814 | ||
|
|
ea11923550 | ||
|
|
b60cb88c7f | ||
|
|
3d8bb2c890 | ||
|
|
7af91d425f | ||
|
|
1d63c3d610 | ||
|
|
78de0c9222 | ||
|
|
ec704aafdc | ||
|
|
770dea9325 | ||
|
|
ebc2d490d1 | ||
|
|
f307036bde | ||
|
|
2fa3ed5180 | ||
|
|
f276c3a821 | ||
|
|
291088ae4e | ||
|
|
3c2b995fba | ||
|
|
355fa94365 | ||
|
|
b2863017b1 | ||
|
|
3a98a7ae7f | ||
|
|
a9ca9107b9 | ||
|
|
d3bb572da6 | ||
|
|
b28b7c6dd9 | ||
|
|
e8180f9901 | ||
|
|
19b1201b2b | ||
|
|
e0de7ef87b | ||
|
|
a330f0b3bb | ||
|
|
bd7a49d448 | ||
|
|
be006d2887 | ||
|
|
4687ced5db | ||
|
|
bcd7bd1229 | ||
|
|
f9103d9a30 | ||
|
|
0ad68518bb | ||
|
|
459c7fba36 | ||
|
|
481f9edb15 | ||
|
|
d7421c22f8 | ||
|
|
ef8ed3681c | ||
|
|
db320b1b82 | ||
|
|
4a009f9172 | ||
|
|
b4efaebff0 | ||
|
|
d27ea4c9e5 | ||
|
|
72930485b5 | ||
|
|
6bbfa1874b | ||
|
|
f3ea00bc85 | ||
|
|
4cedd6b038 | ||
|
|
33e0c93826 | ||
|
|
9a9d4759b2 | ||
|
|
5201729545 | ||
|
|
78812ec6bf | ||
|
|
ffb466da4e | ||
|
|
10d257d643 | ||
|
|
41b32c6059 | ||
|
|
c5b467306d | ||
|
|
efb01a055a | ||
|
|
81be49c755 | ||
|
|
047bd22aae | ||
|
|
88e0527724 | ||
|
|
890f44f46e | ||
|
|
9d0b8f19d9 | ||
|
|
89bacb7e7d | ||
|
|
d5239f8709 | ||
|
|
c4ebb66d61 | ||
|
|
c342549d00 | ||
|
|
7cb1293b60 | ||
|
|
51fce92e11 | ||
|
|
f831641204 | ||
|
|
4159ab8169 | ||
|
|
6170fec251 | ||
|
|
3a6643c2fd | ||
|
|
189b3e03df | ||
|
|
d5540e18e6 | ||
|
|
bd7be035e0 | ||
|
|
94e39e2364 | ||
|
|
4189c0f9aa | ||
|
|
605a24d94e | ||
|
|
2a0510c9f4 | ||
|
|
9b8c9be0b5 | ||
|
|
5cbb7e43e0 | ||
|
|
52e04b3d02 | ||
|
|
314f62194a | ||
|
|
472c74006f | ||
|
|
9bc9c93bd4 | ||
|
|
3381e2c3e7 | ||
|
|
65a042921f | ||
|
|
ca5566d209 | ||
|
|
4175472ad6 | ||
|
|
cb9459f85c | ||
|
|
a163cc2d5a | ||
|
|
8f01f25414 | ||
|
|
dac5b248b1 | ||
|
|
d06b63e99f | ||
|
|
7824444bfc | ||
|
|
2ae4d7d708 | ||
|
|
d781da8164 | ||
|
|
2391b459b8 | ||
|
|
2ab3e2df2c | ||
|
|
247c49a40f | ||
|
|
935d1495c5 | ||
|
|
5ac16300b0 | ||
|
|
6ce42dd075 | ||
|
|
56245ddcf5 | ||
|
|
e11f676e34 | ||
|
|
e78389b554 | ||
|
|
e114f0abb8 | ||
|
|
7ed2c3e45b | ||
|
|
7f59e33b19 | ||
|
|
ae2dfe404b | ||
|
|
720af2269a | ||
|
|
5270b33bd3 | ||
|
|
631863c63b | ||
|
|
505accda38 | ||
|
|
5421c9728b | ||
|
|
b81b430987 | ||
|
|
9ac88c8551 | ||
|
|
84992d6475 |
No files matched your search
+12
-1
@@ -14,6 +14,7 @@
|
||||
# 3. Run cmake to generate project files for Windows, add more options to enable required third-party libraries.
|
||||
# See thirdparty.inc for more information.
|
||||
# sample command: cmake -G "Visual Studio 12 Win64" -DGFLAGS=1 -DSNAPPY=1 -DJEMALLOC=1 ..
|
||||
# OR for VS Studio 15 cmake -G "Visual Studio 14 Win64" -DGFLAGS=1 -DSNAPPY=1 -DJEMALLOC=1 ..
|
||||
# 4. Then build the project in debug mode (you may want to add /m[:<N>] flag to run msbuild in <N> parallel threads
|
||||
# or simply /m ot use all avail cores)
|
||||
# msbuild rocksdb.sln
|
||||
@@ -58,7 +59,8 @@ add_custom_command(OUTPUT ${BUILD_VERSION_CC}
|
||||
add_custom_target(GenerateBuildVersion DEPENDS ${BUILD_VERSION_CC})
|
||||
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /Zi /nologo /EHsc /GS /Gd /GR /GF /fp:precise /Zc:wchar_t /Zc:forScope /errorReport:queue")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /FC /d2Zi+ /W3 /WX /wd4127 /wd4244 /wd4267 /wd4800 /wd4804 /wd4996")
|
||||
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /FC /d2Zi+ /W3 /WX /wd4127 /wd4800 /wd4996 /wd4351")
|
||||
|
||||
# Used to run CI build and tests so we can run faster
|
||||
set(OPTIMIZE_DEBUG_DEFAULT 0) # Debug build is unoptimized by default use -DOPTDBG=1 to optimize
|
||||
@@ -189,6 +191,7 @@ set(SOURCES
|
||||
util/coding.cc
|
||||
util/compaction_job_stats_impl.cc
|
||||
util/comparator.cc
|
||||
util/concurrent_arena.cc
|
||||
util/crc32c.cc
|
||||
util/db_info_dumper.cc
|
||||
util/delete_scheduler_impl.cc
|
||||
@@ -218,6 +221,7 @@ set(SOURCES
|
||||
util/options_sanity_check.cc
|
||||
util/perf_context.cc
|
||||
util/perf_level.cc
|
||||
util/random.cc
|
||||
util/rate_limiter.cc
|
||||
util/skiplistrep.cc
|
||||
util/slice.cc
|
||||
@@ -241,6 +245,7 @@ set(SOURCES
|
||||
utilities/document/document_db.cc
|
||||
utilities/document/json_document.cc
|
||||
utilities/document/json_document_builder.cc
|
||||
utilities/env_mirror.cc
|
||||
utilities/flashcache/flashcache.cc
|
||||
utilities/geodb/geodb_impl.cc
|
||||
utilities/leveldb_options/leveldb_options.cc
|
||||
@@ -249,6 +254,7 @@ set(SOURCES
|
||||
utilities/merge_operators/string_append/stringappend2.cc
|
||||
utilities/merge_operators/put.cc
|
||||
utilities/merge_operators/uint64add.cc
|
||||
utilities/options/options_util.cc
|
||||
utilities/redis/redis_lists.cc
|
||||
utilities/spatialdb/spatial_db.cc
|
||||
utilities/table_properties_collectors/compact_on_deletion_collector.cc
|
||||
@@ -299,6 +305,7 @@ set(C_TESTS db/c_test.c)
|
||||
set(TESTS
|
||||
db/column_family_test.cc
|
||||
db/compact_files_test.cc
|
||||
db/compaction_iterator_test.cc
|
||||
db/compaction_job_test.cc
|
||||
db/compaction_job_stats_test.cc
|
||||
db/compaction_picker_test.cc
|
||||
@@ -322,12 +329,14 @@ set(TESTS
|
||||
db/file_indexer_test.cc
|
||||
db/filename_test.cc
|
||||
db/flush_job_test.cc
|
||||
db/inlineskiplist_test.cc
|
||||
db/listener_test.cc
|
||||
db/log_test.cc
|
||||
db/manual_compaction_test.cc
|
||||
db/memtable_list_test.cc
|
||||
db/merge_test.cc
|
||||
db/merge_helper_test.cc
|
||||
db/options_file_test.cc
|
||||
db/perf_context_test.cc
|
||||
db/plain_table_db_test.cc
|
||||
db/prefix_test.cc
|
||||
@@ -359,6 +368,7 @@ set(TESTS
|
||||
util/cache_test.cc
|
||||
util/coding_test.cc
|
||||
util/crc32c_test.cc
|
||||
util/delete_scheduler_test.cc
|
||||
util/dynamic_bloom_test.cc
|
||||
util/env_test.cc
|
||||
util/event_logger_test.cc
|
||||
@@ -380,6 +390,7 @@ set(TESTS
|
||||
utilities/geodb/geodb_test.cc
|
||||
utilities/memory/memory_test.cc
|
||||
utilities/merge_operators/string_append/stringappend_test.cc
|
||||
utilities/options/options_util_test.cc
|
||||
utilities/redis/redis_lists_test.cc
|
||||
utilities/spatialdb/spatial_db_test.cc
|
||||
utilities/table_properties_collectors/compact_on_deletion_collector_test.cc
|
||||
|
||||
+27
-3
@@ -1,16 +1,40 @@
|
||||
# Rocksdb Change Log
|
||||
|
||||
## 4.4.1 (2/18/2016)
|
||||
|
||||
## 4.4.0 (1/14/2016)
|
||||
### Public API Changes
|
||||
* Change names in CompactionPri and add a new one.
|
||||
* Deprecate options.soft_rate_limit and add options.soft_pending_compaction_bytes_limit.
|
||||
* If options.max_write_buffer_number > 3, writes will be slowed down when writing to the last write buffer to delay a full stop.
|
||||
* Introduce CompactionJobInfo::compaction_reason, this field include the reason to trigger the compaction.
|
||||
* After slow down is triggered, if estimated pending compaction bytes keep increasing, slowdown more.
|
||||
* Increase default options.delayed_write_rate to 2MB/s.
|
||||
* Added a new parameter --path to ldb tool. --path accepts the name of either MANIFEST, SST or a WAL file. Either --db or --path can be used when calling ldb.
|
||||
|
||||
## 4.3.0 (12/8/2015)
|
||||
### New Features
|
||||
* CompactionFilter has new member function called IgnoreSnapshots which allows CompactionFilter to be called even if there are snapshots later than the key.
|
||||
* RocksDB will now persist options under the same directory as the RocksDB database on successful DB::Open, CreateColumnFamily, DropColumnFamily, and SetOptions.
|
||||
* Introduce LoadLatestOptions() in rocksdb/utilities/options_util.h. This function can construct the latest DBOptions / ColumnFamilyOptions used by the specified RocksDB intance.
|
||||
* Introduce CheckOptionsCompatibility() in rocksdb/utilities/options_util.h. This function checks whether the input set of options is able to open the specified DB successfully.
|
||||
|
||||
### Public API Changes
|
||||
* When options.db_write_buffer_size triggers, only the column family with the largest column family size will be flushed, not all the column families.
|
||||
|
||||
## 4.2.0 (11/9/2015)
|
||||
### New Features
|
||||
* Introduce CreateLoggerFromOptions(), this function create a Logger for provided DBOptions.
|
||||
* Add GetAggregatedIntProperty(), which returns the sum of the GetIntProperty of all the column families.
|
||||
* Introduce CreateLoggerFromOptions(), this function create a Logger for provided DBOptions.
|
||||
* Add GetAggregatedIntProperty(), which returns the sum of the GetIntProperty of all the column families.
|
||||
* Add MemoryUtil in rocksdb/utilities/memory.h. It currently offers a way to get the memory usage by type from a list rocksdb instances.
|
||||
|
||||
### Public API Changes
|
||||
* CompactionFilter::Context includes information of Column Family ID
|
||||
* The need-compaction hint given by TablePropertiesCollector::NeedCompact() will be persistent and recoverable after DB recovery. This introduces a breaking format change. If you use this experimental feature, including NewCompactOnDeletionCollectorFactory() in the new version, you may not be able to directly downgrade the DB back to version 4.0 or lower.
|
||||
* TablePropertiesCollectorFactory::CreateTablePropertiesCollector() now takes an option Context, containing the information of column family ID for the file being written.
|
||||
* Remove DefaultCompactionFilterFactory.
|
||||
|
||||
|
||||
## 4.1.0 (10/8/2015)
|
||||
### New Features
|
||||
* Added single delete operation as a more efficient way to delete keys that have not been overwritten.
|
||||
@@ -198,7 +222,7 @@
|
||||
* Support Multiple DB paths in universal style compactions
|
||||
* Add feature of storing plain table index and bloom filter in SST file.
|
||||
* CompactRange() will never output compacted files to level 0. This used to be the case when all the compaction input files were at level 0.
|
||||
* Added iterate_upper_bound to define the extent upto which the forward iterator will return entries. This will prevent iterating over delete markers and overwritten entries for edge cases where you want to break out the iterator anyways. This may improve perfomance in case there are a large number of delete markers or overwritten entries.
|
||||
* Added iterate_upper_bound to define the extent upto which the forward iterator will return entries. This will prevent iterating over delete markers and overwritten entries for edge cases where you want to break out the iterator anyways. This may improve performance in case there are a large number of delete markers or overwritten entries.
|
||||
|
||||
### Public API changes
|
||||
* DBOptions.db_paths now is a vector of a DBPath structure which indicates both of path and target size
|
||||
|
||||
@@ -274,6 +274,7 @@ TESTS = \
|
||||
block_based_filter_block_test \
|
||||
full_filter_block_test \
|
||||
histogram_test \
|
||||
inlineskiplist_test \
|
||||
log_test \
|
||||
manual_compaction_test \
|
||||
memenv_test \
|
||||
@@ -283,6 +284,7 @@ TESTS = \
|
||||
memory_test \
|
||||
merge_test \
|
||||
merger_test \
|
||||
options_file_test \
|
||||
redis_test \
|
||||
reduce_levels_test \
|
||||
plain_table_db_test \
|
||||
@@ -310,6 +312,7 @@ TESTS = \
|
||||
rate_limiter_test \
|
||||
delete_scheduler_test \
|
||||
options_test \
|
||||
options_util_test \
|
||||
event_logger_test \
|
||||
cuckoo_table_builder_test \
|
||||
cuckoo_table_reader_test \
|
||||
@@ -343,6 +346,7 @@ TOOLS = \
|
||||
rocksdb_dump \
|
||||
rocksdb_undump
|
||||
|
||||
# TODO: add back forward_iterator_bench, after making it build in all environemnts.
|
||||
BENCHMARKS = db_bench table_reader_bench cache_bench memtablerep_bench
|
||||
|
||||
# if user didn't config LIBNAME, set the default
|
||||
@@ -604,7 +608,7 @@ asan_crash_test:
|
||||
$(MAKE) clean
|
||||
|
||||
valgrind_check: $(TESTS)
|
||||
for t in $(filter-out skiplist_test,$(TESTS)); do \
|
||||
for t in $(filter-out %skiplist_test,$(TESTS)); do \
|
||||
$(VALGRIND_VER) $(VALGRIND_OPTS) ./$$t; \
|
||||
ret_code=$$?; \
|
||||
if [ $$ret_code -ne 0 ]; then \
|
||||
@@ -801,6 +805,9 @@ json_document_test: utilities/document/json_document_test.o $(LIBOBJECTS) $(TEST
|
||||
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)
|
||||
|
||||
ttl_test: utilities/ttl/ttl_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
@@ -861,6 +868,9 @@ table_test: table/table_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
block_test: table/block_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
inlineskiplist_test: db/inlineskiplist_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
skiplist_test: db/skiplist_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
@@ -900,6 +910,9 @@ merge_test: db/merge_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
merger_test: table/merger_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
options_file_test: db/options_file_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
deletefile_test: db/deletefile_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
@@ -921,7 +934,7 @@ cuckoo_table_reader_test: table/cuckoo_table_reader_test.o $(LIBOBJECTS) $(TESTH
|
||||
cuckoo_table_db_test: db/cuckoo_table_db_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
listener_test: db/listener_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
listener_test: db/listener_test.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
thread_list_test: util/thread_list_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
@@ -933,6 +946,9 @@ compact_files_test: db/compact_files_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
options_test: util/options_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
options_util_test: utilities/options/options_util_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
event_logger_test: util/event_logger_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
|
||||
@@ -37,3 +37,12 @@ Check out our RocksDB Protobuf merge operator at: https://github.com/vladb38/roc
|
||||
|
||||
## Santanader UK/Cloudera Profession Services
|
||||
Check out their blog post: http://blog.cloudera.com/blog/2015/08/inside-santanders-near-real-time-data-ingest-architecture/
|
||||
|
||||
## Airbnb
|
||||
Airbnb is using RocksDB as a storage engine for their personalized search service. You can learn more about it here: https://www.youtube.com/watch?v=ASQ6XMtogMs
|
||||
|
||||
## Pinterest
|
||||
Pinterest's Object Retrieval System uses RocksDB for storage: https://www.youtube.com/watch?v=MtFEVEs_2Vo
|
||||
|
||||
## Smyte
|
||||
[Smyte](https://www.smyte.com/) uses RocksDB as the storage layer for their core key-value storage, high-performance counters and time-windowed HyperLogLog services.
|
||||
+6
-2
@@ -2,10 +2,14 @@ version: 1.0.{build}
|
||||
before_build:
|
||||
- md %APPVEYOR_BUILD_FOLDER%\build
|
||||
- cd %APPVEYOR_BUILD_FOLDER%\build
|
||||
- cmake -G "Visual Studio 12 Win64" ..
|
||||
- cmake -G "Visual Studio 12 Win64" -DOPTDBG=1 ..
|
||||
- cd ..
|
||||
build:
|
||||
project: build\rocksdb.sln
|
||||
parallel: true
|
||||
verbosity: minimal
|
||||
test: off
|
||||
test:
|
||||
test_script:
|
||||
- ps: build_tools\run_ci_db_test.ps1 -EnableRerun -Run db_test -Exclude DBTest.Randomized,DBTest.FileCreationRandomFailure -Concurrency 18
|
||||
- ps: build_tools\run_ci_db_test.ps1 -Run env_test -Concurrency 1
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
version: 1.0.{build}
|
||||
before_build:
|
||||
- md %APPVEYOR_BUILD_FOLDER%\build
|
||||
- cd %APPVEYOR_BUILD_FOLDER%\build
|
||||
- cmake -G "Visual Studio 12 Win64" -DOPTDBG=1 ..
|
||||
- cd ..
|
||||
build:
|
||||
project: build\rocksdb.sln
|
||||
parallel: true
|
||||
verbosity: minimal
|
||||
test:
|
||||
test_script:
|
||||
- ps: build_tools\run_ci_db_test.ps1
|
||||
notifications:
|
||||
- provider: Email
|
||||
to:
|
||||
- svmtrocksdb@microsoft.com
|
||||
subject: "Build {{status}}"
|
||||
message: "{{message}}, {{commitId}}, ..."
|
||||
on_build_success: false
|
||||
on_build_failure: true
|
||||
on_build_status_changed: true
|
||||
@@ -0,0 +1,16 @@
|
||||
GCC_BASE=/mnt/vol/engshare/fbcode/third-party2/gcc/4.9.x/centos6-native/1317bc4/
|
||||
CLANG_BASE=/mnt/vol/engshare/fbcode/third-party2/clang/3.7.1/centos6-native/9d9ecb9/
|
||||
LIBGCC_BASE=/mnt/gvfs/third-party2/libgcc/ea2fd1278810d3af2ea52218d2767e09d786dbd0/4.9.x/gcc-4.9-glibc-2.20/024dbc3
|
||||
GLIBC_BASE=/mnt/gvfs/third-party2/glibc/f5484f168c0e4d19823d41df052c5870c6e575a4/2.20/gcc-4.9-glibc-2.20/500e281
|
||||
SNAPPY_BASE=/mnt/gvfs/third-party2/snappy/cbf6f1f209e5bd160bdc5d971744e039f36b1566/1.1.3/gcc-4.9-glibc-2.20/e9936bf
|
||||
ZLIB_BASE=/mnt/gvfs/third-party2/zlib/6d39cb54708049f527e713ad19f2aadb9d3667e8/1.2.8/gcc-4.9-glibc-2.20/e9936bf
|
||||
BZIP2_BASE=/mnt/gvfs/third-party2/bzip2/2ddd45f0853bfc8bb1c27f0f447236a1a26c338a/1.0.6/gcc-4.9-glibc-2.20/e9936bf
|
||||
LZ4_BASE=/mnt/gvfs/third-party2/lz4/6858fac689e0f92e584224d91bdb0e39f6c8320d/r131/gcc-4.9-glibc-2.20/e9936bf
|
||||
ZSTD_BASE=/mnt/gvfs/third-party2/zstd/9c833012dea9ae6de1df2cbdcb7aebc57ba2cb39/0.5.0/gcc-4.9-glibc-2.20/e9936bf
|
||||
GFLAGS_BASE=/mnt/gvfs/third-party2/gflags/c7275a4ceae0aca0929e56964a31dafc53c1ee96/2.1.1/gcc-4.8.1-glibc-2.17/c3f970a
|
||||
JEMALLOC_BASE=/mnt/gvfs/third-party2/jemalloc/378c8f95cf28c833e121ca73432101e451c3e62a/4.0.3/gcc-4.9-glibc-2.20/a6c5e1e
|
||||
NUMA_BASE=/mnt/gvfs/third-party2/numa/ae54a5ed22cdabb1c6446dce4e8ffae5b4446d73/2.0.8/gcc-4.9-glibc-2.20/e9936bf
|
||||
LIBUNWIND_BASE=/mnt/gvfs/third-party2/libunwind/303048f72efc92ae079e62dfc84823401aecfd94/trunk/gcc-4.9-glibc-2.20/12266b1
|
||||
KERNEL_HEADERS_BASE=/mnt/gvfs/third-party2/kernel-headers/1a48835975c66d30e47770ec419758ed3b9ba010/3.10.62-62_fbk17_03959_ge29cc63/gcc-4.9-glibc-2.20/da39a3e
|
||||
BINUTILS_BASE=/mnt/gvfs/third-party2/binutils/a5b8152b2a15ce8a98808cf954fbccec825a97bc/2.25/centos6-native/da39a3e
|
||||
VALGRIND_BASE=/mnt/gvfs/third-party2/valgrind/af85c56f424cd5edfc2c97588299b44ecdec96bb/3.10.0/gcc-4.9-glibc-2.20/e9936bf
|
||||
@@ -0,0 +1,16 @@
|
||||
GCC_BASE=/mnt/vol/engshare/fbcode/third-party2/gcc/4.8.1/centos6-native/cc6c9dc/
|
||||
CLANG_BASE=/mnt/vol/engshare/fbcode/third-party2/clang/3.7.1/centos6-native/9d9ecb9/
|
||||
LIBGCC_BASE=/mnt/gvfs/third-party2/libgcc/ea2fd1278810d3af2ea52218d2767e09d786dbd0/4.8.1/gcc-4.8.1-glibc-2.17/8aac7fc
|
||||
GLIBC_BASE=/mnt/gvfs/third-party2/glibc/f5484f168c0e4d19823d41df052c5870c6e575a4/2.17/gcc-4.8.1-glibc-2.17/99df8fc
|
||||
SNAPPY_BASE=/mnt/gvfs/third-party2/snappy/cbf6f1f209e5bd160bdc5d971744e039f36b1566/1.1.3/gcc-4.8.1-glibc-2.17/c3f970a
|
||||
ZLIB_BASE=/mnt/gvfs/third-party2/zlib/6d39cb54708049f527e713ad19f2aadb9d3667e8/1.2.8/gcc-4.8.1-glibc-2.17/c3f970a
|
||||
BZIP2_BASE=/mnt/gvfs/third-party2/bzip2/2ddd45f0853bfc8bb1c27f0f447236a1a26c338a/1.0.6/gcc-4.8.1-glibc-2.17/c3f970a
|
||||
LZ4_BASE=/mnt/gvfs/third-party2/lz4/6858fac689e0f92e584224d91bdb0e39f6c8320d/r131/gcc-4.8.1-glibc-2.17/c3f970a
|
||||
ZSTD_BASE=/mnt/gvfs/third-party2/zstd/9c833012dea9ae6de1df2cbdcb7aebc57ba2cb39/0.5.0/gcc-4.8.1-glibc-2.17/c3f970a
|
||||
GFLAGS_BASE=/mnt/gvfs/third-party2/gflags/c7275a4ceae0aca0929e56964a31dafc53c1ee96/2.1.1/gcc-4.8.1-glibc-2.17/c3f970a
|
||||
JEMALLOC_BASE=/mnt/gvfs/third-party2/jemalloc/378c8f95cf28c833e121ca73432101e451c3e62a/4.0.3/gcc-4.8.1-glibc-2.17/8d31e51
|
||||
NUMA_BASE=/mnt/gvfs/third-party2/numa/ae54a5ed22cdabb1c6446dce4e8ffae5b4446d73/2.0.8/gcc-4.8.1-glibc-2.17/c3f970a
|
||||
LIBUNWIND_BASE=/mnt/gvfs/third-party2/libunwind/303048f72efc92ae079e62dfc84823401aecfd94/trunk/gcc-4.8.1-glibc-2.17/675d945
|
||||
KERNEL_HEADERS_BASE=/mnt/gvfs/third-party2/kernel-headers/1a48835975c66d30e47770ec419758ed3b9ba010/3.10.62-62_fbk17_03959_ge29cc63/gcc-4.8.1-glibc-2.17/da39a3e
|
||||
BINUTILS_BASE=/mnt/gvfs/third-party2/binutils/a5b8152b2a15ce8a98808cf954fbccec825a97bc/2.25/centos6-native/da39a3e
|
||||
VALGRIND_BASE=/mnt/gvfs/third-party2/valgrind/af85c56f424cd5edfc2c97588299b44ecdec96bb/3.8.1/gcc-4.8.1-glibc-2.17/c3f970a
|
||||
@@ -6,103 +6,104 @@
|
||||
# Environment variables that change the behavior of this script:
|
||||
# PIC_BUILD -- if true, it will only take pic versions of libraries from fbcode. libraries that don't have pic variant will not be included
|
||||
|
||||
|
||||
BASEDIR=`dirname $BASH_SOURCE`
|
||||
source "$BASEDIR/dependencies.sh"
|
||||
|
||||
CFLAGS=""
|
||||
|
||||
# location of libgcc
|
||||
LIBGCC_BASE="/mnt/gvfs/third-party2/libgcc/0473c80518a10d6efcbe24c5eeca3fb4ec9b519c/4.9.x/gcc-4.9-glibc-2.20/e1a7e4e"
|
||||
# libgcc
|
||||
LIBGCC_INCLUDE="$LIBGCC_BASE/include"
|
||||
LIBGCC_LIBS=" -L $LIBGCC_BASE/libs"
|
||||
|
||||
# location of glibc
|
||||
GLIBC_REV=7397bed99280af5d9543439cdb7d018af7542720
|
||||
GLIBC_INCLUDE="/mnt/gvfs/third-party2/glibc/$GLIBC_REV/2.20/gcc-4.9-glibc-2.20/99df8fc/include"
|
||||
GLIBC_LIBS=" -L /mnt/gvfs/third-party2/glibc/$GLIBC_REV/2.20/gcc-4.9-glibc-2.20/99df8fc/lib"
|
||||
|
||||
SNAPPY_INCLUDE=" -I /mnt/gvfs/third-party2/snappy/b0f269b3ca47770121aa159b99e1d8d2ab260e1f/1.0.3/gcc-4.9-glibc-2.20/c32916f/include/"
|
||||
# glibc
|
||||
GLIBC_INCLUDE="$GLIBC_BASE/include"
|
||||
GLIBC_LIBS=" -L $GLIB_BASE/lib"
|
||||
|
||||
# snappy
|
||||
SNAPPY_INCLUDE=" -I $SNAPPY_BASE/include/"
|
||||
if test -z $PIC_BUILD; then
|
||||
SNAPPY_LIBS=" /mnt/gvfs/third-party2/snappy/b0f269b3ca47770121aa159b99e1d8d2ab260e1f/1.0.3/gcc-4.9-glibc-2.20/c32916f/lib/libsnappy.a"
|
||||
SNAPPY_LIBS=" $SNAPPY_BASE/lib/libsnappy.a"
|
||||
else
|
||||
SNAPPY_LIBS=" /mnt/gvfs/third-party2/snappy/b0f269b3ca47770121aa159b99e1d8d2ab260e1f/1.0.3/gcc-4.9-glibc-2.20/c32916f/lib/libsnappy_pic.a"
|
||||
SNAPPY_LIBS=" $SNAPPY_BASE/lib/libsnappy_pic.a"
|
||||
fi
|
||||
|
||||
CFLAGS+=" -DSNAPPY"
|
||||
|
||||
if test -z $PIC_BUILD; then
|
||||
# location of zlib headers and libraries
|
||||
ZLIB_INCLUDE=" -I /mnt/gvfs/third-party2/zlib/feb983d9667f4cf5e9da07ce75abc824764b67a1/1.2.8/gcc-4.9-glibc-2.20/4230243/include/"
|
||||
ZLIB_LIBS=" /mnt/gvfs/third-party2/zlib/feb983d9667f4cf5e9da07ce75abc824764b67a1/1.2.8/gcc-4.9-glibc-2.20/4230243/lib/libz.a"
|
||||
ZLIB_INCLUDE=" -I $ZLIB_BASE/include/"
|
||||
ZLIB_LIBS=" $ZLIB_BASE/lib/libz.a"
|
||||
CFLAGS+=" -DZLIB"
|
||||
|
||||
# location of bzip headers and libraries
|
||||
BZIP_INCLUDE=" -I /mnt/gvfs/third-party2/bzip2/af004cceebb2dfd173ca29933ea5915e727aad2f/1.0.6/gcc-4.9-glibc-2.20/4230243/include/"
|
||||
BZIP_LIBS=" /mnt/gvfs/third-party2/bzip2/af004cceebb2dfd173ca29933ea5915e727aad2f/1.0.6/gcc-4.9-glibc-2.20/4230243/lib/libbz2.a"
|
||||
BZIP_INCLUDE=" -I $BZIP2_BASE/include/"
|
||||
BZIP_LIBS=" $BZIP2_BASE/lib/libbz2.a"
|
||||
CFLAGS+=" -DBZIP2"
|
||||
|
||||
LZ4_INCLUDE=" -I /mnt/gvfs/third-party2/lz4/79d2943e2dd7208a3e0b06cf95e9f85f05fe9e1b/r124/gcc-4.9-glibc-2.20/4230243/include/"
|
||||
LZ4_LIBS=" /mnt/gvfs/third-party2/lz4/79d2943e2dd7208a3e0b06cf95e9f85f05fe9e1b/r124/gcc-4.9-glibc-2.20/4230243/lib/liblz4.a"
|
||||
LZ4_INCLUDE=" -I $LZ4_BASE/include/"
|
||||
LZ4_LIBS=" $LZ4_BASE/lib/liblz4.a"
|
||||
CFLAGS+=" -DLZ4"
|
||||
|
||||
ZSTD_REV=810b81b4705def5243e998b54701f3c504e4009e
|
||||
ZSTD_INCLUDE=" -I /mnt/gvfs/third-party2/zstd/$ZSTD_REV/0.4.2/gcc-4.8.1-glibc-2.17/c3f970a/include"
|
||||
ZSTD_LIBS=" /mnt/gvfs/third-party2/zstd/$ZSTD_REV/0.4.2/gcc-4.8.1-glibc-2.17/c3f970a/lib/libzstd.a"
|
||||
ZSTD_INCLUDE=" -I $ZSTD_BASE/include/"
|
||||
ZSTD_LIBS=" $ZSTD_BASE/lib/libzstd.a"
|
||||
CFLAGS+=" -DZSTD"
|
||||
fi
|
||||
|
||||
# location of gflags headers and libraries
|
||||
GFLAGS_INCLUDE=" -I /mnt/gvfs/third-party2/gflags/0fa60e2b88de3e469db6c482d6e6dac72f5d65f9/1.6/gcc-4.9-glibc-2.20/4230243/include/"
|
||||
GFLAGS_INCLUDE=" -I $GFLAGS_BASE/include/"
|
||||
if test -z $PIC_BUILD; then
|
||||
GFLAGS_LIBS=" /mnt/gvfs/third-party2/gflags/0fa60e2b88de3e469db6c482d6e6dac72f5d65f9/1.6/gcc-4.9-glibc-2.20/4230243/lib/libgflags.a"
|
||||
GFLAGS_LIBS=" $GFLAGS_BASE/lib/libgflags.a"
|
||||
else
|
||||
GFLAGS_LIBS=" /mnt/gvfs/third-party2/gflags/0fa60e2b88de3e469db6c482d6e6dac72f5d65f9/1.6/gcc-4.9-glibc-2.20/4230243/lib/libgflags_pic.a"
|
||||
GFLAGS_LIBS=" $GFLAGS_BASE/lib/libgflags_pic.a"
|
||||
fi
|
||||
CFLAGS+=" -DGFLAGS=google"
|
||||
|
||||
# location of jemalloc
|
||||
JEMALLOC_INCLUDE=" -I /mnt/gvfs/third-party2/jemalloc/bcd68e5e419efa4e61b9486d6854564d6d75a0b5/3.6.0/gcc-4.9-glibc-2.20/2aafc78/include/"
|
||||
JEMALLOC_LIB=" /mnt/gvfs/third-party2/jemalloc/bcd68e5e419efa4e61b9486d6854564d6d75a0b5/3.6.0/gcc-4.9-glibc-2.20/2aafc78/lib/libjemalloc.a"
|
||||
JEMALLOC_INCLUDE=" -I $JEMALLOC_BASE/include/"
|
||||
JEMALLOC_LIB=" $JEMALLOC_BASE/lib/libjemalloc.a"
|
||||
|
||||
if test -z $PIC_BUILD; then
|
||||
# location of numa
|
||||
NUMA_INCLUDE=" -I /mnt/gvfs/third-party2/numa/bbefc39ecbf31d0ca184168eb613ef8d397790ee/2.0.8/gcc-4.9-glibc-2.20/4230243/include/"
|
||||
NUMA_LIB=" /mnt/gvfs/third-party2/numa/bbefc39ecbf31d0ca184168eb613ef8d397790ee/2.0.8/gcc-4.9-glibc-2.20/4230243/lib/libnuma.a"
|
||||
NUMA_INCLUDE=" -I $NUMA_BASE/include/"
|
||||
NUMA_LIB=" $NUMA_BASE/lib/libnuma.a"
|
||||
CFLAGS+=" -DNUMA"
|
||||
|
||||
# location of libunwind
|
||||
LIBUNWIND="/mnt/gvfs/third-party2/libunwind/1de3b75e0afedfe5585b231bbb340ec7a1542335/1.1/gcc-4.9-glibc-2.20/34235e8/lib/libunwind.a"
|
||||
LIBUNWIND="$LIBUNWIND_BASE/lib/libunwind.a"
|
||||
fi
|
||||
|
||||
# use Intel SSE support for checksum calculations
|
||||
export USE_SSE=1
|
||||
|
||||
BINUTILS="/mnt/gvfs/third-party2/binutils/0b6ad0c88ddd903333a48ae8bff134efac468e4a/2.25/centos6-native/da39a3e/bin"
|
||||
BINUTILS="$BINUTILS_BASE/bin"
|
||||
AR="$BINUTILS/ar"
|
||||
|
||||
DEPS_INCLUDE="$SNAPPY_INCLUDE $ZLIB_INCLUDE $BZIP_INCLUDE $LZ4_INCLUDE $ZSTD_INCLUDE $GFLAGS_INCLUDE $NUMA_INCLUDE"
|
||||
|
||||
GCC_BASE="/mnt/gvfs/third-party2/gcc/1c67a0b88f64d4d9ced0382d141c76aaa7d62fba/4.9.x/centos6-native/1317bc4"
|
||||
STDLIBS="-L $GCC_BASE/lib64"
|
||||
|
||||
CLANG_BASE="/mnt/gvfs/third-party2/clang/d81444dd214df3d2466734de45bb264a0486acc3/dev"
|
||||
CLANG_BIN="$CLANG_BASE/centos6-native/af4b1a0/bin"
|
||||
CLANG_BIN="$CLANG_BASE/bin"
|
||||
CLANG_LIB="$CLANG_BASE/lib"
|
||||
CLANG_SRC="$CLANG_BASE/../../src"
|
||||
|
||||
CLANG_ANALYZER="$CLANG_BIN/clang++"
|
||||
CLANG_SCAN_BUILD="$CLANG_BASE/src/clang/tools/scan-build/scan-build"
|
||||
CLANG_SCAN_BUILD="$CLANG_SRC/clang/tools/scan-build/scan-build"
|
||||
|
||||
if [ -z "$USE_CLANG" ]; then
|
||||
# gcc
|
||||
CC="$GCC_BASE/bin/gcc"
|
||||
CXX="$GCC_BASE/bin/g++"
|
||||
|
||||
|
||||
CFLAGS+=" -B$BINUTILS/gold"
|
||||
CFLAGS+=" -isystem $GLIBC_INCLUDE"
|
||||
CFLAGS+=" -isystem $LIBGCC_INCLUDE"
|
||||
else
|
||||
# clang
|
||||
CLANG_INCLUDE="$CLANG_BASE/gcc-4.9-glibc-2.20/74c386f/lib/clang/dev/include/"
|
||||
# clang
|
||||
CLANG_INCLUDE="$CLANG_LIB/clang/*/include"
|
||||
CC="$CLANG_BIN/clang"
|
||||
CXX="$CLANG_BIN/clang++"
|
||||
|
||||
KERNEL_HEADERS_INCLUDE="/mnt/gvfs/third-party2/kernel-headers/ffd14f660a43c4b92717986b1bba66722ef089d0/3.2.18_70_fbk11_00129_gc8882d0/gcc-4.9-glibc-2.20/da39a3e/include"
|
||||
KERNEL_HEADERS_INCLUDE="$KERNEL_HEADERS_BASE/include"
|
||||
|
||||
CFLAGS+=" -B$BINUTILS/gold -nostdinc -nostdlib"
|
||||
CFLAGS+=" -isystem $LIBGCC_BASE/include/c++/4.9.x "
|
||||
@@ -128,6 +129,6 @@ PLATFORM_LDFLAGS="$LIBGCC_LIBS $GLIBC_LIBS $STDLIBS -lgcc -lstdc++"
|
||||
|
||||
EXEC_LDFLAGS_SHARED="$SNAPPY_LIBS $ZLIB_LIBS $BZIP_LIBS $LZ4_LIBS $ZSTD_LIBS $GFLAGS_LIBS"
|
||||
|
||||
VALGRIND_VER="/mnt/gvfs/third-party2/valgrind/6c45ef049cbf11c2df593addb712cd891049e737/3.10.0/gcc-4.9-glibc-2.20/4230243/bin/"
|
||||
VALGRIND_VER="$VALGRIND_BASE/bin/"
|
||||
|
||||
export CC CXX AR CFLAGS CXXFLAGS EXEC_LDFLAGS EXEC_LDFLAGS_SHARED VALGRIND_VER JEMALLOC_LIB JEMALLOC_INCLUDE CLANG_ANALYZER CLANG_SCAN_BUILD
|
||||
@@ -4,80 +4,77 @@
|
||||
# fbcode settings. It uses the latest g++ compiler and also
|
||||
# uses jemalloc
|
||||
|
||||
BASEDIR=`dirname $BASH_SOURCE`
|
||||
source "$BASEDIR/dependencies_4.8.1.sh"
|
||||
|
||||
# location of libgcc
|
||||
LIBGCC_BASE="/mnt/gvfs/third-party2/libgcc/7712e757d7355cb51292454ee0b7b46a467fdfed/4.8.1/gcc-4.8.1-glibc-2.17/8aac7fc"
|
||||
LIBGCC_INCLUDE="$LIBGCC_BASE/include"
|
||||
LIBGCC_LIBS=" -L $LIBGCC_BASE/libs"
|
||||
|
||||
# location of glibc
|
||||
GLIBC_REV=6e40560b4e0b6d690fd1cf8c7a43ad7452b04cfa
|
||||
GLIBC_INCLUDE="/mnt/gvfs/third-party2/glibc/$GLIBC_REV/2.17/gcc-4.8.1-glibc-2.17/99df8fc/include"
|
||||
GLIBC_LIBS=" -L /mnt/gvfs/third-party2/glibc/$GLIBC_REV/2.17/gcc-4.8.1-glibc-2.17/99df8fc/lib"
|
||||
GLIBC_INCLUDE="$GLIBC_BASE/include"
|
||||
GLIBC_LIBS=" -L $GLIBC_BASE/lib"
|
||||
|
||||
# location of snappy headers and libraries
|
||||
SNAPPY_INCLUDE=" -I /mnt/gvfs/third-party2/snappy/aef17f6c0b44b4fe408bd06f67c93701ab0a6ceb/1.0.3/gcc-4.8.1-glibc-2.17/43d84e2/include"
|
||||
SNAPPY_LIBS=" /mnt/gvfs/third-party2/snappy/aef17f6c0b44b4fe408bd06f67c93701ab0a6ceb/1.0.3/gcc-4.8.1-glibc-2.17/43d84e2/lib/libsnappy.a"
|
||||
SNAPPY_INCLUDE=" -I $SNAPPY_BASE/include"
|
||||
SNAPPY_LIBS=" $SNAPPY_BASE/lib/libsnappy.a"
|
||||
|
||||
# location of zlib headers and libraries
|
||||
ZLIB_INCLUDE=" -I /mnt/gvfs/third-party2/zlib/25c6216928b4d77b59ddeca0990ff6fe9ac16b81/1.2.5/gcc-4.8.1-glibc-2.17/c3f970a/include"
|
||||
ZLIB_LIBS=" /mnt/gvfs/third-party2/zlib/25c6216928b4d77b59ddeca0990ff6fe9ac16b81/1.2.5/gcc-4.8.1-glibc-2.17/c3f970a/lib/libz.a"
|
||||
ZLIB_INCLUDE=" -I $ZLIB_BASE/include"
|
||||
ZLIB_LIBS=" $ZLIB_BASE/lib/libz.a"
|
||||
|
||||
# location of bzip headers and libraries
|
||||
BZIP_INCLUDE=" -I /mnt/gvfs/third-party2/bzip2/c9ef7629c2aa0024f7a416e87602f06eb88f5eac/1.0.6/gcc-4.8.1-glibc-2.17/c3f970a/include/"
|
||||
BZIP_LIBS=" /mnt/gvfs/third-party2/bzip2/c9ef7629c2aa0024f7a416e87602f06eb88f5eac/1.0.6/gcc-4.8.1-glibc-2.17/c3f970a/lib/libbz2.a"
|
||||
BZIP2_INCLUDE=" -I $BZIP2_BASE/include/"
|
||||
BZIP2_LIBS=" $BZIP2_BASE/lib/libbz2.a"
|
||||
|
||||
LZ4_REV=065ec7e38fe83329031f6668c43bef83eff5808b
|
||||
LZ4_INCLUDE=" -I /mnt/gvfs/third-party2/lz4/$LZ4_REV/r108/gcc-4.8.1-glibc-2.17/c3f970a/include"
|
||||
LZ4_LIBS=" /mnt/gvfs/third-party2/lz4/$LZ4_REV/r108/gcc-4.8.1-glibc-2.17/c3f970a/lib/liblz4.a"
|
||||
LZ4_INCLUDE=" -I $LZ4_BASE/include"
|
||||
LZ4_LIBS=" $LZ4_BASE/lib/liblz4.a"
|
||||
|
||||
ZSTD_REV=810b81b4705def5243e998b54701f3c504e4009e
|
||||
ZSTD_INCLUDE=" -I /mnt/gvfs/third-party2/zstd/$ZSTD_REV/0.4.2/gcc-4.8.1-glibc-2.17/c3f970a/include"
|
||||
ZSTD_LIBS=" /mnt/gvfs/third-party2/zstd/$ZSTD_REV/0.4.2/gcc-4.8.1-glibc-2.17/c3f970a/lib/libzstd.a"
|
||||
ZSTD_INCLUDE=" -I $ZSTD_BASE/include"
|
||||
ZSTD_LIBS=" $ZSTD_BASE/lib/libzstd.a"
|
||||
|
||||
# location of gflags headers and libraries
|
||||
GFLAGS_INCLUDE=" -I /mnt/gvfs/third-party2/gflags/1ad047a6e6f6673991918ecadc670868205a243a/1.6/gcc-4.8.1-glibc-2.17/c3f970a/include/"
|
||||
GFLAGS_LIBS=" /mnt/gvfs/third-party2/gflags/1ad047a6e6f6673991918ecadc670868205a243a/1.6/gcc-4.8.1-glibc-2.17/c3f970a/lib/libgflags.a"
|
||||
GFLAGS_INCLUDE=" -I $GFLAGS_BASE/include/"
|
||||
GFLAGS_LIBS=" $GFLAGS_BASE/lib/libgflags.a"
|
||||
|
||||
# location of jemalloc
|
||||
JEMALLOC_INCLUDE=" -I /mnt/gvfs/third-party2/jemalloc/3691c776ac26dd8781e84f8888b6a0fbdbc0a9ed/dev/gcc-4.8.1-glibc-2.17/4d53c6f/include"
|
||||
JEMALLOC_LIB="/mnt/gvfs/third-party2/jemalloc/3691c776ac26dd8781e84f8888b6a0fbdbc0a9ed/dev/gcc-4.8.1-glibc-2.17/4d53c6f/lib/libjemalloc.a"
|
||||
JEMALLOC_INCLUDE=" -I $JEMALLOC_BASE/include"
|
||||
JEMALLOC_LIB="$JEMALLOC_BASE/lib/libjemalloc.a"
|
||||
|
||||
# location of numa
|
||||
NUMA_REV=829d10dac0230f99cd7e1778869d2adf3da24b65
|
||||
NUMA_INCLUDE=" -I /mnt/gvfs/third-party2/numa/$NUMA_REV/2.0.8/gcc-4.8.1-glibc-2.17/c3f970a/include/"
|
||||
NUMA_LIB=" /mnt/gvfs/third-party2/numa/$NUMA_REV/2.0.8/gcc-4.8.1-glibc-2.17/c3f970a/lib/libnuma.a"
|
||||
NUMA_INCLUDE=" -I $NUMA_BASE/include/"
|
||||
NUMA_LIB=" $NUMA_BASE/lib/libnuma.a"
|
||||
|
||||
# location of libunwind
|
||||
LIBUNWIND_REV=2c060e64064559905d46fd194000d61592087bdc
|
||||
LIBUNWIND="/mnt/gvfs/third-party2/libunwind/$LIBUNWIND_REV/1.1/gcc-4.8.1-glibc-2.17/675d945/lib/libunwind.a"
|
||||
LIBUNWIND="$LIBUNWIND_BASE/lib/libunwind.a"
|
||||
|
||||
# use Intel SSE support for checksum calculations
|
||||
export USE_SSE=1
|
||||
|
||||
BINUTILS="/mnt/gvfs/third-party2/binutils/2aff2e7b474cd3e6ab23495ad1224b7d214b9f8e/2.21.1/centos6-native/da39a3e/bin"
|
||||
BINUTILS="$BINUTILS_BASE/bin"
|
||||
AR="$BINUTILS/ar"
|
||||
|
||||
DEPS_INCLUDE="$SNAPPY_INCLUDE $ZLIB_INCLUDE $BZIP_INCLUDE $LZ4_INCLUDE $ZSTD_INCLUDE $GFLAGS_INCLUDE $NUMA_INCLUDE"
|
||||
DEPS_INCLUDE="$SNAPPY_INCLUDE $ZLIB_INCLUDE $BZIP2_INCLUDE $LZ4_INCLUDE $ZSTD_INCLUDE $GFLAGS_INCLUDE $NUMA_INCLUDE"
|
||||
|
||||
GCC_BASE="/mnt/gvfs/third-party2/gcc/1ec615e23800f0815d474478ba476a0adc3fe788/4.8.1/centos6-native/cc6c9dc"
|
||||
STDLIBS="-L $GCC_BASE/lib64"
|
||||
|
||||
if [ -z "$USE_CLANG" ]; then
|
||||
# gcc
|
||||
CC="$GCC_BASE/bin/gcc"
|
||||
CXX="$GCC_BASE/bin/g++"
|
||||
|
||||
|
||||
CFLAGS="-B$BINUTILS/gold -m64 -mtune=generic"
|
||||
CFLAGS+=" -isystem $GLIBC_INCLUDE"
|
||||
CFLAGS+=" -isystem $LIBGCC_INCLUDE"
|
||||
else
|
||||
# clang
|
||||
CLANG_BASE="/mnt/gvfs/third-party2/clang/9ab68376f938992c4eb5946ca68f90c3185cffc8/3.4"
|
||||
CLANG_INCLUDE="$CLANG_BASE/gcc-4.8.1-glibc-2.17/fb0f730/lib/clang/3.4/include"
|
||||
CC="$CLANG_BASE/centos6-native/9cefd8a/bin/clang"
|
||||
CXX="$CLANG_BASE/centos6-native/9cefd8a/bin/clang++"
|
||||
# clang
|
||||
CLANG_BIN="$CLANG_BASE/bin"
|
||||
CLANG_LIB="$CLANG_BASE/lib"
|
||||
CLANG_INCLUDE="$CLANG_LIB/clang/*/include"
|
||||
CC="$CLANG_BIN/clang"
|
||||
CXX="$CLANG_BIN/clang++"
|
||||
|
||||
KERNEL_HEADERS_INCLUDE="/mnt/gvfs/third-party2/kernel-headers/a683ed7135276731065a9d76d3016c9731f4e2f9/3.2.18_70_fbk11_00129_gc8882d0/gcc-4.8.1-glibc-2.17/da39a3e/include/"
|
||||
KERNEL_HEADERS_INCLUDE="$KERNEL_HEADERS_BASE/include/"
|
||||
|
||||
CFLAGS="-B$BINUTILS/gold -nostdinc -nostdlib"
|
||||
CFLAGS+=" -isystem $LIBGCC_BASE/include/c++/4.8.1 "
|
||||
@@ -95,16 +92,15 @@ CFLAGS+=" -DROCKSDB_PLATFORM_POSIX -DROCKSDB_LIB_IO_POSIX -DROCKSDB_FALLOCATE_PR
|
||||
CFLAGS+=" -DSNAPPY -DGFLAGS=google -DZLIB -DBZIP2 -DLZ4 -DZSTD -DNUMA"
|
||||
CXXFLAGS+=" $CFLAGS"
|
||||
|
||||
EXEC_LDFLAGS=" $SNAPPY_LIBS $ZLIB_LIBS $BZIP_LIBS $LZ4_LIBS $ZSTD_LIBS $GFLAGS_LIBS $NUMA_LIB"
|
||||
EXEC_LDFLAGS=" $SNAPPY_LIBS $ZLIB_LIBS $BZIP2_LIBS $LZ4_LIBS $ZSTD_LIBS $GFLAGS_LIBS $NUMA_LIB"
|
||||
EXEC_LDFLAGS+=" -Wl,--dynamic-linker,/usr/local/fbcode/gcc-4.8.1-glibc-2.17/lib/ld.so"
|
||||
EXEC_LDFLAGS+=" $LIBUNWIND"
|
||||
EXEC_LDFLAGS+=" -Wl,-rpath=/usr/local/fbcode/gcc-4.8.1-glibc-2.17/lib"
|
||||
|
||||
PLATFORM_LDFLAGS="$LIBGCC_LIBS $GLIBC_LIBS $STDLIBS -lgcc -lstdc++"
|
||||
|
||||
EXEC_LDFLAGS_SHARED="$SNAPPY_LIBS $ZLIB_LIBS $BZIP_LIBS $LZ4_LIBS $ZSTD_LIBS $GFLAGS_LIBS"
|
||||
EXEC_LDFLAGS_SHARED="$SNAPPY_LIBS $ZLIB_LIBS $BZIP2_LIBS $LZ4_LIBS $ZSTD_LIBS $GFLAGS_LIBS"
|
||||
|
||||
VALGRIND_REV=b2a9f85e4b70cd03abc85a7f3027fbc4cef35bd0
|
||||
VALGRIND_VER="/mnt/gvfs/third-party2/valgrind/$VALGRIND_REV/3.8.1/gcc-4.8.1-glibc-2.17/c3f970a/bin/"
|
||||
VALGRIND_VER="$VALGRIND_BASE/bin/"
|
||||
|
||||
export CC CXX AR CFLAGS CXXFLAGS EXEC_LDFLAGS EXEC_LDFLAGS_SHARED VALGRIND_VER JEMALLOC_LIB JEMALLOC_INCLUDE
|
||||
@@ -243,7 +243,7 @@ make release
|
||||
--bloom_bits=10 \
|
||||
--num=$((NUM / 4)) \
|
||||
--reads=$((NUM / 4)) \
|
||||
--writes_per_second=1000 \
|
||||
--benchmark_write_rate_limit=$(( 110 * 1024 )) \
|
||||
--write_buffer_size=100000000 \
|
||||
--cache_size=6442450944 \
|
||||
--cache_numshardbits=6 \
|
||||
@@ -329,7 +329,7 @@ common_in_mem_args="--db=/dev/shm/rocksdb \
|
||||
--use_existing_db=1 \
|
||||
--duration=600 \
|
||||
--threads=32 \
|
||||
--writes_per_second=81920 > ${STAT_FILE}.readwhilewriting_in_ram
|
||||
--benchmark_write_rate_limit=9502720 > ${STAT_FILE}.readwhilewriting_in_ram
|
||||
|
||||
# Seekrandomwhilewriting
|
||||
./db_bench \
|
||||
@@ -342,7 +342,7 @@ common_in_mem_args="--db=/dev/shm/rocksdb \
|
||||
--use_tailing_iterator=1 \
|
||||
--duration=600 \
|
||||
--threads=32 \
|
||||
--writes_per_second=81920 > ${STAT_FILE}.seekwhilewriting_in_ram
|
||||
--benchmark_write_rate_limit=9502720 > ${STAT_FILE}.seekwhilewriting_in_ram
|
||||
|
||||
# measure fillseq with bunch of column families
|
||||
./db_bench \
|
||||
|
||||
@@ -68,7 +68,7 @@ CLANG="USE_CLANG=1"
|
||||
LITE="OPT=-DROCKSDB_LITE"
|
||||
TSAN="COMPILE_WITH_TSAN=1"
|
||||
DISABLE_JEMALLOC="DISABLE_JEMALLOC=1"
|
||||
PARSER="'parser':'egrep \'Failure|^#|Abort\''"
|
||||
PARSER="'parser':'egrep \'Failure|^#|Abort|Expected|Actual|GoogleTestFailure|^==\''"
|
||||
|
||||
ARTIFACTS=" 'artifacts': [
|
||||
{
|
||||
|
||||
+142
-97
@@ -1,17 +1,19 @@
|
||||
# This script enables you running RocksDB tests by running
|
||||
# All the tests in paralell and utilizing all the cores
|
||||
# All the tests in parallel and utilizing all the cores
|
||||
# For db_test the script first lists and parses the tests
|
||||
# and then fires them up in parallel using async PS Job functionality
|
||||
# Run the script from the enlistment
|
||||
Param(
|
||||
[switch]$EnableJE = $false, # Use je executable
|
||||
[switch]$EnableRerun = $false, # Rerun failed tests sequentially at the end
|
||||
[string]$WorkFolder = "", # Direct tests to use that folder
|
||||
[int]$Limit = -1, # -1 means run all otherwise limit for testing purposes
|
||||
[string]$Exclude = "", # Expect a comma separated list, no spaces
|
||||
[string]$Run = "db_test", # Run db_test|tests
|
||||
[string]$Run = "db_test", # Run db_test|tests|testname1,testname2...
|
||||
# Number of async tasks that would run concurrently. Recommend a number below 64.
|
||||
# However, CPU utlization really depends on the storage media. Recommend ram based disk.
|
||||
[int]$Concurrency = 62
|
||||
# a value of 1 will run everything serially
|
||||
[int]$Concurrency = 16
|
||||
)
|
||||
|
||||
# Folders and commands must be fullpath to run assuming
|
||||
@@ -52,7 +54,7 @@ if($EnableJE) {
|
||||
}
|
||||
|
||||
Write-Output "Root: $RootFolder, WorkFolder: $WorkFolder"
|
||||
Write-Output "Binaries: $BinariesFolder exe: $db_test"
|
||||
Write-Output "Binaries: $BinariesFolder db_test: $db_test"
|
||||
|
||||
#Exclusions that we do not want to run
|
||||
$ExcludeTests = New-Object System.Collections.Generic.HashSet[string]
|
||||
@@ -60,7 +62,7 @@ $ExcludeTests = New-Object System.Collections.Generic.HashSet[string]
|
||||
|
||||
if($Exclude -ne "") {
|
||||
Write-Host "Exclude: $Exclude"
|
||||
$l = $Exclude -split ','
|
||||
$l = $Exclude -split ' '
|
||||
ForEach($t in $l) { $ExcludeTests.Add($t) | Out-Null }
|
||||
}
|
||||
|
||||
@@ -112,48 +114,69 @@ function Normalize-DbTests($HashTable) {
|
||||
|
||||
$test_log = $test -replace '[\./]','_'
|
||||
$test_log += ".log"
|
||||
$log_path = -join ($LogFolder, $test_log)
|
||||
|
||||
# Add to a hashtable
|
||||
$HashTable.Add($test, $test_log);
|
||||
$HashTable.Add($test, $log_path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# The function removes trailing .exe siffix if any,
|
||||
# creates a name for the log file
|
||||
function MakeAndAdd([string]$token, $HashTable) {
|
||||
$test_name = $token -replace '.exe$', ''
|
||||
$log_name = -join ($test_name, ".log")
|
||||
$log_path = -join ($LogFolder, $log_name)
|
||||
if(!$ExcludeTests.Contains($test_name)) {
|
||||
$HashTable.Add($test_name, $log_path)
|
||||
} else {
|
||||
Write-Warning "Test $test_name is excluded"
|
||||
}
|
||||
}
|
||||
|
||||
# The function scans build\Debug folder to discover
|
||||
# Test executables. It then populates a table with
|
||||
# Test executable name -> Log file
|
||||
function Discover-TestBinaries($HashTable) {
|
||||
function Discover-TestBinaries([string]$Pattern, $HashTable) {
|
||||
|
||||
$Exclusions = @("db_test*", "db_sanity_test*")
|
||||
if($EnableJE) {
|
||||
$p = -join ($BinariesFolder, "*_test_je.exe")
|
||||
} else {
|
||||
$p = -join ($BinariesFolder, "*_test.exe")
|
||||
}
|
||||
|
||||
$p = -join ($BinariesFolder, $pattern)
|
||||
|
||||
Write-Host "Path: $p"
|
||||
|
||||
dir -Path $p -Exclude $Exclusions | ForEach-Object {
|
||||
$t = ($_.Name) -replace '.exe$', ''
|
||||
if($ExcludeTests.Contains($t)) {
|
||||
continue
|
||||
}
|
||||
$test_log = -join ($t, ".log")
|
||||
$HashTable.Add($t, $test_log)
|
||||
MakeAndAdd -token ($_.Name) -HashTable $HashTable
|
||||
}
|
||||
}
|
||||
|
||||
$TestToLog = [ordered]@{}
|
||||
$TestsToRun = [ordered]@{}
|
||||
|
||||
if($Run -ceq "db_test") {
|
||||
Normalize-DbTests -HashTable $TestToLog
|
||||
Normalize-DbTests -HashTable $TestsToRun
|
||||
} elseif($Run -ceq "tests") {
|
||||
Discover-TestBinaries -HashTable $TestToLog
|
||||
if($EnableJE) {
|
||||
$pattern = "*_test_je.exe"
|
||||
} else {
|
||||
$pattern = "*_test.exe"
|
||||
}
|
||||
Discover-TestBinaries -Pattern $pattern -HashTable $TestsToRun
|
||||
} else {
|
||||
Write-Warning "Invalid -Run option value"
|
||||
exit 2
|
||||
|
||||
$test_list = $Run -split ' '
|
||||
|
||||
ForEach($t in $test_list) {
|
||||
MakeAndAdd -token $t -HashTable $TestsToRun
|
||||
}
|
||||
}
|
||||
|
||||
$NumTestsToStart = $TestsToRun.Count
|
||||
if($Limit -ge 0 -and $NumTestsToStart -gt $Limit) {
|
||||
$NumTestsToStart = $Limit
|
||||
}
|
||||
|
||||
Write-Host "Attempting to start: " ($TestToLog.Count) " tests"
|
||||
Write-Host "Attempting to start: $NumTestsToStart tests"
|
||||
|
||||
# Invoke a test with a filter and redirect all output
|
||||
$InvokeTestCase = {
|
||||
@@ -167,101 +190,123 @@ $InvokeTestAsync = {
|
||||
&$exe > $log 2>&1
|
||||
}
|
||||
|
||||
$jobs = @()
|
||||
$JobToLog = @{}
|
||||
# Hash that contains tests to rerun if any failed
|
||||
# Those tests will be rerun sequentially
|
||||
$Rerun = [ordered]@{}
|
||||
# Test limiting factor here
|
||||
$count = 0
|
||||
|
||||
# Overall status
|
||||
[bool]$success = $true;
|
||||
|
||||
# Wait for all to finish and get the results
|
||||
while(($JobToLog.Count -gt 0) -or
|
||||
($TestToLog.Count -gt 0)) {
|
||||
|
||||
# Make sure we have maximum concurrent jobs running if anything
|
||||
# and the $Limit either not set or allows to proceed
|
||||
while(($JobToLog.Count -lt $Concurrency) -and
|
||||
(($TestToLog.Count -gt 0) -and
|
||||
(($Limit -lt 0) -or ($count -lt $Limit)))) {
|
||||
|
||||
|
||||
# We only need the first key
|
||||
foreach($key in $TestToLog.keys) {
|
||||
$k = $key
|
||||
break
|
||||
}
|
||||
|
||||
Write-Host "Starting $k"
|
||||
$log_path = -join ($LogFolder, ($TestToLog.$k))
|
||||
|
||||
if($Run -ceq "db_test") {
|
||||
$job = Start-Job -Name $k -ScriptBlock $InvokeTestCase -ArgumentList @($db_test,$k,$log_path)
|
||||
} else {
|
||||
[string]$Exe = -Join ($BinariesFolder, $k)
|
||||
$job = Start-Job -Name $k -ScriptBlock $InvokeTestAsync -ArgumentList @($exe,$log_path)
|
||||
}
|
||||
|
||||
$JobToLog.Add($job, $log_path)
|
||||
$TestToLog.Remove($k)
|
||||
|
||||
++$count
|
||||
}
|
||||
|
||||
if($JobToLog.Count -lt 1) {
|
||||
break
|
||||
}
|
||||
|
||||
function RunJobs($TestToLog, [int]$ConcurrencyVal, [bool]$AddForRerun)
|
||||
{
|
||||
# Array to wait for any of the running jobs
|
||||
$jobs = @()
|
||||
foreach($k in $JobToLog.Keys) { $jobs += $k }
|
||||
# Hash JobToLog
|
||||
$JobToLog = @{}
|
||||
|
||||
$completed = Wait-Job -Job $jobs -Any
|
||||
$log = $JobToLog[$completed]
|
||||
$JobToLog.Remove($completed)
|
||||
# Wait for all to finish and get the results
|
||||
while(($JobToLog.Count -gt 0) -or
|
||||
($TestToLog.Count -gt 0)) {
|
||||
|
||||
$message = -join @($completed.Name, " State: ", ($completed.State))
|
||||
# Make sure we have maximum concurrent jobs running if anything
|
||||
# and the $Limit either not set or allows to proceed
|
||||
while(($JobToLog.Count -lt $ConcurrencyVal) -and
|
||||
(($TestToLog.Count -gt 0) -and
|
||||
(($Limit -lt 0) -or ($count -lt $Limit)))) {
|
||||
|
||||
$log_content = @(Get-Content $log)
|
||||
|
||||
if($completed.State -ne "Completed") {
|
||||
$success = $false
|
||||
Write-Warning $message
|
||||
$log_content | Write-Warning
|
||||
} else {
|
||||
# Scan the log. If we find PASSED and no occurence of FAILED
|
||||
# then it is a success
|
||||
[bool]$pass_found = $false
|
||||
ForEach($l in $log_content) {
|
||||
|
||||
if(($l -match "^\[\s+FAILED") -or
|
||||
($l -match "Assertion failed:")) {
|
||||
$pass_found = $false
|
||||
# We only need the first key
|
||||
foreach($key in $TestToLog.keys) {
|
||||
$k = $key
|
||||
break
|
||||
}
|
||||
|
||||
if(($l -match "^\[\s+PASSED") -or
|
||||
($l -match " : PASSED$") -or
|
||||
($l -match "^PASSED") -or
|
||||
($l -match "Passed all tests!") ) {
|
||||
$pass_found = $true
|
||||
Write-Host "Starting $k"
|
||||
$log_path = ($TestToLog.$k)
|
||||
|
||||
if($Run -ceq "db_test") {
|
||||
$job = Start-Job -Name $k -ScriptBlock $InvokeTestCase -ArgumentList @($db_test,$k,$log_path)
|
||||
} else {
|
||||
[string]$Exe = -Join ($BinariesFolder, $k)
|
||||
$job = Start-Job -Name $k -ScriptBlock $InvokeTestAsync -ArgumentList @($exe,$log_path)
|
||||
}
|
||||
|
||||
$JobToLog.Add($job, $log_path)
|
||||
$TestToLog.Remove($k)
|
||||
|
||||
++$count
|
||||
}
|
||||
|
||||
if(!$pass_found) {
|
||||
$success = $false;
|
||||
if($JobToLog.Count -lt 1) {
|
||||
break
|
||||
}
|
||||
|
||||
$jobs = @()
|
||||
foreach($k in $JobToLog.Keys) { $jobs += $k }
|
||||
|
||||
$completed = Wait-Job -Job $jobs -Any
|
||||
$log = $JobToLog[$completed]
|
||||
$JobToLog.Remove($completed)
|
||||
|
||||
$message = -join @($completed.Name, " State: ", ($completed.State))
|
||||
|
||||
$log_content = @(Get-Content $log)
|
||||
|
||||
if($completed.State -ne "Completed") {
|
||||
$success = $false
|
||||
Write-Warning $message
|
||||
$log_content | Write-Warning
|
||||
} else {
|
||||
Write-Host $message
|
||||
}
|
||||
}
|
||||
# Scan the log. If we find PASSED and no occurrence of FAILED
|
||||
# then it is a success
|
||||
[bool]$pass_found = $false
|
||||
ForEach($l in $log_content) {
|
||||
|
||||
# Remove cached job info from the system
|
||||
# Should be no output
|
||||
Receive-Job -Job $completed | Out-Null
|
||||
if(($l -match "^\[\s+FAILED") -or
|
||||
($l -match "Assertion failed:")) {
|
||||
$pass_found = $false
|
||||
break
|
||||
}
|
||||
|
||||
if(($l -match "^\[\s+PASSED") -or
|
||||
($l -match " : PASSED$") -or
|
||||
($l -match "^PASS$") -or # Special c_test case
|
||||
($l -match "Passed all tests!") ) {
|
||||
$pass_found = $true
|
||||
}
|
||||
}
|
||||
|
||||
if(!$pass_found) {
|
||||
$success = $false;
|
||||
Write-Warning $message
|
||||
$log_content | Write-Warning
|
||||
if($AddForRerun) {
|
||||
$Rerun.Add($completed.Name, $log)
|
||||
}
|
||||
} else {
|
||||
Write-Host $message
|
||||
}
|
||||
}
|
||||
|
||||
# Remove cached job info from the system
|
||||
# Should be no output
|
||||
Receive-Job -Job $completed | Out-Null
|
||||
}
|
||||
}
|
||||
|
||||
RunJobs -TestToLog $TestsToRun -ConcurrencyVal $Concurrency -AddForRerun $EnableRerun
|
||||
|
||||
if($Rerun.Count -gt 0) {
|
||||
Write-Host "Rerunning " ($Rerun.Count) " tests sequentially"
|
||||
$success = $true
|
||||
$count = 0
|
||||
RunJobs -TestToLog $Rerun -ConcurrencyVal 1 -AddForRerun $false
|
||||
}
|
||||
|
||||
Get-Date
|
||||
|
||||
|
||||
if(!$success) {
|
||||
# This does not succeed killing off jobs quick
|
||||
# So we simply exit
|
||||
@@ -270,4 +315,4 @@ if(!$success) {
|
||||
exit 12345
|
||||
}
|
||||
|
||||
|
||||
|
||||
Executable
+127
@@ -0,0 +1,127 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# Update dependencies.sh file with the latest avaliable versions
|
||||
|
||||
BASEDIR=$(dirname $0)
|
||||
OUTPUT=""
|
||||
|
||||
function log_variable()
|
||||
{
|
||||
echo "$1=${!1}" >> "$OUTPUT"
|
||||
}
|
||||
|
||||
|
||||
TP2_LATEST="/mnt/vol/engshare/fbcode/third-party2"
|
||||
## $1 => lib name
|
||||
## $2 => lib version (if not provided, will try to pick latest)
|
||||
## $3 => platform (if not provided, will try to pick latest gcc)
|
||||
##
|
||||
## get_lib_base will set a variable named ${LIB_NAME}_BASE to the lib location
|
||||
function get_lib_base()
|
||||
{
|
||||
local lib_name=$1
|
||||
local lib_version=$2
|
||||
local lib_platform=$3
|
||||
|
||||
local result="$TP2_LATEST/$lib_name/"
|
||||
|
||||
# Lib Version
|
||||
if [ -z "$lib_version" ] || [ "$lib_version" = "LATEST" ]; then
|
||||
# version is not provided, use latest
|
||||
result=`ls -dr1v $result/*/ | head -n1`
|
||||
else
|
||||
result="$result/$lib_version/"
|
||||
fi
|
||||
|
||||
# Lib Platform
|
||||
if [ -z "$lib_platform" ]; then
|
||||
# platform is not provided, use latest gcc
|
||||
result=`ls -dr1v $result/gcc-*[^fb]/ | head -n1`
|
||||
else
|
||||
result="$result/$lib_platform/"
|
||||
fi
|
||||
|
||||
result=`ls -1d $result/*/ | head -n1`
|
||||
|
||||
# lib_name => LIB_NAME_BASE
|
||||
local __res_var=${lib_name^^}"_BASE"
|
||||
__res_var=`echo $__res_var | tr - _`
|
||||
# LIB_NAME_BASE=$result
|
||||
eval $__res_var=`readlink -f $result`
|
||||
|
||||
log_variable $__res_var
|
||||
}
|
||||
|
||||
###########################################################
|
||||
# 4.9.x dependencies #
|
||||
###########################################################
|
||||
|
||||
OUTPUT="$BASEDIR/dependencies.sh"
|
||||
|
||||
rm -f "$OUTPUT"
|
||||
touch "$OUTPUT"
|
||||
|
||||
echo "Writing dependencies to $OUTPUT"
|
||||
|
||||
# Compilers locations
|
||||
GCC_BASE=`ls -d1 $TP2_LATEST/gcc/4.9.x/centos6-native/*/ | head -n1`
|
||||
CLANG_BASE=`ls -d1 $TP2_LATEST/clang/3.7.1/centos6-native/*/ | head -n1`
|
||||
|
||||
log_variable GCC_BASE
|
||||
log_variable CLANG_BASE
|
||||
|
||||
# Libraries locations
|
||||
get_lib_base libgcc 4.9.x
|
||||
get_lib_base glibc 2.20
|
||||
get_lib_base snappy LATEST
|
||||
get_lib_base zlib LATEST
|
||||
get_lib_base bzip2 LATEST
|
||||
get_lib_base lz4 LATEST
|
||||
get_lib_base zstd LATEST
|
||||
get_lib_base gflags LATEST
|
||||
get_lib_base jemalloc LATEST
|
||||
get_lib_base numa LATEST
|
||||
get_lib_base libunwind LATEST
|
||||
|
||||
get_lib_base kernel-headers LATEST
|
||||
get_lib_base binutils LATEST centos6-native
|
||||
get_lib_base valgrind LATEST
|
||||
|
||||
git diff $OUTPUT
|
||||
|
||||
###########################################################
|
||||
# 4.8.1 dependencies #
|
||||
###########################################################
|
||||
|
||||
OUTPUT="$BASEDIR/dependencies_4.8.1.sh"
|
||||
|
||||
rm -f "$OUTPUT"
|
||||
touch "$OUTPUT"
|
||||
|
||||
echo "Writing 4.8.1 dependencies to $OUTPUT"
|
||||
|
||||
# Compilers locations
|
||||
GCC_BASE=`ls -d1 $TP2_LATEST/gcc/4.8.1/centos6-native/*/ | head -n1`
|
||||
CLANG_BASE=`ls -d1 $TP2_LATEST/clang/3.7.1/centos6-native/*/ | head -n1`
|
||||
|
||||
log_variable GCC_BASE
|
||||
log_variable CLANG_BASE
|
||||
|
||||
# Libraries locations
|
||||
get_lib_base libgcc 4.8.1 gcc-4.8.1-glibc-2.17
|
||||
get_lib_base glibc 2.17 gcc-4.8.1-glibc-2.17
|
||||
get_lib_base snappy LATEST gcc-4.8.1-glibc-2.17
|
||||
get_lib_base zlib LATEST gcc-4.8.1-glibc-2.17
|
||||
get_lib_base bzip2 LATEST gcc-4.8.1-glibc-2.17
|
||||
get_lib_base lz4 LATEST gcc-4.8.1-glibc-2.17
|
||||
get_lib_base zstd LATEST gcc-4.8.1-glibc-2.17
|
||||
get_lib_base gflags LATEST gcc-4.8.1-glibc-2.17
|
||||
get_lib_base jemalloc LATEST gcc-4.8.1-glibc-2.17
|
||||
get_lib_base numa LATEST gcc-4.8.1-glibc-2.17
|
||||
get_lib_base libunwind LATEST gcc-4.8.1-glibc-2.17
|
||||
|
||||
get_lib_base kernel-headers LATEST gcc-4.8.1-glibc-2.17
|
||||
get_lib_base binutils LATEST centos6-native
|
||||
get_lib_base valgrind 3.8.1 gcc-4.8.1-glibc-2.17
|
||||
|
||||
git diff $OUTPUT
|
||||
+3
-1
@@ -59,6 +59,7 @@ Status BuildTable(
|
||||
const std::vector<std::unique_ptr<IntTblPropCollectorFactory>>*
|
||||
int_tbl_prop_collector_factories,
|
||||
uint32_t column_family_id, std::vector<SequenceNumber> snapshots,
|
||||
SequenceNumber earliest_write_conflict_snapshot,
|
||||
const CompressionType compression,
|
||||
const CompressionOptions& compression_opts, bool paranoid_file_checks,
|
||||
InternalStats* internal_stats, const Env::IOPriority io_priority,
|
||||
@@ -96,7 +97,8 @@ Status BuildTable(
|
||||
snapshots.empty() ? 0 : snapshots.back());
|
||||
|
||||
CompactionIterator c_iter(iter, internal_comparator.user_comparator(),
|
||||
&merge, kMaxSequenceNumber, &snapshots, env,
|
||||
&merge, kMaxSequenceNumber, &snapshots,
|
||||
earliest_write_conflict_snapshot, env,
|
||||
true /* internal key corruption is not ok */);
|
||||
c_iter.SeekToFirst();
|
||||
for (; c_iter.Valid(); c_iter.Next()) {
|
||||
|
||||
@@ -56,6 +56,7 @@ extern Status BuildTable(
|
||||
const std::vector<std::unique_ptr<IntTblPropCollectorFactory>>*
|
||||
int_tbl_prop_collector_factories,
|
||||
uint32_t column_family_id, std::vector<SequenceNumber> snapshots,
|
||||
SequenceNumber earliest_write_conflict_snapshot,
|
||||
const CompressionType compression,
|
||||
const CompressionOptions& compression_opts, bool paranoid_file_checks,
|
||||
InternalStats* internal_stats,
|
||||
|
||||
@@ -1766,6 +1766,11 @@ void rocksdb_options_set_memtable_prefix_bloom_probes(
|
||||
opt->rep.memtable_prefix_bloom_probes = v;
|
||||
}
|
||||
|
||||
void rocksdb_options_set_memtable_prefix_bloom_huge_page_tlb_size(
|
||||
rocksdb_options_t* opt, size_t v) {
|
||||
opt->rep.memtable_prefix_bloom_huge_page_tlb_size = v;
|
||||
}
|
||||
|
||||
void rocksdb_options_set_hash_skip_list_rep(
|
||||
rocksdb_options_t *opt, size_t bucket_count,
|
||||
int32_t skiplist_height, int32_t skiplist_branching_factor) {
|
||||
@@ -1965,7 +1970,7 @@ void rocksdb_filterpolicy_destroy(rocksdb_filterpolicy_t* filter) {
|
||||
delete filter;
|
||||
}
|
||||
|
||||
rocksdb_filterpolicy_t* rocksdb_filterpolicy_create_bloom(int bits_per_key) {
|
||||
rocksdb_filterpolicy_t* rocksdb_filterpolicy_create_bloom_format(int bits_per_key, bool original_format) {
|
||||
// Make a rocksdb_filterpolicy_t, but override all of its methods so
|
||||
// they delegate to a NewBloomFilterPolicy() instead of user
|
||||
// supplied C functions.
|
||||
@@ -1983,13 +1988,21 @@ rocksdb_filterpolicy_t* rocksdb_filterpolicy_create_bloom(int bits_per_key) {
|
||||
static void DoNothing(void*) { }
|
||||
};
|
||||
Wrapper* wrapper = new Wrapper;
|
||||
wrapper->rep_ = NewBloomFilterPolicy(bits_per_key);
|
||||
wrapper->rep_ = NewBloomFilterPolicy(bits_per_key, original_format);
|
||||
wrapper->state_ = nullptr;
|
||||
wrapper->delete_filter_ = nullptr;
|
||||
wrapper->destructor_ = &Wrapper::DoNothing;
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
rocksdb_filterpolicy_t* rocksdb_filterpolicy_create_bloom_full(int bits_per_key) {
|
||||
return rocksdb_filterpolicy_create_bloom_format(bits_per_key, false);
|
||||
}
|
||||
|
||||
rocksdb_filterpolicy_t* rocksdb_filterpolicy_create_bloom(int bits_per_key) {
|
||||
return rocksdb_filterpolicy_create_bloom_format(bits_per_key, true);
|
||||
}
|
||||
|
||||
rocksdb_mergeoperator_t* rocksdb_mergeoperator_create(
|
||||
void* state, void (*destructor)(void*),
|
||||
char* (*full_merge)(void*, const char* key, size_t key_length,
|
||||
|
||||
+140
-28
@@ -68,6 +68,20 @@ const std::string& ColumnFamilyHandleImpl::GetName() const {
|
||||
return cfd()->GetName();
|
||||
}
|
||||
|
||||
Status ColumnFamilyHandleImpl::GetDescriptor(ColumnFamilyDescriptor* desc) {
|
||||
#ifndef ROCKSDB_LITE
|
||||
// accessing mutable cf-options requires db mutex.
|
||||
InstrumentedMutexLock l(mutex_);
|
||||
*desc = ColumnFamilyDescriptor(
|
||||
cfd()->GetName(),
|
||||
BuildColumnFamilyOptions(*cfd()->options(),
|
||||
*cfd()->GetLatestMutableCFOptions()));
|
||||
return Status::OK();
|
||||
#else
|
||||
return Status::NotSupported();
|
||||
#endif // !ROCKSDB_LITE
|
||||
}
|
||||
|
||||
const Comparator* ColumnFamilyHandleImpl::user_comparator() const {
|
||||
return cfd()->user_comparator();
|
||||
}
|
||||
@@ -110,6 +124,24 @@ Status CheckCompressionSupported(const ColumnFamilyOptions& cf_options) {
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status CheckConcurrentWritesSupported(const ColumnFamilyOptions& cf_options) {
|
||||
if (cf_options.inplace_update_support) {
|
||||
return Status::InvalidArgument(
|
||||
"In-place memtable updates (inplace_update_support) is not compatible "
|
||||
"with concurrent writes (allow_concurrent_memtable_write)");
|
||||
}
|
||||
if (cf_options.filter_deletes) {
|
||||
return Status::InvalidArgument(
|
||||
"Delete filtering (filter_deletes) is not compatible with concurrent "
|
||||
"memtable writes (allow_concurrent_memtable_writes)");
|
||||
}
|
||||
if (!cf_options.memtable_factory->IsInsertConcurrentlySupported()) {
|
||||
return Status::InvalidArgument(
|
||||
"Memtable doesn't concurrent writes (allow_concurrent_memtable_write)");
|
||||
}
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
ColumnFamilyOptions SanitizeOptions(const DBOptions& db_options,
|
||||
const InternalKeyComparator* icmp,
|
||||
const ColumnFamilyOptions& src) {
|
||||
@@ -175,6 +207,12 @@ ColumnFamilyOptions SanitizeOptions(const DBOptions& db_options,
|
||||
result.level0_stop_writes_trigger = std::numeric_limits<int>::max();
|
||||
}
|
||||
|
||||
if (result.level0_file_num_compaction_trigger == 0) {
|
||||
Warn(db_options.info_log.get(),
|
||||
"level0_file_num_compaction_trigger cannot be 0");
|
||||
result.level0_file_num_compaction_trigger = 1;
|
||||
}
|
||||
|
||||
if (result.level0_stop_writes_trigger <
|
||||
result.level0_slowdown_writes_trigger ||
|
||||
result.level0_slowdown_writes_trigger <
|
||||
@@ -309,7 +347,8 @@ ColumnFamilyData::ColumnFamilyData(
|
||||
log_number_(0),
|
||||
column_family_set_(column_family_set),
|
||||
pending_flush_(false),
|
||||
pending_compaction_(false) {
|
||||
pending_compaction_(false),
|
||||
prev_compaction_needed_bytes_(0) {
|
||||
Ref();
|
||||
|
||||
// Convert user defined table properties collector factories to internal ones.
|
||||
@@ -427,13 +466,66 @@ void ColumnFamilyData::SetDropped() {
|
||||
column_family_set_->RemoveColumnFamily(this);
|
||||
}
|
||||
|
||||
const double kSlowdownRatio = 1.2;
|
||||
|
||||
namespace {
|
||||
std::unique_ptr<WriteControllerToken> SetupDelay(
|
||||
uint64_t max_write_rate, WriteController* write_controller,
|
||||
uint64_t compaction_needed_bytes, uint64_t prev_compaction_neeed_bytes,
|
||||
bool auto_comapctions_disabled) {
|
||||
const uint64_t kMinWriteRate = 1024u; // Minimum write rate 1KB/s.
|
||||
|
||||
uint64_t write_rate = write_controller->delayed_write_rate();
|
||||
|
||||
if (auto_comapctions_disabled) {
|
||||
// When auto compaction is disabled, always use the value user gave.
|
||||
write_rate = max_write_rate;
|
||||
} else if (write_controller->NeedsDelay() && max_write_rate > kMinWriteRate) {
|
||||
// If user gives rate less than kMinWriteRate, don't adjust it.
|
||||
//
|
||||
// If already delayed, need to adjust based on previous compaction debt.
|
||||
// When there are two or more column families require delay, we always
|
||||
// increase or reduce write rate based on information for one single
|
||||
// column family. It is likely to be OK but we can improve if there is a
|
||||
// problem.
|
||||
// Ignore compaction_needed_bytes = 0 case because compaction_needed_bytes
|
||||
// is only available in level-based compaction
|
||||
//
|
||||
// If the compaction debt stays the same as previously, we also further slow
|
||||
// down. It usually means a mem table is full. It's mainly for the case
|
||||
// where both of flush and compaction are much slower than the speed we
|
||||
// insert to mem tables, so we need to actively slow down before we get
|
||||
// feedback signal from compaction and flushes to avoid the full stop
|
||||
// because of hitting the max write buffer number.
|
||||
if (prev_compaction_neeed_bytes > 0 &&
|
||||
prev_compaction_neeed_bytes <= compaction_needed_bytes) {
|
||||
write_rate = static_cast<uint64_t>(static_cast<double>(write_rate) /
|
||||
kSlowdownRatio);
|
||||
if (write_rate < kMinWriteRate) {
|
||||
write_rate = kMinWriteRate;
|
||||
}
|
||||
} else if (prev_compaction_neeed_bytes > compaction_needed_bytes) {
|
||||
// We are speeding up by ratio of kSlowdownRatio when we have paid
|
||||
// compaction debt. But we'll never speed up to faster than the write rate
|
||||
// given by users.
|
||||
write_rate = static_cast<uint64_t>(static_cast<double>(write_rate) *
|
||||
kSlowdownRatio);
|
||||
if (write_rate > max_write_rate) {
|
||||
write_rate = max_write_rate;
|
||||
}
|
||||
}
|
||||
}
|
||||
return write_controller->GetDelayToken(write_rate);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void ColumnFamilyData::RecalculateWriteStallConditions(
|
||||
const MutableCFOptions& mutable_cf_options) {
|
||||
if (current_ != nullptr) {
|
||||
auto* vstorage = current_->storage_info();
|
||||
const double score = vstorage->max_compaction_score();
|
||||
const int max_level = vstorage->max_compaction_score_level();
|
||||
auto write_controller = column_family_set_->write_controller_;
|
||||
uint64_t compaction_needed_bytes =
|
||||
vstorage->estimated_compaction_needed_bytes();
|
||||
|
||||
if (imm()->NumNotFlushed() >= mutable_cf_options.max_write_buffer_number) {
|
||||
write_controller_token_ = write_controller->GetStopToken();
|
||||
@@ -443,6 +535,21 @@ void ColumnFamilyData::RecalculateWriteStallConditions(
|
||||
"(waiting for flush), max_write_buffer_number is set to %d",
|
||||
name_.c_str(), imm()->NumNotFlushed(),
|
||||
mutable_cf_options.max_write_buffer_number);
|
||||
} else if (mutable_cf_options.max_write_buffer_number > 3 &&
|
||||
imm()->NumNotFlushed() >=
|
||||
mutable_cf_options.max_write_buffer_number - 1) {
|
||||
write_controller_token_ =
|
||||
SetupDelay(ioptions_.delayed_write_rate, write_controller,
|
||||
compaction_needed_bytes, prev_compaction_needed_bytes_,
|
||||
mutable_cf_options.disable_auto_compactions);
|
||||
internal_stats_->AddCFStats(InternalStats::MEMTABLE_SLOWDOWN, 1);
|
||||
Log(InfoLogLevel::WARN_LEVEL, ioptions_.info_log,
|
||||
"[%s] Stalling writes because we have %d immutable memtables "
|
||||
"(waiting for flush), max_write_buffer_number is set to %d "
|
||||
"rate %" PRIu64,
|
||||
name_.c_str(), imm()->NumNotFlushed(),
|
||||
mutable_cf_options.max_write_buffer_number,
|
||||
write_controller->delayed_write_rate());
|
||||
} else if (vstorage->l0_delay_trigger_count() >=
|
||||
mutable_cf_options.level0_stop_writes_trigger) {
|
||||
write_controller_token_ = write_controller->GetStopToken();
|
||||
@@ -455,37 +562,50 @@ void ColumnFamilyData::RecalculateWriteStallConditions(
|
||||
"[%s] Stopping writes because we have %d level-0 files",
|
||||
name_.c_str(), vstorage->l0_delay_trigger_count());
|
||||
} else if (mutable_cf_options.hard_pending_compaction_bytes_limit > 0 &&
|
||||
vstorage->estimated_compaction_needed_bytes() >=
|
||||
compaction_needed_bytes >=
|
||||
mutable_cf_options.hard_pending_compaction_bytes_limit) {
|
||||
write_controller_token_ = write_controller->GetStopToken();
|
||||
internal_stats_->AddCFStats(
|
||||
InternalStats::HARD_PENDING_COMPACTION_BYTES_LIMIT, 1);
|
||||
Log(InfoLogLevel::WARN_LEVEL, ioptions_.info_log,
|
||||
"[%s] Stopping writes because estimated pending compaction "
|
||||
"bytes exceed %" PRIu64,
|
||||
name_.c_str(), vstorage->estimated_compaction_needed_bytes());
|
||||
"[%s] Stopping writes because of estimated pending compaction "
|
||||
"bytes %" PRIu64,
|
||||
name_.c_str(), compaction_needed_bytes);
|
||||
} else if (mutable_cf_options.level0_slowdown_writes_trigger >= 0 &&
|
||||
vstorage->l0_delay_trigger_count() >=
|
||||
mutable_cf_options.level0_slowdown_writes_trigger) {
|
||||
write_controller_token_ = write_controller->GetDelayToken();
|
||||
write_controller_token_ =
|
||||
SetupDelay(ioptions_.delayed_write_rate, write_controller,
|
||||
compaction_needed_bytes, prev_compaction_needed_bytes_,
|
||||
mutable_cf_options.disable_auto_compactions);
|
||||
internal_stats_->AddCFStats(InternalStats::LEVEL0_SLOWDOWN_TOTAL, 1);
|
||||
if (compaction_picker_->IsLevel0CompactionInProgress()) {
|
||||
internal_stats_->AddCFStats(
|
||||
InternalStats::LEVEL0_SLOWDOWN_WITH_COMPACTION, 1);
|
||||
}
|
||||
Log(InfoLogLevel::WARN_LEVEL, ioptions_.info_log,
|
||||
"[%s] Stalling writes because we have %d level-0 files",
|
||||
name_.c_str(), vstorage->l0_delay_trigger_count());
|
||||
} else if (mutable_cf_options.soft_rate_limit > 0.0 &&
|
||||
score > mutable_cf_options.soft_rate_limit) {
|
||||
write_controller_token_ = write_controller->GetDelayToken();
|
||||
internal_stats_->RecordLevelNSlowdown(max_level, true);
|
||||
"[%s] Stalling writes because we have %d level-0 files "
|
||||
"rate %" PRIu64,
|
||||
name_.c_str(), vstorage->l0_delay_trigger_count(),
|
||||
write_controller->delayed_write_rate());
|
||||
} else if (mutable_cf_options.soft_pending_compaction_bytes_limit > 0 &&
|
||||
vstorage->estimated_compaction_needed_bytes() >=
|
||||
mutable_cf_options.soft_pending_compaction_bytes_limit) {
|
||||
write_controller_token_ =
|
||||
SetupDelay(ioptions_.delayed_write_rate, write_controller,
|
||||
compaction_needed_bytes, prev_compaction_needed_bytes_,
|
||||
mutable_cf_options.disable_auto_compactions);
|
||||
internal_stats_->AddCFStats(
|
||||
InternalStats::SOFT_PENDING_COMPACTION_BYTES_LIMIT, 1);
|
||||
Log(InfoLogLevel::WARN_LEVEL, ioptions_.info_log,
|
||||
"[%s] Stalling writes because we hit soft limit on level %d",
|
||||
name_.c_str(), max_level);
|
||||
"[%s] Stalling writes because of estimated pending compaction "
|
||||
"bytes %" PRIu64 " rate %" PRIu64,
|
||||
name_.c_str(), vstorage->estimated_compaction_needed_bytes(),
|
||||
write_controller->delayed_write_rate());
|
||||
} else {
|
||||
write_controller_token_.reset();
|
||||
}
|
||||
prev_compaction_needed_bytes_ = compaction_needed_bytes;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -539,13 +659,12 @@ const int ColumnFamilyData::kCompactAllLevels = -1;
|
||||
const int ColumnFamilyData::kCompactToBaseLevel = -2;
|
||||
|
||||
Compaction* ColumnFamilyData::CompactRange(
|
||||
const MutableCFOptions& mutable_cf_options,
|
||||
int input_level, int output_level, uint32_t output_path_id,
|
||||
const InternalKey* begin, const InternalKey* end,
|
||||
InternalKey** compaction_end) {
|
||||
const MutableCFOptions& mutable_cf_options, int input_level,
|
||||
int output_level, uint32_t output_path_id, const InternalKey* begin,
|
||||
const InternalKey* end, InternalKey** compaction_end, bool* conflict) {
|
||||
auto* result = compaction_picker_->CompactRange(
|
||||
GetName(), mutable_cf_options, current_->storage_info(), input_level,
|
||||
output_level, output_path_id, begin, end, compaction_end);
|
||||
output_level, output_path_id, begin, end, compaction_end, conflict);
|
||||
if (result != nullptr) {
|
||||
result->SetInputVersion(current_);
|
||||
}
|
||||
@@ -831,13 +950,6 @@ ColumnFamilyHandle* ColumnFamilyMemTablesImpl::GetColumnFamilyHandle() {
|
||||
return &handle_;
|
||||
}
|
||||
|
||||
void ColumnFamilyMemTablesImpl::CheckMemtableFull() {
|
||||
if (current_ != nullptr && current_->mem()->ShouldScheduleFlush()) {
|
||||
flush_scheduler_->ScheduleFlush(current_);
|
||||
current_->mem()->MarkFlushScheduled();
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t GetColumnFamilyID(ColumnFamilyHandle* column_family) {
|
||||
uint32_t column_family_id = 0;
|
||||
if (column_family != nullptr) {
|
||||
|
||||
+48
-34
@@ -19,12 +19,10 @@
|
||||
#include "db/write_controller.h"
|
||||
#include "db/table_cache.h"
|
||||
#include "db/table_properties_collector.h"
|
||||
#include "db/flush_scheduler.h"
|
||||
#include "rocksdb/compaction_job_stats.h"
|
||||
#include "rocksdb/db.h"
|
||||
#include "rocksdb/env.h"
|
||||
#include "rocksdb/options.h"
|
||||
#include "util/instrumented_mutex.h"
|
||||
#include "util/mutable_cf_options.h"
|
||||
#include "util/thread_local.h"
|
||||
|
||||
@@ -44,6 +42,8 @@ class LogBuffer;
|
||||
class InstrumentedMutex;
|
||||
class InstrumentedMutexLock;
|
||||
|
||||
extern const double kSlowdownRatio;
|
||||
|
||||
// ColumnFamilyHandleImpl is the class that clients use to access different
|
||||
// column families. It has non-trivial destructor, which gets called when client
|
||||
// is done using the column family
|
||||
@@ -59,6 +59,7 @@ class ColumnFamilyHandleImpl : public ColumnFamilyHandle {
|
||||
|
||||
virtual uint32_t GetID() const override;
|
||||
virtual const std::string& GetName() const override;
|
||||
virtual Status GetDescriptor(ColumnFamilyDescriptor* desc) override;
|
||||
|
||||
private:
|
||||
ColumnFamilyData* cfd_;
|
||||
@@ -132,6 +133,9 @@ struct SuperVersion {
|
||||
|
||||
extern Status CheckCompressionSupported(const ColumnFamilyOptions& cf_options);
|
||||
|
||||
extern Status CheckConcurrentWritesSupported(
|
||||
const ColumnFamilyOptions& cf_options);
|
||||
|
||||
extern ColumnFamilyOptions SanitizeOptions(const DBOptions& db_options,
|
||||
const InternalKeyComparator* icmp,
|
||||
const ColumnFamilyOptions& src);
|
||||
@@ -156,14 +160,16 @@ class ColumnFamilyData {
|
||||
// thread-safe
|
||||
const std::string& GetName() const { return name_; }
|
||||
|
||||
// Ref() can only be called whily holding a DB mutex or during a
|
||||
// single-threaded write.
|
||||
// Ref() can only be called from a context where the caller can guarantee
|
||||
// that ColumnFamilyData is alive (while holding a non-zero ref already,
|
||||
// holding a DB mutex, or as the leader in a write batch group).
|
||||
void Ref() { refs_.fetch_add(1, std::memory_order_relaxed); }
|
||||
// will just decrease reference count to 0, but will not delete it. returns
|
||||
// true if the ref count was decreased to zero. in that case, it can be
|
||||
// deleted by the caller immediately, or later, by calling
|
||||
// FreeDeadColumnFamilies()
|
||||
// Unref() can only be called while holding a DB mutex
|
||||
|
||||
// Unref decreases the reference count, but does not handle deletion
|
||||
// when the count goes to 0. If this method returns true then the
|
||||
// caller should delete the instance immediately, or later, by calling
|
||||
// FreeDeadColumnFamilies(). Unref() can only be called while holding
|
||||
// a DB mutex, or during single-threaded recovery.
|
||||
bool Unref() {
|
||||
int old_refs = refs_.fetch_sub(1, std::memory_order_relaxed);
|
||||
assert(old_refs > 0);
|
||||
@@ -224,7 +230,7 @@ class ColumnFamilyData {
|
||||
MemTable* mem() { return mem_; }
|
||||
Version* current() { return current_; }
|
||||
Version* dummy_versions() { return dummy_versions_; }
|
||||
void SetCurrent(Version* current);
|
||||
void SetCurrent(Version* _current);
|
||||
uint64_t GetNumLiveVersions() const; // REQUIRE: DB mutex held
|
||||
uint64_t GetTotalSstFilesSize() const; // REQUIRE: DB mutex held
|
||||
void SetMemtable(MemTable* new_mem) { mem_ = new_mem; }
|
||||
@@ -249,11 +255,11 @@ class ColumnFamilyData {
|
||||
// A flag to tell a manual compaction's output is base level.
|
||||
static const int kCompactToBaseLevel;
|
||||
// REQUIRES: DB mutex held
|
||||
Compaction* CompactRange(
|
||||
const MutableCFOptions& mutable_cf_options,
|
||||
int input_level, int output_level, uint32_t output_path_id,
|
||||
const InternalKey* begin, const InternalKey* end,
|
||||
InternalKey** compaction_end);
|
||||
Compaction* CompactRange(const MutableCFOptions& mutable_cf_options,
|
||||
int input_level, int output_level,
|
||||
uint32_t output_path_id, const InternalKey* begin,
|
||||
const InternalKey* end, InternalKey** compaction_end,
|
||||
bool* manual_conflict);
|
||||
|
||||
CompactionPicker* compaction_picker() { return compaction_picker_.get(); }
|
||||
// thread-safe
|
||||
@@ -305,6 +311,14 @@ class ColumnFamilyData {
|
||||
bool pending_flush() { return pending_flush_; }
|
||||
bool pending_compaction() { return pending_compaction_; }
|
||||
|
||||
// Recalculate some small conditions, which are changed only during
|
||||
// compaction, adding new memtable and/or
|
||||
// recalculation of compaction score. These values are used in
|
||||
// DBImpl::MakeRoomForWrite function to decide, if it need to make
|
||||
// a write stall
|
||||
void RecalculateWriteStallConditions(
|
||||
const MutableCFOptions& mutable_cf_options);
|
||||
|
||||
private:
|
||||
friend class ColumnFamilySet;
|
||||
ColumnFamilyData(uint32_t id, const std::string& name,
|
||||
@@ -314,14 +328,6 @@ class ColumnFamilyData {
|
||||
const DBOptions* db_options, const EnvOptions& env_options,
|
||||
ColumnFamilySet* column_family_set);
|
||||
|
||||
// Recalculate some small conditions, which are changed only during
|
||||
// compaction, adding new memtable and/or
|
||||
// recalculation of compaction score. These values are used in
|
||||
// DBImpl::MakeRoomForWrite function to decide, if it need to make
|
||||
// a write stall
|
||||
void RecalculateWriteStallConditions(
|
||||
const MutableCFOptions& mutable_cf_options);
|
||||
|
||||
uint32_t id_;
|
||||
const std::string name_;
|
||||
Version* dummy_versions_; // Head of circular doubly-linked list of versions.
|
||||
@@ -382,6 +388,8 @@ class ColumnFamilyData {
|
||||
// If true --> this ColumnFamily is currently present in
|
||||
// DBImpl::compaction_queue_
|
||||
bool pending_compaction_;
|
||||
|
||||
uint64_t prev_compaction_needed_bytes_;
|
||||
};
|
||||
|
||||
// ColumnFamilySet has interesting thread-safety requirements
|
||||
@@ -493,15 +501,18 @@ class ColumnFamilySet {
|
||||
// memtables of different column families (specified by ID in the write batch)
|
||||
class ColumnFamilyMemTablesImpl : public ColumnFamilyMemTables {
|
||||
public:
|
||||
explicit ColumnFamilyMemTablesImpl(ColumnFamilySet* column_family_set,
|
||||
FlushScheduler* flush_scheduler)
|
||||
: column_family_set_(column_family_set),
|
||||
current_(nullptr),
|
||||
flush_scheduler_(flush_scheduler) {}
|
||||
explicit ColumnFamilyMemTablesImpl(ColumnFamilySet* column_family_set)
|
||||
: column_family_set_(column_family_set), current_(nullptr) {}
|
||||
|
||||
// Constructs a ColumnFamilyMemTablesImpl equivalent to one constructed
|
||||
// with the arguments used to construct *orig.
|
||||
explicit ColumnFamilyMemTablesImpl(ColumnFamilyMemTablesImpl* orig)
|
||||
: column_family_set_(orig->column_family_set_), current_(nullptr) {}
|
||||
|
||||
// sets current_ to ColumnFamilyData with column_family_id
|
||||
// returns false if column family doesn't exist
|
||||
// REQUIRES: under a DB mutex OR from a write thread
|
||||
// REQUIRES: use this function of DBImpl::column_family_memtables_ should be
|
||||
// under a DB mutex OR from a write thread
|
||||
bool Seek(uint32_t column_family_id) override;
|
||||
|
||||
// Returns log number of the selected column family
|
||||
@@ -509,20 +520,23 @@ class ColumnFamilyMemTablesImpl : public ColumnFamilyMemTables {
|
||||
uint64_t GetLogNumber() const override;
|
||||
|
||||
// REQUIRES: Seek() called first
|
||||
// REQUIRES: under a DB mutex OR from a write thread
|
||||
// REQUIRES: use this function of DBImpl::column_family_memtables_ should be
|
||||
// under a DB mutex OR from a write thread
|
||||
virtual MemTable* GetMemTable() const override;
|
||||
|
||||
// Returns column family handle for the selected column family
|
||||
// REQUIRES: under a DB mutex OR from a write thread
|
||||
// REQUIRES: use this function of DBImpl::column_family_memtables_ should be
|
||||
// under a DB mutex OR from a write thread
|
||||
virtual ColumnFamilyHandle* GetColumnFamilyHandle() override;
|
||||
|
||||
// REQUIRES: under a DB mutex OR from a write thread
|
||||
virtual void CheckMemtableFull() override;
|
||||
// Cannot be called while another thread is calling Seek().
|
||||
// REQUIRES: use this function of DBImpl::column_family_memtables_ should be
|
||||
// under a DB mutex OR from a write thread
|
||||
virtual ColumnFamilyData* current() override { return current_; }
|
||||
|
||||
private:
|
||||
ColumnFamilySet* column_family_set_;
|
||||
ColumnFamilyData* current_;
|
||||
FlushScheduler* flush_scheduler_;
|
||||
ColumnFamilyHandleInternal handle_;
|
||||
};
|
||||
|
||||
|
||||
+1166
-26
File diff suppressed because it is too large.
Load diff
@@ -12,6 +12,7 @@
|
||||
#include "rocksdb/db.h"
|
||||
#include "rocksdb/env.h"
|
||||
#include "util/string_util.h"
|
||||
#include "util/sync_point.h"
|
||||
#include "util/testharness.h"
|
||||
|
||||
namespace rocksdb {
|
||||
@@ -53,6 +54,63 @@ class FlushedFileCollector : public EventListener {
|
||||
std::mutex mutex_;
|
||||
};
|
||||
|
||||
TEST_F(CompactFilesTest, L0ConflictsFiles) {
|
||||
Options options;
|
||||
// to trigger compaction more easily
|
||||
const int kWriteBufferSize = 10000;
|
||||
const int kLevel0Trigger = 2;
|
||||
options.create_if_missing = true;
|
||||
options.compaction_style = kCompactionStyleLevel;
|
||||
// Small slowdown and stop trigger for experimental purpose.
|
||||
options.level0_slowdown_writes_trigger = 20;
|
||||
options.level0_stop_writes_trigger = 20;
|
||||
options.level0_stop_writes_trigger = 20;
|
||||
options.write_buffer_size = kWriteBufferSize;
|
||||
options.level0_file_num_compaction_trigger = kLevel0Trigger;
|
||||
options.compression = kNoCompression;
|
||||
|
||||
DB* db = nullptr;
|
||||
DestroyDB(db_name_, options);
|
||||
Status s = DB::Open(options, db_name_, &db);
|
||||
assert(s.ok());
|
||||
assert(db);
|
||||
|
||||
rocksdb::SyncPoint::GetInstance()->LoadDependency({
|
||||
{"CompactFilesImpl:0", "BackgroundCallCompaction:0"},
|
||||
{"BackgroundCallCompaction:1", "CompactFilesImpl:1"},
|
||||
});
|
||||
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
// create couple files
|
||||
// Background compaction starts and waits in BackgroundCallCompaction:0
|
||||
for (int i = 0; i < kLevel0Trigger * 4; ++i) {
|
||||
db->Put(WriteOptions(), ToString(i), "");
|
||||
db->Put(WriteOptions(), ToString(100 - i), "");
|
||||
db->Flush(FlushOptions());
|
||||
}
|
||||
|
||||
rocksdb::ColumnFamilyMetaData meta;
|
||||
db->GetColumnFamilyMetaData(&meta);
|
||||
std::string file1;
|
||||
for (auto& file : meta.levels[0].files) {
|
||||
ASSERT_EQ(0, meta.levels[0].level);
|
||||
if (file1 == "") {
|
||||
file1 = file.db_path + "/" + file.name;
|
||||
} else {
|
||||
std::string file2 = file.db_path + "/" + file.name;
|
||||
// Another thread starts a compact files and creates an L0 compaction
|
||||
// The background compaction then notices that there is an L0 compaction
|
||||
// already in progress and doesn't do an L0 compaction
|
||||
// Once the background compaction finishes, the compact files finishes
|
||||
ASSERT_OK(
|
||||
db->CompactFiles(rocksdb::CompactionOptions(), {file1, file2}, 0));
|
||||
break;
|
||||
}
|
||||
}
|
||||
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
||||
delete db;
|
||||
}
|
||||
|
||||
TEST_F(CompactFilesTest, ObsoleteFiles) {
|
||||
Options options;
|
||||
// to trigger compaction more easily
|
||||
|
||||
+16
-8
@@ -46,7 +46,7 @@ void Compaction::GetBoundaryKeys(
|
||||
Slice* largest_user_key) {
|
||||
bool initialized = false;
|
||||
const Comparator* ucmp = vstorage->InternalComparator()->user_comparator();
|
||||
for (uint32_t i = 0; i < inputs.size(); ++i) {
|
||||
for (size_t i = 0; i < inputs.size(); ++i) {
|
||||
if (inputs[i].files.empty()) {
|
||||
continue;
|
||||
}
|
||||
@@ -128,8 +128,8 @@ bool Compaction::TEST_IsBottommostLevel(
|
||||
bool Compaction::IsFullCompaction(
|
||||
VersionStorageInfo* vstorage,
|
||||
const std::vector<CompactionInputFiles>& inputs) {
|
||||
int num_files_in_compaction = 0;
|
||||
int total_num_files = 0;
|
||||
size_t num_files_in_compaction = 0;
|
||||
size_t total_num_files = 0;
|
||||
for (int l = 0; l < vstorage->num_levels(); l++) {
|
||||
total_num_files += vstorage->NumLevelFiles(l);
|
||||
}
|
||||
@@ -147,7 +147,8 @@ Compaction::Compaction(VersionStorageInfo* vstorage,
|
||||
uint32_t _output_path_id, CompressionType _compression,
|
||||
std::vector<FileMetaData*> _grandparents,
|
||||
bool _manual_compaction, double _score,
|
||||
bool _deletion_compaction)
|
||||
bool _deletion_compaction,
|
||||
CompactionReason _compaction_reason)
|
||||
: start_level_(_inputs[0].level),
|
||||
output_level_(_output_level),
|
||||
max_output_file_size_(_target_file_size),
|
||||
@@ -167,8 +168,12 @@ Compaction::Compaction(VersionStorageInfo* vstorage,
|
||||
score_(_score),
|
||||
bottommost_level_(IsBottommostLevel(output_level_, vstorage, inputs_)),
|
||||
is_full_compaction_(IsFullCompaction(vstorage, inputs_)),
|
||||
is_manual_compaction_(_manual_compaction) {
|
||||
is_manual_compaction_(_manual_compaction),
|
||||
compaction_reason_(_compaction_reason) {
|
||||
MarkFilesBeingCompacted(true);
|
||||
if (is_manual_compaction_) {
|
||||
compaction_reason_ = CompactionReason::kManualCompaction;
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
for (size_t i = 1; i < inputs_.size(); ++i) {
|
||||
@@ -184,6 +189,9 @@ Compaction::Compaction(VersionStorageInfo* vstorage,
|
||||
&arena_);
|
||||
}
|
||||
}
|
||||
|
||||
Slice smallest_user_key;
|
||||
GetBoundaryKeys(vstorage, inputs_, &smallest_user_key, &largest_user_key_);
|
||||
}
|
||||
|
||||
Compaction::~Compaction() {
|
||||
@@ -311,7 +319,7 @@ bool Compaction::ShouldStopBefore(const Slice& internal_key) {
|
||||
// Mark (or clear) each file that is being compacted
|
||||
void Compaction::MarkFilesBeingCompacted(bool mark_as_compacted) {
|
||||
for (size_t i = 0; i < num_input_levels(); i++) {
|
||||
for (unsigned int j = 0; j < inputs_[i].size(); j++) {
|
||||
for (size_t j = 0; j < inputs_[i].size(); j++) {
|
||||
assert(mark_as_compacted ? !inputs_[i][j]->being_compacted :
|
||||
inputs_[i][j]->being_compacted);
|
||||
inputs_[i][j]->being_compacted = mark_as_compacted;
|
||||
@@ -371,7 +379,7 @@ int InputSummary(const std::vector<FileMetaData*>& files, char* output,
|
||||
int len) {
|
||||
*output = '\0';
|
||||
int write = 0;
|
||||
for (unsigned int i = 0; i < files.size(); i++) {
|
||||
for (size_t i = 0; i < files.size(); i++) {
|
||||
int sz = len - write;
|
||||
int ret;
|
||||
char sztxt[16];
|
||||
@@ -428,7 +436,7 @@ uint64_t Compaction::OutputFilePreallocationSize() {
|
||||
}
|
||||
// Over-estimate slightly so we don't end up just barely crossing
|
||||
// the threshold
|
||||
return preallocation_size * 1.1;
|
||||
return preallocation_size + (preallocation_size / 10);
|
||||
}
|
||||
|
||||
std::unique_ptr<CompactionFilter> Compaction::CreateCompactionFilter() const {
|
||||
|
||||
+12
-1
@@ -41,7 +41,8 @@ class Compaction {
|
||||
uint32_t output_path_id, CompressionType compression,
|
||||
std::vector<FileMetaData*> grandparents,
|
||||
bool manual_compaction = false, double score = -1,
|
||||
bool deletion_compaction = false);
|
||||
bool deletion_compaction = false,
|
||||
CompactionReason compaction_reason = CompactionReason::kUnknown);
|
||||
|
||||
// No copying allowed
|
||||
Compaction(const Compaction&) = delete;
|
||||
@@ -218,6 +219,10 @@ class Compaction {
|
||||
output_table_properties_ = std::move(tp);
|
||||
}
|
||||
|
||||
Slice GetLargestUserKey() const { return largest_user_key_; }
|
||||
|
||||
CompactionReason compaction_reason() { return compaction_reason_; }
|
||||
|
||||
private:
|
||||
// mark (or clear) all files that are being compacted
|
||||
void MarkFilesBeingCompacted(bool mark_as_compacted);
|
||||
@@ -284,6 +289,12 @@ class Compaction {
|
||||
|
||||
// table properties of output files
|
||||
TablePropertiesCollection output_table_properties_;
|
||||
|
||||
// largest user keys in compaction
|
||||
Slice largest_user_key_;
|
||||
|
||||
// Reason for compaction
|
||||
CompactionReason compaction_reason_;
|
||||
};
|
||||
|
||||
// Utility function
|
||||
|
||||
+123
-22
@@ -13,12 +13,14 @@ namespace rocksdb {
|
||||
CompactionIterator::CompactionIterator(
|
||||
InternalIterator* input, const Comparator* cmp, MergeHelper* merge_helper,
|
||||
SequenceNumber last_sequence, std::vector<SequenceNumber>* snapshots,
|
||||
Env* env, bool expect_valid_internal_key, Compaction* compaction,
|
||||
SequenceNumber earliest_write_conflict_snapshot, Env* env,
|
||||
bool expect_valid_internal_key, Compaction* compaction,
|
||||
const CompactionFilter* compaction_filter, LogBuffer* log_buffer)
|
||||
: input_(input),
|
||||
cmp_(cmp),
|
||||
merge_helper_(merge_helper),
|
||||
snapshots_(snapshots),
|
||||
earliest_write_conflict_snapshot_(earliest_write_conflict_snapshot),
|
||||
env_(env),
|
||||
expect_valid_internal_key_(expect_valid_internal_key),
|
||||
compaction_(compaction),
|
||||
@@ -42,6 +44,11 @@ CompactionIterator::CompactionIterator(
|
||||
earliest_snapshot_ = snapshots_->at(0);
|
||||
latest_snapshot_ = snapshots_->back();
|
||||
}
|
||||
if (compaction_filter_ != nullptr && compaction_filter_->IgnoreSnapshots()) {
|
||||
ignore_snapshots_ = true;
|
||||
} else {
|
||||
ignore_snapshots_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
void CompactionIterator::ResetRecordCounts() {
|
||||
@@ -90,6 +97,11 @@ void CompactionIterator::Next() {
|
||||
NextFromInput();
|
||||
}
|
||||
|
||||
if (valid_) {
|
||||
// Record that we've ouputted a record for the current key.
|
||||
has_outputted_key_ = true;
|
||||
}
|
||||
|
||||
PrepareOutput();
|
||||
}
|
||||
|
||||
@@ -136,11 +148,14 @@ void CompactionIterator::NextFromInput() {
|
||||
key_ = current_key_.SetKey(key_, &ikey_);
|
||||
current_user_key_ = ikey_.user_key;
|
||||
has_current_user_key_ = true;
|
||||
has_outputted_key_ = false;
|
||||
current_user_key_sequence_ = kMaxSequenceNumber;
|
||||
current_user_key_snapshot_ = 0;
|
||||
|
||||
// apply the compaction filter to the first occurrence of the user key
|
||||
if (compaction_filter_ != nullptr && ikey_.type == kTypeValue &&
|
||||
(visible_at_tip_ || ikey_.sequence > latest_snapshot_)) {
|
||||
(visible_at_tip_ || ikey_.sequence > latest_snapshot_ ||
|
||||
ignore_snapshots_)) {
|
||||
// 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,
|
||||
@@ -170,6 +185,9 @@ void CompactionIterator::NextFromInput() {
|
||||
} else {
|
||||
// Update the current key to reflect the new sequence number/type without
|
||||
// copying the user key.
|
||||
// TODO(rven): Compaction filter does not process keys in this path
|
||||
// Need to have the compaction filter process multiple versions
|
||||
// if we have versions on both sides of a snapshot
|
||||
current_key_.UpdateInternalKey(ikey_.sequence, ikey_.type);
|
||||
key_ = current_key_.GetKey();
|
||||
ikey_.user_key = current_key_.GetUserKey();
|
||||
@@ -187,31 +205,99 @@ void CompactionIterator::NextFromInput() {
|
||||
visible_at_tip_ ? visible_at_tip_ : findEarliestVisibleSnapshot(
|
||||
ikey_.sequence, &prev_snapshot);
|
||||
|
||||
if (ikey_.type == kTypeSingleDeletion) {
|
||||
if (clear_and_output_next_key_) {
|
||||
// In the previous iteration we encountered a single delete that we could
|
||||
// not compact out. We will keep this Put, but can drop it's data.
|
||||
// (See Optimization 3, below.)
|
||||
assert(ikey_.type == kTypeValue);
|
||||
assert(current_user_key_snapshot_ == last_snapshot);
|
||||
|
||||
value_.clear();
|
||||
valid_ = true;
|
||||
clear_and_output_next_key_ = false;
|
||||
} else if (ikey_.type == kTypeSingleDeletion) {
|
||||
// We can compact out a SingleDelete if:
|
||||
// 1) We encounter the corresponding PUT -OR- we know that this key
|
||||
// doesn't appear past this output level
|
||||
// =AND=
|
||||
// 2) We've already returned a record in this snapshot -OR-
|
||||
// there are no earlier earliest_write_conflict_snapshot.
|
||||
//
|
||||
// Rule 1 is needed for SingleDelete correctness. Rule 2 is needed to
|
||||
// allow Transactions to do write-conflict checking (if we compacted away
|
||||
// all keys, then we wouldn't know that a write happened in this
|
||||
// snapshot). If there is no earlier snapshot, then we know that there
|
||||
// are no active transactions that need to know about any writes.
|
||||
//
|
||||
// Optimization 3:
|
||||
// If we encounter a SingleDelete followed by a PUT and Rule 2 is NOT
|
||||
// true, then we must output a SingleDelete. In this case, we will decide
|
||||
// to also output the PUT. While we are compacting less by outputting the
|
||||
// PUT now, hopefully this will lead to better compaction in the future
|
||||
// when Rule 2 is later true (Ie, We are hoping we can later compact out
|
||||
// both the SingleDelete and the Put, while we couldn't if we only
|
||||
// outputted the SingleDelete now).
|
||||
// In this case, we can save space by removing the PUT's value as it will
|
||||
// never be read.
|
||||
//
|
||||
// Deletes and Merges are not supported on the same key that has a
|
||||
// SingleDelete as it is not possible to correctly do any partial
|
||||
// compaction of such a combination of operations. The result of mixing
|
||||
// those operations for a given key is documented as being undefined. So
|
||||
// we can choose how to handle such a combinations of operations. We will
|
||||
// try to compact out as much as we can in these cases.
|
||||
|
||||
// The easiest way to process a SingleDelete during iteration is to peek
|
||||
// ahead at the next key.
|
||||
ParsedInternalKey next_ikey;
|
||||
input_->Next();
|
||||
|
||||
// Check whether the current key is valid, not corrupt and the same
|
||||
// Check whether the next key exists, is not corrupt, and is the same key
|
||||
// as the single delete.
|
||||
if (input_->Valid() && ParseInternalKey(input_->key(), &next_ikey) &&
|
||||
cmp_->Equal(ikey_.user_key, next_ikey.user_key)) {
|
||||
// Mixing single deletes and merges is not supported. Consecutive
|
||||
// single deletes are not valid.
|
||||
if (next_ikey.type != kTypeValue) {
|
||||
assert(false);
|
||||
status_ =
|
||||
Status::InvalidArgument("Put expected after single delete.");
|
||||
break;
|
||||
}
|
||||
|
||||
// Check whether the current key belongs to the same snapshot as the
|
||||
// single delete.
|
||||
// Check whether the next key belongs to the same snapshot as the
|
||||
// SingleDelete.
|
||||
if (prev_snapshot == 0 || next_ikey.sequence > prev_snapshot) {
|
||||
// Found the matching value, we can drop the single delete and the
|
||||
// value.
|
||||
++iter_stats_.num_record_drop_hidden;
|
||||
++iter_stats_.num_record_drop_obsolete;
|
||||
input_->Next();
|
||||
if (next_ikey.type == kTypeSingleDeletion) {
|
||||
// We encountered two SingleDeletes in a row. This could be due to
|
||||
// unexpected user input.
|
||||
// Skip the first SingleDelete and let the next iteration decide how
|
||||
// to handle the second SingleDelete
|
||||
|
||||
// First SingleDelete has been skipped since we already called
|
||||
// input_->Next().
|
||||
++iter_stats_.num_record_drop_obsolete;
|
||||
} else if ((ikey_.sequence <= earliest_write_conflict_snapshot_) ||
|
||||
has_outputted_key_) {
|
||||
// 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
|
||||
// snapshot (Rule 2 above).
|
||||
|
||||
// Note: it doesn't matter whether the second key is a Put or if it
|
||||
// is an unexpected Merge or Delete. We will compact it out
|
||||
// either way.
|
||||
++iter_stats_.num_record_drop_hidden;
|
||||
++iter_stats_.num_record_drop_obsolete;
|
||||
// Already called input_->Next() once. Call it a second time to
|
||||
// skip past the second key.
|
||||
input_->Next();
|
||||
} else {
|
||||
// Found a matching value, but we cannot drop both keys since
|
||||
// there is an earlier snapshot and we need to leave behind a record
|
||||
// to know that a write happened in this snapshot (Rule 2 above).
|
||||
// Clear the value and output the SingleDelete. (The value will be
|
||||
// outputted on the next iteration.)
|
||||
++iter_stats_.num_record_drop_hidden;
|
||||
|
||||
// Setting valid_ to true will output the current SingleDelete
|
||||
valid_ = true;
|
||||
|
||||
// Set up the Put to be outputted in the next iteration.
|
||||
// (Optimization 3).
|
||||
clear_and_output_next_key_ = true;
|
||||
}
|
||||
} else {
|
||||
// We hit the next snapshot without hitting a put, so the iterator
|
||||
// returns the single delete.
|
||||
@@ -226,11 +312,14 @@ 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 &&
|
||||
if (compaction_ != nullptr && ikey_.sequence <= earliest_snapshot_ &&
|
||||
compaction_->KeyNotExistsBeyondOutputLevel(ikey_.user_key,
|
||||
&level_ptrs_)) {
|
||||
// Key doesn't exist outside of this range.
|
||||
// Can compact out this SingleDelete.
|
||||
++iter_stats_.num_record_drop_obsolete;
|
||||
} else {
|
||||
// Output SingleDelete
|
||||
valid_ = true;
|
||||
}
|
||||
}
|
||||
@@ -244,6 +333,10 @@ void CompactionIterator::NextFromInput() {
|
||||
// same key, then this kv is not visible in any snapshot.
|
||||
// Hidden by an newer entry for same user key
|
||||
// TODO: 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_);
|
||||
++iter_stats_.num_record_drop_hidden; // (A)
|
||||
input_->Next();
|
||||
@@ -262,6 +355,9 @@ void CompactionIterator::NextFromInput() {
|
||||
// smaller sequence numbers will be dropped in the next
|
||||
// few iterations of this loop (by rule (A) above).
|
||||
// Therefore this deletion marker is obsolete and can be dropped.
|
||||
//
|
||||
// Note: Dropping this Delete will not affect TransactionDB
|
||||
// write-conflict checking since it is earlier than any snapshot.
|
||||
++iter_stats_.num_record_drop_obsolete;
|
||||
input_->Next();
|
||||
} else if (ikey_.type == kTypeMerge) {
|
||||
@@ -310,9 +406,14 @@ void CompactionIterator::PrepareOutput() {
|
||||
// Zeroing out the sequence number leads to better compression.
|
||||
// If this is the bottommost level (no files in lower levels)
|
||||
// and the earliest snapshot is larger than this seqno
|
||||
// and the userkey differs from the last userkey in compaction
|
||||
// then we can squash the seqno to zero.
|
||||
|
||||
// This is safe for TransactionDB write-conflict checking since transactions
|
||||
// only care about sequence number larger than any active snapshots.
|
||||
if (bottommost_level_ && valid_ && ikey_.sequence < earliest_snapshot_ &&
|
||||
ikey_.type != kTypeMerge) {
|
||||
ikey_.type != kTypeMerge &&
|
||||
!cmp_->Equal(compaction_->GetLargestUserKey(), ikey_.user_key)) {
|
||||
assert(ikey_.type != kTypeDeletion && ikey_.type != kTypeSingleDeletion);
|
||||
ikey_.sequence = 0;
|
||||
current_key_.UpdateInternalKey(0, ikey_.type);
|
||||
|
||||
@@ -39,7 +39,8 @@ class CompactionIterator {
|
||||
public:
|
||||
CompactionIterator(InternalIterator* input, const Comparator* cmp,
|
||||
MergeHelper* merge_helper, SequenceNumber last_sequence,
|
||||
std::vector<SequenceNumber>* snapshots, Env* env,
|
||||
std::vector<SequenceNumber>* snapshots,
|
||||
SequenceNumber earliest_write_conflict_snapshot, Env* env,
|
||||
bool expect_valid_internal_key,
|
||||
Compaction* compaction = nullptr,
|
||||
const CompactionFilter* compaction_filter = nullptr,
|
||||
@@ -88,6 +89,7 @@ class CompactionIterator {
|
||||
const Comparator* cmp_;
|
||||
MergeHelper* merge_helper_;
|
||||
const std::vector<SequenceNumber>* snapshots_;
|
||||
const SequenceNumber earliest_write_conflict_snapshot_;
|
||||
Env* env_;
|
||||
bool expect_valid_internal_key_;
|
||||
Compaction* compaction_;
|
||||
@@ -98,6 +100,7 @@ class CompactionIterator {
|
||||
SequenceNumber visible_at_tip_;
|
||||
SequenceNumber earliest_snapshot_;
|
||||
SequenceNumber latest_snapshot_;
|
||||
bool ignore_snapshots_;
|
||||
|
||||
// State
|
||||
//
|
||||
@@ -124,6 +127,14 @@ class CompactionIterator {
|
||||
Slice current_user_key_;
|
||||
SequenceNumber current_user_key_sequence_;
|
||||
SequenceNumber current_user_key_snapshot_;
|
||||
|
||||
// True if the iterator has already returned a record for the current key.
|
||||
bool has_outputted_key_ = false;
|
||||
|
||||
// truncated the value of the next key and output it without applying any
|
||||
// compaction rules. This is used for outputting a put after a single delete.
|
||||
bool clear_and_output_next_key_ = false;
|
||||
|
||||
MergeOutputIterator merge_out_iter_;
|
||||
std::string compaction_filter_value_;
|
||||
// "level_ptrs" holds indices that remember which file of an associated
|
||||
|
||||
@@ -20,9 +20,9 @@ class CompactionIteratorTest : public testing::Test {
|
||||
nullptr, 0U, false, 0));
|
||||
iter_.reset(new test::VectorIterator(ks, vs));
|
||||
iter_->SeekToFirst();
|
||||
c_iter_.reset(new CompactionIterator(iter_.get(), cmp_, merge_helper_.get(),
|
||||
last_sequence, &snapshots_,
|
||||
Env::Default(), false));
|
||||
c_iter_.reset(new CompactionIterator(
|
||||
iter_.get(), cmp_, merge_helper_.get(), last_sequence, &snapshots_,
|
||||
kMaxSequenceNumber, Env::Default(), false));
|
||||
}
|
||||
|
||||
const Comparator* cmp_;
|
||||
|
||||
+30
-21
@@ -212,6 +212,7 @@ CompactionJob::CompactionJob(
|
||||
std::atomic<bool>* shutting_down, LogBuffer* log_buffer,
|
||||
Directory* db_directory, Directory* output_directory, Statistics* stats,
|
||||
std::vector<SequenceNumber> existing_snapshots,
|
||||
SequenceNumber earliest_write_conflict_snapshot,
|
||||
std::shared_ptr<Cache> table_cache, EventLogger* event_logger,
|
||||
bool paranoid_file_checks, bool measure_io_stats, const std::string& dbname,
|
||||
CompactionJobStats* compaction_job_stats)
|
||||
@@ -230,6 +231,7 @@ CompactionJob::CompactionJob(
|
||||
output_directory_(output_directory),
|
||||
stats_(stats),
|
||||
existing_snapshots_(std::move(existing_snapshots)),
|
||||
earliest_write_conflict_snapshot_(earliest_write_conflict_snapshot),
|
||||
table_cache_(std::move(table_cache)),
|
||||
event_logger_(event_logger),
|
||||
paranoid_file_checks_(paranoid_file_checks),
|
||||
@@ -332,11 +334,6 @@ struct RangeWithSize {
|
||||
: range(a, b), size(s) {}
|
||||
};
|
||||
|
||||
bool SliceCompare(const Comparator* cmp, const Slice& a, const Slice& b) {
|
||||
// Returns true if a < b
|
||||
return cmp->Compare(ExtractUserKey(a), ExtractUserKey(b)) < 0;
|
||||
}
|
||||
|
||||
// Generates a histogram representing potential divisions of key ranges from
|
||||
// the input. It adds the starting and/or ending keys of certain input files
|
||||
// to the working set and then finds the approximate size of data in between
|
||||
@@ -345,14 +342,13 @@ bool SliceCompare(const Comparator* cmp, const Slice& a, const Slice& b) {
|
||||
void CompactionJob::GenSubcompactionBoundaries() {
|
||||
auto* c = compact_->compaction;
|
||||
auto* cfd = c->column_family_data();
|
||||
std::set<Slice, std::function<bool(const Slice& a, const Slice& b)> > bounds(
|
||||
std::bind(&SliceCompare, cfd->user_comparator(), std::placeholders::_1,
|
||||
std::placeholders::_2));
|
||||
const Comparator* cfd_comparator = cfd->user_comparator();
|
||||
std::vector<Slice> bounds;
|
||||
int start_lvl = c->start_level();
|
||||
int out_lvl = c->output_level();
|
||||
|
||||
// Add the starting and/or ending key of certain input files as a potential
|
||||
// boundary (because we're inserting into a set, it avoids duplicates)
|
||||
// boundary
|
||||
for (size_t lvl_idx = 0; lvl_idx < c->num_input_levels(); lvl_idx++) {
|
||||
int lvl = c->level(lvl_idx);
|
||||
if (lvl >= start_lvl && lvl <= out_lvl) {
|
||||
@@ -367,27 +363,37 @@ void CompactionJob::GenSubcompactionBoundaries() {
|
||||
// For level 0 add the starting and ending key of each file since the
|
||||
// files may have greatly differing key ranges (not range-partitioned)
|
||||
for (size_t i = 0; i < num_files; i++) {
|
||||
bounds.emplace(flevel->files[i].smallest_key);
|
||||
bounds.emplace(flevel->files[i].largest_key);
|
||||
bounds.emplace_back(flevel->files[i].smallest_key);
|
||||
bounds.emplace_back(flevel->files[i].largest_key);
|
||||
}
|
||||
} else {
|
||||
// For all other levels add the smallest/largest key in the level to
|
||||
// encompass the range covered by that level
|
||||
bounds.emplace(flevel->files[0].smallest_key);
|
||||
bounds.emplace(flevel->files[num_files - 1].largest_key);
|
||||
bounds.emplace_back(flevel->files[0].smallest_key);
|
||||
bounds.emplace_back(flevel->files[num_files - 1].largest_key);
|
||||
if (lvl == out_lvl) {
|
||||
// For the last level include the starting keys of all files since
|
||||
// the last level is the largest and probably has the widest key
|
||||
// range. Since it's range partitioned, the ending key of one file
|
||||
// and the starting key of the next are very close (or identical).
|
||||
for (size_t i = 1; i < num_files; i++) {
|
||||
bounds.emplace(flevel->files[i].smallest_key);
|
||||
bounds.emplace_back(flevel->files[i].smallest_key);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::sort(bounds.begin(), bounds.end(),
|
||||
[cfd_comparator] (const Slice& a, const Slice& b) -> bool {
|
||||
return cfd_comparator->Compare(ExtractUserKey(a), ExtractUserKey(b)) < 0;
|
||||
});
|
||||
// Remove duplicated entries from bounds
|
||||
bounds.erase(std::unique(bounds.begin(), bounds.end(),
|
||||
[cfd_comparator] (const Slice& a, const Slice& b) -> bool {
|
||||
return cfd_comparator->Compare(ExtractUserKey(a), ExtractUserKey(b)) == 0;
|
||||
}), bounds.end());
|
||||
|
||||
// Combine consecutive pairs of boundaries into ranges with an approximate
|
||||
// size of data covered by keys in that range
|
||||
uint64_t sum = 0;
|
||||
@@ -409,9 +415,12 @@ void CompactionJob::GenSubcompactionBoundaries() {
|
||||
|
||||
// Group the ranges into subcompactions
|
||||
const double min_file_fill_percent = 4.0 / 5;
|
||||
uint64_t max_output_files = std::ceil(
|
||||
sum / min_file_fill_percent /
|
||||
cfd->GetCurrentMutableCFOptions()->MaxFileSizeForLevel(out_lvl));
|
||||
uint64_t max_output_files =
|
||||
static_cast<uint64_t>(
|
||||
std::ceil(
|
||||
sum / min_file_fill_percent /
|
||||
cfd->GetCurrentMutableCFOptions()->MaxFileSizeForLevel(out_lvl))
|
||||
);
|
||||
uint64_t subcompactions =
|
||||
std::min({static_cast<uint64_t>(ranges.size()),
|
||||
static_cast<uint64_t>(db_options_.max_subcompactions),
|
||||
@@ -424,12 +433,12 @@ void CompactionJob::GenSubcompactionBoundaries() {
|
||||
// sizes becomes >= the expected mean size of a subcompaction
|
||||
sum = 0;
|
||||
for (size_t i = 0; i < ranges.size() - 1; i++) {
|
||||
sum += ranges[i].size;
|
||||
if (subcompactions == 1) {
|
||||
// If there's only one left to schedule then it goes to the end so no
|
||||
// need to put an end boundary
|
||||
break;
|
||||
continue;
|
||||
}
|
||||
sum += ranges[i].size;
|
||||
if (sum >= mean) {
|
||||
boundaries_.emplace_back(ExtractUserKey(ranges[i].range.limit));
|
||||
sizes_.emplace_back(sum);
|
||||
@@ -638,8 +647,8 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
|
||||
Status status;
|
||||
sub_compact->c_iter.reset(new CompactionIterator(
|
||||
input.get(), cfd->user_comparator(), &merge, versions_->LastSequence(),
|
||||
&existing_snapshots_, env_, false, sub_compact->compaction,
|
||||
compaction_filter));
|
||||
&existing_snapshots_, earliest_write_conflict_snapshot_, env_, false,
|
||||
sub_compact->compaction, compaction_filter));
|
||||
auto c_iter = sub_compact->c_iter.get();
|
||||
c_iter->SeekToFirst();
|
||||
const auto& c_iter_stats = c_iter->iter_stats();
|
||||
|
||||
@@ -58,6 +58,7 @@ class CompactionJob {
|
||||
Directory* db_directory, Directory* output_directory,
|
||||
Statistics* stats,
|
||||
std::vector<SequenceNumber> existing_snapshots,
|
||||
SequenceNumber earliest_write_conflict_snapshot,
|
||||
std::shared_ptr<Cache> table_cache, EventLogger* event_logger,
|
||||
bool paranoid_file_checks, bool measure_io_stats,
|
||||
const std::string& dbname,
|
||||
@@ -134,6 +135,12 @@ class CompactionJob {
|
||||
// entirely within s1 and s2, then the earlier version of k1 can be safely
|
||||
// deleted because that version is not visible in any snapshot.
|
||||
std::vector<SequenceNumber> existing_snapshots_;
|
||||
|
||||
// This is the earliest snapshot that could be used for write-conflict
|
||||
// checking by a transaction. For any user-key newer than this snapshot, we
|
||||
// should make sure not to remove evidence that a write occurred.
|
||||
SequenceNumber earliest_write_conflict_snapshot_;
|
||||
|
||||
std::shared_ptr<Cache> table_cache_;
|
||||
|
||||
EventLogger* event_logger_;
|
||||
|
||||
@@ -551,8 +551,9 @@ uint64_t EstimatedFileSize(
|
||||
const size_t kFooterSize = 512;
|
||||
|
||||
uint64_t data_size =
|
||||
static_cast<uint64_t>(
|
||||
num_records * (key_size + value_size * compression_ratio +
|
||||
kPerKeyOverhead);
|
||||
kPerKeyOverhead));
|
||||
|
||||
return data_size + kFooterSize
|
||||
+ num_records * bloom_bits_per_key / 8 // filter block
|
||||
|
||||
+260
-41
@@ -162,9 +162,12 @@ class CompactionJobTest : public testing::Test {
|
||||
auto key = ToString(i * kMatchingKeys + k);
|
||||
auto value = ToString(i * kKeysPerFile + k);
|
||||
InternalKey internal_key(key, ++sequence_number, kTypeValue);
|
||||
|
||||
// This is how the key will look like once it's written in bottommost
|
||||
// file
|
||||
InternalKey bottommost_internal_key(key, 0, kTypeValue);
|
||||
InternalKey bottommost_internal_key(
|
||||
key, (key == "9999") ? sequence_number : 0, kTypeValue);
|
||||
|
||||
if (corrupt_id(k)) {
|
||||
test::CorruptKeyType(&internal_key);
|
||||
test::CorruptKeyType(&bottommost_internal_key);
|
||||
@@ -217,9 +220,11 @@ class CompactionJobTest : public testing::Test {
|
||||
cfd_ = versions_->GetColumnFamilySet()->GetDefault();
|
||||
}
|
||||
|
||||
void RunCompaction(const std::vector<std::vector<FileMetaData*>>& input_files,
|
||||
const stl_wrappers::KVMap& expected_results,
|
||||
const std::vector<SequenceNumber>& snapshots = {}) {
|
||||
void RunCompaction(
|
||||
const std::vector<std::vector<FileMetaData*>>& input_files,
|
||||
const stl_wrappers::KVMap& expected_results,
|
||||
const std::vector<SequenceNumber>& snapshots = {},
|
||||
SequenceNumber earliest_write_conflict_snapshot = kMaxSequenceNumber) {
|
||||
auto cfd = versions_->GetColumnFamilySet()->GetDefault();
|
||||
|
||||
size_t num_input_files = 0;
|
||||
@@ -243,11 +248,11 @@ class CompactionJobTest : public testing::Test {
|
||||
LogBuffer log_buffer(InfoLogLevel::INFO_LEVEL, db_options_.info_log.get());
|
||||
mutex_.Lock();
|
||||
EventLogger event_logger(db_options_.info_log.get());
|
||||
CompactionJob compaction_job(0, &compaction, db_options_, env_options_,
|
||||
versions_.get(), &shutting_down_, &log_buffer,
|
||||
nullptr, nullptr, nullptr, snapshots,
|
||||
table_cache_, &event_logger, false, false,
|
||||
dbname_, &compaction_job_stats_);
|
||||
CompactionJob compaction_job(
|
||||
0, &compaction, db_options_, env_options_, versions_.get(),
|
||||
&shutting_down_, &log_buffer, nullptr, nullptr, nullptr, snapshots,
|
||||
earliest_write_conflict_snapshot, table_cache_, &event_logger, false,
|
||||
false, dbname_, &compaction_job_stats_);
|
||||
|
||||
VerifyInitializationOfCompactionJobStats(compaction_job_stats_);
|
||||
|
||||
@@ -346,7 +351,7 @@ TEST_F(CompactionJobTest, SimpleOverwrite) {
|
||||
|
||||
auto expected_results =
|
||||
mock::MakeMockFile({{KeyStr("a", 0U, kTypeValue), "val2"},
|
||||
{KeyStr("b", 0U, kTypeValue), "val3"}});
|
||||
{KeyStr("b", 4U, kTypeValue), "val3"}});
|
||||
|
||||
SetLastSequence(4U);
|
||||
auto files = cfd_->current()->storage_info()->LevelFiles(0);
|
||||
@@ -399,7 +404,7 @@ TEST_F(CompactionJobTest, SimpleMerge) {
|
||||
|
||||
auto expected_results =
|
||||
mock::MakeMockFile({{KeyStr("a", 0U, kTypeValue), "3,4,5"},
|
||||
{KeyStr("b", 0U, kTypeValue), "1,2"}});
|
||||
{KeyStr("b", 2U, kTypeValue), "1,2"}});
|
||||
|
||||
SetLastSequence(5U);
|
||||
auto files = cfd_->current()->storage_info()->LevelFiles(0);
|
||||
@@ -559,34 +564,144 @@ TEST_F(CompactionJobTest, SimpleSingleDelete) {
|
||||
TEST_F(CompactionJobTest, SingleDeleteSnapshots) {
|
||||
NewDB();
|
||||
|
||||
auto file1 = mock::MakeMockFile({{KeyStr("A", 12U, kTypeSingleDeletion), ""},
|
||||
{KeyStr("a", 12U, kTypeSingleDeletion), ""},
|
||||
{KeyStr("b", 21U, kTypeSingleDeletion), ""},
|
||||
{KeyStr("c", 22U, kTypeSingleDeletion), ""},
|
||||
{KeyStr("d", 9U, kTypeSingleDeletion), ""}});
|
||||
auto file1 = mock::MakeMockFile({
|
||||
{KeyStr("A", 12U, kTypeSingleDeletion), ""},
|
||||
{KeyStr("a", 12U, kTypeSingleDeletion), ""},
|
||||
{KeyStr("b", 21U, kTypeSingleDeletion), ""},
|
||||
{KeyStr("c", 22U, kTypeSingleDeletion), ""},
|
||||
{KeyStr("d", 9U, kTypeSingleDeletion), ""},
|
||||
{KeyStr("f", 21U, kTypeSingleDeletion), ""},
|
||||
{KeyStr("j", 11U, kTypeSingleDeletion), ""},
|
||||
{KeyStr("j", 9U, kTypeSingleDeletion), ""},
|
||||
{KeyStr("k", 12U, kTypeSingleDeletion), ""},
|
||||
{KeyStr("k", 11U, kTypeSingleDeletion), ""},
|
||||
{KeyStr("l", 3U, kTypeSingleDeletion), ""},
|
||||
{KeyStr("l", 2U, kTypeSingleDeletion), ""},
|
||||
});
|
||||
AddMockFile(file1);
|
||||
|
||||
auto file2 = mock::MakeMockFile({{KeyStr("0", 2U, kTypeSingleDeletion), ""},
|
||||
{KeyStr("a", 11U, kTypeValue), "val1"},
|
||||
{KeyStr("b", 11U, kTypeValue), "val2"},
|
||||
{KeyStr("c", 21U, kTypeValue), "val3"},
|
||||
{KeyStr("d", 8U, kTypeValue), "val4"},
|
||||
{KeyStr("e", 2U, kTypeSingleDeletion), ""}});
|
||||
auto file2 = mock::MakeMockFile({
|
||||
{KeyStr("0", 2U, kTypeSingleDeletion), ""},
|
||||
{KeyStr("a", 11U, kTypeValue), "val1"},
|
||||
{KeyStr("b", 11U, kTypeValue), "val2"},
|
||||
{KeyStr("c", 21U, kTypeValue), "val3"},
|
||||
{KeyStr("d", 8U, kTypeValue), "val4"},
|
||||
{KeyStr("e", 2U, kTypeSingleDeletion), ""},
|
||||
{KeyStr("f", 1U, kTypeValue), "val1"},
|
||||
{KeyStr("g", 11U, kTypeSingleDeletion), ""},
|
||||
{KeyStr("h", 2U, kTypeSingleDeletion), ""},
|
||||
{KeyStr("m", 12U, kTypeValue), "val1"},
|
||||
{KeyStr("m", 11U, kTypeSingleDeletion), ""},
|
||||
{KeyStr("m", 8U, kTypeValue), "val2"},
|
||||
});
|
||||
AddMockFile(file2);
|
||||
|
||||
auto file3 = mock::MakeMockFile({{KeyStr("A", 1U, kTypeValue), "val"},
|
||||
{KeyStr("e", 1U, kTypeValue), "val"}});
|
||||
auto file3 = mock::MakeMockFile({
|
||||
{KeyStr("A", 1U, kTypeValue), "val"},
|
||||
{KeyStr("e", 1U, kTypeValue), "val"},
|
||||
});
|
||||
AddMockFile(file3, 2);
|
||||
|
||||
auto expected_results =
|
||||
mock::MakeMockFile({{KeyStr("A", 12U, kTypeSingleDeletion), ""},
|
||||
{KeyStr("b", 21U, kTypeSingleDeletion), ""},
|
||||
{KeyStr("b", 11U, kTypeValue), "val2"},
|
||||
{KeyStr("e", 2U, kTypeSingleDeletion), ""}});
|
||||
auto expected_results = mock::MakeMockFile({
|
||||
{KeyStr("A", 12U, kTypeSingleDeletion), ""},
|
||||
{KeyStr("a", 12U, kTypeSingleDeletion), ""},
|
||||
{KeyStr("a", 11U, kTypeValue), ""},
|
||||
{KeyStr("b", 21U, kTypeSingleDeletion), ""},
|
||||
{KeyStr("b", 11U, kTypeValue), "val2"},
|
||||
{KeyStr("c", 22U, kTypeSingleDeletion), ""},
|
||||
{KeyStr("c", 21U, kTypeValue), ""},
|
||||
{KeyStr("e", 2U, kTypeSingleDeletion), ""},
|
||||
{KeyStr("f", 21U, kTypeSingleDeletion), ""},
|
||||
{KeyStr("f", 1U, kTypeValue), "val1"},
|
||||
{KeyStr("g", 11U, kTypeSingleDeletion), ""},
|
||||
{KeyStr("j", 11U, kTypeSingleDeletion), ""},
|
||||
{KeyStr("k", 11U, kTypeSingleDeletion), ""},
|
||||
{KeyStr("m", 12U, kTypeValue), "val1"},
|
||||
{KeyStr("m", 11U, kTypeSingleDeletion), ""},
|
||||
{KeyStr("m", 8U, kTypeValue), "val2"},
|
||||
});
|
||||
|
||||
SetLastSequence(22U);
|
||||
auto files = cfd_->current()->storage_info()->LevelFiles(0);
|
||||
RunCompaction({files}, expected_results, {10U, 20U});
|
||||
RunCompaction({files}, expected_results, {10U, 20U}, 10U);
|
||||
}
|
||||
|
||||
TEST_F(CompactionJobTest, EarliestWriteConflictSnapshot) {
|
||||
NewDB();
|
||||
|
||||
// Test multiple snapshots where the earliest snapshot is not a
|
||||
// write-conflic-snapshot.
|
||||
|
||||
auto file1 = mock::MakeMockFile({
|
||||
{KeyStr("A", 24U, kTypeSingleDeletion), ""},
|
||||
{KeyStr("A", 23U, kTypeValue), "val"},
|
||||
{KeyStr("B", 24U, kTypeSingleDeletion), ""},
|
||||
{KeyStr("B", 23U, kTypeValue), "val"},
|
||||
{KeyStr("D", 24U, kTypeSingleDeletion), ""},
|
||||
{KeyStr("G", 32U, kTypeSingleDeletion), ""},
|
||||
{KeyStr("G", 31U, kTypeValue), "val"},
|
||||
{KeyStr("G", 24U, kTypeSingleDeletion), ""},
|
||||
{KeyStr("G", 23U, kTypeValue), "val2"},
|
||||
{KeyStr("H", 31U, kTypeValue), "val"},
|
||||
{KeyStr("H", 24U, kTypeSingleDeletion), ""},
|
||||
{KeyStr("H", 23U, kTypeValue), "val"},
|
||||
{KeyStr("I", 35U, kTypeSingleDeletion), ""},
|
||||
{KeyStr("I", 34U, kTypeValue), "val2"},
|
||||
{KeyStr("I", 33U, kTypeSingleDeletion), ""},
|
||||
{KeyStr("I", 32U, kTypeValue), "val3"},
|
||||
{KeyStr("I", 31U, kTypeSingleDeletion), ""},
|
||||
{KeyStr("J", 34U, kTypeValue), "val"},
|
||||
{KeyStr("J", 33U, kTypeSingleDeletion), ""},
|
||||
{KeyStr("J", 25U, kTypeValue), "val2"},
|
||||
{KeyStr("J", 24U, kTypeSingleDeletion), ""},
|
||||
});
|
||||
AddMockFile(file1);
|
||||
|
||||
auto file2 = mock::MakeMockFile({
|
||||
{KeyStr("A", 14U, kTypeSingleDeletion), ""},
|
||||
{KeyStr("A", 13U, kTypeValue), "val2"},
|
||||
{KeyStr("C", 14U, kTypeSingleDeletion), ""},
|
||||
{KeyStr("C", 13U, kTypeValue), "val"},
|
||||
{KeyStr("E", 12U, kTypeSingleDeletion), ""},
|
||||
{KeyStr("F", 4U, kTypeSingleDeletion), ""},
|
||||
{KeyStr("F", 3U, kTypeValue), "val"},
|
||||
{KeyStr("G", 14U, kTypeSingleDeletion), ""},
|
||||
{KeyStr("G", 13U, kTypeValue), "val3"},
|
||||
{KeyStr("H", 14U, kTypeSingleDeletion), ""},
|
||||
{KeyStr("H", 13U, kTypeValue), "val2"},
|
||||
{KeyStr("I", 13U, kTypeValue), "val4"},
|
||||
{KeyStr("I", 12U, kTypeSingleDeletion), ""},
|
||||
{KeyStr("I", 11U, kTypeValue), "val5"},
|
||||
{KeyStr("J", 15U, kTypeValue), "val3"},
|
||||
{KeyStr("J", 14U, kTypeSingleDeletion), ""},
|
||||
});
|
||||
AddMockFile(file2);
|
||||
|
||||
auto expected_results = mock::MakeMockFile({
|
||||
{KeyStr("A", 24U, kTypeSingleDeletion), ""},
|
||||
{KeyStr("A", 23U, kTypeValue), ""},
|
||||
{KeyStr("B", 24U, kTypeSingleDeletion), ""},
|
||||
{KeyStr("B", 23U, kTypeValue), ""},
|
||||
{KeyStr("D", 24U, kTypeSingleDeletion), ""},
|
||||
{KeyStr("E", 12U, kTypeSingleDeletion), ""},
|
||||
{KeyStr("G", 32U, kTypeSingleDeletion), ""},
|
||||
{KeyStr("G", 31U, kTypeValue), ""},
|
||||
{KeyStr("H", 31U, kTypeValue), "val"},
|
||||
{KeyStr("I", 35U, kTypeSingleDeletion), ""},
|
||||
{KeyStr("I", 34U, kTypeValue), ""},
|
||||
{KeyStr("I", 31U, kTypeSingleDeletion), ""},
|
||||
{KeyStr("I", 13U, kTypeValue), "val4"},
|
||||
{KeyStr("J", 34U, kTypeValue), "val"},
|
||||
{KeyStr("J", 33U, kTypeSingleDeletion), ""},
|
||||
{KeyStr("J", 25U, kTypeValue), "val2"},
|
||||
{KeyStr("J", 24U, kTypeSingleDeletion), ""},
|
||||
{KeyStr("J", 15U, kTypeValue), "val3"},
|
||||
{KeyStr("J", 14U, kTypeSingleDeletion), ""},
|
||||
});
|
||||
|
||||
SetLastSequence(24U);
|
||||
auto files = cfd_->current()->storage_info()->LevelFiles(0);
|
||||
RunCompaction({files}, expected_results, {10U, 20U, 30U}, 20U);
|
||||
}
|
||||
|
||||
TEST_F(CompactionJobTest, SingleDeleteZeroSeq) {
|
||||
@@ -604,7 +719,7 @@ TEST_F(CompactionJobTest, SingleDeleteZeroSeq) {
|
||||
AddMockFile(file2);
|
||||
|
||||
auto expected_results = mock::MakeMockFile({
|
||||
{KeyStr("dummy", 0U, kTypeValue), "val2"},
|
||||
{KeyStr("dummy", 5U, kTypeValue), "val2"},
|
||||
});
|
||||
|
||||
SetLastSequence(22U);
|
||||
@@ -616,9 +731,22 @@ TEST_F(CompactionJobTest, MultiSingleDelete) {
|
||||
// Tests three scenarios involving multiple single delete/put pairs:
|
||||
//
|
||||
// A: Put Snapshot SDel Put SDel -> Put Snapshot SDel
|
||||
// B: Put SDel Put SDel -> (Removed)
|
||||
// B: Snapshot Put SDel Put SDel Snapshot -> Snapshot SDel Snapshot
|
||||
// C: SDel Put SDel Snapshot Put -> Snapshot Put
|
||||
// D: (Put) SDel Snapshot Put SDel -> (Put) SDel Snapshot
|
||||
// D: (Put) SDel Snapshot Put SDel -> (Put) SDel Snapshot SDel
|
||||
// E: Put SDel Snapshot Put SDel -> Snapshot SDel
|
||||
// F: Put SDel Put Sdel Snapshot -> removed
|
||||
// G: Snapshot SDel Put SDel Put -> Snapshot Put SDel
|
||||
// H: (Put) Put SDel Put Sdel Snapshot -> Removed
|
||||
// I: (Put) Snapshot Put SDel Put SDel -> SDel
|
||||
// J: Put Put SDel Put SDel SDel Snapshot Put Put SDel SDel Put
|
||||
// -> Snapshot Put
|
||||
// K: SDel SDel Put SDel Put Put Snapshot SDel Put SDel SDel Put SDel
|
||||
// -> Snapshot Put Snapshot SDel
|
||||
// L: SDel Put Del Put SDel Snapshot Del Put Del SDel Put SDel
|
||||
// -> Snapshot SDel
|
||||
// M: (Put) SDel Put Del Put SDel Snapshot Put Del SDel Put SDel Del
|
||||
// -> SDel Snapshot Del
|
||||
NewDB();
|
||||
|
||||
auto file1 = mock::MakeMockFile({
|
||||
@@ -630,6 +758,34 @@ TEST_F(CompactionJobTest, MultiSingleDelete) {
|
||||
{KeyStr("C", 14U, kTypeValue), "val3"},
|
||||
{KeyStr("D", 12U, kTypeSingleDeletion), ""},
|
||||
{KeyStr("D", 11U, kTypeValue), "val4"},
|
||||
{KeyStr("G", 15U, kTypeValue), "val"},
|
||||
{KeyStr("G", 14U, kTypeSingleDeletion), ""},
|
||||
{KeyStr("G", 13U, kTypeValue), "val"},
|
||||
{KeyStr("I", 14U, kTypeSingleDeletion), ""},
|
||||
{KeyStr("I", 13U, kTypeValue), "val"},
|
||||
{KeyStr("J", 15U, kTypeValue), "val"},
|
||||
{KeyStr("J", 14U, kTypeSingleDeletion), ""},
|
||||
{KeyStr("J", 13U, kTypeSingleDeletion), ""},
|
||||
{KeyStr("J", 12U, kTypeValue), "val"},
|
||||
{KeyStr("J", 11U, kTypeValue), "val"},
|
||||
{KeyStr("K", 16U, kTypeSingleDeletion), ""},
|
||||
{KeyStr("K", 15U, kTypeValue), "val1"},
|
||||
{KeyStr("K", 14U, kTypeSingleDeletion), ""},
|
||||
{KeyStr("K", 13U, kTypeSingleDeletion), ""},
|
||||
{KeyStr("K", 12U, kTypeValue), "val2"},
|
||||
{KeyStr("K", 11U, kTypeSingleDeletion), ""},
|
||||
{KeyStr("L", 16U, kTypeSingleDeletion), ""},
|
||||
{KeyStr("L", 15U, kTypeValue), "val"},
|
||||
{KeyStr("L", 14U, kTypeSingleDeletion), ""},
|
||||
{KeyStr("L", 13U, kTypeDeletion), ""},
|
||||
{KeyStr("L", 12U, kTypeValue), "val"},
|
||||
{KeyStr("L", 11U, kTypeDeletion), ""},
|
||||
{KeyStr("M", 16U, kTypeDeletion), ""},
|
||||
{KeyStr("M", 15U, kTypeSingleDeletion), ""},
|
||||
{KeyStr("M", 14U, kTypeValue), "val"},
|
||||
{KeyStr("M", 13U, kTypeSingleDeletion), ""},
|
||||
{KeyStr("M", 12U, kTypeDeletion), ""},
|
||||
{KeyStr("M", 11U, kTypeValue), "val"},
|
||||
});
|
||||
AddMockFile(file1);
|
||||
|
||||
@@ -641,24 +797,87 @@ TEST_F(CompactionJobTest, MultiSingleDelete) {
|
||||
{KeyStr("C", 9U, kTypeValue), "val6"},
|
||||
{KeyStr("C", 8U, kTypeSingleDeletion), ""},
|
||||
{KeyStr("D", 10U, kTypeSingleDeletion), ""},
|
||||
{KeyStr("E", 12U, kTypeSingleDeletion), ""},
|
||||
{KeyStr("E", 11U, kTypeValue), "val"},
|
||||
{KeyStr("E", 5U, kTypeSingleDeletion), ""},
|
||||
{KeyStr("E", 4U, kTypeValue), "val"},
|
||||
{KeyStr("F", 6U, kTypeSingleDeletion), ""},
|
||||
{KeyStr("F", 5U, kTypeValue), "val"},
|
||||
{KeyStr("F", 4U, kTypeSingleDeletion), ""},
|
||||
{KeyStr("F", 3U, kTypeValue), "val"},
|
||||
{KeyStr("G", 12U, kTypeSingleDeletion), ""},
|
||||
{KeyStr("H", 6U, kTypeSingleDeletion), ""},
|
||||
{KeyStr("H", 5U, kTypeValue), "val"},
|
||||
{KeyStr("H", 4U, kTypeSingleDeletion), ""},
|
||||
{KeyStr("H", 3U, kTypeValue), "val"},
|
||||
{KeyStr("I", 12U, kTypeSingleDeletion), ""},
|
||||
{KeyStr("I", 11U, kTypeValue), "val"},
|
||||
{KeyStr("J", 6U, kTypeSingleDeletion), ""},
|
||||
{KeyStr("J", 5U, kTypeSingleDeletion), ""},
|
||||
{KeyStr("J", 4U, kTypeValue), "val"},
|
||||
{KeyStr("J", 3U, kTypeSingleDeletion), ""},
|
||||
{KeyStr("J", 2U, kTypeValue), "val"},
|
||||
{KeyStr("K", 8U, kTypeValue), "val3"},
|
||||
{KeyStr("K", 7U, kTypeValue), "val4"},
|
||||
{KeyStr("K", 6U, kTypeSingleDeletion), ""},
|
||||
{KeyStr("K", 5U, kTypeValue), "val5"},
|
||||
{KeyStr("K", 2U, kTypeSingleDeletion), ""},
|
||||
{KeyStr("K", 1U, kTypeSingleDeletion), ""},
|
||||
{KeyStr("L", 5U, kTypeSingleDeletion), ""},
|
||||
{KeyStr("L", 4U, kTypeValue), "val"},
|
||||
{KeyStr("L", 3U, kTypeDeletion), ""},
|
||||
{KeyStr("L", 2U, kTypeValue), "val"},
|
||||
{KeyStr("L", 1U, kTypeSingleDeletion), ""},
|
||||
{KeyStr("M", 10U, kTypeSingleDeletion), ""},
|
||||
{KeyStr("M", 7U, kTypeValue), "val"},
|
||||
{KeyStr("M", 5U, kTypeDeletion), ""},
|
||||
{KeyStr("M", 4U, kTypeValue), "val"},
|
||||
{KeyStr("M", 3U, kTypeSingleDeletion), ""},
|
||||
});
|
||||
AddMockFile(file2);
|
||||
|
||||
auto file3 = mock::MakeMockFile({
|
||||
{KeyStr("D", 11U, kTypeValue), "val"},
|
||||
{KeyStr("D", 1U, kTypeValue), "val"},
|
||||
{KeyStr("H", 1U, kTypeValue), "val"},
|
||||
{KeyStr("I", 2U, kTypeValue), "val"},
|
||||
});
|
||||
AddMockFile(file3, 2);
|
||||
|
||||
auto expected_results = mock::MakeMockFile({
|
||||
{KeyStr("A", 12U, kTypeSingleDeletion), ""},
|
||||
{KeyStr("A", 10U, kTypeValue), "val"},
|
||||
{KeyStr("C", 14U, kTypeValue), "val3"},
|
||||
{KeyStr("D", 10U, kTypeSingleDeletion), ""},
|
||||
auto file4 = mock::MakeMockFile({
|
||||
{KeyStr("M", 1U, kTypeValue), "val"},
|
||||
});
|
||||
AddMockFile(file4, 2);
|
||||
|
||||
auto expected_results =
|
||||
mock::MakeMockFile({{KeyStr("A", 14U, kTypeSingleDeletion), ""},
|
||||
{KeyStr("A", 13U, kTypeValue), ""},
|
||||
{KeyStr("A", 12U, kTypeSingleDeletion), ""},
|
||||
{KeyStr("A", 10U, kTypeValue), "val"},
|
||||
{KeyStr("B", 14U, kTypeSingleDeletion), ""},
|
||||
{KeyStr("B", 13U, kTypeValue), ""},
|
||||
{KeyStr("C", 14U, kTypeValue), "val3"},
|
||||
{KeyStr("D", 12U, kTypeSingleDeletion), ""},
|
||||
{KeyStr("D", 11U, kTypeValue), ""},
|
||||
{KeyStr("D", 10U, kTypeSingleDeletion), ""},
|
||||
{KeyStr("E", 12U, kTypeSingleDeletion), ""},
|
||||
{KeyStr("E", 11U, kTypeValue), ""},
|
||||
{KeyStr("G", 15U, kTypeValue), "val"},
|
||||
{KeyStr("G", 12U, kTypeSingleDeletion), ""},
|
||||
{KeyStr("I", 14U, kTypeSingleDeletion), ""},
|
||||
{KeyStr("I", 13U, kTypeValue), ""},
|
||||
{KeyStr("J", 15U, kTypeValue), "val"},
|
||||
{KeyStr("K", 16U, kTypeSingleDeletion), ""},
|
||||
{KeyStr("K", 15U, kTypeValue), ""},
|
||||
{KeyStr("K", 11U, kTypeSingleDeletion), ""},
|
||||
{KeyStr("K", 8U, kTypeValue), "val3"},
|
||||
{KeyStr("L", 16U, kTypeSingleDeletion), ""},
|
||||
{KeyStr("L", 15U, kTypeValue), ""},
|
||||
{KeyStr("M", 16U, kTypeDeletion), ""},
|
||||
{KeyStr("M", 3U, kTypeSingleDeletion), ""}});
|
||||
|
||||
SetLastSequence(22U);
|
||||
auto files = cfd_->current()->storage_info()->LevelFiles(0);
|
||||
RunCompaction({files}, expected_results, {10U});
|
||||
RunCompaction({files}, expected_results, {10U}, 10U);
|
||||
}
|
||||
|
||||
// This test documents the behavior where a corrupt key follows a deletion or a
|
||||
@@ -684,7 +903,7 @@ TEST_F(CompactionJobTest, CorruptionAfterDeletion) {
|
||||
mock::MakeMockFile({{test::KeyStr("A", 0U, kTypeValue), "val3"},
|
||||
{test::KeyStr("a", 0U, kTypeValue, true), "val"},
|
||||
{test::KeyStr("b", 0U, kTypeValue, true), "val"},
|
||||
{test::KeyStr("c", 0U, kTypeValue), "val2"}});
|
||||
{test::KeyStr("c", 1U, kTypeValue), "val2"}});
|
||||
|
||||
SetLastSequence(6U);
|
||||
auto files = cfd_->current()->storage_info()->LevelFiles(0);
|
||||
|
||||
+119
-33
@@ -243,7 +243,7 @@ bool CompactionPicker::ExpandWhileOverlapping(const std::string& cf_name,
|
||||
// Returns true if any one of specified files are being compacted
|
||||
bool CompactionPicker::FilesInCompaction(
|
||||
const std::vector<FileMetaData*>& files) {
|
||||
for (unsigned int i = 0; i < files.size(); i++) {
|
||||
for (size_t i = 0; i < files.size(); i++) {
|
||||
if (files[i]->being_compacted) {
|
||||
return true;
|
||||
}
|
||||
@@ -255,16 +255,30 @@ Compaction* CompactionPicker::FormCompaction(
|
||||
const CompactionOptions& compact_options,
|
||||
const std::vector<CompactionInputFiles>& input_files, int output_level,
|
||||
VersionStorageInfo* vstorage, const MutableCFOptions& mutable_cf_options,
|
||||
uint32_t output_path_id) const {
|
||||
uint32_t output_path_id) {
|
||||
uint64_t max_grandparent_overlap_bytes =
|
||||
output_level + 1 < vstorage->num_levels() ?
|
||||
mutable_cf_options.MaxGrandParentOverlapBytes(output_level + 1) :
|
||||
std::numeric_limits<uint64_t>::max();
|
||||
assert(input_files.size());
|
||||
return new Compaction(
|
||||
|
||||
// TODO(rven ): we might be able to run concurrent level 0 compaction
|
||||
// if the key ranges of the two compactions do not overlap, but for now
|
||||
// we do not allow it.
|
||||
if ((input_files[0].level == 0) && !level0_compactions_in_progress_.empty()) {
|
||||
return nullptr;
|
||||
}
|
||||
auto c = new Compaction(
|
||||
vstorage, mutable_cf_options, input_files, output_level,
|
||||
compact_options.output_file_size_limit, max_grandparent_overlap_bytes,
|
||||
output_path_id, compact_options.compression, /* grandparents */ {}, true);
|
||||
|
||||
// If it's level 0 compaction, make sure we don't execute any other level 0
|
||||
// compactions in parallel
|
||||
if ((c != nullptr) && (input_files[0].level == 0)) {
|
||||
level0_compactions_in_progress_.insert(c);
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
Status CompactionPicker::GetCompactionInputsFromFileNumbers(
|
||||
@@ -441,7 +455,7 @@ Compaction* CompactionPicker::CompactRange(
|
||||
const std::string& cf_name, const MutableCFOptions& mutable_cf_options,
|
||||
VersionStorageInfo* vstorage, int input_level, int output_level,
|
||||
uint32_t output_path_id, const InternalKey* begin, const InternalKey* end,
|
||||
InternalKey** compaction_end) {
|
||||
InternalKey** compaction_end, bool* manual_conflict) {
|
||||
// CompactionPickerFIFO has its own implementation of compact range
|
||||
assert(ioptions_.compaction_style != kCompactionStyleFIFO);
|
||||
|
||||
@@ -467,6 +481,12 @@ Compaction* CompactionPicker::CompactRange(
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if ((start_level == 0) && (!level0_compactions_in_progress_.empty())) {
|
||||
*manual_conflict = true;
|
||||
// Only one level 0 compaction allowed
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::vector<CompactionInputFiles> inputs(vstorage->num_levels() -
|
||||
start_level);
|
||||
for (int level = start_level; level < vstorage->num_levels(); level++) {
|
||||
@@ -475,13 +495,21 @@ Compaction* CompactionPicker::CompactRange(
|
||||
for (FileMetaData* f : vstorage->LevelFiles(level)) {
|
||||
files.push_back(f);
|
||||
}
|
||||
if (FilesInCompaction(files)) {
|
||||
*manual_conflict = true;
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
return new Compaction(
|
||||
Compaction* c = new Compaction(
|
||||
vstorage, mutable_cf_options, std::move(inputs), output_level,
|
||||
mutable_cf_options.MaxFileSizeForLevel(output_level),
|
||||
/* max_grandparent_overlap_bytes */ LLONG_MAX, output_path_id,
|
||||
GetCompressionType(ioptions_, output_level, 1),
|
||||
/* grandparents */ {}, /* is manual */ true);
|
||||
if (start_level == 0) {
|
||||
level0_compactions_in_progress_.insert(c);
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
CompactionInputFiles inputs;
|
||||
@@ -500,6 +528,13 @@ Compaction* CompactionPicker::CompactRange(
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if ((input_level == 0) && (!level0_compactions_in_progress_.empty())) {
|
||||
// Only one level 0 compaction allowed
|
||||
TEST_SYNC_POINT("CompactionPicker::CompactRange:Conflict");
|
||||
*manual_conflict = true;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Avoid compacting too much in one shot in case the range is large.
|
||||
// But we cannot do this for level-0 since level-0 files can overlap
|
||||
// and we must not pick one file and drop another older file if the
|
||||
@@ -522,9 +557,10 @@ Compaction* CompactionPicker::CompactRange(
|
||||
assert(output_path_id < static_cast<uint32_t>(ioptions_.db_paths.size()));
|
||||
|
||||
if (ExpandWhileOverlapping(cf_name, vstorage, &inputs) == false) {
|
||||
// manual compaction is currently single-threaded, so it should never
|
||||
// manual compaction is now multi-threaded, so it can
|
||||
// happen that ExpandWhileOverlapping fails
|
||||
assert(false);
|
||||
// we handle it higher in RunManualCompaction
|
||||
*manual_conflict = true;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@@ -543,9 +579,10 @@ Compaction* CompactionPicker::CompactRange(
|
||||
int parent_index = -1;
|
||||
if (!SetupOtherInputs(cf_name, mutable_cf_options, vstorage, &inputs,
|
||||
&output_level_inputs, &parent_index, -1)) {
|
||||
// manual compaction is currently single-threaded, so it should never
|
||||
// manual compaction is now multi-threaded, so it can
|
||||
// happen that SetupOtherInputs fails
|
||||
assert(false);
|
||||
// we handle it higher in RunManualCompaction
|
||||
*manual_conflict = true;
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
@@ -554,6 +591,12 @@ Compaction* CompactionPicker::CompactRange(
|
||||
if (!output_level_inputs.empty()) {
|
||||
compaction_inputs.push_back(output_level_inputs);
|
||||
}
|
||||
for (size_t i = 0; i < compaction_inputs.size(); i++) {
|
||||
if (FilesInCompaction(compaction_inputs[i].files)) {
|
||||
*manual_conflict = true;
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<FileMetaData*> grandparents;
|
||||
GetGrandparents(vstorage, inputs, output_level_inputs, &grandparents);
|
||||
@@ -566,6 +609,9 @@ Compaction* CompactionPicker::CompactRange(
|
||||
std::move(grandparents), /* is manual compaction */ true);
|
||||
|
||||
TEST_SYNC_POINT_CALLBACK("CompactionPicker::CompactRange:Return", compaction);
|
||||
if (input_level == 0) {
|
||||
level0_compactions_in_progress_.insert(compaction);
|
||||
}
|
||||
return compaction;
|
||||
}
|
||||
|
||||
@@ -870,22 +916,40 @@ Compaction* LevelCompactionPicker::PickCompaction(
|
||||
int base_index = -1;
|
||||
CompactionInputFiles inputs;
|
||||
double score = 0;
|
||||
CompactionReason compaction_reason = CompactionReason::kUnknown;
|
||||
|
||||
// Find the compactions by size on all levels.
|
||||
bool skipped_l0 = false;
|
||||
for (int i = 0; i < NumberLevels() - 1; i++) {
|
||||
score = vstorage->CompactionScore(i);
|
||||
level = vstorage->CompactionScoreLevel(i);
|
||||
assert(i == 0 || score <= vstorage->CompactionScore(i - 1));
|
||||
if (score >= 1) {
|
||||
if (skipped_l0 && level == vstorage->base_level()) {
|
||||
// If L0->base_level compaction is pending, don't schedule further
|
||||
// compaction from base level. Otherwise L0->base_level compaction
|
||||
// may starve.
|
||||
continue;
|
||||
}
|
||||
output_level = (level == 0) ? vstorage->base_level() : level + 1;
|
||||
if (PickCompactionBySize(vstorage, level, output_level, &inputs,
|
||||
&parent_index, &base_index) &&
|
||||
ExpandWhileOverlapping(cf_name, vstorage, &inputs)) {
|
||||
// found the compaction!
|
||||
if (level == 0) {
|
||||
// L0 score = `num L0 files` / `level0_file_num_compaction_trigger`
|
||||
compaction_reason = CompactionReason::kLevelL0FilesNum;
|
||||
} else {
|
||||
// L1+ score = `Level files size` / `MaxBytesForLevel`
|
||||
compaction_reason = CompactionReason::kLevelMaxLevelSize;
|
||||
}
|
||||
break;
|
||||
} else {
|
||||
// didn't find the compaction, clear the inputs
|
||||
inputs.clear();
|
||||
if (level == 0) {
|
||||
skipped_l0 = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -898,6 +962,9 @@ Compaction* LevelCompactionPicker::PickCompaction(
|
||||
parent_index = base_index = -1;
|
||||
PickFilesMarkedForCompactionExperimental(cf_name, vstorage, &inputs, &level,
|
||||
&output_level);
|
||||
if (!inputs.empty()) {
|
||||
compaction_reason = CompactionReason::kFilesMarkedForCompaction;
|
||||
}
|
||||
}
|
||||
if (inputs.empty()) {
|
||||
return nullptr;
|
||||
@@ -948,7 +1015,8 @@ Compaction* LevelCompactionPicker::PickCompaction(
|
||||
mutable_cf_options.MaxGrandParentOverlapBytes(level),
|
||||
GetPathId(ioptions_, mutable_cf_options, output_level),
|
||||
GetCompressionType(ioptions_, output_level, vstorage->base_level()),
|
||||
std::move(grandparents), is_manual, score);
|
||||
std::move(grandparents), is_manual, score,
|
||||
false /* deletion_compaction */, compaction_reason);
|
||||
|
||||
// If it's level 0 compaction, make sure we don't execute any other level 0
|
||||
// compactions in parallel
|
||||
@@ -1019,6 +1087,7 @@ bool LevelCompactionPicker::PickCompactionBySize(VersionStorageInfo* vstorage,
|
||||
// could be made better by looking at key-ranges that are
|
||||
// being compacted at level 0.
|
||||
if (level == 0 && !level0_compactions_in_progress_.empty()) {
|
||||
TEST_SYNC_POINT("LevelCompactionPicker::PickCompactionBySize:0");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1095,18 +1164,19 @@ void UniversalCompactionPicker::SortedRun::Dump(char* out_buf,
|
||||
}
|
||||
|
||||
void UniversalCompactionPicker::SortedRun::DumpSizeInfo(
|
||||
char* out_buf, size_t out_buf_size, int sorted_run_count) const {
|
||||
char* out_buf, size_t out_buf_size, size_t sorted_run_count) const {
|
||||
if (level == 0) {
|
||||
assert(file != nullptr);
|
||||
snprintf(out_buf, out_buf_size,
|
||||
"file %" PRIu64
|
||||
"[%d] "
|
||||
"file %" PRIu64 "[%" ROCKSDB_PRIszt
|
||||
"] "
|
||||
"with size %" PRIu64 " (compensated size %" PRIu64 ")",
|
||||
file->fd.GetNumber(), sorted_run_count, file->fd.GetFileSize(),
|
||||
file->compensated_file_size);
|
||||
} else {
|
||||
snprintf(out_buf, out_buf_size,
|
||||
"level %d[%d] "
|
||||
"level %d[%" ROCKSDB_PRIszt
|
||||
"] "
|
||||
"with size %" PRIu64 " (compensated size %" PRIu64 ")",
|
||||
level, sorted_run_count, size, compensated_file_size);
|
||||
}
|
||||
@@ -1234,8 +1304,9 @@ Compaction* UniversalCompactionPicker::PickCompaction(
|
||||
std::vector<SortedRun> sorted_runs =
|
||||
CalculateSortedRuns(*vstorage, ioptions_);
|
||||
|
||||
if (sorted_runs.size() <
|
||||
(unsigned int)mutable_cf_options.level0_file_num_compaction_trigger) {
|
||||
if (sorted_runs.size() == 0 ||
|
||||
sorted_runs.size() <
|
||||
(unsigned int)mutable_cf_options.level0_file_num_compaction_trigger) {
|
||||
LogToBuffer(log_buffer, "[%s] Universal: nothing to do\n", cf_name.c_str());
|
||||
return nullptr;
|
||||
}
|
||||
@@ -1387,16 +1458,21 @@ Compaction* UniversalCompactionPicker::PickCompactionUniversalReadAmp(
|
||||
|
||||
const SortedRun* sr = nullptr;
|
||||
bool done = false;
|
||||
int start_index = 0;
|
||||
size_t start_index = 0;
|
||||
unsigned int candidate_count = 0;
|
||||
|
||||
unsigned int max_files_to_compact = std::min(max_merge_width,
|
||||
max_number_of_files_to_compact);
|
||||
min_merge_width = std::max(min_merge_width, 2U);
|
||||
|
||||
// Caller checks the size before executing this function. This invariant is
|
||||
// important because otherwise we may have a possible integer underflow when
|
||||
// dealing with unsigned types.
|
||||
assert(sorted_runs.size() > 0);
|
||||
|
||||
// Considers a candidate file only if it is smaller than the
|
||||
// total size accumulated so far.
|
||||
for (unsigned int loop = 0; loop < sorted_runs.size(); loop++) {
|
||||
for (size_t loop = 0; loop < sorted_runs.size(); loop++) {
|
||||
candidate_count = 0;
|
||||
|
||||
// Skip files that are already being compacted
|
||||
@@ -1428,7 +1504,7 @@ Compaction* UniversalCompactionPicker::PickCompactionUniversalReadAmp(
|
||||
}
|
||||
|
||||
// Check if the succeeding files need compaction.
|
||||
for (unsigned int i = loop + 1;
|
||||
for (size_t i = loop + 1;
|
||||
candidate_count < max_files_to_compact && i < sorted_runs.size();
|
||||
i++) {
|
||||
const SortedRun* succeeding_sr = &sorted_runs[i];
|
||||
@@ -1470,7 +1546,7 @@ Compaction* UniversalCompactionPicker::PickCompactionUniversalReadAmp(
|
||||
done = true;
|
||||
break;
|
||||
} else {
|
||||
for (unsigned int i = loop;
|
||||
for (size_t i = loop;
|
||||
i < loop + candidate_count && i < sorted_runs.size(); i++) {
|
||||
const SortedRun* skipping_sr = &sorted_runs[i];
|
||||
char file_num_buf[256];
|
||||
@@ -1483,7 +1559,7 @@ Compaction* UniversalCompactionPicker::PickCompactionUniversalReadAmp(
|
||||
if (!done || candidate_count <= 1) {
|
||||
return nullptr;
|
||||
}
|
||||
unsigned int first_index_after = start_index + candidate_count;
|
||||
size_t first_index_after = start_index + candidate_count;
|
||||
// Compression is enabled if files compacted earlier already reached
|
||||
// size ratio of compression.
|
||||
bool enable_compression = true;
|
||||
@@ -1524,7 +1600,7 @@ Compaction* UniversalCompactionPicker::PickCompactionUniversalReadAmp(
|
||||
for (size_t i = 0; i < inputs.size(); ++i) {
|
||||
inputs[i].level = start_level + static_cast<int>(i);
|
||||
}
|
||||
for (unsigned int i = start_index; i < first_index_after; i++) {
|
||||
for (size_t i = start_index; i < first_index_after; i++) {
|
||||
auto& picking_sr = sorted_runs[i];
|
||||
if (picking_sr.level == 0) {
|
||||
FileMetaData* picking_file = picking_sr.file;
|
||||
@@ -1541,11 +1617,18 @@ Compaction* UniversalCompactionPicker::PickCompactionUniversalReadAmp(
|
||||
file_num_buf);
|
||||
}
|
||||
|
||||
CompactionReason compaction_reason;
|
||||
if (max_number_of_files_to_compact == UINT_MAX) {
|
||||
compaction_reason = CompactionReason::kUniversalSortedRunNum;
|
||||
} else {
|
||||
compaction_reason = CompactionReason::kUniversalSizeRatio;
|
||||
}
|
||||
return new Compaction(
|
||||
vstorage, mutable_cf_options, std::move(inputs), output_level,
|
||||
mutable_cf_options.MaxFileSizeForLevel(output_level), LLONG_MAX, path_id,
|
||||
GetCompressionType(ioptions_, start_level, 1, enable_compression),
|
||||
/* grandparents */ {}, /* is manual */ false, score);
|
||||
/* grandparents */ {}, /* is manual */ false, score,
|
||||
false /* deletion_compaction */, compaction_reason);
|
||||
}
|
||||
|
||||
// Look at overall size amplification. If size amplification
|
||||
@@ -1564,11 +1647,11 @@ Compaction* UniversalCompactionPicker::PickCompactionUniversalSizeAmp(
|
||||
|
||||
unsigned int candidate_count = 0;
|
||||
uint64_t candidate_size = 0;
|
||||
unsigned int start_index = 0;
|
||||
size_t start_index = 0;
|
||||
const SortedRun* sr = nullptr;
|
||||
|
||||
// Skip files that are already being compacted
|
||||
for (unsigned int loop = 0; loop < sorted_runs.size() - 1; loop++) {
|
||||
for (size_t loop = 0; loop < sorted_runs.size() - 1; loop++) {
|
||||
sr = &sorted_runs[loop];
|
||||
if (!sr->being_compacted) {
|
||||
start_index = loop; // Consider this as the first candidate.
|
||||
@@ -1588,13 +1671,14 @@ Compaction* UniversalCompactionPicker::PickCompactionUniversalSizeAmp(
|
||||
{
|
||||
char file_num_buf[kFormatFileNumberBufSize];
|
||||
sr->Dump(file_num_buf, sizeof(file_num_buf), true);
|
||||
LogToBuffer(log_buffer, "[%s] Universal: First candidate %s[%d] %s",
|
||||
LogToBuffer(log_buffer,
|
||||
"[%s] Universal: First candidate %s[%" ROCKSDB_PRIszt "] %s",
|
||||
cf_name.c_str(), file_num_buf, start_index,
|
||||
" to reduce size amp.\n");
|
||||
}
|
||||
|
||||
// keep adding up all the remaining files
|
||||
for (unsigned int loop = start_index; loop < sorted_runs.size() - 1; loop++) {
|
||||
for (size_t loop = start_index; loop < sorted_runs.size() - 1; loop++) {
|
||||
sr = &sorted_runs[loop];
|
||||
if (sr->being_compacted) {
|
||||
char file_num_buf[kFormatFileNumberBufSize];
|
||||
@@ -1634,7 +1718,7 @@ Compaction* UniversalCompactionPicker::PickCompactionUniversalSizeAmp(
|
||||
|
||||
// Estimate total file size
|
||||
uint64_t estimated_total_size = 0;
|
||||
for (unsigned int loop = start_index; loop < sorted_runs.size(); loop++) {
|
||||
for (size_t loop = start_index; loop < sorted_runs.size(); loop++) {
|
||||
estimated_total_size += sorted_runs[loop].size;
|
||||
}
|
||||
uint32_t path_id = GetPathId(ioptions_, estimated_total_size);
|
||||
@@ -1645,7 +1729,7 @@ Compaction* UniversalCompactionPicker::PickCompactionUniversalSizeAmp(
|
||||
inputs[i].level = start_level + static_cast<int>(i);
|
||||
}
|
||||
// We always compact all the files, so always compress.
|
||||
for (unsigned int loop = start_index; loop < sorted_runs.size(); loop++) {
|
||||
for (size_t loop = start_index; loop < sorted_runs.size(); loop++) {
|
||||
auto& picking_sr = sorted_runs[loop];
|
||||
if (picking_sr.level == 0) {
|
||||
FileMetaData* f = picking_sr.file;
|
||||
@@ -1657,7 +1741,7 @@ Compaction* UniversalCompactionPicker::PickCompactionUniversalSizeAmp(
|
||||
}
|
||||
}
|
||||
char file_num_buf[256];
|
||||
sr->DumpSizeInfo(file_num_buf, sizeof(file_num_buf), loop);
|
||||
picking_sr.DumpSizeInfo(file_num_buf, sizeof(file_num_buf), loop);
|
||||
LogToBuffer(log_buffer, "[%s] Universal: size amp picking %s",
|
||||
cf_name.c_str(), file_num_buf);
|
||||
}
|
||||
@@ -1668,7 +1752,9 @@ Compaction* UniversalCompactionPicker::PickCompactionUniversalSizeAmp(
|
||||
mutable_cf_options.MaxFileSizeForLevel(vstorage->num_levels() - 1),
|
||||
/* max_grandparent_overlap_bytes */ LLONG_MAX, path_id,
|
||||
GetCompressionType(ioptions_, vstorage->num_levels() - 1, 1),
|
||||
/* grandparents */ {}, /* is manual */ false, score);
|
||||
/* grandparents */ {}, /* is manual */ false, score,
|
||||
false /* deletion_compaction */,
|
||||
CompactionReason::kUniversalSizeAmplification);
|
||||
}
|
||||
|
||||
bool FIFOCompactionPicker::NeedsCompaction(const VersionStorageInfo* vstorage)
|
||||
@@ -1727,7 +1813,7 @@ Compaction* FIFOCompactionPicker::PickCompaction(
|
||||
Compaction* c = new Compaction(
|
||||
vstorage, mutable_cf_options, std::move(inputs), 0, 0, 0, 0,
|
||||
kNoCompression, {}, /* is manual */ false, vstorage->CompactionScore(0),
|
||||
/* is deletion compaction */ true);
|
||||
/* is deletion compaction */ true, CompactionReason::kFIFOMaxSize);
|
||||
level0_compactions_in_progress_.insert(c);
|
||||
return c;
|
||||
}
|
||||
@@ -1736,7 +1822,7 @@ Compaction* FIFOCompactionPicker::CompactRange(
|
||||
const std::string& cf_name, const MutableCFOptions& mutable_cf_options,
|
||||
VersionStorageInfo* vstorage, int input_level, int output_level,
|
||||
uint32_t output_path_id, const InternalKey* begin, const InternalKey* end,
|
||||
InternalKey** compaction_end) {
|
||||
InternalKey** compaction_end, bool* manual_conflict) {
|
||||
assert(input_level == 0);
|
||||
assert(output_level == 0);
|
||||
*compaction_end = nullptr;
|
||||
|
||||
+11
-9
@@ -60,7 +60,7 @@ class CompactionPicker {
|
||||
const std::string& cf_name, const MutableCFOptions& mutable_cf_options,
|
||||
VersionStorageInfo* vstorage, int input_level, int output_level,
|
||||
uint32_t output_path_id, const InternalKey* begin, const InternalKey* end,
|
||||
InternalKey** compaction_end);
|
||||
InternalKey** compaction_end, bool* manual_conflict);
|
||||
|
||||
// The maximum allowed output level. Default value is NumberLevels() - 1.
|
||||
virtual int MaxOutputLevel() const {
|
||||
@@ -94,7 +94,7 @@ class CompactionPicker {
|
||||
const CompactionOptions& compact_options,
|
||||
const std::vector<CompactionInputFiles>& input_files, int output_level,
|
||||
VersionStorageInfo* vstorage, const MutableCFOptions& mutable_cf_options,
|
||||
uint32_t output_path_id) const;
|
||||
uint32_t output_path_id);
|
||||
|
||||
// Converts a set of compaction input file numbers into
|
||||
// a list of CompactionInputFiles.
|
||||
@@ -251,7 +251,7 @@ class UniversalCompactionPicker : public CompactionPicker {
|
||||
|
||||
// sorted_run_count is added into the string to print
|
||||
void DumpSizeInfo(char* out_buf, size_t out_buf_size,
|
||||
int sorted_run_count) const;
|
||||
size_t sorted_run_count) const;
|
||||
|
||||
int level;
|
||||
// `file` Will be null for level > 0. For level = 0, the sorted run is
|
||||
@@ -302,7 +302,7 @@ class FIFOCompactionPicker : public CompactionPicker {
|
||||
const std::string& cf_name, const MutableCFOptions& mutable_cf_options,
|
||||
VersionStorageInfo* vstorage, int input_level, int output_level,
|
||||
uint32_t output_path_id, const InternalKey* begin, const InternalKey* end,
|
||||
InternalKey** compaction_end) override;
|
||||
InternalKey** compaction_end, bool* manual_conflict) override;
|
||||
|
||||
// The maximum allowed output level. Always returns 0.
|
||||
virtual int MaxOutputLevel() const override {
|
||||
@@ -329,11 +329,13 @@ class NullCompactionPicker : public CompactionPicker {
|
||||
}
|
||||
|
||||
// Always return "nullptr"
|
||||
Compaction* CompactRange(
|
||||
const std::string& cf_name, const MutableCFOptions& mutable_cf_options,
|
||||
VersionStorageInfo* vstorage, int input_level, int output_level,
|
||||
uint32_t output_path_id, const InternalKey* begin, const InternalKey* end,
|
||||
InternalKey** compaction_end) override {
|
||||
Compaction* CompactRange(const std::string& cf_name,
|
||||
const MutableCFOptions& mutable_cf_options,
|
||||
VersionStorageInfo* vstorage, int input_level,
|
||||
int output_level, uint32_t output_path_id,
|
||||
const InternalKey* begin, const InternalKey* end,
|
||||
InternalKey** compaction_end,
|
||||
bool* manual_conflict) override {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
@@ -580,6 +580,101 @@ TEST_F(CompactionPickerTest, OverlappingUserKeys3) {
|
||||
ASSERT_EQ(7U, compaction->input(1, 1)->fd.GetNumber());
|
||||
}
|
||||
|
||||
TEST_F(CompactionPickerTest, NotScheduleL1IfL0WithHigherPri1) {
|
||||
NewVersionStorage(6, kCompactionStyleLevel);
|
||||
mutable_cf_options_.level0_file_num_compaction_trigger = 2;
|
||||
mutable_cf_options_.max_bytes_for_level_base = 900000000U;
|
||||
|
||||
// 6 L0 files, score 3.
|
||||
Add(0, 1U, "000", "400", 1U);
|
||||
Add(0, 2U, "001", "400", 1U, 0, 0);
|
||||
Add(0, 3U, "001", "400", 1000000000U, 0, 0);
|
||||
Add(0, 31U, "001", "400", 1000000000U, 0, 0);
|
||||
Add(0, 32U, "001", "400", 1000000000U, 0, 0);
|
||||
Add(0, 33U, "001", "400", 1000000000U, 0, 0);
|
||||
|
||||
// L1 total size 2GB, score 2.2. If one file being comapcted, score 1.1.
|
||||
Add(1, 4U, "050", "300", 1000000000U, 0, 0);
|
||||
file_map_[4u].first->being_compacted = true;
|
||||
Add(1, 5U, "301", "350", 1000000000U, 0, 0);
|
||||
|
||||
// Output level overlaps with the beginning and the end of the chain
|
||||
Add(2, 6U, "050", "100", 1U);
|
||||
Add(2, 7U, "300", "400", 1U);
|
||||
|
||||
// No compaction should be scheduled, if L0 has higher priority than L1
|
||||
// but L0->L1 compaction is blocked by a file in L1 being compacted.
|
||||
UpdateVersionStorageInfo();
|
||||
ASSERT_EQ(0, vstorage_->CompactionScoreLevel(0));
|
||||
ASSERT_EQ(1, vstorage_->CompactionScoreLevel(1));
|
||||
std::unique_ptr<Compaction> compaction(level_compaction_picker.PickCompaction(
|
||||
cf_name_, mutable_cf_options_, vstorage_.get(), &log_buffer_));
|
||||
ASSERT_TRUE(compaction.get() == nullptr);
|
||||
}
|
||||
|
||||
TEST_F(CompactionPickerTest, NotScheduleL1IfL0WithHigherPri2) {
|
||||
NewVersionStorage(6, kCompactionStyleLevel);
|
||||
mutable_cf_options_.level0_file_num_compaction_trigger = 2;
|
||||
mutable_cf_options_.max_bytes_for_level_base = 900000000U;
|
||||
|
||||
// 6 L0 files, score 3.
|
||||
Add(0, 1U, "000", "400", 1U);
|
||||
Add(0, 2U, "001", "400", 1U, 0, 0);
|
||||
Add(0, 3U, "001", "400", 1000000000U, 0, 0);
|
||||
Add(0, 31U, "001", "400", 1000000000U, 0, 0);
|
||||
Add(0, 32U, "001", "400", 1000000000U, 0, 0);
|
||||
Add(0, 33U, "001", "400", 1000000000U, 0, 0);
|
||||
|
||||
// L1 total size 2GB, score 2.2. If one file being comapcted, score 1.1.
|
||||
Add(1, 4U, "050", "300", 1000000000U, 0, 0);
|
||||
Add(1, 5U, "301", "350", 1000000000U, 0, 0);
|
||||
|
||||
// Output level overlaps with the beginning and the end of the chain
|
||||
Add(2, 6U, "050", "100", 1U);
|
||||
Add(2, 7U, "300", "400", 1U);
|
||||
|
||||
// If no file in L1 being compacted, L0->L1 compaction will be scheduled.
|
||||
UpdateVersionStorageInfo(); // being_compacted flag is cleared here.
|
||||
ASSERT_EQ(0, vstorage_->CompactionScoreLevel(0));
|
||||
ASSERT_EQ(1, vstorage_->CompactionScoreLevel(1));
|
||||
std::unique_ptr<Compaction> compaction(level_compaction_picker.PickCompaction(
|
||||
cf_name_, mutable_cf_options_, vstorage_.get(), &log_buffer_));
|
||||
ASSERT_TRUE(compaction.get() != nullptr);
|
||||
}
|
||||
|
||||
TEST_F(CompactionPickerTest, NotScheduleL1IfL0WithHigherPri3) {
|
||||
NewVersionStorage(6, kCompactionStyleLevel);
|
||||
mutable_cf_options_.level0_file_num_compaction_trigger = 2;
|
||||
mutable_cf_options_.max_bytes_for_level_base = 900000000U;
|
||||
|
||||
// 6 L0 files, score 3.
|
||||
Add(0, 1U, "000", "400", 1U);
|
||||
Add(0, 2U, "001", "400", 1U, 0, 0);
|
||||
Add(0, 3U, "001", "400", 1000000000U, 0, 0);
|
||||
Add(0, 31U, "001", "400", 1000000000U, 0, 0);
|
||||
Add(0, 32U, "001", "400", 1000000000U, 0, 0);
|
||||
Add(0, 33U, "001", "400", 1000000000U, 0, 0);
|
||||
|
||||
// L1 score more than 6.
|
||||
Add(1, 4U, "050", "300", 1000000000U, 0, 0);
|
||||
file_map_[4u].first->being_compacted = true;
|
||||
Add(1, 5U, "301", "350", 1000000000U, 0, 0);
|
||||
Add(1, 51U, "351", "400", 6000000000U, 0, 0);
|
||||
|
||||
// Output level overlaps with the beginning and the end of the chain
|
||||
Add(2, 6U, "050", "100", 1U);
|
||||
Add(2, 7U, "300", "400", 1U);
|
||||
|
||||
// If score in L1 is larger than L0, L1 compaction goes through despite
|
||||
// there is pending L0 compaction.
|
||||
UpdateVersionStorageInfo();
|
||||
ASSERT_EQ(1, vstorage_->CompactionScoreLevel(0));
|
||||
ASSERT_EQ(0, vstorage_->CompactionScoreLevel(1));
|
||||
std::unique_ptr<Compaction> compaction(level_compaction_picker.PickCompaction(
|
||||
cf_name_, mutable_cf_options_, vstorage_.get(), &log_buffer_));
|
||||
ASSERT_TRUE(compaction.get() != nullptr);
|
||||
}
|
||||
|
||||
TEST_F(CompactionPickerTest, EstimateCompactionBytesNeeded1) {
|
||||
int num_levels = ioptions_.num_levels;
|
||||
ioptions_.level_compaction_dynamic_level_bytes = false;
|
||||
|
||||
@@ -18,6 +18,13 @@ namespace rocksdb {
|
||||
void CancelAllBackgroundWork(DB* db, bool wait) {
|
||||
(dynamic_cast<DBImpl*>(db))->CancelAllBackgroundWork(wait);
|
||||
}
|
||||
|
||||
Status DeleteFilesInRange(DB* db, ColumnFamilyHandle* column_family,
|
||||
const Slice* begin, const Slice* end) {
|
||||
return (dynamic_cast<DBImpl*>(db))
|
||||
->DeleteFilesInRange(column_family, begin, end);
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
#endif // ROCKSDB_LITE
|
||||
+18
-8
@@ -11,6 +11,7 @@
|
||||
|
||||
#include "rocksdb/db.h"
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/stat.h>
|
||||
@@ -104,8 +105,8 @@ class CorruptionTest : public testing::Test {
|
||||
}
|
||||
|
||||
void Check(int min_expected, int max_expected) {
|
||||
unsigned int next_expected = 0;
|
||||
int missed = 0;
|
||||
uint64_t next_expected = 0;
|
||||
uint64_t missed = 0;
|
||||
int bad_keys = 0;
|
||||
int bad_values = 0;
|
||||
int correct = 0;
|
||||
@@ -126,7 +127,7 @@ class CorruptionTest : public testing::Test {
|
||||
continue;
|
||||
}
|
||||
missed += (key - next_expected);
|
||||
next_expected = static_cast<unsigned int>(key + 1);
|
||||
next_expected = key + 1;
|
||||
if (iter->value() != Value(static_cast<int>(key), &value_space)) {
|
||||
bad_values++;
|
||||
} else {
|
||||
@@ -136,8 +137,9 @@ class CorruptionTest : public testing::Test {
|
||||
delete iter;
|
||||
|
||||
fprintf(stderr,
|
||||
"expected=%d..%d; got=%d; bad_keys=%d; bad_values=%d; missed=%d\n",
|
||||
min_expected, max_expected, correct, bad_keys, bad_values, missed);
|
||||
"expected=%d..%d; got=%d; bad_keys=%d; bad_values=%d; missed=%llu\n",
|
||||
min_expected, max_expected, correct, bad_keys, bad_values,
|
||||
static_cast<unsigned long long>(missed));
|
||||
ASSERT_LE(min_expected, correct);
|
||||
ASSERT_GE(max_expected, correct);
|
||||
}
|
||||
@@ -183,7 +185,7 @@ class CorruptionTest : public testing::Test {
|
||||
FileType type;
|
||||
std::string fname;
|
||||
int picked_number = -1;
|
||||
for (unsigned int i = 0; i < filenames.size(); i++) {
|
||||
for (size_t i = 0; i < filenames.size(); i++) {
|
||||
if (ParseFileName(filenames[i], &number, &type) &&
|
||||
type == filetype &&
|
||||
static_cast<int>(number) > picked_number) { // Pick latest file
|
||||
@@ -232,8 +234,16 @@ class CorruptionTest : public testing::Test {
|
||||
|
||||
// Return the value to associate with the specified key
|
||||
Slice Value(int k, std::string* storage) {
|
||||
Random r(k);
|
||||
return test::RandomString(&r, kValueSize, storage);
|
||||
if (k == 0) {
|
||||
// Ugh. Random seed of 0 used to produce no entropy. This code
|
||||
// preserves the implementation that was in place when all of the
|
||||
// magic values in this file were picked.
|
||||
*storage = std::string(kValueSize, ' ');
|
||||
return Slice(*storage);
|
||||
} else {
|
||||
Random r(k);
|
||||
return test::RandomString(&r, kValueSize, storage);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
+197
-123
@@ -39,6 +39,7 @@ int main() {
|
||||
#include <condition_variable>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "db/db_impl.h"
|
||||
#include "db/version_set.h"
|
||||
@@ -376,8 +377,8 @@ DEFINE_int32(compaction_readahead_size, 0, "Compaction readahead size");
|
||||
DEFINE_int32(random_access_max_buffer_size, 1024 * 1024,
|
||||
"Maximum windows randomaccess buffer size");
|
||||
|
||||
DEFINE_int32(writable_file_max_buffer_size, 1024 * 1024,
|
||||
"Maximum write buffer for Writeable File");
|
||||
DEFINE_int32(writable_file_max_buffer_size, 1024 * 1024,
|
||||
"Maximum write buffer for Writable File");
|
||||
|
||||
DEFINE_int32(skip_table_builder_flush, false, "Skip flushing block in "
|
||||
"table builder ");
|
||||
@@ -419,10 +420,6 @@ static class std::shared_ptr<rocksdb::Statistics> dbstats;
|
||||
DEFINE_int64(writes, -1, "Number of write operations to do. If negative, do"
|
||||
" --num reads.");
|
||||
|
||||
DEFINE_int32(writes_per_second, 0, "Per-thread rate limit on writes and merges "
|
||||
" per second. No limit when <= 0. Only for the readwhilewriting "
|
||||
" and readwhilemerging tests.");
|
||||
|
||||
DEFINE_bool(sync, false, "Sync all writes to disk");
|
||||
|
||||
DEFINE_bool(disable_data_sync, false, "If true, do not wait until data is"
|
||||
@@ -631,17 +628,34 @@ static bool ValidateRateLimit(const char* flagname, double value) {
|
||||
}
|
||||
return true;
|
||||
}
|
||||
DEFINE_double(soft_rate_limit, 0.0, "");
|
||||
DEFINE_double(soft_rate_limit, 0.0, "DEPRECATED");
|
||||
|
||||
DEFINE_double(hard_rate_limit, 0.0, "DEPRECATED");
|
||||
|
||||
DEFINE_uint64(soft_pending_compaction_bytes_limit, 64ull * 1024 * 1024 * 1024,
|
||||
"Slowdown writes if pending compaction bytes exceed this number");
|
||||
|
||||
DEFINE_uint64(hard_pending_compaction_bytes_limit, 128ull * 1024 * 1024 * 1024,
|
||||
"Stop writes if pending compaction bytes exceed this number");
|
||||
|
||||
DEFINE_uint64(delayed_write_rate, 2097152u,
|
||||
DEFINE_uint64(delayed_write_rate, 8388608u,
|
||||
"Limited bytes allowed to DB when soft_rate_limit or "
|
||||
"level0_slowdown_writes_trigger triggers");
|
||||
|
||||
DEFINE_bool(allow_concurrent_memtable_write, false,
|
||||
"Allow multi-writers to update mem tables in parallel.");
|
||||
|
||||
DEFINE_bool(enable_write_thread_adaptive_yield, false,
|
||||
"Use a yielding spin loop for brief writer thread waits.");
|
||||
|
||||
DEFINE_uint64(
|
||||
write_thread_max_yield_usec, 100,
|
||||
"Maximum microseconds for enable_write_thread_adaptive_yield operation.");
|
||||
|
||||
DEFINE_uint64(write_thread_slow_yield_usec, 3,
|
||||
"The threshold at which a slow yield is considered a signal that "
|
||||
"other processes or threads want the core.");
|
||||
|
||||
DEFINE_int32(rate_limit_delay_max_milliseconds, 1000,
|
||||
"When hard_rate_limit is set then this is the max time a put will"
|
||||
" be stalled.");
|
||||
@@ -650,7 +664,8 @@ DEFINE_uint64(rate_limiter_bytes_per_sec, 0, "Set options.rate_limiter value.");
|
||||
|
||||
DEFINE_uint64(
|
||||
benchmark_write_rate_limit, 0,
|
||||
"If non-zero, db_bench will rate-limit the writes going into RocksDB");
|
||||
"If non-zero, db_bench will rate-limit the writes going into RocksDB. This "
|
||||
"is the global rate in bytes/second.");
|
||||
|
||||
DEFINE_int32(max_grandparent_overlap_factor, 10, "Control maximum bytes of "
|
||||
"overlaps in grandparent (i.e., level+2) before we stop building a"
|
||||
@@ -741,11 +756,6 @@ DEFINE_bool(identity_as_first_hash, false, "the first hash function of cuckoo "
|
||||
"table becomes an identity function. This is only valid when key "
|
||||
"is 8 bytes");
|
||||
|
||||
enum PutOrMerge {
|
||||
kPut,
|
||||
kMerge
|
||||
};
|
||||
|
||||
enum RepFactory {
|
||||
kSkipList,
|
||||
kPrefixHash,
|
||||
@@ -1163,19 +1173,49 @@ class ReporterAgent {
|
||||
bool stop_;
|
||||
};
|
||||
|
||||
enum OperationType : unsigned char {
|
||||
kRead = 0,
|
||||
kWrite,
|
||||
kDelete,
|
||||
kSeek,
|
||||
kMerge,
|
||||
kUpdate,
|
||||
kCompress,
|
||||
kUncompress,
|
||||
kCrc,
|
||||
kHash,
|
||||
kOthers
|
||||
};
|
||||
|
||||
static std::unordered_map<OperationType, std::string, std::hash<unsigned char>>
|
||||
OperationTypeString = {
|
||||
{kRead, "read"},
|
||||
{kWrite, "write"},
|
||||
{kDelete, "delete"},
|
||||
{kSeek, "seek"},
|
||||
{kMerge, "merge"},
|
||||
{kUpdate, "update"},
|
||||
{kCompress, "compress"},
|
||||
{kCompress, "uncompress"},
|
||||
{kCrc, "crc"},
|
||||
{kHash, "hash"},
|
||||
{kOthers, "op"}
|
||||
};
|
||||
|
||||
class Stats {
|
||||
private:
|
||||
int id_;
|
||||
double start_;
|
||||
double finish_;
|
||||
uint64_t start_;
|
||||
uint64_t finish_;
|
||||
double seconds_;
|
||||
int64_t done_;
|
||||
int64_t last_report_done_;
|
||||
int64_t next_report_;
|
||||
int64_t bytes_;
|
||||
double last_op_finish_;
|
||||
double last_report_finish_;
|
||||
HistogramImpl hist_;
|
||||
uint64_t done_;
|
||||
uint64_t last_report_done_;
|
||||
uint64_t next_report_;
|
||||
uint64_t bytes_;
|
||||
uint64_t last_op_finish_;
|
||||
uint64_t last_report_finish_;
|
||||
std::unordered_map<OperationType, HistogramImpl,
|
||||
std::hash<unsigned char>> hist_;
|
||||
std::string message_;
|
||||
bool exclude_from_merge_;
|
||||
ReporterAgent* reporter_agent_; // does not own
|
||||
@@ -1191,7 +1231,7 @@ class Stats {
|
||||
id_ = id;
|
||||
next_report_ = FLAGS_stats_interval ? FLAGS_stats_interval : 100;
|
||||
last_op_finish_ = start_;
|
||||
hist_.Clear();
|
||||
hist_.clear();
|
||||
done_ = 0;
|
||||
last_report_done_ = 0;
|
||||
bytes_ = 0;
|
||||
@@ -1208,7 +1248,15 @@ class Stats {
|
||||
if (other.exclude_from_merge_)
|
||||
return;
|
||||
|
||||
hist_.Merge(other.hist_);
|
||||
for (auto it = other.hist_.begin(); it != other.hist_.end(); ++it) {
|
||||
auto this_it = hist_.find(it->first);
|
||||
if (this_it != hist_.end()) {
|
||||
this_it->second.Merge(other.hist_.at(it->first));
|
||||
} else {
|
||||
hist_.insert({ it->first, it->second });
|
||||
}
|
||||
}
|
||||
|
||||
done_ += other.done_;
|
||||
bytes_ += other.bytes_;
|
||||
seconds_ += other.seconds_;
|
||||
@@ -1261,16 +1309,29 @@ class Stats {
|
||||
}
|
||||
}
|
||||
|
||||
void FinishedOps(DBWithColumnFamilies* db_with_cfh, DB* db, int64_t num_ops) {
|
||||
void ResetLastOpTime() {
|
||||
// Set to now to avoid latency from calls to SleepForMicroseconds
|
||||
last_op_finish_ = FLAGS_env->NowMicros();
|
||||
}
|
||||
|
||||
void FinishedOps(DBWithColumnFamilies* db_with_cfh, DB* db, int64_t num_ops,
|
||||
enum OperationType op_type = kOthers) {
|
||||
if (reporter_agent_) {
|
||||
reporter_agent_->ReportFinishedOps(num_ops);
|
||||
}
|
||||
if (FLAGS_histogram) {
|
||||
double now = FLAGS_env->NowMicros();
|
||||
double micros = now - last_op_finish_;
|
||||
hist_.Add(micros);
|
||||
uint64_t now = FLAGS_env->NowMicros();
|
||||
uint64_t micros = now - last_op_finish_;
|
||||
|
||||
if (hist_.find(op_type) == hist_.end())
|
||||
{
|
||||
HistogramImpl hist_temp;
|
||||
hist_.insert({op_type, hist_temp});
|
||||
}
|
||||
hist_[op_type].Add(micros);
|
||||
|
||||
if (micros > 20000 && !FLAGS_stats_interval) {
|
||||
fprintf(stderr, "long op: %.1f micros%30s\r", micros, "");
|
||||
fprintf(stderr, "long op: %" PRIu64 " micros%30s\r", micros, "");
|
||||
fflush(stderr);
|
||||
}
|
||||
last_op_finish_ = now;
|
||||
@@ -1288,7 +1349,7 @@ class Stats {
|
||||
else next_report_ += 100000;
|
||||
fprintf(stderr, "... finished %" PRIu64 " ops%30s\r", done_, "");
|
||||
} else {
|
||||
double now = FLAGS_env->NowMicros();
|
||||
uint64_t now = FLAGS_env->NowMicros();
|
||||
int64_t usecs_since_last = now - last_report_finish_;
|
||||
|
||||
// Determine whether to print status where interval is either
|
||||
@@ -1304,7 +1365,7 @@ class Stats {
|
||||
fprintf(stderr,
|
||||
"%s ... thread %d: (%" PRIu64 ",%" PRIu64 ") ops and "
|
||||
"(%.1f,%.1f) ops/second in (%.6f,%.6f) seconds\n",
|
||||
FLAGS_env->TimeToString((uint64_t) now/1000000).c_str(),
|
||||
FLAGS_env->TimeToString(now/1000000).c_str(),
|
||||
id_,
|
||||
done_ - last_report_done_, done_,
|
||||
(done_ - last_report_done_) /
|
||||
@@ -1397,7 +1458,11 @@ class Stats {
|
||||
(extra.empty() ? "" : " "),
|
||||
extra.c_str());
|
||||
if (FLAGS_histogram) {
|
||||
fprintf(stdout, "Microseconds per op:\n%s\n", hist_.ToString().c_str());
|
||||
for (auto it = hist_.begin(); it != hist_.end(); ++it) {
|
||||
fprintf(stdout, "Microseconds per %s:\n%s\n",
|
||||
OperationTypeString[it->first].c_str(),
|
||||
it->second.ToString().c_str());
|
||||
}
|
||||
}
|
||||
if (FLAGS_report_file_operations) {
|
||||
ReportFileOpEnv* env = static_cast<ReportFileOpEnv*>(FLAGS_env);
|
||||
@@ -1454,7 +1519,7 @@ struct ThreadState {
|
||||
|
||||
class Duration {
|
||||
public:
|
||||
Duration(int max_seconds, int64_t max_ops, int64_t ops_per_stage = 0) {
|
||||
Duration(uint64_t max_seconds, int64_t max_ops, int64_t ops_per_stage = 0) {
|
||||
max_seconds_ = max_seconds;
|
||||
max_ops_= max_ops;
|
||||
ops_per_stage_ = (ops_per_stage > 0) ? ops_per_stage : max_ops;
|
||||
@@ -1471,8 +1536,8 @@ class Duration {
|
||||
if (max_seconds_) {
|
||||
// Recheck every appx 1000 ops (exact iff increment is factor of 1000)
|
||||
if ((ops_/1000) != ((ops_-increment)/1000)) {
|
||||
double now = FLAGS_env->NowMicros();
|
||||
return ((now - start_at_) / 1000000.0) >= max_seconds_;
|
||||
uint64_t now = FLAGS_env->NowMicros();
|
||||
return ((now - start_at_) / 1000000) >= max_seconds_;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
@@ -1482,11 +1547,11 @@ class Duration {
|
||||
}
|
||||
|
||||
private:
|
||||
int max_seconds_;
|
||||
uint64_t max_seconds_;
|
||||
int64_t max_ops_;
|
||||
int64_t ops_per_stage_;
|
||||
int64_t ops_;
|
||||
double start_at_;
|
||||
uint64_t start_at_;
|
||||
};
|
||||
|
||||
class Benchmark {
|
||||
@@ -1570,7 +1635,8 @@ class Benchmark {
|
||||
(((FLAGS_key_size + FLAGS_value_size * FLAGS_compression_ratio)
|
||||
* num_)
|
||||
/ 1048576.0));
|
||||
fprintf(stdout, "Writes per second: %d\n", FLAGS_writes_per_second);
|
||||
fprintf(stdout, "Write rate: %" PRIu64 " bytes/second\n",
|
||||
FLAGS_benchmark_write_rate_limit);
|
||||
if (FLAGS_enable_numa) {
|
||||
fprintf(stderr, "Running in NUMA enabled mode.\n");
|
||||
#ifndef NUMA
|
||||
@@ -1584,9 +1650,8 @@ class Benchmark {
|
||||
#endif
|
||||
}
|
||||
|
||||
const char* compression =
|
||||
CompressionTypeToString(FLAGS_compression_type_e).c_str();
|
||||
fprintf(stdout, "Compression: %s\n", compression);
|
||||
auto compression = CompressionTypeToString(FLAGS_compression_type_e);
|
||||
fprintf(stdout, "Compression: %s\n", compression.c_str());
|
||||
|
||||
switch (FLAGS_rep_factory) {
|
||||
case kPrefixHash:
|
||||
@@ -1607,7 +1672,7 @@ class Benchmark {
|
||||
}
|
||||
fprintf(stdout, "Perf Level: %d\n", FLAGS_perf_level);
|
||||
|
||||
PrintWarnings(compression);
|
||||
PrintWarnings(compression.c_str());
|
||||
fprintf(stdout, "------------------------------------------------\n");
|
||||
}
|
||||
|
||||
@@ -1659,7 +1724,11 @@ class Benchmark {
|
||||
|
||||
#if defined(__linux)
|
||||
time_t now = time(nullptr);
|
||||
fprintf(stderr, "Date: %s", ctime(&now)); // ctime() adds newline
|
||||
char buf[52];
|
||||
// Lint complains about ctime() usage, so replace it with ctime_r(). The
|
||||
// requirement is to provide a buffer which is at least 26 bytes.
|
||||
fprintf(stderr, "Date: %s",
|
||||
ctime_r(&now, buf)); // ctime_r() adds newline
|
||||
|
||||
FILE* cpuinfo = fopen("/proc/cpuinfo", "r");
|
||||
if (cpuinfo != nullptr) {
|
||||
@@ -1740,7 +1809,7 @@ class Benchmark {
|
||||
|
||||
std::vector<std::string> files;
|
||||
FLAGS_env->GetChildren(FLAGS_db, &files);
|
||||
for (unsigned int i = 0; i < files.size(); i++) {
|
||||
for (size_t i = 0; i < files.size(); i++) {
|
||||
if (Slice(files[i]).starts_with("heap-")) {
|
||||
FLAGS_env->DeleteFile(FLAGS_db + "/" + files[i]);
|
||||
}
|
||||
@@ -2138,7 +2207,7 @@ class Benchmark {
|
||||
uint32_t crc = 0;
|
||||
while (bytes < 500 * 1048576) {
|
||||
crc = crc32c::Value(data.data(), size);
|
||||
thread->stats.FinishedOps(nullptr, nullptr, 1);
|
||||
thread->stats.FinishedOps(nullptr, nullptr, 1, kCrc);
|
||||
bytes += size;
|
||||
}
|
||||
// Print so result is not dead
|
||||
@@ -2157,7 +2226,7 @@ class Benchmark {
|
||||
unsigned int xxh32 = 0;
|
||||
while (bytes < 500 * 1048576) {
|
||||
xxh32 = XXH32(data.data(), size, 0);
|
||||
thread->stats.FinishedOps(nullptr, nullptr, 1);
|
||||
thread->stats.FinishedOps(nullptr, nullptr, 1, kHash);
|
||||
bytes += size;
|
||||
}
|
||||
// Print so result is not dead
|
||||
@@ -2178,7 +2247,7 @@ class Benchmark {
|
||||
ptr = ap.load(std::memory_order_acquire);
|
||||
}
|
||||
count++;
|
||||
thread->stats.FinishedOps(nullptr, nullptr, 1);
|
||||
thread->stats.FinishedOps(nullptr, nullptr, 1, kOthers);
|
||||
}
|
||||
if (ptr == nullptr) exit(1); // Disable unused variable warning.
|
||||
}
|
||||
@@ -2196,7 +2265,7 @@ class Benchmark {
|
||||
ok = CompressSlice(input, &compressed);
|
||||
produced += compressed.size();
|
||||
bytes += input.size();
|
||||
thread->stats.FinishedOps(nullptr, nullptr, 1);
|
||||
thread->stats.FinishedOps(nullptr, nullptr, 1, kCompress);
|
||||
}
|
||||
|
||||
if (!ok) {
|
||||
@@ -2221,12 +2290,19 @@ class Benchmark {
|
||||
while (ok && bytes < 1024 * 1048576) {
|
||||
char *uncompressed = nullptr;
|
||||
switch (FLAGS_compression_type_e) {
|
||||
case rocksdb::kSnappyCompression:
|
||||
// allocate here to make comparison fair
|
||||
uncompressed = new char[input.size()];
|
||||
ok = Snappy_Uncompress(compressed.data(), compressed.size(),
|
||||
uncompressed);
|
||||
break;
|
||||
case rocksdb::kSnappyCompression: {
|
||||
// get size and allocate here to make comparison fair
|
||||
size_t ulength = 0;
|
||||
if (!Snappy_GetUncompressedLength(compressed.data(),
|
||||
compressed.size(), &ulength)) {
|
||||
ok = false;
|
||||
break;
|
||||
}
|
||||
uncompressed = new char[ulength];
|
||||
ok = Snappy_Uncompress(compressed.data(), compressed.size(),
|
||||
uncompressed);
|
||||
break;
|
||||
}
|
||||
case rocksdb::kZlibCompression:
|
||||
uncompressed = Zlib_Uncompress(compressed.data(), compressed.size(),
|
||||
&decompress_size, 2);
|
||||
@@ -2257,7 +2333,7 @@ class Benchmark {
|
||||
}
|
||||
delete[] uncompressed;
|
||||
bytes += input.size();
|
||||
thread->stats.FinishedOps(nullptr, nullptr, 1);
|
||||
thread->stats.FinishedOps(nullptr, nullptr, 1, kUncompress);
|
||||
}
|
||||
|
||||
if (!ok) {
|
||||
@@ -2319,8 +2395,7 @@ class Benchmark {
|
||||
fprintf(stderr, "Open flash device failed\n");
|
||||
exit(1);
|
||||
}
|
||||
flashcache_aware_env_ =
|
||||
std::move(NewFlashcacheAwareEnv(FLAGS_env, cachedev_fd_));
|
||||
flashcache_aware_env_ = NewFlashcacheAwareEnv(FLAGS_env, cachedev_fd_);
|
||||
if (flashcache_aware_env_.get() == nullptr) {
|
||||
fprintf(stderr, "Failed to open flashcache device at %s\n",
|
||||
FLAGS_flashcache_dev.c_str());
|
||||
@@ -2448,7 +2523,7 @@ class Benchmark {
|
||||
block_based_options.block_size = FLAGS_block_size;
|
||||
block_based_options.block_restart_interval = FLAGS_block_restart_interval;
|
||||
block_based_options.filter_policy = filter_policy_;
|
||||
block_based_options.skip_table_builder_flush =
|
||||
block_based_options.skip_table_builder_flush =
|
||||
FLAGS_skip_table_builder_flush;
|
||||
block_based_options.format_version = 2;
|
||||
options.table_factory.reset(
|
||||
@@ -2488,9 +2563,17 @@ class Benchmark {
|
||||
}
|
||||
options.soft_rate_limit = FLAGS_soft_rate_limit;
|
||||
options.hard_rate_limit = FLAGS_hard_rate_limit;
|
||||
options.soft_pending_compaction_bytes_limit =
|
||||
FLAGS_soft_pending_compaction_bytes_limit;
|
||||
options.hard_pending_compaction_bytes_limit =
|
||||
FLAGS_hard_pending_compaction_bytes_limit;
|
||||
options.delayed_write_rate = FLAGS_delayed_write_rate;
|
||||
options.allow_concurrent_memtable_write =
|
||||
FLAGS_allow_concurrent_memtable_write;
|
||||
options.enable_write_thread_adaptive_yield =
|
||||
FLAGS_enable_write_thread_adaptive_yield;
|
||||
options.write_thread_max_yield_usec = FLAGS_write_thread_max_yield_usec;
|
||||
options.write_thread_slow_yield_usec = FLAGS_write_thread_slow_yield_usec;
|
||||
options.rate_limit_delay_max_milliseconds =
|
||||
FLAGS_rate_limit_delay_max_milliseconds;
|
||||
options.table_cache_numshardbits = FLAGS_table_cache_numshardbits;
|
||||
@@ -2765,14 +2848,22 @@ class Benchmark {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
size_t id = thread->rand.Next() % num_key_gens;
|
||||
DBWithColumnFamilies* db_with_cfh = SelectDBWithCfh(id);
|
||||
batch.Clear();
|
||||
|
||||
if (thread->shared->write_rate_limiter.get() != nullptr) {
|
||||
thread->shared->write_rate_limiter->Request(
|
||||
entries_per_batch_ * (value_size_ + key_size_),
|
||||
Env::IO_HIGH);
|
||||
// Set time at which last op finished to Now() to hide latency and
|
||||
// sleep from rate limiter. Also, do the check once per batch, not
|
||||
// once per write.
|
||||
thread->stats.ResetLastOpTime();
|
||||
}
|
||||
|
||||
for (int64_t j = 0; j < entries_per_batch_; j++) {
|
||||
if (thread->shared->write_rate_limiter.get() != nullptr) {
|
||||
thread->shared->write_rate_limiter->Request(value_size_ + key_size_,
|
||||
Env::IO_HIGH);
|
||||
}
|
||||
int64_t rand_num = key_gens[id]->Next();
|
||||
GenerateKeyFromInt(rand_num, FLAGS_num, &key);
|
||||
if (FLAGS_num_column_families <= 1) {
|
||||
@@ -2788,7 +2879,7 @@ class Benchmark {
|
||||
}
|
||||
s = db_with_cfh->db->Write(write_options_, &batch);
|
||||
thread->stats.FinishedOps(db_with_cfh, db_with_cfh->db,
|
||||
entries_per_batch_);
|
||||
entries_per_batch_, kWrite);
|
||||
if (!s.ok()) {
|
||||
fprintf(stderr, "put error: %s\n", s.ToString().c_str());
|
||||
exit(1);
|
||||
@@ -2816,7 +2907,7 @@ class Benchmark {
|
||||
int64_t bytes = 0;
|
||||
for (iter->SeekToFirst(); i < reads_ && iter->Valid(); iter->Next()) {
|
||||
bytes += iter->key().size() + iter->value().size();
|
||||
thread->stats.FinishedOps(nullptr, db, 1);
|
||||
thread->stats.FinishedOps(nullptr, db, 1, kRead);
|
||||
++i;
|
||||
}
|
||||
delete iter;
|
||||
@@ -2839,7 +2930,7 @@ class Benchmark {
|
||||
int64_t bytes = 0;
|
||||
for (iter->SeekToLast(); i < reads_ && iter->Valid(); iter->Prev()) {
|
||||
bytes += iter->key().size() + iter->value().size();
|
||||
thread->stats.FinishedOps(nullptr, db, 1);
|
||||
thread->stats.FinishedOps(nullptr, db, 1, kRead);
|
||||
++i;
|
||||
}
|
||||
delete iter;
|
||||
@@ -2879,7 +2970,7 @@ class Benchmark {
|
||||
++nonexist;
|
||||
}
|
||||
}
|
||||
thread->stats.FinishedOps(nullptr, db, 100);
|
||||
thread->stats.FinishedOps(nullptr, db, 100, kRead);
|
||||
} while (!duration.Done(100));
|
||||
|
||||
char msg[100];
|
||||
@@ -2947,7 +3038,7 @@ class Benchmark {
|
||||
fprintf(stderr, "Get returned an error: %s\n", s.ToString().c_str());
|
||||
abort();
|
||||
}
|
||||
thread->stats.FinishedOps(db_with_cfh, db_with_cfh->db, 1);
|
||||
thread->stats.FinishedOps(db_with_cfh, db_with_cfh->db, 1, kRead);
|
||||
}
|
||||
|
||||
char msg[100];
|
||||
@@ -2972,7 +3063,7 @@ class Benchmark {
|
||||
std::vector<std::unique_ptr<const char[]> > key_guards;
|
||||
std::vector<std::string> values(entries_per_batch_);
|
||||
while (static_cast<int64_t>(keys.size()) < entries_per_batch_) {
|
||||
key_guards.push_back(std::move(std::unique_ptr<const char[]>()));
|
||||
key_guards.push_back(std::unique_ptr<const char[]>());
|
||||
keys.push_back(AllocateKey(&key_guards.back()));
|
||||
}
|
||||
|
||||
@@ -2995,7 +3086,7 @@ class Benchmark {
|
||||
abort();
|
||||
}
|
||||
}
|
||||
thread->stats.FinishedOps(nullptr, db, entries_per_batch_);
|
||||
thread->stats.FinishedOps(nullptr, db, entries_per_batch_, kRead);
|
||||
}
|
||||
|
||||
char msg[100];
|
||||
@@ -3011,7 +3102,7 @@ class Benchmark {
|
||||
DB* db = SelectDB(thread);
|
||||
Iterator* iter = db->NewIterator(options);
|
||||
delete iter;
|
||||
thread->stats.FinishedOps(nullptr, db, 1);
|
||||
thread->stats.FinishedOps(nullptr, db, 1, kOthers);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3019,7 +3110,7 @@ class Benchmark {
|
||||
if (thread->tid > 0) {
|
||||
IteratorCreation(thread);
|
||||
} else {
|
||||
BGWriter(thread, kPut);
|
||||
BGWriter(thread, kWrite);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3088,7 +3179,7 @@ class Benchmark {
|
||||
assert(iter_to_use->status().ok());
|
||||
}
|
||||
|
||||
thread->stats.FinishedOps(&db_, db_.db, 1);
|
||||
thread->stats.FinishedOps(&db_, db_.db, 1, kSeek);
|
||||
}
|
||||
delete single_iter;
|
||||
for (auto iter : multi_iters) {
|
||||
@@ -3109,7 +3200,7 @@ class Benchmark {
|
||||
if (thread->tid > 0) {
|
||||
SeekRandom(thread);
|
||||
} else {
|
||||
BGWriter(thread, kPut);
|
||||
BGWriter(thread, kWrite);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3137,7 +3228,7 @@ class Benchmark {
|
||||
batch.Delete(key);
|
||||
}
|
||||
auto s = db->Write(write_options_, &batch);
|
||||
thread->stats.FinishedOps(nullptr, db, entries_per_batch_);
|
||||
thread->stats.FinishedOps(nullptr, db, entries_per_batch_, kDelete);
|
||||
if (!s.ok()) {
|
||||
fprintf(stderr, "del error: %s\n", s.ToString().c_str());
|
||||
exit(1);
|
||||
@@ -3158,7 +3249,7 @@ class Benchmark {
|
||||
if (thread->tid > 0) {
|
||||
ReadRandom(thread);
|
||||
} else {
|
||||
BGWriter(thread, kPut);
|
||||
BGWriter(thread, kWrite);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3170,19 +3261,16 @@ class Benchmark {
|
||||
}
|
||||
}
|
||||
|
||||
void BGWriter(ThreadState* thread, enum PutOrMerge write_merge) {
|
||||
void BGWriter(ThreadState* thread, enum OperationType write_merge) {
|
||||
// Special thread that keeps writing until other threads are done.
|
||||
RandomGenerator gen;
|
||||
double last = FLAGS_env->NowMicros();
|
||||
int writes_per_second_by_10 = 0;
|
||||
int num_writes = 0;
|
||||
int64_t bytes = 0;
|
||||
|
||||
// --writes_per_second rate limit is enforced per 100 milliseconds
|
||||
// intervals to avoid a burst of writes at the start of each second.
|
||||
|
||||
if (FLAGS_writes_per_second > 0)
|
||||
writes_per_second_by_10 = FLAGS_writes_per_second / 10;
|
||||
std::unique_ptr<RateLimiter> write_rate_limiter;
|
||||
if (FLAGS_benchmark_write_rate_limit > 0) {
|
||||
write_rate_limiter.reset(
|
||||
NewGenericRateLimiter(FLAGS_benchmark_write_rate_limit));
|
||||
}
|
||||
|
||||
// Don't merge stats from this thread with the readers.
|
||||
thread->stats.SetExcludeFromMerge();
|
||||
@@ -3203,7 +3291,7 @@ class Benchmark {
|
||||
GenerateKeyFromInt(thread->rand.Next() % FLAGS_num, FLAGS_num, &key);
|
||||
Status s;
|
||||
|
||||
if (write_merge == kPut) {
|
||||
if (write_merge == kWrite) {
|
||||
s = db->Put(write_options_, key, gen.Generate(value_size_));
|
||||
} else {
|
||||
s = db->Merge(write_options_, key, gen.Generate(value_size_));
|
||||
@@ -3214,20 +3302,12 @@ class Benchmark {
|
||||
exit(1);
|
||||
}
|
||||
bytes += key.size() + value_size_;
|
||||
thread->stats.FinishedOps(&db_, db_.db, 1);
|
||||
thread->stats.FinishedOps(&db_, db_.db, 1, kWrite);
|
||||
|
||||
++num_writes;
|
||||
if (writes_per_second_by_10 && num_writes >= writes_per_second_by_10) {
|
||||
double now = FLAGS_env->NowMicros();
|
||||
double usecs_since_last = now - last;
|
||||
|
||||
num_writes = 0;
|
||||
last = now;
|
||||
|
||||
if (usecs_since_last < 100000.0) {
|
||||
FLAGS_env->SleepForMicroseconds(100000.0 - usecs_since_last);
|
||||
last = FLAGS_env->NowMicros();
|
||||
}
|
||||
if (FLAGS_benchmark_write_rate_limit > 0) {
|
||||
write_rate_limiter->Request(
|
||||
entries_per_batch_ * (value_size_ + key_size_),
|
||||
Env::IO_HIGH);
|
||||
}
|
||||
}
|
||||
thread->stats.AddBytes(bytes);
|
||||
@@ -3355,6 +3435,7 @@ class Benchmark {
|
||||
}
|
||||
get_weight--;
|
||||
gets_done++;
|
||||
thread->stats.FinishedOps(&db_, db_.db, 1, kRead);
|
||||
} else if (put_weight > 0) {
|
||||
// then do all the corresponding number of puts
|
||||
// for all the gets we have done earlier
|
||||
@@ -3365,6 +3446,7 @@ class Benchmark {
|
||||
}
|
||||
put_weight--;
|
||||
puts_done++;
|
||||
thread->stats.FinishedOps(&db_, db_.db, 1, kWrite);
|
||||
} else if (delete_weight > 0) {
|
||||
Status s = DeleteMany(db, write_options_, key);
|
||||
if (!s.ok()) {
|
||||
@@ -3373,9 +3455,8 @@ class Benchmark {
|
||||
}
|
||||
delete_weight--;
|
||||
deletes_done++;
|
||||
thread->stats.FinishedOps(&db_, db_.db, 1, kDelete);
|
||||
}
|
||||
|
||||
thread->stats.FinishedOps(&db_, db_.db, 1);
|
||||
}
|
||||
char msg[100];
|
||||
snprintf(msg, sizeof(msg),
|
||||
@@ -3422,6 +3503,7 @@ class Benchmark {
|
||||
}
|
||||
get_weight--;
|
||||
reads_done++;
|
||||
thread->stats.FinishedOps(nullptr, db, 1, kRead);
|
||||
} else if (put_weight > 0) {
|
||||
// then do all the corresponding number of puts
|
||||
// for all the gets we have done earlier
|
||||
@@ -3432,8 +3514,8 @@ class Benchmark {
|
||||
}
|
||||
put_weight--;
|
||||
writes_done++;
|
||||
thread->stats.FinishedOps(nullptr, db, 1, kWrite);
|
||||
}
|
||||
thread->stats.FinishedOps(nullptr, db, 1);
|
||||
}
|
||||
char msg[100];
|
||||
snprintf(msg, sizeof(msg), "( reads:%" PRIu64 " writes:%" PRIu64 \
|
||||
@@ -3475,7 +3557,7 @@ class Benchmark {
|
||||
exit(1);
|
||||
}
|
||||
bytes += key.size() + value_size_;
|
||||
thread->stats.FinishedOps(nullptr, db, 1);
|
||||
thread->stats.FinishedOps(nullptr, db, 1, kUpdate);
|
||||
}
|
||||
char msg[100];
|
||||
snprintf(msg, sizeof(msg),
|
||||
@@ -3530,7 +3612,7 @@ class Benchmark {
|
||||
exit(1);
|
||||
}
|
||||
bytes += key.size() + value.size();
|
||||
thread->stats.FinishedOps(nullptr, db, 1);
|
||||
thread->stats.FinishedOps(nullptr, db, 1, kUpdate);
|
||||
}
|
||||
|
||||
char msg[100];
|
||||
@@ -3568,7 +3650,7 @@ class Benchmark {
|
||||
exit(1);
|
||||
}
|
||||
bytes += key.size() + value_size_;
|
||||
thread->stats.FinishedOps(nullptr, db, 1);
|
||||
thread->stats.FinishedOps(nullptr, db, 1, kMerge);
|
||||
}
|
||||
|
||||
// Print some statistics
|
||||
@@ -3610,9 +3692,8 @@ class Benchmark {
|
||||
fprintf(stderr, "merge error: %s\n", s.ToString().c_str());
|
||||
exit(1);
|
||||
}
|
||||
|
||||
num_merges++;
|
||||
|
||||
thread->stats.FinishedOps(nullptr, db, 1, kMerge);
|
||||
} else {
|
||||
Status s = db->Get(options, key, &value);
|
||||
if (value.length() > max_length)
|
||||
@@ -3625,12 +3706,9 @@ class Benchmark {
|
||||
} else if (!s.IsNotFound()) {
|
||||
num_hits++;
|
||||
}
|
||||
|
||||
num_gets++;
|
||||
|
||||
thread->stats.FinishedOps(nullptr, db, 1, kRead);
|
||||
}
|
||||
|
||||
thread->stats.FinishedOps(nullptr, db, 1);
|
||||
}
|
||||
|
||||
char msg[100];
|
||||
@@ -3657,7 +3735,7 @@ class Benchmark {
|
||||
GenerateKeyFromInt(i, FLAGS_num, &key);
|
||||
iter->Seek(key);
|
||||
assert(iter->Valid() && iter->key() == key);
|
||||
thread->stats.FinishedOps(nullptr, db, 1);
|
||||
thread->stats.FinishedOps(nullptr, db, 1, kSeek);
|
||||
|
||||
for (int j = 0; j < FLAGS_seek_nexts && i + 1 < FLAGS_num; ++j) {
|
||||
if (!FLAGS_reverse_iterator) {
|
||||
@@ -3667,12 +3745,12 @@ class Benchmark {
|
||||
}
|
||||
GenerateKeyFromInt(++i, FLAGS_num, &key);
|
||||
assert(iter->Valid() && iter->key() == key);
|
||||
thread->stats.FinishedOps(nullptr, db, 1);
|
||||
thread->stats.FinishedOps(nullptr, db, 1, kSeek);
|
||||
}
|
||||
|
||||
iter->Seek(key);
|
||||
assert(iter->Valid() && iter->key() == key);
|
||||
thread->stats.FinishedOps(nullptr, db, 1);
|
||||
thread->stats.FinishedOps(nullptr, db, 1, kSeek);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3826,15 +3904,11 @@ class Benchmark {
|
||||
}
|
||||
}
|
||||
|
||||
if (txn) {
|
||||
delete txn;
|
||||
}
|
||||
if (batch) {
|
||||
delete batch;
|
||||
}
|
||||
delete txn;
|
||||
delete batch;
|
||||
|
||||
if (!failed) {
|
||||
thread->stats.FinishedOps(nullptr, db, 1);
|
||||
thread->stats.FinishedOps(nullptr, db, 1, kOthers);
|
||||
}
|
||||
|
||||
transactions_done++;
|
||||
@@ -3960,7 +4034,7 @@ class Benchmark {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
thread->stats.FinishedOps(nullptr, db, 1);
|
||||
thread->stats.FinishedOps(nullptr, db, 1, kOthers);
|
||||
}
|
||||
|
||||
char msg[200];
|
||||
@@ -4013,7 +4087,7 @@ int main(int argc, char** argv) {
|
||||
|
||||
std::vector<std::string> fanout = rocksdb::StringSplit(
|
||||
FLAGS_max_bytes_for_level_multiplier_additional, ',');
|
||||
for (unsigned int j= 0; j < fanout.size(); j++) {
|
||||
for (size_t j = 0; j < fanout.size(); j++) {
|
||||
FLAGS_max_bytes_for_level_multiplier_additional_v.push_back(
|
||||
#ifndef CYGWIN
|
||||
std::stoi(fanout[j]));
|
||||
|
||||
@@ -47,6 +47,24 @@ class DeleteFilter : public CompactionFilter {
|
||||
virtual const char* Name() const override { return "DeleteFilter"; }
|
||||
};
|
||||
|
||||
class DeleteISFilter : public CompactionFilter {
|
||||
public:
|
||||
virtual bool Filter(int level, const Slice& key, const Slice& value,
|
||||
std::string* new_value,
|
||||
bool* value_changed) const override {
|
||||
cfilter_count++;
|
||||
int i = std::stoi(key.ToString());
|
||||
if (i > 5 && i <= 105) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
virtual bool IgnoreSnapshots() const override { return true; }
|
||||
|
||||
virtual const char* Name() const override { return "DeleteFilter"; }
|
||||
};
|
||||
|
||||
class DelayFilter : public CompactionFilter {
|
||||
public:
|
||||
explicit DelayFilter(DBTestBase* d) : db_test(d) {}
|
||||
@@ -141,6 +159,21 @@ class DeleteFilterFactory : public CompactionFilterFactory {
|
||||
virtual const char* Name() const override { return "DeleteFilterFactory"; }
|
||||
};
|
||||
|
||||
// Delete Filter Factory which ignores snapshots
|
||||
class DeleteISFilterFactory : public CompactionFilterFactory {
|
||||
public:
|
||||
virtual std::unique_ptr<CompactionFilter> CreateCompactionFilter(
|
||||
const CompactionFilter::Context& context) override {
|
||||
if (context.is_manual_compaction) {
|
||||
return std::unique_ptr<CompactionFilter>(new DeleteISFilter());
|
||||
} else {
|
||||
return std::unique_ptr<CompactionFilter>(nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
virtual const char* Name() const override { return "DeleteFilterFactory"; }
|
||||
};
|
||||
|
||||
class DelayFilterFactory : public CompactionFilterFactory {
|
||||
public:
|
||||
explicit DelayFilterFactory(DBTestBase* d) : db_test(d) {}
|
||||
@@ -550,7 +583,7 @@ TEST_F(DBTestCompactionFilter, CompactionFilterContextManual) {
|
||||
iter->Next();
|
||||
}
|
||||
ASSERT_EQ(total, 700);
|
||||
ASSERT_EQ(count, 1);
|
||||
ASSERT_EQ(count, 2);
|
||||
}
|
||||
}
|
||||
#endif // ROCKSDB_LITE
|
||||
@@ -620,6 +653,68 @@ TEST_F(DBTestCompactionFilter, CompactionFilterSnapshot) {
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
||||
ASSERT_EQ(0U, CountLiveFiles());
|
||||
}
|
||||
|
||||
// Compaction filters should only be applied to records that are newer than the
|
||||
// latest snapshot. However, if the compaction filter asks to ignore snapshots
|
||||
// records newer than the snapshot will also be processed
|
||||
TEST_F(DBTestCompactionFilter, CompactionFilterIgnoreSnapshot) {
|
||||
std::string five = ToString(5);
|
||||
Options options;
|
||||
options.compaction_filter_factory = std::make_shared<DeleteISFilterFactory>();
|
||||
options.disable_auto_compactions = true;
|
||||
options.create_if_missing = true;
|
||||
options = CurrentOptions(options);
|
||||
DestroyAndReopen(options);
|
||||
|
||||
// Put some data.
|
||||
const Snapshot* snapshot = nullptr;
|
||||
for (int table = 0; table < 4; ++table) {
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
Put(ToString(table * 100 + i), "val");
|
||||
}
|
||||
Flush();
|
||||
|
||||
if (table == 0) {
|
||||
snapshot = db_->GetSnapshot();
|
||||
}
|
||||
}
|
||||
assert(snapshot != nullptr);
|
||||
|
||||
cfilter_count = 0;
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
||||
// The filter should delete 40 records.
|
||||
ASSERT_EQ(40U, cfilter_count);
|
||||
|
||||
{
|
||||
// Scan the entire database as of the snapshot to ensure
|
||||
// that nothing is left
|
||||
ReadOptions read_options;
|
||||
read_options.snapshot = snapshot;
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options));
|
||||
iter->SeekToFirst();
|
||||
int count = 0;
|
||||
while (iter->Valid()) {
|
||||
count++;
|
||||
iter->Next();
|
||||
}
|
||||
ASSERT_EQ(count, 6);
|
||||
read_options.snapshot = 0;
|
||||
std::unique_ptr<Iterator> iter1(db_->NewIterator(read_options));
|
||||
iter1->SeekToFirst();
|
||||
count = 0;
|
||||
while (iter1->Valid()) {
|
||||
count++;
|
||||
iter1->Next();
|
||||
}
|
||||
// We have deleted 10 keys from 40 using the compaction filter
|
||||
// Keys 6-9 before the snapshot and 100-105 after the snapshot
|
||||
ASSERT_EQ(count, 30);
|
||||
}
|
||||
|
||||
// Release the snapshot and compact again -> now all records should be
|
||||
// removed.
|
||||
db_->ReleaseSnapshot(snapshot);
|
||||
}
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
+561
-20
@@ -10,6 +10,7 @@
|
||||
#include "db/db_test_util.h"
|
||||
#include "port/stack_trace.h"
|
||||
#include "rocksdb/experimental.h"
|
||||
#include "rocksdb/utilities/convenience.h"
|
||||
#include "util/sync_point.h"
|
||||
namespace rocksdb {
|
||||
|
||||
@@ -21,11 +22,13 @@ class DBCompactionTest : public DBTestBase {
|
||||
DBCompactionTest() : DBTestBase("/db_compaction_test") {}
|
||||
};
|
||||
|
||||
class DBCompactionTestWithParam : public DBTestBase,
|
||||
public testing::WithParamInterface<uint32_t> {
|
||||
class DBCompactionTestWithParam
|
||||
: public DBTestBase,
|
||||
public testing::WithParamInterface<std::tuple<uint32_t, bool>> {
|
||||
public:
|
||||
DBCompactionTestWithParam() : DBTestBase("/db_compaction_test") {
|
||||
max_subcompactions_ = GetParam();
|
||||
max_subcompactions_ = std::get<0>(GetParam());
|
||||
exclusive_manual_compaction_ = std::get<1>(GetParam());
|
||||
}
|
||||
|
||||
// Required if inheriting from testing::WithParamInterface<>
|
||||
@@ -33,6 +36,7 @@ class DBCompactionTestWithParam : public DBTestBase,
|
||||
static void TearDownTestCase() {}
|
||||
|
||||
uint32_t max_subcompactions_;
|
||||
bool exclusive_manual_compaction_;
|
||||
};
|
||||
|
||||
namespace {
|
||||
@@ -65,6 +69,32 @@ class OnFileDeletionListener : public EventListener {
|
||||
std::string expected_file_name_;
|
||||
};
|
||||
|
||||
class FlushedFileCollector : public EventListener {
|
||||
public:
|
||||
FlushedFileCollector() {}
|
||||
~FlushedFileCollector() {}
|
||||
|
||||
virtual void OnFlushCompleted(DB* db, const FlushJobInfo& info) override {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
flushed_files_.push_back(info.file_path);
|
||||
}
|
||||
|
||||
std::vector<std::string> GetFlushedFiles() {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
std::vector<std::string> result;
|
||||
for (auto fname : flushed_files_) {
|
||||
result.push_back(fname);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void ClearFlushedFiles() { flushed_files_.clear(); }
|
||||
|
||||
private:
|
||||
std::vector<std::string> flushed_files_;
|
||||
std::mutex mutex_;
|
||||
};
|
||||
|
||||
static const int kCDTValueSize = 1000;
|
||||
static const int kCDTKeysPerBuffer = 4;
|
||||
static const int kCDTNumLevels = 8;
|
||||
@@ -160,7 +190,7 @@ const SstFileMetaData* PickFileRandomly(
|
||||
auto result = rand->Uniform(file_id);
|
||||
return &(level_meta.files[result]);
|
||||
}
|
||||
file_id -= level_meta.files.size();
|
||||
file_id -= static_cast<uint32_t>(level_meta.files.size());
|
||||
}
|
||||
assert(false);
|
||||
return nullptr;
|
||||
@@ -239,9 +269,12 @@ TEST_F(DBCompactionTest, SkipStatsUpdateTest) {
|
||||
env_->random_file_open_counter_.store(0);
|
||||
Reopen(options);
|
||||
|
||||
// As stats-update is disabled, we expect a very low
|
||||
// number of random file open.
|
||||
ASSERT_LT(env_->random_file_open_counter_.load(), 5);
|
||||
// As stats-update is disabled, we expect a very low number of
|
||||
// random file open.
|
||||
// Note that this number must be changed accordingly if we change
|
||||
// the number of files needed to be opened in the DB::Open process.
|
||||
const int kMaxFileOpenCount = 10;
|
||||
ASSERT_LT(env_->random_file_open_counter_.load(), kMaxFileOpenCount);
|
||||
|
||||
// Repeat the reopen process, but this time we enable
|
||||
// stats-update.
|
||||
@@ -251,7 +284,7 @@ TEST_F(DBCompactionTest, SkipStatsUpdateTest) {
|
||||
|
||||
// Since we do a normal stats update on db-open, there
|
||||
// will be more random open files.
|
||||
ASSERT_GT(env_->random_file_open_counter_.load(), 5);
|
||||
ASSERT_GT(env_->random_file_open_counter_.load(), kMaxFileOpenCount);
|
||||
}
|
||||
|
||||
TEST_F(DBCompactionTest, TestTableReaderForCompaction) {
|
||||
@@ -458,12 +491,15 @@ TEST_F(DBCompactionTest, DisableStatsUpdateReopen) {
|
||||
|
||||
|
||||
TEST_P(DBCompactionTestWithParam, CompactionTrigger) {
|
||||
const int kNumKeysPerFile = 100;
|
||||
|
||||
Options options;
|
||||
options.write_buffer_size = 110 << 10; // 110KB
|
||||
options.arena_block_size = 4 << 10;
|
||||
options.num_levels = 3;
|
||||
options.level0_file_num_compaction_trigger = 3;
|
||||
options.max_subcompactions = max_subcompactions_;
|
||||
options.memtable_factory.reset(new SpecialSkipListFactory(kNumKeysPerFile));
|
||||
options = CurrentOptions(options);
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
|
||||
@@ -473,20 +509,24 @@ TEST_P(DBCompactionTestWithParam, CompactionTrigger) {
|
||||
num++) {
|
||||
std::vector<std::string> values;
|
||||
// Write 100KB (100 values, each 1K)
|
||||
for (int i = 0; i < 100; i++) {
|
||||
for (int i = 0; i < kNumKeysPerFile; i++) {
|
||||
values.push_back(RandomString(&rnd, 990));
|
||||
ASSERT_OK(Put(1, Key(i), values[i]));
|
||||
}
|
||||
// put extra key to trigger flush
|
||||
ASSERT_OK(Put(1, "", ""));
|
||||
dbfull()->TEST_WaitForFlushMemTable(handles_[1]);
|
||||
ASSERT_EQ(NumTableFilesAtLevel(0, 1), num + 1);
|
||||
}
|
||||
|
||||
// generate one more file in level-0, and should trigger level-0 compaction
|
||||
std::vector<std::string> values;
|
||||
for (int i = 0; i < 100; i++) {
|
||||
for (int i = 0; i < kNumKeysPerFile; i++) {
|
||||
values.push_back(RandomString(&rnd, 990));
|
||||
ASSERT_OK(Put(1, Key(i), values[i]));
|
||||
}
|
||||
// put extra key to trigger flush
|
||||
ASSERT_OK(Put(1, "", ""));
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
|
||||
ASSERT_EQ(NumTableFilesAtLevel(0, 1), 0);
|
||||
@@ -550,6 +590,61 @@ TEST_F(DBCompactionTest, MinorCompactionsHappen) {
|
||||
} while (ChangeCompactOptions());
|
||||
}
|
||||
|
||||
TEST_F(DBCompactionTest, ZeroSeqIdCompaction) {
|
||||
Options options;
|
||||
options.compaction_style = kCompactionStyleLevel;
|
||||
options.level0_file_num_compaction_trigger = 3;
|
||||
|
||||
FlushedFileCollector* collector = new FlushedFileCollector();
|
||||
options.listeners.emplace_back(collector);
|
||||
|
||||
// compaction options
|
||||
CompactionOptions compact_opt;
|
||||
compact_opt.compression = kNoCompression;
|
||||
compact_opt.output_file_size_limit = 4096;
|
||||
const size_t key_len =
|
||||
static_cast<size_t>(compact_opt.output_file_size_limit) / 5;
|
||||
|
||||
options = CurrentOptions(options);
|
||||
DestroyAndReopen(options);
|
||||
|
||||
std::vector<const Snapshot*> snaps;
|
||||
|
||||
// create first file and flush to l0
|
||||
for (auto& key : {"1", "2", "3", "3", "3", "3"}) {
|
||||
Put(key, std::string(key_len, 'A'));
|
||||
snaps.push_back(dbfull()->GetSnapshot());
|
||||
}
|
||||
Flush();
|
||||
dbfull()->TEST_WaitForFlushMemTable();
|
||||
|
||||
// create second file and flush to l0
|
||||
for (auto& key : {"3", "4", "5", "6", "7", "8"}) {
|
||||
Put(key, std::string(key_len, 'A'));
|
||||
snaps.push_back(dbfull()->GetSnapshot());
|
||||
}
|
||||
Flush();
|
||||
dbfull()->TEST_WaitForFlushMemTable();
|
||||
|
||||
// move both files down to l1
|
||||
dbfull()->CompactFiles(compact_opt, collector->GetFlushedFiles(), 1);
|
||||
|
||||
// release snap so that first instance of key(3) can have seqId=0
|
||||
for (auto snap : snaps) {
|
||||
dbfull()->ReleaseSnapshot(snap);
|
||||
}
|
||||
|
||||
// create 3 files in l0 so to trigger compaction
|
||||
for (int i = 0; i < options.level0_file_num_compaction_trigger; i++) {
|
||||
Put("2", std::string(1, 'A'));
|
||||
Flush();
|
||||
dbfull()->TEST_WaitForFlushMemTable();
|
||||
}
|
||||
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
ASSERT_OK(Put("", ""));
|
||||
}
|
||||
|
||||
// Check that writes done during a memtable compaction are recovered
|
||||
// if the database is shutdown during the memtable compaction.
|
||||
TEST_F(DBCompactionTest, RecoverDuringMemtableCompaction) {
|
||||
@@ -607,8 +702,11 @@ TEST_P(DBCompactionTestWithParam, TrivialMoveOneFile) {
|
||||
ASSERT_EQ(metadata.size(), 1U);
|
||||
LiveFileMetaData level0_file = metadata[0]; // L0 file meta
|
||||
|
||||
CompactRangeOptions cro;
|
||||
cro.exclusive_manual_compaction = exclusive_manual_compaction_;
|
||||
|
||||
// Compaction will initiate a trivial move from L0 to L1
|
||||
dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr);
|
||||
dbfull()->CompactRange(cro, nullptr, nullptr);
|
||||
|
||||
// File moved From L0 to L1
|
||||
ASSERT_EQ(NumTableFilesAtLevel(0, 0), 0); // 0 files in L0
|
||||
@@ -660,7 +758,7 @@ TEST_P(DBCompactionTestWithParam, TrivialMoveNonOverlappingFiles) {
|
||||
|
||||
Random rnd(301);
|
||||
std::map<int32_t, std::string> values;
|
||||
for (uint32_t i = 0; i < ranges.size(); i++) {
|
||||
for (size_t i = 0; i < ranges.size(); i++) {
|
||||
for (int32_t j = ranges[i].first; j <= ranges[i].second; j++) {
|
||||
values[j] = RandomString(&rnd, value_size);
|
||||
ASSERT_OK(Put(Key(j), values[j]));
|
||||
@@ -672,14 +770,17 @@ TEST_P(DBCompactionTestWithParam, TrivialMoveNonOverlappingFiles) {
|
||||
ASSERT_EQ(level0_files, ranges.size()); // Multiple files in L0
|
||||
ASSERT_EQ(NumTableFilesAtLevel(1, 0), 0); // No files in L1
|
||||
|
||||
CompactRangeOptions cro;
|
||||
cro.exclusive_manual_compaction = exclusive_manual_compaction_;
|
||||
|
||||
// Since data is non-overlapping we expect compaction to initiate
|
||||
// a trivial move
|
||||
db_->CompactRange(CompactRangeOptions(), nullptr, nullptr);
|
||||
db_->CompactRange(cro, nullptr, nullptr);
|
||||
// We expect that all the files were trivially moved from L0 to L1
|
||||
ASSERT_EQ(NumTableFilesAtLevel(0, 0), 0);
|
||||
ASSERT_EQ(NumTableFilesAtLevel(1, 0) /* level1_files */, level0_files);
|
||||
|
||||
for (uint32_t i = 0; i < ranges.size(); i++) {
|
||||
for (size_t i = 0; i < ranges.size(); i++) {
|
||||
for (int32_t j = ranges[i].first; j <= ranges[i].second; j++) {
|
||||
ASSERT_EQ(Get(Key(j)), values[j]);
|
||||
}
|
||||
@@ -703,7 +804,7 @@ TEST_P(DBCompactionTestWithParam, TrivialMoveNonOverlappingFiles) {
|
||||
{500, 560}, // this range overlap with the next one
|
||||
{551, 599},
|
||||
};
|
||||
for (uint32_t i = 0; i < ranges.size(); i++) {
|
||||
for (size_t i = 0; i < ranges.size(); i++) {
|
||||
for (int32_t j = ranges[i].first; j <= ranges[i].second; j++) {
|
||||
values[j] = RandomString(&rnd, value_size);
|
||||
ASSERT_OK(Put(Key(j), values[j]));
|
||||
@@ -711,9 +812,9 @@ TEST_P(DBCompactionTestWithParam, TrivialMoveNonOverlappingFiles) {
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
|
||||
db_->CompactRange(CompactRangeOptions(), nullptr, nullptr);
|
||||
db_->CompactRange(cro, nullptr, nullptr);
|
||||
|
||||
for (uint32_t i = 0; i < ranges.size(); i++) {
|
||||
for (size_t i = 0; i < ranges.size(); i++) {
|
||||
for (int32_t j = ranges[i].first; j <= ranges[i].second; j++) {
|
||||
ASSERT_EQ(Get(Key(j)), values[j]);
|
||||
}
|
||||
@@ -767,6 +868,7 @@ TEST_P(DBCompactionTestWithParam, TrivialMoveTargetLevel) {
|
||||
CompactRangeOptions compact_options;
|
||||
compact_options.change_level = true;
|
||||
compact_options.target_level = 6;
|
||||
compact_options.exclusive_manual_compaction = exclusive_manual_compaction_;
|
||||
ASSERT_OK(db_->CompactRange(compact_options, nullptr, nullptr));
|
||||
// 2 files in L6
|
||||
ASSERT_EQ("0,0,0,0,0,0,2", FilesPerLevel(0));
|
||||
@@ -782,6 +884,382 @@ TEST_P(DBCompactionTestWithParam, TrivialMoveTargetLevel) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(DBCompactionTestWithParam, ManualCompactionPartial) {
|
||||
int32_t trivial_move = 0;
|
||||
int32_t non_trivial_move = 0;
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::BackgroundCompaction:TrivialMove",
|
||||
[&](void* arg) { trivial_move++; });
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::BackgroundCompaction:NonTrivial",
|
||||
[&](void* arg) { non_trivial_move++; });
|
||||
bool first = true;
|
||||
bool second = true;
|
||||
rocksdb::SyncPoint::GetInstance()->LoadDependency(
|
||||
{{"DBCompaction::ManualPartial:4", "DBCompaction::ManualPartial:1"},
|
||||
{"DBCompaction::ManualPartial:2", "DBCompaction::ManualPartial:3"}});
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::BackgroundCompaction:NonTrivial:AfterRun", [&](void* arg) {
|
||||
if (first) {
|
||||
TEST_SYNC_POINT("DBCompaction::ManualPartial:4");
|
||||
first = false;
|
||||
TEST_SYNC_POINT("DBCompaction::ManualPartial:3");
|
||||
} else if (second) {
|
||||
TEST_SYNC_POINT("DBCompaction::ManualPartial:2");
|
||||
}
|
||||
});
|
||||
|
||||
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
Options options = CurrentOptions();
|
||||
options.write_buffer_size = 10 * 1024 * 1024;
|
||||
options.num_levels = 7;
|
||||
options.max_subcompactions = max_subcompactions_;
|
||||
options.level0_file_num_compaction_trigger = 3;
|
||||
options.max_background_compactions = 3;
|
||||
options.target_file_size_base = 1 << 23; // 8 MB
|
||||
|
||||
DestroyAndReopen(options);
|
||||
int32_t value_size = 10 * 1024; // 10 KB
|
||||
|
||||
// Add 2 non-overlapping files
|
||||
Random rnd(301);
|
||||
std::map<int32_t, std::string> values;
|
||||
|
||||
// file 1 [0 => 100]
|
||||
for (int32_t i = 0; i < 100; i++) {
|
||||
values[i] = RandomString(&rnd, value_size);
|
||||
ASSERT_OK(Put(Key(i), values[i]));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
// file 2 [100 => 300]
|
||||
for (int32_t i = 100; i < 300; i++) {
|
||||
values[i] = RandomString(&rnd, value_size);
|
||||
ASSERT_OK(Put(Key(i), values[i]));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
// 2 files in L0
|
||||
ASSERT_EQ("2", FilesPerLevel(0));
|
||||
CompactRangeOptions compact_options;
|
||||
compact_options.change_level = true;
|
||||
compact_options.target_level = 6;
|
||||
compact_options.exclusive_manual_compaction = exclusive_manual_compaction_;
|
||||
// Trivial move the two non-overlapping files to level 6
|
||||
ASSERT_OK(db_->CompactRange(compact_options, nullptr, nullptr));
|
||||
// 2 files in L6
|
||||
ASSERT_EQ("0,0,0,0,0,0,2", FilesPerLevel(0));
|
||||
|
||||
ASSERT_EQ(trivial_move, 1);
|
||||
ASSERT_EQ(non_trivial_move, 0);
|
||||
|
||||
// file 3 [ 0 => 200]
|
||||
for (int32_t i = 0; i < 200; i++) {
|
||||
values[i] = RandomString(&rnd, value_size);
|
||||
ASSERT_OK(Put(Key(i), values[i]));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
// 1 files in L0
|
||||
ASSERT_EQ("1,0,0,0,0,0,2", FilesPerLevel(0));
|
||||
ASSERT_OK(dbfull()->TEST_CompactRange(0, nullptr, nullptr, nullptr, false));
|
||||
ASSERT_OK(dbfull()->TEST_CompactRange(1, nullptr, nullptr, nullptr, false));
|
||||
ASSERT_OK(dbfull()->TEST_CompactRange(2, nullptr, nullptr, nullptr, false));
|
||||
ASSERT_OK(dbfull()->TEST_CompactRange(3, nullptr, nullptr, nullptr, false));
|
||||
ASSERT_OK(dbfull()->TEST_CompactRange(4, nullptr, nullptr, nullptr, false));
|
||||
// 2 files in L6, 1 file in L5
|
||||
ASSERT_EQ("0,0,0,0,0,1,2", FilesPerLevel(0));
|
||||
|
||||
ASSERT_EQ(trivial_move, 6);
|
||||
ASSERT_EQ(non_trivial_move, 0);
|
||||
|
||||
std::thread threads([&] {
|
||||
compact_options.change_level = false;
|
||||
compact_options.exclusive_manual_compaction = false;
|
||||
std::string begin_string = Key(0);
|
||||
std::string end_string = Key(199);
|
||||
Slice begin(begin_string);
|
||||
Slice end(end_string);
|
||||
ASSERT_OK(db_->CompactRange(compact_options, &begin, &end));
|
||||
});
|
||||
|
||||
TEST_SYNC_POINT("DBCompaction::ManualPartial:1");
|
||||
// file 4 [300 => 400)
|
||||
for (int32_t i = 300; i <= 400; i++) {
|
||||
values[i] = RandomString(&rnd, value_size);
|
||||
ASSERT_OK(Put(Key(i), values[i]));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
// file 5 [400 => 500)
|
||||
for (int32_t i = 400; i <= 500; i++) {
|
||||
values[i] = RandomString(&rnd, value_size);
|
||||
ASSERT_OK(Put(Key(i), values[i]));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
// file 6 [500 => 600)
|
||||
for (int32_t i = 500; i <= 600; i++) {
|
||||
values[i] = RandomString(&rnd, value_size);
|
||||
ASSERT_OK(Put(Key(i), values[i]));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
// 3 files in L0
|
||||
ASSERT_EQ("3,0,0,0,0,1,2", FilesPerLevel(0));
|
||||
// 1 file in L6, 1 file in L1
|
||||
dbfull()->TEST_WaitForFlushMemTable();
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
ASSERT_EQ("0,1,0,0,0,0,1", FilesPerLevel(0));
|
||||
threads.join();
|
||||
|
||||
for (int32_t i = 0; i < 600; i++) {
|
||||
ASSERT_EQ(Get(Key(i)), values[i]);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBCompactionTest, ManualPartialFill) {
|
||||
int32_t trivial_move = 0;
|
||||
int32_t non_trivial_move = 0;
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::BackgroundCompaction:TrivialMove",
|
||||
[&](void* arg) { trivial_move++; });
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::BackgroundCompaction:NonTrivial",
|
||||
[&](void* arg) { non_trivial_move++; });
|
||||
bool first = true;
|
||||
bool second = true;
|
||||
rocksdb::SyncPoint::GetInstance()->LoadDependency(
|
||||
{{"DBCompaction::PartialFill:4", "DBCompaction::PartialFill:1"},
|
||||
{"DBCompaction::PartialFill:2", "DBCompaction::PartialFill:3"}});
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::BackgroundCompaction:NonTrivial:AfterRun", [&](void* arg) {
|
||||
if (first) {
|
||||
TEST_SYNC_POINT("DBCompaction::PartialFill:4");
|
||||
first = false;
|
||||
TEST_SYNC_POINT("DBCompaction::PartialFill:3");
|
||||
} else if (second) {
|
||||
}
|
||||
});
|
||||
|
||||
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
Options options = CurrentOptions();
|
||||
options.write_buffer_size = 10 * 1024 * 1024;
|
||||
options.max_bytes_for_level_multiplier = 2;
|
||||
options.num_levels = 4;
|
||||
options.level0_file_num_compaction_trigger = 3;
|
||||
options.max_background_compactions = 3;
|
||||
|
||||
DestroyAndReopen(options);
|
||||
int32_t value_size = 10 * 1024; // 10 KB
|
||||
|
||||
// Add 2 non-overlapping files
|
||||
Random rnd(301);
|
||||
std::map<int32_t, std::string> values;
|
||||
|
||||
// file 1 [0 => 100]
|
||||
for (int32_t i = 0; i < 100; i++) {
|
||||
values[i] = RandomString(&rnd, value_size);
|
||||
ASSERT_OK(Put(Key(i), values[i]));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
// file 2 [100 => 300]
|
||||
for (int32_t i = 100; i < 300; i++) {
|
||||
values[i] = RandomString(&rnd, value_size);
|
||||
ASSERT_OK(Put(Key(i), values[i]));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
// 2 files in L0
|
||||
ASSERT_EQ("2", FilesPerLevel(0));
|
||||
CompactRangeOptions compact_options;
|
||||
compact_options.change_level = true;
|
||||
compact_options.target_level = 2;
|
||||
ASSERT_OK(db_->CompactRange(compact_options, nullptr, nullptr));
|
||||
// 2 files in L2
|
||||
ASSERT_EQ("0,0,2", FilesPerLevel(0));
|
||||
|
||||
ASSERT_EQ(trivial_move, 1);
|
||||
ASSERT_EQ(non_trivial_move, 0);
|
||||
|
||||
// file 3 [ 0 => 200]
|
||||
for (int32_t i = 0; i < 200; i++) {
|
||||
values[i] = RandomString(&rnd, value_size);
|
||||
ASSERT_OK(Put(Key(i), values[i]));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
// 2 files in L2, 1 in L0
|
||||
ASSERT_EQ("1,0,2", FilesPerLevel(0));
|
||||
ASSERT_OK(dbfull()->TEST_CompactRange(0, nullptr, nullptr, nullptr, false));
|
||||
// 2 files in L2, 1 in L1
|
||||
ASSERT_EQ("0,1,2", FilesPerLevel(0));
|
||||
|
||||
ASSERT_EQ(trivial_move, 2);
|
||||
ASSERT_EQ(non_trivial_move, 0);
|
||||
|
||||
std::thread threads([&] {
|
||||
compact_options.change_level = false;
|
||||
compact_options.exclusive_manual_compaction = false;
|
||||
std::string begin_string = Key(0);
|
||||
std::string end_string = Key(199);
|
||||
Slice begin(begin_string);
|
||||
Slice end(end_string);
|
||||
ASSERT_OK(db_->CompactRange(compact_options, &begin, &end));
|
||||
});
|
||||
|
||||
TEST_SYNC_POINT("DBCompaction::PartialFill:1");
|
||||
// Many files 4 [300 => 4300)
|
||||
for (int32_t i = 0; i <= 5; i++) {
|
||||
for (int32_t j = 300; j < 4300; j++) {
|
||||
if (j == 2300) {
|
||||
ASSERT_OK(Flush());
|
||||
dbfull()->TEST_WaitForFlushMemTable();
|
||||
}
|
||||
values[j] = RandomString(&rnd, value_size);
|
||||
ASSERT_OK(Put(Key(j), values[j]));
|
||||
}
|
||||
}
|
||||
|
||||
// Verify level sizes
|
||||
uint64_t target_size = 4 * options.max_bytes_for_level_base;
|
||||
for (int32_t i = 1; i < options.num_levels; i++) {
|
||||
ASSERT_LE(SizeAtLevel(i), target_size);
|
||||
target_size *= options.max_bytes_for_level_multiplier;
|
||||
}
|
||||
|
||||
TEST_SYNC_POINT("DBCompaction::PartialFill:2");
|
||||
dbfull()->TEST_WaitForFlushMemTable();
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
threads.join();
|
||||
|
||||
for (int32_t i = 0; i < 4300; i++) {
|
||||
ASSERT_EQ(Get(Key(i)), values[i]);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBCompactionTest, DeleteFileRange) {
|
||||
Options options = CurrentOptions();
|
||||
options.write_buffer_size = 10 * 1024 * 1024;
|
||||
options.max_bytes_for_level_multiplier = 2;
|
||||
options.num_levels = 4;
|
||||
options.level0_file_num_compaction_trigger = 3;
|
||||
options.max_background_compactions = 3;
|
||||
|
||||
DestroyAndReopen(options);
|
||||
int32_t value_size = 10 * 1024; // 10 KB
|
||||
|
||||
// Add 2 non-overlapping files
|
||||
Random rnd(301);
|
||||
std::map<int32_t, std::string> values;
|
||||
|
||||
// file 1 [0 => 100]
|
||||
for (int32_t i = 0; i < 100; i++) {
|
||||
values[i] = RandomString(&rnd, value_size);
|
||||
ASSERT_OK(Put(Key(i), values[i]));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
// file 2 [100 => 300]
|
||||
for (int32_t i = 100; i < 300; i++) {
|
||||
values[i] = RandomString(&rnd, value_size);
|
||||
ASSERT_OK(Put(Key(i), values[i]));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
// 2 files in L0
|
||||
ASSERT_EQ("2", FilesPerLevel(0));
|
||||
CompactRangeOptions compact_options;
|
||||
compact_options.change_level = true;
|
||||
compact_options.target_level = 2;
|
||||
ASSERT_OK(db_->CompactRange(compact_options, nullptr, nullptr));
|
||||
// 2 files in L2
|
||||
ASSERT_EQ("0,0,2", FilesPerLevel(0));
|
||||
|
||||
// file 3 [ 0 => 200]
|
||||
for (int32_t i = 0; i < 200; i++) {
|
||||
values[i] = RandomString(&rnd, value_size);
|
||||
ASSERT_OK(Put(Key(i), values[i]));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
// Many files 4 [300 => 4300)
|
||||
for (int32_t i = 0; i <= 5; i++) {
|
||||
for (int32_t j = 300; j < 4300; j++) {
|
||||
if (j == 2300) {
|
||||
ASSERT_OK(Flush());
|
||||
dbfull()->TEST_WaitForFlushMemTable();
|
||||
}
|
||||
values[j] = RandomString(&rnd, value_size);
|
||||
ASSERT_OK(Put(Key(j), values[j]));
|
||||
}
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
dbfull()->TEST_WaitForFlushMemTable();
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
|
||||
// Verify level sizes
|
||||
uint64_t target_size = 4 * options.max_bytes_for_level_base;
|
||||
for (int32_t i = 1; i < options.num_levels; i++) {
|
||||
ASSERT_LE(SizeAtLevel(i), target_size);
|
||||
target_size *= options.max_bytes_for_level_multiplier;
|
||||
}
|
||||
|
||||
size_t old_num_files = CountFiles();
|
||||
std::string begin_string = Key(1000);
|
||||
std::string end_string = Key(2000);
|
||||
Slice begin(begin_string);
|
||||
Slice end(end_string);
|
||||
ASSERT_OK(DeleteFilesInRange(db_, db_->DefaultColumnFamily(), &begin, &end));
|
||||
|
||||
int32_t deleted_count = 0;
|
||||
for (int32_t i = 0; i < 4300; i++) {
|
||||
if (i < 1000 || i > 2000) {
|
||||
ASSERT_EQ(Get(Key(i)), values[i]);
|
||||
} else {
|
||||
ReadOptions roptions;
|
||||
std::string result;
|
||||
Status s = db_->Get(roptions, Key(i), &result);
|
||||
ASSERT_TRUE(s.IsNotFound() || s.ok());
|
||||
if (s.IsNotFound()) {
|
||||
deleted_count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
ASSERT_GT(deleted_count, 0);
|
||||
begin_string = Key(5000);
|
||||
end_string = Key(6000);
|
||||
Slice begin1(begin_string);
|
||||
Slice end1(end_string);
|
||||
// Try deleting files in range which contain no keys
|
||||
ASSERT_OK(
|
||||
DeleteFilesInRange(db_, db_->DefaultColumnFamily(), &begin1, &end1));
|
||||
|
||||
// Push data from level 0 to level 1 to force all data to be deleted
|
||||
// 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(
|
||||
DeleteFilesInRange(db_, db_->DefaultColumnFamily(), nullptr, nullptr));
|
||||
|
||||
int32_t deleted_count2 = 0;
|
||||
for (int32_t i = 0; i < 4300; i++) {
|
||||
ReadOptions roptions;
|
||||
std::string result;
|
||||
Status s = db_->Get(roptions, Key(i), &result);
|
||||
ASSERT_TRUE(s.IsNotFound());
|
||||
deleted_count2++;
|
||||
}
|
||||
ASSERT_GT(deleted_count2, deleted_count);
|
||||
size_t new_num_files = CountFiles();
|
||||
ASSERT_GT(old_num_files, new_num_files);
|
||||
}
|
||||
|
||||
TEST_P(DBCompactionTestWithParam, TrivialMoveToLastLevelWithFiles) {
|
||||
int32_t trivial_move = 0;
|
||||
int32_t non_trivial_move = 0;
|
||||
@@ -815,6 +1293,7 @@ TEST_P(DBCompactionTestWithParam, TrivialMoveToLastLevelWithFiles) {
|
||||
CompactRangeOptions compact_options;
|
||||
compact_options.change_level = true;
|
||||
compact_options.target_level = 3;
|
||||
compact_options.exclusive_manual_compaction = exclusive_manual_compaction_;
|
||||
ASSERT_OK(db_->CompactRange(compact_options, nullptr, nullptr));
|
||||
ASSERT_EQ("0,0,0,1", FilesPerLevel(0));
|
||||
ASSERT_EQ(trivial_move, 1);
|
||||
@@ -828,8 +1307,10 @@ TEST_P(DBCompactionTestWithParam, TrivialMoveToLastLevelWithFiles) {
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
ASSERT_EQ("1,0,0,1", FilesPerLevel(0));
|
||||
CompactRangeOptions cro;
|
||||
cro.exclusive_manual_compaction = exclusive_manual_compaction_;
|
||||
// Compaction will do L0=>L1 L1=>L2 L2=>L3 (3 trivial moves)
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
||||
ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr));
|
||||
ASSERT_EQ("0,0,0,2", FilesPerLevel(0));
|
||||
ASSERT_EQ(trivial_move, 4);
|
||||
ASSERT_EQ(non_trivial_move, 0);
|
||||
@@ -846,6 +1327,8 @@ TEST_P(DBCompactionTestWithParam, LevelCompactionThirdPath) {
|
||||
options.db_paths.emplace_back(dbname_, 500 * 1024);
|
||||
options.db_paths.emplace_back(dbname_ + "_2", 4 * 1024 * 1024);
|
||||
options.db_paths.emplace_back(dbname_ + "_3", 1024 * 1024 * 1024);
|
||||
options.memtable_factory.reset(
|
||||
new SpecialSkipListFactory(KNumKeysByGenerateNewFile - 1));
|
||||
options.compaction_style = kCompactionStyleLevel;
|
||||
options.write_buffer_size = 110 << 10; // 110KB
|
||||
options.arena_block_size = 4 << 10;
|
||||
@@ -961,6 +1444,8 @@ TEST_P(DBCompactionTestWithParam, LevelCompactionPathUse) {
|
||||
options.db_paths.emplace_back(dbname_, 500 * 1024);
|
||||
options.db_paths.emplace_back(dbname_ + "_2", 4 * 1024 * 1024);
|
||||
options.db_paths.emplace_back(dbname_ + "_3", 1024 * 1024 * 1024);
|
||||
options.memtable_factory.reset(
|
||||
new SpecialSkipListFactory(KNumKeysByGenerateNewFile - 1));
|
||||
options.compaction_style = kCompactionStyleLevel;
|
||||
options.write_buffer_size = 110 << 10; // 110KB
|
||||
options.arena_block_size = 4 << 10;
|
||||
@@ -1129,6 +1614,7 @@ TEST_P(DBCompactionTestWithParam, ConvertCompactionStyle) {
|
||||
compact_options.target_level = 0;
|
||||
compact_options.bottommost_level_compaction =
|
||||
BottommostLevelCompaction::kForce;
|
||||
compact_options.exclusive_manual_compaction = exclusive_manual_compaction_;
|
||||
dbfull()->CompactRange(compact_options, handles_[1], nullptr, nullptr);
|
||||
|
||||
// Only 1 file in L0
|
||||
@@ -1262,7 +1748,9 @@ TEST_P(DBCompactionTestWithParam, ManualCompaction) {
|
||||
|
||||
uint64_t prev_block_cache_add =
|
||||
options.statistics->getTickerCount(BLOCK_CACHE_ADD);
|
||||
db_->CompactRange(CompactRangeOptions(), handles_[1], nullptr, nullptr);
|
||||
CompactRangeOptions cro;
|
||||
cro.exclusive_manual_compaction = exclusive_manual_compaction_;
|
||||
db_->CompactRange(cro, handles_[1], nullptr, nullptr);
|
||||
// Verify manual compaction doesn't fill block cache
|
||||
ASSERT_EQ(prev_block_cache_add,
|
||||
options.statistics->getTickerCount(BLOCK_CACHE_ADD));
|
||||
@@ -1341,6 +1829,7 @@ TEST_P(DBCompactionTestWithParam, ManualLevelCompactionOutputPathId) {
|
||||
ASSERT_EQ(1, GetSstFileCount(options.db_paths[0].path));
|
||||
CompactRangeOptions compact_options;
|
||||
compact_options.target_path_id = 1;
|
||||
compact_options.exclusive_manual_compaction = exclusive_manual_compaction_;
|
||||
db_->CompactRange(compact_options, handles_[1], nullptr, nullptr);
|
||||
|
||||
ASSERT_EQ("0,1", FilesPerLevel(1));
|
||||
@@ -1601,6 +2090,8 @@ TEST_P(DBCompactionTestWithParam, CompressLevelCompaction) {
|
||||
return;
|
||||
}
|
||||
Options options = CurrentOptions();
|
||||
options.memtable_factory.reset(
|
||||
new SpecialSkipListFactory(KNumKeysByGenerateNewFile - 1));
|
||||
options.compaction_style = kCompactionStyleLevel;
|
||||
options.write_buffer_size = 110 << 10; // 110KB
|
||||
options.arena_block_size = 4 << 10;
|
||||
@@ -1851,7 +2342,57 @@ TEST_P(DBCompactionTestWithParam, ForceBottommostLevelCompaction) {
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(DBCompactionTestWithParam, DBCompactionTestWithParam,
|
||||
::testing::Values(1, 4));
|
||||
::testing::Values(std::make_tuple(1, true),
|
||||
std::make_tuple(1, false),
|
||||
std::make_tuple(4, true),
|
||||
std::make_tuple(4, false)));
|
||||
|
||||
class CompactionPriTest : public DBTestBase,
|
||||
public testing::WithParamInterface<uint32_t> {
|
||||
public:
|
||||
CompactionPriTest() : DBTestBase("/compaction_pri_test") {
|
||||
compaction_pri_ = GetParam();
|
||||
}
|
||||
|
||||
// Required if inheriting from testing::WithParamInterface<>
|
||||
static void SetUpTestCase() {}
|
||||
static void TearDownTestCase() {}
|
||||
|
||||
uint32_t compaction_pri_;
|
||||
};
|
||||
|
||||
TEST_P(CompactionPriTest, Test) {
|
||||
Options options;
|
||||
options.write_buffer_size = 16 * 1024;
|
||||
options.compaction_pri = static_cast<CompactionPri>(compaction_pri_);
|
||||
options.hard_pending_compaction_bytes_limit = 256 * 1024;
|
||||
options.max_bytes_for_level_base = 64 * 1024;
|
||||
options.max_bytes_for_level_multiplier = 4;
|
||||
options.compression = kNoCompression;
|
||||
options = CurrentOptions(options);
|
||||
DestroyAndReopen(options);
|
||||
|
||||
Random rnd(301);
|
||||
const int kNKeys = 5000;
|
||||
int keys[kNKeys];
|
||||
for (int i = 0; i < kNKeys; i++) {
|
||||
keys[i] = i;
|
||||
}
|
||||
std::random_shuffle(std::begin(keys), std::end(keys));
|
||||
|
||||
for (int i = 0; i < kNKeys; i++) {
|
||||
ASSERT_OK(Put(Key(keys[i]), RandomString(&rnd, 102)));
|
||||
}
|
||||
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
for (int i = 0; i < kNKeys; i++) {
|
||||
ASSERT_NE("NOT_FOUND", Get(Key(i)));
|
||||
}
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(CompactionPriTest, CompactionPriTest,
|
||||
::testing::Values(0, 1, 2));
|
||||
|
||||
#endif // !defined(ROCKSDB_LITE)
|
||||
} // namespace rocksdb
|
||||
|
||||
|
||||
+843
-229
File diff suppressed because it is too large.
Load diff
+82
-19
@@ -144,6 +144,9 @@ class DBImpl : public DB {
|
||||
virtual Status PauseBackgroundWork() override;
|
||||
virtual Status ContinueBackgroundWork() override;
|
||||
|
||||
virtual Status EnableAutoCompaction(
|
||||
const std::vector<ColumnFamilyHandle*>& column_family_handles) override;
|
||||
|
||||
using DB::SetOptions;
|
||||
Status SetOptions(
|
||||
ColumnFamilyHandle* column_family,
|
||||
@@ -185,6 +188,8 @@ class DBImpl : public DB {
|
||||
const TransactionLogIterator::ReadOptions&
|
||||
read_options = TransactionLogIterator::ReadOptions()) override;
|
||||
virtual Status DeleteFile(std::string name) override;
|
||||
Status DeleteFilesInRange(ColumnFamilyHandle* column_family,
|
||||
const Slice* begin, const Slice* end);
|
||||
|
||||
virtual void GetLiveFilesMetaData(
|
||||
std::vector<LiveFileMetaData>* metadata) override;
|
||||
@@ -223,13 +228,30 @@ class DBImpl : public DB {
|
||||
bool include_history);
|
||||
|
||||
// For a given key, check to see if there are any records for this key
|
||||
// in the memtables, including memtable history.
|
||||
|
||||
// On success, *seq will contain the sequence number for the
|
||||
// latest such change or kMaxSequenceNumber if no records were present.
|
||||
// Returns OK on success, other status on error reading memtables.
|
||||
Status GetLatestSequenceForKeyFromMemtable(SuperVersion* sv, const Slice& key,
|
||||
SequenceNumber* seq);
|
||||
// in the memtables, including memtable history. If cache_only is false,
|
||||
// SST files will also be checked.
|
||||
//
|
||||
// If a key is found, *found_record_for_key will be set to true and
|
||||
// *seq will will be set to the stored sequence number for the latest
|
||||
// operation on this key or kMaxSequenceNumber if unknown.
|
||||
// If no key is found, *found_record_for_key will be set to false.
|
||||
//
|
||||
// Note: If cache_only=false, it is possible for *seq to be set to 0 if
|
||||
// the sequence number has been cleared from the record. If the caller is
|
||||
// holding an active db snapshot, we know the missing sequence must be less
|
||||
// than the snapshot's sequence number (sequence numbers are only cleared
|
||||
// when there are no earlier active snapshots).
|
||||
//
|
||||
// If NotFound is returned and found_record_for_key is set to false, then no
|
||||
// record for this key was found. If the caller is holding an active db
|
||||
// snapshot, we know that no key could have existing after this snapshot
|
||||
// (since we do not compact keys that have an earlier snapshot).
|
||||
//
|
||||
// Returns OK or NotFound on success,
|
||||
// other status on unexpected error.
|
||||
Status GetLatestSequenceForKey(SuperVersion* sv, const Slice& key,
|
||||
bool cache_only, SequenceNumber* seq,
|
||||
bool* found_record_for_key);
|
||||
|
||||
using DB::AddFile;
|
||||
virtual Status AddFile(ColumnFamilyHandle* column_family,
|
||||
@@ -240,6 +262,12 @@ class DBImpl : public DB {
|
||||
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
// Similar to GetSnapshot(), but also lets the db know that this snapshot
|
||||
// will be used for transaction write-conflict checking. The DB can then
|
||||
// make sure not to compact any keys that would prevent a write-conflict from
|
||||
// being detected.
|
||||
const Snapshot* GetSnapshotForWriteConflictBoundary();
|
||||
|
||||
// checks if all live files exist on file system and that their file sizes
|
||||
// match to our in-memory records
|
||||
virtual Status CheckConsistency();
|
||||
@@ -249,6 +277,7 @@ class DBImpl : public DB {
|
||||
Status RunManualCompaction(ColumnFamilyData* cfd, int input_level,
|
||||
int output_level, uint32_t output_path_id,
|
||||
const Slice* begin, const Slice* end,
|
||||
bool exclusive,
|
||||
bool disallow_trivial_move = false);
|
||||
|
||||
// Return an internal iterator over the current state of the database.
|
||||
@@ -314,6 +343,8 @@ class DBImpl : public DB {
|
||||
|
||||
Cache* TEST_table_cache() { return table_cache_.get(); }
|
||||
|
||||
WriteController& TEST_write_controler() { return write_controller_; }
|
||||
|
||||
#endif // NDEBUG
|
||||
|
||||
// Returns the list of live files in 'live' and the list
|
||||
@@ -398,6 +429,17 @@ class DBImpl : public DB {
|
||||
SuperVersion* super_version,
|
||||
Arena* arena);
|
||||
|
||||
// Except in DB::Open(), WriteOptionsFile can only be called when:
|
||||
// 1. WriteThread::Writer::EnterUnbatched() is used.
|
||||
// 2. db_mutex is held
|
||||
Status WriteOptionsFile();
|
||||
|
||||
// The following two functions can only be called when:
|
||||
// 1. WriteThread::Writer::EnterUnbatched() is used.
|
||||
// 2. db_mutex is NOT held
|
||||
Status RenameTempFileToOptionsFile(const std::string& file_name);
|
||||
Status DeleteObsoleteOptionsFiles();
|
||||
|
||||
void NotifyOnFlushCompleted(ColumnFamilyData* cfd, FileMetaData* file_meta,
|
||||
const MutableCFOptions& mutable_cf_options,
|
||||
int job_id, TableProperties prop);
|
||||
@@ -513,12 +555,13 @@ class DBImpl : public DB {
|
||||
void MaybeScheduleFlushOrCompaction();
|
||||
void SchedulePendingFlush(ColumnFamilyData* cfd);
|
||||
void SchedulePendingCompaction(ColumnFamilyData* cfd);
|
||||
static void BGWorkCompaction(void* db);
|
||||
static void BGWorkCompaction(void* arg);
|
||||
static void BGWorkFlush(void* db);
|
||||
void BackgroundCallCompaction();
|
||||
static void UnscheduleCallback(void* arg);
|
||||
void BackgroundCallCompaction(void* arg);
|
||||
void BackgroundCallFlush();
|
||||
Status BackgroundCompaction(bool* madeProgress, JobContext* job_context,
|
||||
LogBuffer* log_buffer);
|
||||
LogBuffer* log_buffer, void* m = 0);
|
||||
Status BackgroundFlush(bool* madeProgress, JobContext* job_context,
|
||||
LogBuffer* log_buffer);
|
||||
|
||||
@@ -546,18 +589,25 @@ class DBImpl : public DB {
|
||||
// helper function to call after some of the logs_ were synced
|
||||
void MarkLogsSynced(uint64_t up_to, bool synced_dir, const Status& status);
|
||||
|
||||
const Snapshot* GetSnapshotImpl(bool is_write_conflict_boundary);
|
||||
|
||||
// table_cache_ provides its own synchronization
|
||||
std::shared_ptr<Cache> table_cache_;
|
||||
|
||||
// Lock over the persistent DB state. Non-nullptr iff successfully acquired.
|
||||
FileLock* db_lock_;
|
||||
|
||||
// The mutex for options file related operations.
|
||||
// NOTE: should never acquire options_file_mutex_ and mutex_ at the
|
||||
// same time.
|
||||
InstrumentedMutex options_files_mutex_;
|
||||
// State below is protected by mutex_
|
||||
InstrumentedMutex mutex_;
|
||||
|
||||
std::atomic<bool> shutting_down_;
|
||||
// This condition variable is signaled on these conditions:
|
||||
// * whenever bg_compaction_scheduled_ goes down to 0
|
||||
// * if bg_manual_only_ > 0, whenever a compaction finishes, even if it hasn't
|
||||
// * if AnyManualCompaction, whenever a compaction finishes, even if it hasn't
|
||||
// made any progress
|
||||
// * whenever a compaction made any progress
|
||||
// * whenever bg_flush_scheduled_ value decreases (i.e. whenever a flush is
|
||||
@@ -715,11 +765,6 @@ class DBImpl : public DB {
|
||||
// stores the number of compactions are currently running
|
||||
int num_running_compactions_;
|
||||
|
||||
// If non-zero, MaybeScheduleFlushOrCompaction() will only schedule manual
|
||||
// compactions (if manual_compaction_ is not null). This mechanism enables
|
||||
// manual compactions to wait until all other compactions are finished.
|
||||
int bg_manual_only_;
|
||||
|
||||
// number of background memtable flush jobs, submitted to the HIGH pool
|
||||
int bg_flush_scheduled_;
|
||||
|
||||
@@ -732,15 +777,25 @@ class DBImpl : public DB {
|
||||
int input_level;
|
||||
int output_level;
|
||||
uint32_t output_path_id;
|
||||
bool done;
|
||||
Status status;
|
||||
bool done;
|
||||
bool in_progress; // compaction request being processed?
|
||||
bool incomplete; // only part of requested range compacted
|
||||
bool exclusive; // current behavior of only one manual
|
||||
bool disallow_trivial_move; // Force actual compaction to run
|
||||
const InternalKey* begin; // nullptr means beginning of key range
|
||||
const InternalKey* end; // nullptr means end of key range
|
||||
InternalKey* manual_end; // how far we are compacting
|
||||
InternalKey tmp_storage; // Used to keep track of compaction progress
|
||||
bool disallow_trivial_move; // Force actual compaction to run
|
||||
InternalKey tmp_storage1; // Used to keep track of compaction progress
|
||||
Compaction* compaction;
|
||||
};
|
||||
std::deque<ManualCompaction*> manual_compaction_dequeue_;
|
||||
|
||||
struct CompactionArg {
|
||||
DBImpl* db;
|
||||
ManualCompaction* m;
|
||||
};
|
||||
ManualCompaction* manual_compaction_;
|
||||
|
||||
// Have we encountered a background error in paranoid mode?
|
||||
Status bg_error_;
|
||||
@@ -837,6 +892,14 @@ class DBImpl : public DB {
|
||||
DBPropertyType property_type,
|
||||
bool need_out_of_mutex, bool is_locked,
|
||||
uint64_t* value);
|
||||
|
||||
bool HasPendingManualCompaction();
|
||||
bool HasExclusiveManualCompaction();
|
||||
void AddManualCompaction(ManualCompaction* m);
|
||||
void RemoveManualCompaction(ManualCompaction* m);
|
||||
bool ShouldntRunManualCompaction(ManualCompaction* m);
|
||||
bool HaveManualCompaction(ColumnFamilyData* cfd);
|
||||
bool MCOverlap(ManualCompaction* m, ManualCompaction* m1);
|
||||
};
|
||||
|
||||
// Sanitize db options. The caller should delete result.info_log if
|
||||
|
||||
+1
-1
@@ -70,7 +70,7 @@ Status DBImpl::TEST_CompactRange(int level, const Slice* begin,
|
||||
cfd->ioptions()->compaction_style == kCompactionStyleFIFO)
|
||||
? level
|
||||
: level + 1;
|
||||
return RunManualCompaction(cfd, level, output_level, 0, begin, end,
|
||||
return RunManualCompaction(cfd, level, output_level, 0, begin, end, true,
|
||||
disallow_trivial_move);
|
||||
}
|
||||
|
||||
|
||||
+83
-20
@@ -76,7 +76,8 @@ class DBIter: public Iterator {
|
||||
current_entry_is_merged_(false),
|
||||
statistics_(ioptions.statistics),
|
||||
iterate_upper_bound_(iterate_upper_bound),
|
||||
prefix_same_as_start_(prefix_same_as_start) {
|
||||
prefix_same_as_start_(prefix_same_as_start),
|
||||
iter_pinned_(false) {
|
||||
RecordTick(statistics_, NO_ITERATORS);
|
||||
prefix_extractor_ = ioptions.prefix_extractor;
|
||||
max_skip_ = max_sequential_skip_in_iterations;
|
||||
@@ -92,6 +93,9 @@ class DBIter: public Iterator {
|
||||
virtual void SetIter(InternalIterator* iter) {
|
||||
assert(iter_ == nullptr);
|
||||
iter_ = iter;
|
||||
if (iter_ && iter_pinned_) {
|
||||
iter_->PinData();
|
||||
}
|
||||
}
|
||||
virtual bool Valid() const override { return valid_; }
|
||||
virtual Slice key() const override {
|
||||
@@ -110,6 +114,32 @@ class DBIter: public Iterator {
|
||||
return status_;
|
||||
}
|
||||
}
|
||||
virtual Status PinData() {
|
||||
Status s;
|
||||
if (iter_) {
|
||||
s = iter_->PinData();
|
||||
}
|
||||
if (s.ok()) {
|
||||
// Even if iter_ is nullptr, we set iter_pinned_ to true so that when
|
||||
// iter_ is updated using SetIter, we Pin it.
|
||||
iter_pinned_ = true;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
virtual Status ReleasePinnedData() {
|
||||
Status s;
|
||||
if (iter_) {
|
||||
s = iter_->ReleasePinnedData();
|
||||
}
|
||||
if (s.ok()) {
|
||||
iter_pinned_ = false;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
virtual bool IsKeyPinned() const override {
|
||||
assert(valid_);
|
||||
return iter_pinned_ && saved_key_.IsKeyPinned();
|
||||
}
|
||||
|
||||
virtual void Next() override;
|
||||
virtual void Prev() override;
|
||||
@@ -159,6 +189,9 @@ class DBIter: public Iterator {
|
||||
const Slice* iterate_upper_bound_;
|
||||
IterKey prefix_start_;
|
||||
bool prefix_same_as_start_;
|
||||
bool iter_pinned_;
|
||||
// List of operands for merge operator.
|
||||
std::deque<std::string> merge_operands_;
|
||||
|
||||
// No copying allowed
|
||||
DBIter(const DBIter&);
|
||||
@@ -186,9 +219,18 @@ void DBIter::Next() {
|
||||
if (!iter_->Valid()) {
|
||||
iter_->SeekToFirst();
|
||||
}
|
||||
} else if (iter_->Valid() && !current_entry_is_merged_) {
|
||||
// If the current value is not a merge, the iter position is the
|
||||
// current key, which is already returned. We can safely issue a
|
||||
// Next() without checking the current key.
|
||||
// If the current key is a merge, very likely iter already points
|
||||
// to the next internal position.
|
||||
iter_->Next();
|
||||
PERF_COUNTER_ADD(internal_key_skipped_count, 1);
|
||||
}
|
||||
|
||||
// If the current value is merged, we might already hit end of iter_
|
||||
// Now we point to the next internal position, for both of merge and
|
||||
// not merge cases.
|
||||
if (!iter_->Valid()) {
|
||||
valid_ = false;
|
||||
return;
|
||||
@@ -248,18 +290,21 @@ void DBIter::FindNextUserEntryInternal(bool skipping) {
|
||||
case kTypeSingleDeletion:
|
||||
// Arrange to skip all upcoming entries for this key since
|
||||
// they are hidden by this deletion.
|
||||
saved_key_.SetKey(ikey.user_key);
|
||||
saved_key_.SetKey(ikey.user_key,
|
||||
!iter_->IsKeyPinned() /* copy */);
|
||||
skipping = true;
|
||||
num_skipped = 0;
|
||||
PERF_COUNTER_ADD(internal_delete_skipped_count, 1);
|
||||
break;
|
||||
case kTypeValue:
|
||||
valid_ = true;
|
||||
saved_key_.SetKey(ikey.user_key);
|
||||
saved_key_.SetKey(ikey.user_key,
|
||||
!iter_->IsKeyPinned() /* copy */);
|
||||
return;
|
||||
case kTypeMerge:
|
||||
// By now, we are sure the current ikey is going to yield a value
|
||||
saved_key_.SetKey(ikey.user_key);
|
||||
saved_key_.SetKey(ikey.user_key,
|
||||
!iter_->IsKeyPinned() /* copy */);
|
||||
current_entry_is_merged_ = true;
|
||||
valid_ = true;
|
||||
MergeValuesNewToOld(); // Go to a different state machine
|
||||
@@ -419,7 +464,8 @@ void DBIter::PrevInternal() {
|
||||
ParsedInternalKey ikey;
|
||||
|
||||
while (iter_->Valid()) {
|
||||
saved_key_.SetKey(ExtractUserKey(iter_->key()));
|
||||
saved_key_.SetKey(ExtractUserKey(iter_->key()),
|
||||
!iter_->IsKeyPinned() /* copy */);
|
||||
if (FindValueForCurrentKey()) {
|
||||
valid_ = true;
|
||||
if (!iter_->Valid()) {
|
||||
@@ -449,8 +495,7 @@ void DBIter::PrevInternal() {
|
||||
// saved_value_
|
||||
bool DBIter::FindValueForCurrentKey() {
|
||||
assert(iter_->Valid());
|
||||
// Contains operands for merge operator.
|
||||
std::deque<std::string> operands;
|
||||
merge_operands_.clear();
|
||||
// last entry before merge (could be kTypeDeletion, kTypeSingleDeletion or
|
||||
// kTypeValue)
|
||||
ValueType last_not_merge_type = kTypeDeletion;
|
||||
@@ -470,19 +515,19 @@ bool DBIter::FindValueForCurrentKey() {
|
||||
last_key_entry_type = ikey.type;
|
||||
switch (last_key_entry_type) {
|
||||
case kTypeValue:
|
||||
operands.clear();
|
||||
merge_operands_.clear();
|
||||
saved_value_ = iter_->value().ToString();
|
||||
last_not_merge_type = kTypeValue;
|
||||
break;
|
||||
case kTypeDeletion:
|
||||
case kTypeSingleDeletion:
|
||||
operands.clear();
|
||||
merge_operands_.clear();
|
||||
last_not_merge_type = last_key_entry_type;
|
||||
PERF_COUNTER_ADD(internal_delete_skipped_count, 1);
|
||||
break;
|
||||
case kTypeMerge:
|
||||
assert(user_merge_operator_ != nullptr);
|
||||
operands.push_back(iter_->value().ToString());
|
||||
merge_operands_.push_back(iter_->value().ToString());
|
||||
break;
|
||||
default:
|
||||
assert(false);
|
||||
@@ -504,8 +549,9 @@ bool DBIter::FindValueForCurrentKey() {
|
||||
if (last_not_merge_type == kTypeDeletion) {
|
||||
StopWatchNano timer(env_, statistics_ != nullptr);
|
||||
PERF_TIMER_GUARD(merge_operator_time_nanos);
|
||||
user_merge_operator_->FullMerge(saved_key_.GetKey(), nullptr, operands,
|
||||
&saved_value_, logger_);
|
||||
user_merge_operator_->FullMerge(saved_key_.GetKey(), nullptr,
|
||||
merge_operands_, &saved_value_,
|
||||
logger_);
|
||||
RecordTick(statistics_, MERGE_OPERATION_TOTAL_TIME,
|
||||
timer.ElapsedNanos());
|
||||
} else {
|
||||
@@ -516,7 +562,8 @@ bool DBIter::FindValueForCurrentKey() {
|
||||
StopWatchNano timer(env_, statistics_ != nullptr);
|
||||
PERF_TIMER_GUARD(merge_operator_time_nanos);
|
||||
user_merge_operator_->FullMerge(saved_key_.GetKey(), &temp_slice,
|
||||
operands, &saved_value_, logger_);
|
||||
merge_operands_, &saved_value_,
|
||||
logger_);
|
||||
RecordTick(statistics_, MERGE_OPERATION_TOTAL_TIME,
|
||||
timer.ElapsedNanos());
|
||||
}
|
||||
@@ -735,7 +782,7 @@ void DBIter::SeekToLast() {
|
||||
// it will seek to the last key before the
|
||||
// ReadOptions.iterate_upper_bound
|
||||
if (iter_->Valid() && iterate_upper_bound_ != nullptr) {
|
||||
saved_key_.SetKey(*iterate_upper_bound_);
|
||||
saved_key_.SetKey(*iterate_upper_bound_, false /* copy */);
|
||||
std::string last_key;
|
||||
AppendInternalKey(&last_key,
|
||||
ParsedInternalKey(saved_key_.GetKey(), kMaxSequenceNumber,
|
||||
@@ -772,10 +819,15 @@ Iterator* NewDBIterator(Env* env, const ImmutableCFOptions& ioptions,
|
||||
const SequenceNumber& sequence,
|
||||
uint64_t max_sequential_skip_in_iterations,
|
||||
const Slice* iterate_upper_bound,
|
||||
bool prefix_same_as_start) {
|
||||
return new DBIter(env, ioptions, user_key_comparator, internal_iter, sequence,
|
||||
false, max_sequential_skip_in_iterations,
|
||||
iterate_upper_bound, prefix_same_as_start);
|
||||
bool prefix_same_as_start, bool pin_data) {
|
||||
DBIter* db_iter =
|
||||
new DBIter(env, ioptions, user_key_comparator, internal_iter, sequence,
|
||||
false, max_sequential_skip_in_iterations, iterate_upper_bound,
|
||||
prefix_same_as_start);
|
||||
if (pin_data) {
|
||||
db_iter->PinData();
|
||||
}
|
||||
return db_iter;
|
||||
}
|
||||
|
||||
ArenaWrappedDBIter::~ArenaWrappedDBIter() { db_iter_->~DBIter(); }
|
||||
@@ -797,6 +849,13 @@ inline void ArenaWrappedDBIter::Prev() { db_iter_->Prev(); }
|
||||
inline Slice ArenaWrappedDBIter::key() const { return db_iter_->key(); }
|
||||
inline Slice ArenaWrappedDBIter::value() const { return db_iter_->value(); }
|
||||
inline Status ArenaWrappedDBIter::status() const { return db_iter_->status(); }
|
||||
inline Status ArenaWrappedDBIter::PinData() { return db_iter_->PinData(); }
|
||||
inline Status ArenaWrappedDBIter::ReleasePinnedData() {
|
||||
return db_iter_->ReleasePinnedData();
|
||||
}
|
||||
inline bool ArenaWrappedDBIter::IsKeyPinned() const {
|
||||
return db_iter_->IsKeyPinned();
|
||||
}
|
||||
void ArenaWrappedDBIter::RegisterCleanup(CleanupFunction function, void* arg1,
|
||||
void* arg2) {
|
||||
db_iter_->RegisterCleanup(function, arg1, arg2);
|
||||
@@ -806,7 +865,8 @@ ArenaWrappedDBIter* NewArenaWrappedDbIterator(
|
||||
Env* env, const ImmutableCFOptions& ioptions,
|
||||
const Comparator* user_key_comparator, const SequenceNumber& sequence,
|
||||
uint64_t max_sequential_skip_in_iterations,
|
||||
const Slice* iterate_upper_bound, bool prefix_same_as_start) {
|
||||
const Slice* iterate_upper_bound, bool prefix_same_as_start,
|
||||
bool pin_data) {
|
||||
ArenaWrappedDBIter* iter = new ArenaWrappedDBIter();
|
||||
Arena* arena = iter->GetArena();
|
||||
auto mem = arena->AllocateAligned(sizeof(DBIter));
|
||||
@@ -816,6 +876,9 @@ ArenaWrappedDBIter* NewArenaWrappedDbIterator(
|
||||
iterate_upper_bound, prefix_same_as_start);
|
||||
|
||||
iter->SetDBIter(db_iter);
|
||||
if (pin_data) {
|
||||
iter->PinData();
|
||||
}
|
||||
|
||||
return iter;
|
||||
}
|
||||
|
||||
+6
-2
@@ -30,7 +30,8 @@ extern Iterator* NewDBIterator(Env* env, const ImmutableCFOptions& options,
|
||||
const SequenceNumber& sequence,
|
||||
uint64_t max_sequential_skip_in_iterations,
|
||||
const Slice* iterate_upper_bound = nullptr,
|
||||
bool prefix_same_as_start = false);
|
||||
bool prefix_same_as_start = false,
|
||||
bool pin_data = false);
|
||||
|
||||
// A wrapper iterator which wraps DB Iterator and the arena, with which the DB
|
||||
// iterator is supposed be allocated. This class is used as an entry point of
|
||||
@@ -63,6 +64,9 @@ class ArenaWrappedDBIter : public Iterator {
|
||||
virtual Status status() const override;
|
||||
|
||||
void RegisterCleanup(CleanupFunction function, void* arg1, void* arg2);
|
||||
virtual Status PinData();
|
||||
virtual Status ReleasePinnedData();
|
||||
virtual bool IsKeyPinned() const override;
|
||||
|
||||
private:
|
||||
DBIter* db_iter_;
|
||||
@@ -75,6 +79,6 @@ extern ArenaWrappedDBIter* NewArenaWrappedDbIterator(
|
||||
const Comparator* user_key_comparator, const SequenceNumber& sequence,
|
||||
uint64_t max_sequential_skip_in_iterations,
|
||||
const Slice* iterate_upper_bound = nullptr,
|
||||
bool prefix_same_as_start = false);
|
||||
bool prefix_same_as_start = false, bool pin_data = false);
|
||||
|
||||
} // namespace rocksdb
|
||||
@@ -27,7 +27,7 @@ class DBTestXactLogIterator : public DBTestBase {
|
||||
Status status = dbfull()->GetUpdatesSince(seq, &iter);
|
||||
EXPECT_OK(status);
|
||||
EXPECT_TRUE(iter->Valid());
|
||||
return std::move(iter);
|
||||
return iter;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -92,6 +92,10 @@ TablePropertiesCollection
|
||||
DBTablePropertiesTest::TestGetPropertiesOfTablesInRange(
|
||||
std::vector<Range> ranges, std::size_t* num_properties,
|
||||
std::size_t* num_files) {
|
||||
|
||||
// Since we deref zero element in the vector it can not be empty
|
||||
// otherwise we pass an address to some random memory
|
||||
EXPECT_GT(ranges.size(), 0U);
|
||||
// run the query
|
||||
TablePropertiesCollection props;
|
||||
EXPECT_OK(db_->GetPropertiesOfTablesInRange(
|
||||
@@ -101,10 +105,10 @@ DBTablePropertiesTest::TestGetPropertiesOfTablesInRange(
|
||||
// only which fall within requested ranges
|
||||
std::vector<LiveFileMetaData> vmd;
|
||||
db_->GetLiveFilesMetaData(&vmd);
|
||||
for (auto md : vmd) {
|
||||
for (auto& md : vmd) {
|
||||
std::string fn = md.db_path + md.name;
|
||||
bool in_range = false;
|
||||
for (auto r : ranges) {
|
||||
for (auto& r : ranges) {
|
||||
// smallestkey < limit && largestkey >= start
|
||||
if (r.limit.compare(md.smallestkey) >= 0 &&
|
||||
r.start.compare(md.largestkey) <= 0) {
|
||||
@@ -187,11 +191,20 @@ TEST_F(DBTablePropertiesTest, GetPropertiesOfTablesInRange) {
|
||||
for (int j = 0; j < 100; j++) {
|
||||
// create a bunch of ranges
|
||||
std::vector<std::string> random_keys;
|
||||
auto n = 2 * rnd.Uniform(50);
|
||||
// Random returns numbers with zero included
|
||||
// when we pass empty ranges TestGetPropertiesOfTablesInRange()
|
||||
// derefs random memory in the empty ranges[0]
|
||||
// so want to be greater than zero and even since
|
||||
// the below loop requires that random_keys.size() to be even.
|
||||
auto n = 2 * (rnd.Uniform(50) + 1);
|
||||
|
||||
for (uint32_t i = 0; i < n; ++i) {
|
||||
random_keys.push_back(test::RandomKey(&rnd, 5));
|
||||
}
|
||||
|
||||
ASSERT_GT(random_keys.size(), 0U);
|
||||
ASSERT_EQ((random_keys.size() % 2), 0U);
|
||||
|
||||
std::vector<Range> ranges;
|
||||
auto it = random_keys.begin();
|
||||
while (it != random_keys.end()) {
|
||||
|
||||
@@ -140,6 +140,8 @@ TEST_F(DBTestTailingIterator, TailingIteratorTrimSeekToNext) {
|
||||
std::unique_ptr<Iterator> iterh(db_->NewIterator(read_options, handles_[1]));
|
||||
std::string value(1024, 'a');
|
||||
bool file_iters_deleted = false;
|
||||
bool file_iters_renewed_null = false;
|
||||
bool file_iters_renewed_copy = false;
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"ForwardIterator::SeekInternal:Return", [&](void* arg) {
|
||||
ForwardIterator* fiter = reinterpret_cast<ForwardIterator*>(arg);
|
||||
@@ -152,6 +154,12 @@ TEST_F(DBTestTailingIterator, TailingIteratorTrimSeekToNext) {
|
||||
ASSERT_TRUE(!file_iters_deleted ||
|
||||
fiter->TEST_CheckDeletedIters(&deleted_iters, &num_iters));
|
||||
});
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"ForwardIterator::RenewIterators:Null",
|
||||
[&](void* arg) { file_iters_renewed_null = true; });
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"ForwardIterator::RenewIterators:Copy",
|
||||
[&](void* arg) { file_iters_renewed_copy = true; });
|
||||
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
||||
const int num_records = 1000;
|
||||
for (int i = 1; i < num_records; ++i) {
|
||||
@@ -160,7 +168,7 @@ TEST_F(DBTestTailingIterator, TailingIteratorTrimSeekToNext) {
|
||||
char buf3[32];
|
||||
char buf4[32];
|
||||
snprintf(buf1, sizeof(buf1), "00a0%016d", i * 5);
|
||||
snprintf(buf3, sizeof(buf1), "00b0%016d", i * 5);
|
||||
snprintf(buf3, sizeof(buf3), "00b0%016d", i * 5);
|
||||
|
||||
Slice key(buf1, 20);
|
||||
ASSERT_OK(Put(1, key, value));
|
||||
@@ -173,7 +181,7 @@ TEST_F(DBTestTailingIterator, TailingIteratorTrimSeekToNext) {
|
||||
if (i == 299) {
|
||||
file_iters_deleted = true;
|
||||
}
|
||||
snprintf(buf4, sizeof(buf1), "00a0%016d", i * 5 / 2);
|
||||
snprintf(buf4, sizeof(buf4), "00a0%016d", i * 5 / 2);
|
||||
Slice target(buf4, 20);
|
||||
iterh->Seek(target);
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
@@ -203,6 +211,8 @@ TEST_F(DBTestTailingIterator, TailingIteratorTrimSeekToNext) {
|
||||
ASSERT_LE(num_iters, 1);
|
||||
file_iters_deleted = false;
|
||||
}
|
||||
ASSERT_TRUE(file_iters_renewed_null);
|
||||
ASSERT_TRUE(file_iters_renewed_copy);
|
||||
iter = 0;
|
||||
itern = 0;
|
||||
iterh = 0;
|
||||
|
||||
+869
-185
File diff suppressed because it is too large.
Load diff
+17
-6
@@ -18,6 +18,7 @@ SpecialEnv::SpecialEnv(Env* base)
|
||||
rnd_(301),
|
||||
sleep_counter_(this),
|
||||
addon_time_(0),
|
||||
time_elapse_only_sleep_(false),
|
||||
no_sleep_(false) {
|
||||
delay_sstable_sync_.store(false, std::memory_order_release);
|
||||
drop_writes_.store(false, std::memory_order_release);
|
||||
@@ -55,6 +56,7 @@ DBTestBase::DBTestBase(const std::string path)
|
||||
EXPECT_OK(DestroyDB(dbname_, options));
|
||||
db_ = nullptr;
|
||||
Reopen(options);
|
||||
Random::GetTLSInstance()->Reset(0xdeadbeef);
|
||||
}
|
||||
|
||||
DBTestBase::~DBTestBase() {
|
||||
@@ -87,7 +89,8 @@ bool DBTestBase::ChangeOptions(int skip_mask) {
|
||||
option_config_ == kUniversalCompaction ||
|
||||
option_config_ == kUniversalCompactionMultiLevel ||
|
||||
option_config_ == kUniversalSubcompactions ||
|
||||
option_config_ == kFIFOCompaction) {
|
||||
option_config_ == kFIFOCompaction ||
|
||||
option_config_ == kConcurrentSkipList) {
|
||||
continue;
|
||||
}
|
||||
#endif
|
||||
@@ -354,6 +357,11 @@ Options DBTestBase::CurrentOptions(
|
||||
options.max_subcompactions = 4;
|
||||
break;
|
||||
}
|
||||
case kConcurrentSkipList: {
|
||||
options.allow_concurrent_memtable_write = true;
|
||||
options.enable_write_thread_adaptive_yield = true;
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
@@ -367,6 +375,7 @@ Options DBTestBase::CurrentOptions(
|
||||
}
|
||||
options.env = env_;
|
||||
options.create_if_missing = true;
|
||||
options.fail_if_options_file_error = true;
|
||||
return options;
|
||||
}
|
||||
|
||||
@@ -658,14 +667,14 @@ uint64_t DBTestBase::SizeAtLevel(int level) {
|
||||
return sum;
|
||||
}
|
||||
|
||||
int DBTestBase::TotalLiveFiles(int cf) {
|
||||
size_t DBTestBase::TotalLiveFiles(int cf) {
|
||||
ColumnFamilyMetaData cf_meta;
|
||||
if (cf == 0) {
|
||||
db_->GetColumnFamilyMetaData(&cf_meta);
|
||||
} else {
|
||||
db_->GetColumnFamilyMetaData(handles_[cf], &cf_meta);
|
||||
}
|
||||
int num_files = 0;
|
||||
size_t num_files = 0;
|
||||
for (auto& level : cf_meta.levels) {
|
||||
num_files += level.files.size();
|
||||
}
|
||||
@@ -830,7 +839,7 @@ int DBTestBase::GetSstFileCount(std::string path) {
|
||||
// this will generate non-overlapping files since it keeps increasing key_idx
|
||||
void DBTestBase::GenerateNewFile(int cf, Random* rnd, int* key_idx,
|
||||
bool nowait) {
|
||||
for (int i = 0; i < 100; i++) {
|
||||
for (int i = 0; i < KNumKeysByGenerateNewFile; i++) {
|
||||
ASSERT_OK(Put(cf, Key(*key_idx), RandomString(rnd, (i == 99) ? 1 : 990)));
|
||||
(*key_idx)++;
|
||||
}
|
||||
@@ -842,7 +851,7 @@ void DBTestBase::GenerateNewFile(int cf, Random* rnd, int* key_idx,
|
||||
|
||||
// this will generate non-overlapping files since it keeps increasing key_idx
|
||||
void DBTestBase::GenerateNewFile(Random* rnd, int* key_idx, bool nowait) {
|
||||
for (int i = 0; i < 100; i++) {
|
||||
for (int i = 0; i < KNumKeysByGenerateNewFile; i++) {
|
||||
ASSERT_OK(Put(Key(*key_idx), RandomString(rnd, (i == 99) ? 1 : 990)));
|
||||
(*key_idx)++;
|
||||
}
|
||||
@@ -852,8 +861,10 @@ void DBTestBase::GenerateNewFile(Random* rnd, int* key_idx, bool nowait) {
|
||||
}
|
||||
}
|
||||
|
||||
const int DBTestBase::kNumKeysByGenerateNewRandomFile = 51;
|
||||
|
||||
void DBTestBase::GenerateNewRandomFile(Random* rnd, bool nowait) {
|
||||
for (int i = 0; i < 51; i++) {
|
||||
for (int i = 0; i < kNumKeysByGenerateNewRandomFile; i++) {
|
||||
ASSERT_OK(Put("key" + RandomString(rnd, 7), RandomString(rnd, 2000)));
|
||||
}
|
||||
ASSERT_OK(Put("key" + RandomString(rnd, 7), RandomString(rnd, 200)));
|
||||
|
||||
+100
-9
@@ -118,6 +118,84 @@ struct OptionsOverride {
|
||||
|
||||
} // namespace anon
|
||||
|
||||
// A hacky skip list mem table that triggers flush after number of entries.
|
||||
class SpecialMemTableRep : public MemTableRep {
|
||||
public:
|
||||
explicit SpecialMemTableRep(MemTableAllocator* allocator,
|
||||
MemTableRep* memtable, int num_entries_flush)
|
||||
: MemTableRep(allocator),
|
||||
memtable_(memtable),
|
||||
num_entries_flush_(num_entries_flush),
|
||||
num_entries_(0) {}
|
||||
|
||||
virtual KeyHandle Allocate(const size_t len, char** buf) override {
|
||||
return memtable_->Allocate(len, buf);
|
||||
}
|
||||
|
||||
// Insert key into the list.
|
||||
// REQUIRES: nothing that compares equal to key is currently in the list.
|
||||
virtual void Insert(KeyHandle handle) override {
|
||||
memtable_->Insert(handle);
|
||||
num_entries_++;
|
||||
}
|
||||
|
||||
// Returns true iff an entry that compares equal to key is in the list.
|
||||
virtual bool Contains(const char* key) const override {
|
||||
return memtable_->Contains(key);
|
||||
}
|
||||
|
||||
virtual size_t ApproximateMemoryUsage() override {
|
||||
// Return a high memory usage when number of entries exceeds the threshold
|
||||
// to trigger a flush.
|
||||
return (num_entries_ < num_entries_flush_) ? 0 : 1024 * 1024 * 1024;
|
||||
}
|
||||
|
||||
virtual void Get(const LookupKey& k, void* callback_args,
|
||||
bool (*callback_func)(void* arg,
|
||||
const char* entry)) override {
|
||||
memtable_->Get(k, callback_args, callback_func);
|
||||
}
|
||||
|
||||
uint64_t ApproximateNumEntries(const Slice& start_ikey,
|
||||
const Slice& end_ikey) override {
|
||||
return memtable_->ApproximateNumEntries(start_ikey, end_ikey);
|
||||
}
|
||||
|
||||
virtual MemTableRep::Iterator* GetIterator(Arena* arena = nullptr) override {
|
||||
return memtable_->GetIterator(arena);
|
||||
}
|
||||
|
||||
virtual ~SpecialMemTableRep() override {}
|
||||
|
||||
private:
|
||||
unique_ptr<MemTableRep> memtable_;
|
||||
int num_entries_flush_;
|
||||
int num_entries_;
|
||||
};
|
||||
|
||||
// The factory for the hacky skip list mem table that triggers flush after
|
||||
// number of entries exceeds a threshold.
|
||||
class SpecialSkipListFactory : public MemTableRepFactory {
|
||||
public:
|
||||
// After number of inserts exceeds `num_entries_flush` in a mem table, trigger
|
||||
// flush.
|
||||
explicit SpecialSkipListFactory(int num_entries_flush)
|
||||
: num_entries_flush_(num_entries_flush) {}
|
||||
|
||||
virtual MemTableRep* CreateMemTableRep(
|
||||
const MemTableRep::KeyComparator& compare, MemTableAllocator* allocator,
|
||||
const SliceTransform* transform, Logger* logger) override {
|
||||
return new SpecialMemTableRep(
|
||||
allocator, factory_.CreateMemTableRep(compare, allocator, transform, 0),
|
||||
num_entries_flush_);
|
||||
}
|
||||
virtual const char* Name() const override { return "SkipListFactory"; }
|
||||
|
||||
private:
|
||||
SkipListFactory factory_;
|
||||
int num_entries_flush_;
|
||||
};
|
||||
|
||||
// Special Env used to delay background operations
|
||||
class SpecialEnv : public EnvWrapper {
|
||||
public:
|
||||
@@ -327,15 +405,19 @@ class SpecialEnv : public EnvWrapper {
|
||||
|
||||
virtual void SleepForMicroseconds(int micros) override {
|
||||
sleep_counter_.Increment();
|
||||
if (no_sleep_) {
|
||||
if (no_sleep_ || time_elapse_only_sleep_) {
|
||||
addon_time_.fetch_add(micros);
|
||||
} else {
|
||||
}
|
||||
if (!no_sleep_) {
|
||||
target()->SleepForMicroseconds(micros);
|
||||
}
|
||||
}
|
||||
|
||||
virtual Status GetCurrentTime(int64_t* unix_time) override {
|
||||
Status s = target()->GetCurrentTime(unix_time);
|
||||
Status s;
|
||||
if (!time_elapse_only_sleep_) {
|
||||
s = target()->GetCurrentTime(unix_time);
|
||||
}
|
||||
if (s.ok()) {
|
||||
*unix_time += addon_time_.load();
|
||||
}
|
||||
@@ -343,11 +425,13 @@ class SpecialEnv : public EnvWrapper {
|
||||
}
|
||||
|
||||
virtual uint64_t NowNanos() override {
|
||||
return target()->NowNanos() + addon_time_.load() * 1000;
|
||||
return (time_elapse_only_sleep_ ? 0 : target()->NowNanos()) +
|
||||
addon_time_.load() * 1000;
|
||||
}
|
||||
|
||||
virtual uint64_t NowMicros() override {
|
||||
return target()->NowMicros() + addon_time_.load();
|
||||
return (time_elapse_only_sleep_ ? 0 : target()->NowMicros()) +
|
||||
addon_time_.load();
|
||||
}
|
||||
|
||||
Random rnd_;
|
||||
@@ -399,6 +483,9 @@ class SpecialEnv : public EnvWrapper {
|
||||
std::function<void()>* table_write_callback_;
|
||||
|
||||
std::atomic<int64_t> addon_time_;
|
||||
|
||||
bool time_elapse_only_sleep_;
|
||||
|
||||
bool no_sleep_;
|
||||
|
||||
std::atomic<bool> is_wal_sync_thread_safe_{true};
|
||||
@@ -438,9 +525,10 @@ class DBTestBase : public testing::Test {
|
||||
kOptimizeFiltersForHits = 27,
|
||||
kRowCache = 28,
|
||||
kRecycleLogFiles = 29,
|
||||
kLevelSubcompactions = 30,
|
||||
kUniversalSubcompactions = 31,
|
||||
kEnd = 30
|
||||
kConcurrentSkipList = 30,
|
||||
kEnd = 31,
|
||||
kLevelSubcompactions = 31,
|
||||
kUniversalSubcompactions = 32,
|
||||
};
|
||||
int option_config_;
|
||||
|
||||
@@ -574,7 +662,7 @@ class DBTestBase : public testing::Test {
|
||||
|
||||
uint64_t SizeAtLevel(int level);
|
||||
|
||||
int TotalLiveFiles(int cf = 0);
|
||||
size_t TotalLiveFiles(int cf = 0);
|
||||
|
||||
size_t CountLiveFiles();
|
||||
#endif // ROCKSDB_LITE
|
||||
@@ -622,6 +710,9 @@ class DBTestBase : public testing::Test {
|
||||
|
||||
void GenerateNewFile(int fd, Random* rnd, int* key_idx, bool nowait = false);
|
||||
|
||||
static const int kNumKeysByGenerateNewRandomFile;
|
||||
static const int KNumKeysByGenerateNewFile = 100;
|
||||
|
||||
void GenerateNewRandomFile(Random* rnd, bool nowait = false);
|
||||
|
||||
std::string IterStatus(Iterator* iter);
|
||||
|
||||
@@ -14,6 +14,11 @@
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
static uint64_t TestGetTickerCount(const Options& options,
|
||||
Tickers ticker_type) {
|
||||
return options.statistics->getTickerCount(ticker_type);
|
||||
}
|
||||
|
||||
static std::string CompressibleString(Random* rnd, int len) {
|
||||
std::string r;
|
||||
test::CompressibleString(rnd, 0.8, len, &r);
|
||||
@@ -22,12 +27,16 @@ static std::string CompressibleString(Random* rnd, int len) {
|
||||
|
||||
class DBTestUniversalCompactionBase
|
||||
: public DBTestBase,
|
||||
public ::testing::WithParamInterface<int> {
|
||||
public ::testing::WithParamInterface<std::tuple<int, bool>> {
|
||||
public:
|
||||
explicit DBTestUniversalCompactionBase(
|
||||
const std::string& path) : DBTestBase(path) {}
|
||||
virtual void SetUp() override { num_levels_ = GetParam(); }
|
||||
virtual void SetUp() override {
|
||||
num_levels_ = std::get<0>(GetParam());
|
||||
exclusive_manual_compaction_ = std::get<1>(GetParam());
|
||||
}
|
||||
int num_levels_;
|
||||
bool exclusive_manual_compaction_;
|
||||
};
|
||||
|
||||
class DBTestUniversalCompaction : public DBTestUniversalCompactionBase {
|
||||
@@ -112,6 +121,101 @@ class DelayFilterFactory : public CompactionFilterFactory {
|
||||
};
|
||||
} // namespace
|
||||
|
||||
// Make sure we don't trigger a problem if the trigger conditon is given
|
||||
// to be 0, which is invalid.
|
||||
TEST_P(DBTestUniversalCompaction, UniversalCompactionSingleSortedRun) {
|
||||
Options options;
|
||||
options = CurrentOptions(options);
|
||||
|
||||
options.compaction_style = kCompactionStyleUniversal;
|
||||
options.num_levels = num_levels_;
|
||||
// Config universal compaction to always compact to one single sorted run.
|
||||
options.level0_file_num_compaction_trigger = 0;
|
||||
options.compaction_options_universal.size_ratio = 10;
|
||||
options.compaction_options_universal.min_merge_width = 2;
|
||||
options.compaction_options_universal.max_size_amplification_percent = 1;
|
||||
|
||||
options.write_buffer_size = 105 << 10; // 105KB
|
||||
options.arena_block_size = 4 << 10;
|
||||
options.target_file_size_base = 32 << 10; // 32KB
|
||||
// trigger compaction if there are >= 4 files
|
||||
KeepFilterFactory* filter = new KeepFilterFactory(true);
|
||||
filter->expect_manual_compaction_.store(false);
|
||||
options.compaction_filter_factory.reset(filter);
|
||||
|
||||
DestroyAndReopen(options);
|
||||
ASSERT_EQ(1, db_->GetOptions().level0_file_num_compaction_trigger);
|
||||
|
||||
Random rnd(301);
|
||||
int key_idx = 0;
|
||||
|
||||
filter->expect_full_compaction_.store(true);
|
||||
|
||||
for (int num = 0; num < 16; num++) {
|
||||
// Write 100KB file. And immediately it should be compacted to one file.
|
||||
GenerateNewFile(&rnd, &key_idx);
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
ASSERT_EQ(NumSortedRuns(0), 1);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(DBTestUniversalCompaction, OptimizeFiltersForHits) {
|
||||
Options options;
|
||||
options = CurrentOptions(options);
|
||||
options.compaction_style = kCompactionStyleUniversal;
|
||||
options.compaction_options_universal.size_ratio = 5;
|
||||
options.num_levels = num_levels_;
|
||||
options.write_buffer_size = 105 << 10; // 105KB
|
||||
options.arena_block_size = 4 << 10;
|
||||
options.target_file_size_base = 32 << 10; // 32KB
|
||||
// trigger compaction if there are >= 4 files
|
||||
options.level0_file_num_compaction_trigger = 4;
|
||||
BlockBasedTableOptions bbto;
|
||||
bbto.cache_index_and_filter_blocks = true;
|
||||
bbto.filter_policy.reset(NewBloomFilterPolicy(10, false));
|
||||
bbto.whole_key_filtering = true;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(bbto));
|
||||
options.optimize_filters_for_hits = true;
|
||||
options.statistics = rocksdb::CreateDBStatistics();
|
||||
options.memtable_factory.reset(new SpecialSkipListFactory(3));
|
||||
|
||||
DestroyAndReopen(options);
|
||||
|
||||
// block compaction from happening
|
||||
env_->SetBackgroundThreads(1, Env::LOW);
|
||||
test::SleepingBackgroundTask sleeping_task_low;
|
||||
env_->Schedule(&test::SleepingBackgroundTask::DoSleepTask, &sleeping_task_low,
|
||||
Env::Priority::LOW);
|
||||
|
||||
Put("", "");
|
||||
for (int num = 0; num < options.level0_file_num_compaction_trigger; num++) {
|
||||
Put(Key(num * 10), "val");
|
||||
Put(Key(30 + num * 10), "val");
|
||||
Put(Key(60 + num * 10), "val");
|
||||
|
||||
dbfull()->TEST_WaitForFlushMemTable();
|
||||
}
|
||||
|
||||
// Query set of non existing keys
|
||||
for (int i = 5; i < 90; i += 10) {
|
||||
ASSERT_EQ(Get(Key(i)), "NOT_FOUND");
|
||||
}
|
||||
|
||||
// Make sure bloom filter is used at least once.
|
||||
ASSERT_GT(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 0);
|
||||
auto prev_counter = TestGetTickerCount(options, BLOOM_FILTER_USEFUL);
|
||||
|
||||
// Unblock compaction and wait it for happening.
|
||||
sleeping_task_low.WakeUp();
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
|
||||
// The same queries will not trigger bloom filter
|
||||
for (int i = 5; i < 90; i += 10) {
|
||||
ASSERT_EQ(Get(Key(i)), "NOT_FOUND");
|
||||
}
|
||||
ASSERT_EQ(prev_counter, TestGetTickerCount(options, BLOOM_FILTER_USEFUL));
|
||||
}
|
||||
|
||||
// TODO(kailiu) The tests on UniversalCompaction has some issues:
|
||||
// 1. A lot of magic numbers ("11" or "12").
|
||||
// 2. Made assumption on the memtable flush conditions, which may change from
|
||||
@@ -119,6 +223,7 @@ class DelayFilterFactory : public CompactionFilterFactory {
|
||||
TEST_P(DBTestUniversalCompaction, UniversalCompactionTrigger) {
|
||||
Options options;
|
||||
options.compaction_style = kCompactionStyleUniversal;
|
||||
options.compaction_options_universal.size_ratio = 5;
|
||||
options.num_levels = num_levels_;
|
||||
options.write_buffer_size = 105 << 10; // 105KB
|
||||
options.arena_block_size = 4 << 10;
|
||||
@@ -367,6 +472,7 @@ TEST_P(DBTestUniversalCompaction, UniversalCompactionTargetLevel) {
|
||||
CompactRangeOptions compact_options;
|
||||
compact_options.change_level = true;
|
||||
compact_options.target_level = 4;
|
||||
compact_options.exclusive_manual_compaction = exclusive_manual_compaction_;
|
||||
db_->CompactRange(compact_options, nullptr, nullptr);
|
||||
ASSERT_EQ("0,0,0,0,1", FilesPerLevel(0));
|
||||
}
|
||||
@@ -459,7 +565,8 @@ TEST_P(DBTestUniversalCompactionMultiLevels, UniversalCompactionTrivialMove) {
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(DBTestUniversalCompactionMultiLevels,
|
||||
DBTestUniversalCompactionMultiLevels,
|
||||
::testing::Values(3, 20));
|
||||
::testing::Combine(::testing::Values(3, 20),
|
||||
::testing::Bool()));
|
||||
|
||||
class DBTestUniversalCompactionParallel :
|
||||
public DBTestUniversalCompactionBase {
|
||||
@@ -532,7 +639,8 @@ TEST_P(DBTestUniversalCompactionParallel, UniversalCompactionParallel) {
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(DBTestUniversalCompactionParallel,
|
||||
DBTestUniversalCompactionParallel,
|
||||
::testing::Values(1, 10));
|
||||
::testing::Combine(::testing::Values(1, 10),
|
||||
::testing::Bool()));
|
||||
|
||||
TEST_P(DBTestUniversalCompaction, UniversalCompactionOptions) {
|
||||
Options options;
|
||||
@@ -851,7 +959,10 @@ TEST_P(DBTestUniversalCompaction, UniversalCompactionFourPaths) {
|
||||
options.db_paths.emplace_back(dbname_ + "_2", 300 * 1024);
|
||||
options.db_paths.emplace_back(dbname_ + "_3", 500 * 1024);
|
||||
options.db_paths.emplace_back(dbname_ + "_4", 1024 * 1024 * 1024);
|
||||
options.memtable_factory.reset(
|
||||
new SpecialSkipListFactory(KNumKeysByGenerateNewFile - 1));
|
||||
options.compaction_style = kCompactionStyleUniversal;
|
||||
options.compaction_options_universal.size_ratio = 5;
|
||||
options.write_buffer_size = 110 << 10; // 105KB
|
||||
options.arena_block_size = 4 << 10;
|
||||
options.level0_file_num_compaction_trigger = 2;
|
||||
@@ -968,13 +1079,15 @@ TEST_P(DBTestUniversalCompaction, IncreaseUniversalCompactionNumLevels) {
|
||||
int max_key1 = 200;
|
||||
int max_key2 = 600;
|
||||
int max_key3 = 800;
|
||||
const int KNumKeysPerFile = 10;
|
||||
|
||||
// Stage 1: open a DB with universal compaction, num_levels=1
|
||||
Options options = CurrentOptions();
|
||||
options.compaction_style = kCompactionStyleUniversal;
|
||||
options.num_levels = 1;
|
||||
options.write_buffer_size = 100 << 10; // 100KB
|
||||
options.write_buffer_size = 200 << 10; // 200KB
|
||||
options.level0_file_num_compaction_trigger = 3;
|
||||
options.memtable_factory.reset(new SpecialSkipListFactory(KNumKeysPerFile));
|
||||
options = CurrentOptions(options);
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
|
||||
@@ -1019,6 +1132,7 @@ TEST_P(DBTestUniversalCompaction, IncreaseUniversalCompactionNumLevels) {
|
||||
CompactRangeOptions compact_options;
|
||||
compact_options.change_level = true;
|
||||
compact_options.target_level = 0;
|
||||
compact_options.exclusive_manual_compaction = exclusive_manual_compaction_;
|
||||
dbfull()->CompactRange(compact_options, handles_[1], nullptr, nullptr);
|
||||
// Need to restart it once to remove higher level records in manifest.
|
||||
ReopenWithColumnFamilies({"default", "pikachu"}, options);
|
||||
@@ -1047,11 +1161,14 @@ TEST_P(DBTestUniversalCompaction, UniversalCompactionSecondPathRatio) {
|
||||
options.db_paths.emplace_back(dbname_, 500 * 1024);
|
||||
options.db_paths.emplace_back(dbname_ + "_2", 1024 * 1024 * 1024);
|
||||
options.compaction_style = kCompactionStyleUniversal;
|
||||
options.compaction_options_universal.size_ratio = 5;
|
||||
options.write_buffer_size = 110 << 10; // 105KB
|
||||
options.arena_block_size = 4 * 1024;
|
||||
options.arena_block_size = 4 << 10;
|
||||
options.level0_file_num_compaction_trigger = 2;
|
||||
options.num_levels = 1;
|
||||
options.memtable_factory.reset(
|
||||
new SpecialSkipListFactory(KNumKeysByGenerateNewFile - 1));
|
||||
options = CurrentOptions(options);
|
||||
|
||||
std::vector<std::string> filenames;
|
||||
@@ -1139,7 +1256,8 @@ TEST_P(DBTestUniversalCompaction, UniversalCompactionSecondPathRatio) {
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(UniversalCompactionNumLevels, DBTestUniversalCompaction,
|
||||
::testing::Values(1, 3, 5));
|
||||
::testing::Combine(::testing::Values(1, 3, 5),
|
||||
::testing::Bool()));
|
||||
|
||||
class DBTestUniversalManualCompactionOutputPathId
|
||||
: public DBTestUniversalCompactionBase {
|
||||
@@ -1171,6 +1289,7 @@ TEST_P(DBTestUniversalManualCompactionOutputPathId,
|
||||
// Full compaction to DB path 0
|
||||
CompactRangeOptions compact_options;
|
||||
compact_options.target_path_id = 1;
|
||||
compact_options.exclusive_manual_compaction = exclusive_manual_compaction_;
|
||||
db_->CompactRange(compact_options, handles_[1], nullptr, nullptr);
|
||||
ASSERT_EQ(1, TotalLiveFiles(1));
|
||||
ASSERT_EQ(0, GetSstFileCount(options.db_paths[0].path));
|
||||
@@ -1193,6 +1312,7 @@ TEST_P(DBTestUniversalManualCompactionOutputPathId,
|
||||
|
||||
// Full compaction to DB path 0
|
||||
compact_options.target_path_id = 0;
|
||||
compact_options.exclusive_manual_compaction = exclusive_manual_compaction_;
|
||||
db_->CompactRange(compact_options, handles_[1], nullptr, nullptr);
|
||||
ASSERT_EQ(1, TotalLiveFiles(1));
|
||||
ASSERT_EQ(1, GetSstFileCount(options.db_paths[0].path));
|
||||
@@ -1200,13 +1320,15 @@ TEST_P(DBTestUniversalManualCompactionOutputPathId,
|
||||
|
||||
// Fail when compacting to an invalid path ID
|
||||
compact_options.target_path_id = 2;
|
||||
compact_options.exclusive_manual_compaction = exclusive_manual_compaction_;
|
||||
ASSERT_TRUE(db_->CompactRange(compact_options, handles_[1], nullptr, nullptr)
|
||||
.IsInvalidArgument());
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(DBTestUniversalManualCompactionOutputPathId,
|
||||
DBTestUniversalManualCompactionOutputPathId,
|
||||
::testing::Values(1, 8));
|
||||
::testing::Combine(::testing::Values(1, 8),
|
||||
::testing::Bool()));
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
|
||||
+41
-23
@@ -271,7 +271,8 @@ inline LookupKey::~LookupKey() {
|
||||
|
||||
class IterKey {
|
||||
public:
|
||||
IterKey() : key_(space_), buf_size_(sizeof(space_)), key_size_(0) {}
|
||||
IterKey()
|
||||
: buf_(space_), buf_size_(sizeof(space_)), key_(buf_), key_size_(0) {}
|
||||
|
||||
~IterKey() { ResetBuffer(); }
|
||||
|
||||
@@ -293,31 +294,41 @@ class IterKey {
|
||||
void TrimAppend(const size_t shared_len, const char* non_shared_data,
|
||||
const size_t non_shared_len) {
|
||||
assert(shared_len <= key_size_);
|
||||
|
||||
size_t total_size = shared_len + non_shared_len;
|
||||
if (total_size <= buf_size_) {
|
||||
key_size_ = total_size;
|
||||
} else {
|
||||
|
||||
if (IsKeyPinned() /* key is not in buf_ */) {
|
||||
// Copy the key from external memory to buf_ (copy shared_len bytes)
|
||||
EnlargeBufferIfNeeded(total_size);
|
||||
memcpy(buf_, key_, shared_len);
|
||||
} else if (total_size > buf_size_) {
|
||||
// Need to allocate space, delete previous space
|
||||
char* p = new char[total_size];
|
||||
memcpy(p, key_, shared_len);
|
||||
|
||||
if (key_ != space_) {
|
||||
delete[] key_;
|
||||
if (buf_ != space_) {
|
||||
delete[] buf_;
|
||||
}
|
||||
|
||||
key_ = p;
|
||||
key_size_ = total_size;
|
||||
buf_ = p;
|
||||
buf_size_ = total_size;
|
||||
}
|
||||
|
||||
memcpy(key_ + shared_len, non_shared_data, non_shared_len);
|
||||
memcpy(buf_ + shared_len, non_shared_data, non_shared_len);
|
||||
key_ = buf_;
|
||||
key_size_ = total_size;
|
||||
}
|
||||
|
||||
Slice SetKey(const Slice& key) {
|
||||
Slice SetKey(const Slice& key, bool copy = true) {
|
||||
size_t size = key.size();
|
||||
EnlargeBufferIfNeeded(size);
|
||||
memcpy(key_, key.data(), size);
|
||||
if (copy) {
|
||||
// Copy key to buf_
|
||||
EnlargeBufferIfNeeded(size);
|
||||
memcpy(buf_, key.data(), size);
|
||||
key_ = buf_;
|
||||
} else {
|
||||
// Update key_ to point to external memory
|
||||
key_ = key.data();
|
||||
}
|
||||
key_size_ = size;
|
||||
return Slice(key_, key_size_);
|
||||
}
|
||||
@@ -335,11 +346,14 @@ class IterKey {
|
||||
// Update the sequence number in the internal key. Guarantees not to
|
||||
// invalidate slices to the key (and the user key).
|
||||
void UpdateInternalKey(uint64_t seq, ValueType t) {
|
||||
assert(!IsKeyPinned());
|
||||
assert(key_size_ >= 8);
|
||||
uint64_t newval = (seq << 8) | t;
|
||||
EncodeFixed64(&key_[key_size_ - 8], newval);
|
||||
EncodeFixed64(&buf_[key_size_ - 8], newval);
|
||||
}
|
||||
|
||||
bool IsKeyPinned() const { return (key_ != buf_); }
|
||||
|
||||
void SetInternalKey(const Slice& key_prefix, const Slice& user_key,
|
||||
SequenceNumber s,
|
||||
ValueType value_type = kValueTypeForSeek) {
|
||||
@@ -347,10 +361,12 @@ class IterKey {
|
||||
size_t usize = user_key.size();
|
||||
EnlargeBufferIfNeeded(psize + usize + sizeof(uint64_t));
|
||||
if (psize > 0) {
|
||||
memcpy(key_, key_prefix.data(), psize);
|
||||
memcpy(buf_, key_prefix.data(), psize);
|
||||
}
|
||||
memcpy(key_ + psize, user_key.data(), usize);
|
||||
EncodeFixed64(key_ + usize + psize, PackSequenceAndType(s, value_type));
|
||||
memcpy(buf_ + psize, user_key.data(), usize);
|
||||
EncodeFixed64(buf_ + usize + psize, PackSequenceAndType(s, value_type));
|
||||
|
||||
key_ = buf_;
|
||||
key_size_ = psize + usize + sizeof(uint64_t);
|
||||
}
|
||||
|
||||
@@ -377,20 +393,22 @@ class IterKey {
|
||||
void EncodeLengthPrefixedKey(const Slice& key) {
|
||||
auto size = key.size();
|
||||
EnlargeBufferIfNeeded(size + static_cast<size_t>(VarintLength(size)));
|
||||
char* ptr = EncodeVarint32(key_, static_cast<uint32_t>(size));
|
||||
char* ptr = EncodeVarint32(buf_, static_cast<uint32_t>(size));
|
||||
memcpy(ptr, key.data(), size);
|
||||
key_ = buf_;
|
||||
}
|
||||
|
||||
private:
|
||||
char* key_;
|
||||
char* buf_;
|
||||
size_t buf_size_;
|
||||
const char* key_;
|
||||
size_t key_size_;
|
||||
char space_[32]; // Avoid allocation for short keys
|
||||
|
||||
void ResetBuffer() {
|
||||
if (key_ != space_) {
|
||||
delete[] key_;
|
||||
key_ = space_;
|
||||
if (buf_ != space_) {
|
||||
delete[] buf_;
|
||||
buf_ = space_;
|
||||
}
|
||||
buf_size_ = sizeof(space_);
|
||||
key_size_ = 0;
|
||||
@@ -407,7 +425,7 @@ class IterKey {
|
||||
if (key_size > buf_size_) {
|
||||
// Need to enlarge the buffer.
|
||||
ResetBuffer();
|
||||
key_ = new char[key_size];
|
||||
buf_ = new char[key_size];
|
||||
buf_size_ = key_size;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,6 +74,7 @@ class DeleteFileTest : public testing::Test {
|
||||
|
||||
void CloseDB() {
|
||||
delete db_;
|
||||
db_ = nullptr;
|
||||
}
|
||||
|
||||
void AddKeys(int numkeys, int startkey = 0) {
|
||||
|
||||
+4
-1
@@ -8,7 +8,10 @@
|
||||
namespace rocksdb {
|
||||
|
||||
namespace {
|
||||
inline double SafeDivide(double a, double b) { return b == 0.0 ? 0 : a / b; }
|
||||
template<class T>
|
||||
inline T SafeDivide(T a, T b) {
|
||||
return b == 0 ? 0 : a / b;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void EventHelpers::AppendCurrentTime(JSONWriter* jwriter) {
|
||||
|
||||
@@ -645,18 +645,17 @@ class FaultInjectionTest : public testing::Test,
|
||||
return test::RandomString(&r, kValueSize, storage);
|
||||
}
|
||||
|
||||
Status OpenDB() {
|
||||
delete db_;
|
||||
db_ = NULL;
|
||||
env_->ResetState();
|
||||
return DB::Open(options_, dbname_, &db_);
|
||||
}
|
||||
|
||||
void CloseDB() {
|
||||
delete db_;
|
||||
db_ = NULL;
|
||||
}
|
||||
|
||||
Status OpenDB() {
|
||||
CloseDB();
|
||||
env_->ResetState();
|
||||
return DB::Open(options_, dbname_, &db_);
|
||||
}
|
||||
|
||||
void DeleteAllData() {
|
||||
Iterator* iter = db_->NewIterator(ReadOptions());
|
||||
WriteOptions options;
|
||||
@@ -784,6 +783,7 @@ TEST_P(FaultInjectionTest, WriteOptionSyncTest) {
|
||||
// Block the job queue to prevent flush job from running.
|
||||
env_->Schedule(&test::SleepingBackgroundTask::DoSleepTask, &sleeping_task_low,
|
||||
Env::Priority::HIGH);
|
||||
sleeping_task_low.WaitUntilSleeping();
|
||||
|
||||
WriteOptions write_options;
|
||||
write_options.sync = false;
|
||||
@@ -867,6 +867,7 @@ TEST_P(FaultInjectionTest, ManualLogSyncTest) {
|
||||
// Block the job queue to prevent flush job from running.
|
||||
env_->Schedule(&test::SleepingBackgroundTask::DoSleepTask, &sleeping_task_low,
|
||||
Env::Priority::HIGH);
|
||||
sleeping_task_low.WaitUntilSleeping();
|
||||
|
||||
WriteOptions write_options;
|
||||
write_options.sync = false;
|
||||
|
||||
+3
-3
@@ -26,9 +26,9 @@ struct FileLevel;
|
||||
// The file tree structure in Version is prebuilt and the range of each file
|
||||
// is known. On Version::Get(), it uses binary search to find a potential file
|
||||
// and then check if a target key can be found in the file by comparing the key
|
||||
// to each file's smallest and largest key. The results of these comparisions
|
||||
// to each file's smallest and largest key. The results of these comparisons
|
||||
// can be reused beyond checking if a key falls into a file's range.
|
||||
// With some pre-calculated knowledge, each key comparision that has been done
|
||||
// With some pre-calculated knowledge, each key comparison that has been done
|
||||
// can serve as a hint to narrow down further searches: if a key compared to
|
||||
// be smaller than a file's smallest or largest, that comparison can be used
|
||||
// to find out the right bound of next binary search. Similarly, if a key
|
||||
@@ -48,7 +48,7 @@ class FileIndexer {
|
||||
size_t LevelIndexSize(size_t level) const;
|
||||
|
||||
// Return a file index range in the next level to search for a key based on
|
||||
// smallest and largest key comparision for the current file specified by
|
||||
// smallest and largest key comparison for the current file specified by
|
||||
// level and file_index. When *left_index < *right_index, both index should
|
||||
// be valid and fit in the vector size.
|
||||
void GetNextLevelIndex(const size_t level, const size_t file_index,
|
||||
|
||||
+37
-3
@@ -21,6 +21,7 @@
|
||||
#include "util/file_reader_writer.h"
|
||||
#include "util/logging.h"
|
||||
#include "util/stop_watch.h"
|
||||
#include "util/string_util.h"
|
||||
#include "util/sync_point.h"
|
||||
|
||||
namespace rocksdb {
|
||||
@@ -47,8 +48,9 @@ static size_t GetInfoLogPrefix(const std::string& path, char* dest, int len) {
|
||||
path[i] == '_'){
|
||||
dest[write_idx++] = path[i];
|
||||
} else {
|
||||
if (i > 0)
|
||||
if (i > 0) {
|
||||
dest[write_idx++] = '_';
|
||||
}
|
||||
}
|
||||
i++;
|
||||
}
|
||||
@@ -146,7 +148,7 @@ std::string LockFileName(const std::string& dbname) {
|
||||
}
|
||||
|
||||
std::string TempFileName(const std::string& dbname, uint64_t number) {
|
||||
return MakeFileName(dbname, number, "dbtmp");
|
||||
return MakeFileName(dbname, number, kTempFileNameSuffix.c_str());
|
||||
}
|
||||
|
||||
InfoLogPrefix::InfoLogPrefix(bool has_log_dir,
|
||||
@@ -186,6 +188,21 @@ std::string OldInfoLogFileName(const std::string& dbname, uint64_t ts,
|
||||
return log_dir + "/" + info_log_prefix.buf + ".old." + buf;
|
||||
}
|
||||
|
||||
std::string OptionsFileName(const std::string& dbname, uint64_t file_num) {
|
||||
char buffer[256];
|
||||
snprintf(buffer, sizeof(buffer), "%s%06" PRIu64,
|
||||
kOptionsFileNamePrefix.c_str(), file_num);
|
||||
return dbname + "/" + buffer;
|
||||
}
|
||||
|
||||
std::string TempOptionsFileName(const std::string& dbname, uint64_t file_num) {
|
||||
char buffer[256];
|
||||
snprintf(buffer, sizeof(buffer), "%s%06" PRIu64 ".%s",
|
||||
kOptionsFileNamePrefix.c_str(), file_num,
|
||||
kTempFileNameSuffix.c_str());
|
||||
return dbname + "/" + buffer;
|
||||
}
|
||||
|
||||
std::string MetaDatabaseName(const std::string& dbname, uint64_t number) {
|
||||
char buf[100];
|
||||
snprintf(buf, sizeof(buf), "/METADB-%llu",
|
||||
@@ -206,6 +223,8 @@ std::string IdentityFileName(const std::string& dbname) {
|
||||
// dbname/MANIFEST-[0-9]+
|
||||
// dbname/[0-9]+.(log|sst)
|
||||
// dbname/METADB-[0-9]+
|
||||
// dbname/OPTIONS-[0-9]+
|
||||
// dbname/OPTIONS-[0-9]+.dbtmp
|
||||
// Disregards / at the beginning
|
||||
bool ParseFileName(const std::string& fname,
|
||||
uint64_t* number,
|
||||
@@ -268,6 +287,21 @@ bool ParseFileName(const std::string& fname, uint64_t* number,
|
||||
}
|
||||
*type = kMetaDatabase;
|
||||
*number = num;
|
||||
} else if (rest.starts_with(kOptionsFileNamePrefix)) {
|
||||
uint64_t ts_suffix;
|
||||
bool is_temp_file = false;
|
||||
rest.remove_prefix(kOptionsFileNamePrefix.size());
|
||||
const std::string kTempFileNameSuffixWithDot =
|
||||
std::string(".") + kTempFileNameSuffix;
|
||||
if (rest.ends_with(kTempFileNameSuffixWithDot)) {
|
||||
rest.remove_suffix(kTempFileNameSuffixWithDot.size());
|
||||
is_temp_file = true;
|
||||
}
|
||||
if (!ConsumeDecimalNumber(&rest, &ts_suffix)) {
|
||||
return false;
|
||||
}
|
||||
*number = ts_suffix;
|
||||
*type = is_temp_file ? kTempFile : kOptionsFile;
|
||||
} else {
|
||||
// Avoid strtoull() to keep filename format independent of the
|
||||
// current locale
|
||||
@@ -302,7 +336,7 @@ bool ParseFileName(const std::string& fname, uint64_t* number,
|
||||
} else if (suffix == Slice(kRocksDbTFileExt) ||
|
||||
suffix == Slice(kLevelDbTFileExt)) {
|
||||
*type = kTableFile;
|
||||
} else if (suffix == Slice("dbtmp")) {
|
||||
} else if (suffix == Slice(kTempFileNameSuffix)) {
|
||||
*type = kTempFile;
|
||||
} else {
|
||||
return false;
|
||||
|
||||
+15
-1
@@ -36,7 +36,8 @@ enum FileType {
|
||||
kTempFile,
|
||||
kInfoLogFile, // Either the current one, or an old one
|
||||
kMetaDatabase,
|
||||
kIdentityFile
|
||||
kIdentityFile,
|
||||
kOptionsFile
|
||||
};
|
||||
|
||||
// Return the name of the log file with the specified number
|
||||
@@ -114,6 +115,19 @@ extern std::string OldInfoLogFileName(const std::string& dbname, uint64_t ts,
|
||||
const std::string& db_path = "",
|
||||
const std::string& log_dir = "");
|
||||
|
||||
static const std::string kOptionsFileNamePrefix = "OPTIONS-";
|
||||
static const std::string kTempFileNameSuffix = "dbtmp";
|
||||
|
||||
// Return a options file name given the "dbname" and file number.
|
||||
// Format: OPTIONS-[number].dbtmp
|
||||
extern std::string OptionsFileName(const std::string& dbname,
|
||||
uint64_t file_num);
|
||||
|
||||
// Return a temp options file name given the "dbname" and file number.
|
||||
// Format: OPTIONS-[number]
|
||||
extern std::string TempOptionsFileName(const std::string& dbname,
|
||||
uint64_t file_num);
|
||||
|
||||
// Return the name to use for a metadatabase. The result will be prefixed with
|
||||
// "dbname".
|
||||
extern std::string MetaDatabaseName(const std::string& dbname,
|
||||
|
||||
+4
-2
@@ -62,6 +62,7 @@ FlushJob::FlushJob(const std::string& dbname, ColumnFamilyData* cfd,
|
||||
InstrumentedMutex* db_mutex,
|
||||
std::atomic<bool>* shutting_down,
|
||||
std::vector<SequenceNumber> existing_snapshots,
|
||||
SequenceNumber earliest_write_conflict_snapshot,
|
||||
JobContext* job_context, LogBuffer* log_buffer,
|
||||
Directory* db_directory, Directory* output_file_directory,
|
||||
CompressionType output_compression, Statistics* stats,
|
||||
@@ -75,6 +76,7 @@ FlushJob::FlushJob(const std::string& dbname, ColumnFamilyData* cfd,
|
||||
db_mutex_(db_mutex),
|
||||
shutting_down_(shutting_down),
|
||||
existing_snapshots_(std::move(existing_snapshots)),
|
||||
earliest_write_conflict_snapshot_(earliest_write_conflict_snapshot),
|
||||
job_context_(job_context),
|
||||
log_buffer_(log_buffer),
|
||||
db_directory_(db_directory),
|
||||
@@ -235,8 +237,8 @@ Status FlushJob::WriteLevel0Table(const autovector<MemTable*>& mems,
|
||||
cfd_->table_cache(), iter.get(), meta,
|
||||
cfd_->internal_comparator(),
|
||||
cfd_->int_tbl_prop_collector_factories(), cfd_->GetID(),
|
||||
existing_snapshots_, output_compression_,
|
||||
cfd_->ioptions()->compression_opts,
|
||||
existing_snapshots_, earliest_write_conflict_snapshot_,
|
||||
output_compression_, cfd_->ioptions()->compression_opts,
|
||||
mutable_cf_options_.paranoid_file_checks,
|
||||
cfd_->internal_stats(), Env::IO_HIGH, &table_properties_);
|
||||
info.table_properties = table_properties_;
|
||||
|
||||
@@ -58,6 +58,7 @@ class FlushJob {
|
||||
const EnvOptions& env_options, VersionSet* versions,
|
||||
InstrumentedMutex* db_mutex, std::atomic<bool>* shutting_down,
|
||||
std::vector<SequenceNumber> existing_snapshots,
|
||||
SequenceNumber earliest_write_conflict_snapshot,
|
||||
JobContext* job_context, LogBuffer* log_buffer,
|
||||
Directory* db_directory, Directory* output_file_directory,
|
||||
CompressionType output_compression, Statistics* stats,
|
||||
@@ -83,6 +84,7 @@ class FlushJob {
|
||||
InstrumentedMutex* db_mutex_;
|
||||
std::atomic<bool>* shutting_down_;
|
||||
std::vector<SequenceNumber> existing_snapshots_;
|
||||
SequenceNumber earliest_write_conflict_snapshot_;
|
||||
JobContext* job_context_;
|
||||
LogBuffer* log_buffer_;
|
||||
Directory* db_directory_;
|
||||
|
||||
@@ -92,8 +92,8 @@ TEST_F(FlushJobTest, Empty) {
|
||||
FlushJob flush_job(dbname_, versions_->GetColumnFamilySet()->GetDefault(),
|
||||
db_options_, *cfd->GetLatestMutableCFOptions(),
|
||||
env_options_, versions_.get(), &mutex_, &shutting_down_,
|
||||
{}, &job_context, nullptr, nullptr, nullptr,
|
||||
kNoCompression, nullptr, &event_logger);
|
||||
{}, kMaxSequenceNumber, &job_context, nullptr, nullptr,
|
||||
nullptr, kNoCompression, nullptr, &event_logger);
|
||||
ASSERT_OK(flush_job.Run());
|
||||
job_context.Clean();
|
||||
}
|
||||
@@ -131,8 +131,8 @@ TEST_F(FlushJobTest, NonEmpty) {
|
||||
FlushJob flush_job(dbname_, versions_->GetColumnFamilySet()->GetDefault(),
|
||||
db_options_, *cfd->GetLatestMutableCFOptions(),
|
||||
env_options_, versions_.get(), &mutex_, &shutting_down_,
|
||||
{}, &job_context, nullptr, nullptr, nullptr,
|
||||
kNoCompression, nullptr, &event_logger);
|
||||
{}, kMaxSequenceNumber, &job_context, nullptr, nullptr,
|
||||
nullptr, kNoCompression, nullptr, &event_logger);
|
||||
FileMetaData fd;
|
||||
mutex_.Lock();
|
||||
ASSERT_OK(flush_job.Run(&fd));
|
||||
@@ -195,8 +195,8 @@ TEST_F(FlushJobTest, Snapshots) {
|
||||
FlushJob flush_job(dbname_, versions_->GetColumnFamilySet()->GetDefault(),
|
||||
db_options_, *cfd->GetLatestMutableCFOptions(),
|
||||
env_options_, versions_.get(), &mutex_, &shutting_down_,
|
||||
snapshots, &job_context, nullptr, nullptr, nullptr,
|
||||
kNoCompression, nullptr, &event_logger);
|
||||
snapshots, kMaxSequenceNumber, &job_context, nullptr,
|
||||
nullptr, nullptr, kNoCompression, nullptr, &event_logger);
|
||||
mutex_.Lock();
|
||||
ASSERT_OK(flush_job.Run());
|
||||
mutex_.Unlock();
|
||||
|
||||
+50
-32
@@ -13,51 +13,69 @@ namespace rocksdb {
|
||||
|
||||
void FlushScheduler::ScheduleFlush(ColumnFamilyData* cfd) {
|
||||
#ifndef NDEBUG
|
||||
assert(column_families_set_.find(cfd) == column_families_set_.end());
|
||||
column_families_set_.insert(cfd);
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(checking_mutex_);
|
||||
assert(checking_set_.count(cfd) == 0);
|
||||
checking_set_.insert(cfd);
|
||||
}
|
||||
#endif // NDEBUG
|
||||
cfd->Ref();
|
||||
column_families_.push_back(cfd);
|
||||
Node* node = new Node{cfd, head_.load(std::memory_order_relaxed)};
|
||||
while (!head_.compare_exchange_strong(
|
||||
node->next, node, std::memory_order_relaxed, std::memory_order_relaxed)) {
|
||||
// failing CAS updates the first param, so we are already set for
|
||||
// retry. TakeNextColumnFamily won't happen until after another
|
||||
// inter-thread synchronization, so we don't even need release
|
||||
// semantics for this CAS
|
||||
}
|
||||
}
|
||||
|
||||
ColumnFamilyData* FlushScheduler::GetNextColumnFamily() {
|
||||
ColumnFamilyData* cfd = nullptr;
|
||||
while (column_families_.size() > 0) {
|
||||
cfd = column_families_.front();
|
||||
column_families_.pop_front();
|
||||
if (cfd->IsDropped()) {
|
||||
if (cfd->Unref()) {
|
||||
delete cfd;
|
||||
cfd = nullptr;
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
ColumnFamilyData* FlushScheduler::TakeNextColumnFamily() {
|
||||
while (true) {
|
||||
if (Empty()) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
#ifndef NDEBUG
|
||||
if (cfd != nullptr) {
|
||||
auto itr = column_families_set_.find(cfd);
|
||||
assert(itr != column_families_set_.end());
|
||||
column_families_set_.erase(itr);
|
||||
}
|
||||
#endif // NDEBUG
|
||||
return cfd;
|
||||
}
|
||||
|
||||
bool FlushScheduler::Empty() { return column_families_.empty(); }
|
||||
// dequeue the head
|
||||
Node* node = head_.load(std::memory_order_relaxed);
|
||||
head_.store(node->next, std::memory_order_relaxed);
|
||||
ColumnFamilyData* cfd = node->column_family;
|
||||
delete node;
|
||||
|
||||
void FlushScheduler::Clear() {
|
||||
for (auto cfd : column_families_) {
|
||||
#ifndef NDEBUG
|
||||
auto itr = column_families_set_.find(cfd);
|
||||
assert(itr != column_families_set_.end());
|
||||
column_families_set_.erase(itr);
|
||||
{
|
||||
auto iter = checking_set_.find(cfd);
|
||||
assert(iter != checking_set_.end());
|
||||
checking_set_.erase(iter);
|
||||
}
|
||||
#endif // NDEBUG
|
||||
|
||||
if (!cfd->IsDropped()) {
|
||||
// success
|
||||
return cfd;
|
||||
}
|
||||
|
||||
// no longer relevant, retry
|
||||
if (cfd->Unref()) {
|
||||
delete cfd;
|
||||
}
|
||||
}
|
||||
column_families_.clear();
|
||||
}
|
||||
|
||||
bool FlushScheduler::Empty() {
|
||||
auto rv = head_.load(std::memory_order_relaxed) == nullptr;
|
||||
assert(rv == checking_set_.empty());
|
||||
return rv;
|
||||
}
|
||||
|
||||
void FlushScheduler::Clear() {
|
||||
ColumnFamilyData* cfd;
|
||||
while ((cfd = TakeNextColumnFamily()) != nullptr) {
|
||||
if (cfd->Unref()) {
|
||||
delete cfd;
|
||||
}
|
||||
}
|
||||
assert(Empty());
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
+19
-11
@@ -6,34 +6,42 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <deque>
|
||||
#include <atomic>
|
||||
#include <mutex>
|
||||
#include <set>
|
||||
#include <vector>
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
class ColumnFamilyData;
|
||||
|
||||
// This class is thread-compatible. It's should only be accessed from single
|
||||
// write thread (between BeginWrite() and EndWrite())
|
||||
// Unless otherwise noted, all methods on FlushScheduler should be called
|
||||
// only with the DB mutex held or from a single-threaded recovery context.
|
||||
class FlushScheduler {
|
||||
public:
|
||||
FlushScheduler() = default;
|
||||
~FlushScheduler() = default;
|
||||
FlushScheduler() : head_(nullptr) {}
|
||||
|
||||
// May be called from multiple threads at once, but not concurrent with
|
||||
// any other method calls on this instance
|
||||
void ScheduleFlush(ColumnFamilyData* cfd);
|
||||
// Returns Ref()-ed column family. Client needs to Unref()
|
||||
// REQUIRES: db mutex is held (exception is single-threaded recovery)
|
||||
ColumnFamilyData* GetNextColumnFamily();
|
||||
|
||||
// Removes and returns Ref()-ed column family. Client needs to Unref().
|
||||
// Filters column families that have been dropped.
|
||||
ColumnFamilyData* TakeNextColumnFamily();
|
||||
|
||||
bool Empty();
|
||||
|
||||
void Clear();
|
||||
|
||||
private:
|
||||
std::deque<ColumnFamilyData*> column_families_;
|
||||
struct Node {
|
||||
ColumnFamilyData* column_family;
|
||||
Node* next;
|
||||
};
|
||||
|
||||
std::atomic<Node*> head_;
|
||||
#ifndef NDEBUG
|
||||
std::set<ColumnFamilyData*> column_families_set_;
|
||||
std::mutex checking_mutex_;
|
||||
std::set<ColumnFamilyData*> checking_set_;
|
||||
#endif // NDEBUG
|
||||
};
|
||||
|
||||
|
||||
+108
-28
@@ -144,6 +144,23 @@ ForwardIterator::~ForwardIterator() {
|
||||
Cleanup(true);
|
||||
}
|
||||
|
||||
void ForwardIterator::SVCleanup() {
|
||||
if (sv_ != nullptr && sv_->Unref()) {
|
||||
// Job id == 0 means that this is not our background process, but rather
|
||||
// user thread
|
||||
JobContext job_context(0);
|
||||
db_->mutex_.Lock();
|
||||
sv_->Cleanup();
|
||||
db_->FindObsoleteFiles(&job_context, false, true);
|
||||
db_->mutex_.Unlock();
|
||||
delete sv_;
|
||||
if (job_context.HaveSomethingToDelete()) {
|
||||
db_->PurgeObsoleteFiles(job_context);
|
||||
}
|
||||
job_context.Clean();
|
||||
}
|
||||
}
|
||||
|
||||
void ForwardIterator::Cleanup(bool release_sv) {
|
||||
if (mutable_iter_ != nullptr) {
|
||||
mutable_iter_->~InternalIterator();
|
||||
@@ -162,20 +179,7 @@ void ForwardIterator::Cleanup(bool release_sv) {
|
||||
level_iters_.clear();
|
||||
|
||||
if (release_sv) {
|
||||
if (sv_ != nullptr && sv_->Unref()) {
|
||||
// Job id == 0 means that this is not our background process, but rather
|
||||
// user thread
|
||||
JobContext job_context(0);
|
||||
db_->mutex_.Lock();
|
||||
sv_->Cleanup();
|
||||
db_->FindObsoleteFiles(&job_context, false, true);
|
||||
db_->mutex_.Unlock();
|
||||
delete sv_;
|
||||
if (job_context.HaveSomethingToDelete()) {
|
||||
db_->PurgeObsoleteFiles(job_context);
|
||||
}
|
||||
job_context.Clean();
|
||||
}
|
||||
SVCleanup();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -185,9 +189,10 @@ bool ForwardIterator::Valid() const {
|
||||
}
|
||||
|
||||
void ForwardIterator::SeekToFirst() {
|
||||
if (sv_ == nullptr ||
|
||||
sv_ ->version_number != cfd_->GetSuperVersionNumber()) {
|
||||
if (sv_ == nullptr) {
|
||||
RebuildIterators(true);
|
||||
} else if (sv_->version_number != cfd_->GetSuperVersionNumber()) {
|
||||
RenewIterators();
|
||||
} else if (immutable_status_.IsIncomplete()) {
|
||||
ResetIncompleteIterators();
|
||||
}
|
||||
@@ -205,9 +210,10 @@ void ForwardIterator::Seek(const Slice& internal_key) {
|
||||
if (IsOverUpperBound(internal_key)) {
|
||||
valid_ = false;
|
||||
}
|
||||
if (sv_ == nullptr ||
|
||||
sv_ ->version_number != cfd_->GetSuperVersionNumber()) {
|
||||
if (sv_ == nullptr) {
|
||||
RebuildIterators(true);
|
||||
} else if (sv_->version_number != cfd_->GetSuperVersionNumber()) {
|
||||
RenewIterators();
|
||||
} else if (immutable_status_.IsIncomplete()) {
|
||||
ResetIncompleteIterators();
|
||||
}
|
||||
@@ -227,7 +233,9 @@ void ForwardIterator::SeekInternal(const Slice& internal_key,
|
||||
// an option to turn it off.
|
||||
if (seek_to_first || NeedToSeekImmutable(internal_key)) {
|
||||
immutable_status_ = Status::OK();
|
||||
if (has_iter_trimmed_for_upper_bound_) {
|
||||
if ((has_iter_trimmed_for_upper_bound_) &&
|
||||
(cfd_->internal_comparator().InternalKeyComparator::Compare(
|
||||
prev_key_.GetKey(), internal_key) > 0)) {
|
||||
// Some iterators are trimmed. Need to rebuild.
|
||||
RebuildIterators(true);
|
||||
// Already seeked mutable iter, so seek again
|
||||
@@ -254,7 +262,7 @@ void ForwardIterator::SeekInternal(const Slice& internal_key,
|
||||
}
|
||||
const VersionStorageInfo* vstorage = sv_->current->storage_info();
|
||||
const std::vector<FileMetaData*>& l0 = vstorage->LevelFiles(0);
|
||||
for (uint32_t i = 0; i < l0.size(); ++i) {
|
||||
for (size_t i = 0; i < l0.size(); ++i) {
|
||||
if (!l0_iters_[i]) {
|
||||
continue;
|
||||
}
|
||||
@@ -393,7 +401,11 @@ void ForwardIterator::Next() {
|
||||
std::string current_key = key().ToString();
|
||||
Slice old_key(current_key.data(), current_key.size());
|
||||
|
||||
RebuildIterators(true);
|
||||
if (sv_ == nullptr) {
|
||||
RebuildIterators(true);
|
||||
} else {
|
||||
RenewIterators();
|
||||
}
|
||||
SeekInternal(old_key, false);
|
||||
if (!valid_ || key().compare(old_key) != 0) {
|
||||
return;
|
||||
@@ -484,10 +496,81 @@ void ForwardIterator::RebuildIterators(bool refresh_sv) {
|
||||
l0_iters_.push_back(cfd_->table_cache()->NewIterator(
|
||||
read_options_, *cfd_->soptions(), cfd_->internal_comparator(), l0->fd));
|
||||
}
|
||||
BuildLevelIterators(vstorage);
|
||||
current_ = nullptr;
|
||||
is_prev_set_ = false;
|
||||
}
|
||||
|
||||
void ForwardIterator::RenewIterators() {
|
||||
SuperVersion* svnew;
|
||||
assert(sv_);
|
||||
svnew = cfd_->GetReferencedSuperVersion(&(db_->mutex_));
|
||||
|
||||
if (mutable_iter_ != nullptr) {
|
||||
mutable_iter_->~InternalIterator();
|
||||
}
|
||||
for (auto* m : imm_iters_) {
|
||||
m->~InternalIterator();
|
||||
}
|
||||
imm_iters_.clear();
|
||||
|
||||
mutable_iter_ = svnew->mem->NewIterator(read_options_, &arena_);
|
||||
svnew->imm->AddIterators(read_options_, &imm_iters_, &arena_);
|
||||
|
||||
const auto* vstorage = sv_->current->storage_info();
|
||||
const auto& l0_files = vstorage->LevelFiles(0);
|
||||
const auto* vstorage_new = svnew->current->storage_info();
|
||||
const auto& l0_files_new = vstorage_new->LevelFiles(0);
|
||||
size_t iold, inew;
|
||||
bool found;
|
||||
std::vector<InternalIterator*> l0_iters_new;
|
||||
l0_iters_new.reserve(l0_files_new.size());
|
||||
|
||||
for (inew = 0; inew < l0_files_new.size(); inew++) {
|
||||
found = false;
|
||||
for (iold = 0; iold < l0_files.size(); iold++) {
|
||||
if (l0_files[iold] == l0_files_new[inew]) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (found) {
|
||||
if (l0_iters_[iold] == nullptr) {
|
||||
l0_iters_new.push_back(nullptr);
|
||||
TEST_SYNC_POINT_CALLBACK("ForwardIterator::RenewIterators:Null", this);
|
||||
} else {
|
||||
l0_iters_new.push_back(l0_iters_[iold]);
|
||||
l0_iters_[iold] = nullptr;
|
||||
TEST_SYNC_POINT_CALLBACK("ForwardIterator::RenewIterators:Copy", this);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
l0_iters_new.push_back(cfd_->table_cache()->NewIterator(
|
||||
read_options_, *cfd_->soptions(), cfd_->internal_comparator(),
|
||||
l0_files_new[inew]->fd));
|
||||
}
|
||||
|
||||
for (auto* f : l0_iters_) {
|
||||
delete f;
|
||||
}
|
||||
l0_iters_.clear();
|
||||
l0_iters_ = l0_iters_new;
|
||||
|
||||
for (auto* l : level_iters_) {
|
||||
delete l;
|
||||
}
|
||||
level_iters_.clear();
|
||||
BuildLevelIterators(vstorage_new);
|
||||
current_ = nullptr;
|
||||
is_prev_set_ = false;
|
||||
SVCleanup();
|
||||
sv_ = svnew;
|
||||
}
|
||||
|
||||
void ForwardIterator::BuildLevelIterators(const VersionStorageInfo* vstorage) {
|
||||
level_iters_.reserve(vstorage->num_levels() - 1);
|
||||
for (int32_t level = 1; level < vstorage->num_levels(); ++level) {
|
||||
const auto& level_files = vstorage->LevelFiles(level);
|
||||
|
||||
if ((level_files.empty()) ||
|
||||
((read_options_.iterate_upper_bound != nullptr) &&
|
||||
(user_comparator_->Compare(*read_options_.iterate_upper_bound,
|
||||
@@ -502,14 +585,11 @@ void ForwardIterator::RebuildIterators(bool refresh_sv) {
|
||||
new LevelIterator(cfd_, read_options_, level_files));
|
||||
}
|
||||
}
|
||||
|
||||
current_ = nullptr;
|
||||
is_prev_set_ = false;
|
||||
}
|
||||
|
||||
void ForwardIterator::ResetIncompleteIterators() {
|
||||
const auto& l0_files = sv_->current->storage_info()->LevelFiles(0);
|
||||
for (uint32_t i = 0; i < l0_iters_.size(); ++i) {
|
||||
for (size_t i = 0; i < l0_iters_.size(); ++i) {
|
||||
assert(i < l0_files.size());
|
||||
if (!l0_iters_[i] || !l0_iters_[i]->status().IsIncomplete()) {
|
||||
continue;
|
||||
@@ -600,7 +680,7 @@ bool ForwardIterator::NeedToSeekImmutable(const Slice& target) {
|
||||
void ForwardIterator::DeleteCurrentIter() {
|
||||
const VersionStorageInfo* vstorage = sv_->current->storage_info();
|
||||
const std::vector<FileMetaData*>& l0 = vstorage->LevelFiles(0);
|
||||
for (uint32_t i = 0; i < l0.size(); ++i) {
|
||||
for (size_t i = 0; i < l0.size(); ++i) {
|
||||
if (!l0_iters_[i]) {
|
||||
continue;
|
||||
}
|
||||
@@ -632,7 +712,7 @@ bool ForwardIterator::TEST_CheckDeletedIters(int* pdeleted_iters,
|
||||
|
||||
const VersionStorageInfo* vstorage = sv_->current->storage_info();
|
||||
const std::vector<FileMetaData*>& l0 = vstorage->LevelFiles(0);
|
||||
for (uint32_t i = 0; i < l0.size(); ++i) {
|
||||
for (size_t i = 0; i < l0.size(); ++i) {
|
||||
if (!l0_iters_[i]) {
|
||||
retval = true;
|
||||
deleted_iters++;
|
||||
|
||||
@@ -24,6 +24,7 @@ class Env;
|
||||
struct SuperVersion;
|
||||
class ColumnFamilyData;
|
||||
class LevelIterator;
|
||||
class VersionStorageInfo;
|
||||
struct FileMetaData;
|
||||
|
||||
class MinIterComparator {
|
||||
@@ -74,7 +75,10 @@ class ForwardIterator : public InternalIterator {
|
||||
|
||||
private:
|
||||
void Cleanup(bool release_sv);
|
||||
void SVCleanup();
|
||||
void RebuildIterators(bool refresh_sv);
|
||||
void RenewIterators();
|
||||
void BuildLevelIterators(const VersionStorageInfo* vstorage);
|
||||
void ResetIncompleteIterators();
|
||||
void SeekInternal(const Slice& internal_key, bool seek_to_first);
|
||||
void UpdateCurrent();
|
||||
|
||||
@@ -0,0 +1,374 @@
|
||||
// Copyright (c) 2013, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the root directory of this source tree. An additional grant
|
||||
// of patent rights can be found in the PATENTS file in the same directory.
|
||||
|
||||
#ifndef __STDC_FORMAT_MACROS
|
||||
#define __STDC_FORMAT_MACROS
|
||||
#endif
|
||||
|
||||
#if !defined(GFLAGS) || defined(ROCKSDB_LITE)
|
||||
#include <cstdio>
|
||||
int main() {
|
||||
fprintf(stderr, "Please install gflags to run rocksdb tools\n");
|
||||
return 1;
|
||||
}
|
||||
#elif defined(OS_MACOSX) || defined(OS_WIN)
|
||||
// Block forward_iterator_bench under MAC and Windows
|
||||
int main() { return 0; }
|
||||
#else
|
||||
#include <gflags/gflags.h>
|
||||
#include <semaphore.h>
|
||||
#include <atomic>
|
||||
#include <bitset>
|
||||
#include <chrono>
|
||||
#include <climits>
|
||||
#include <condition_variable>
|
||||
#include <limits>
|
||||
#include <mutex>
|
||||
#include <queue>
|
||||
#include <random>
|
||||
#include <thread>
|
||||
|
||||
#include "rocksdb/cache.h"
|
||||
#include "rocksdb/db.h"
|
||||
#include "rocksdb/status.h"
|
||||
#include "rocksdb/table.h"
|
||||
#include "util/testharness.h"
|
||||
|
||||
const int MAX_SHARDS = 100000;
|
||||
|
||||
DEFINE_int32(writers, 8, "");
|
||||
DEFINE_int32(readers, 8, "");
|
||||
DEFINE_int64(rate, 100000, "");
|
||||
DEFINE_int64(value_size, 300, "");
|
||||
DEFINE_int64(shards, 1000, "");
|
||||
DEFINE_int64(memtable_size, 500000000, "");
|
||||
DEFINE_int64(block_cache_size, 300000000, "");
|
||||
DEFINE_int64(block_size, 65536, "");
|
||||
DEFINE_double(runtime, 300.0, "");
|
||||
DEFINE_bool(cache_only_first, true, "");
|
||||
DEFINE_bool(iterate_upper_bound, true, "");
|
||||
|
||||
struct Stats {
|
||||
char pad1[128] __attribute__((__unused__));
|
||||
std::atomic<uint64_t> written{0};
|
||||
char pad2[128] __attribute__((__unused__));
|
||||
std::atomic<uint64_t> read{0};
|
||||
std::atomic<uint64_t> cache_misses{0};
|
||||
char pad3[128] __attribute__((__unused__));
|
||||
} stats;
|
||||
|
||||
struct Key {
|
||||
Key() {}
|
||||
Key(uint64_t shard_in, uint64_t seqno_in)
|
||||
: shard_be(htobe64(shard_in)), seqno_be(htobe64(seqno_in)) {}
|
||||
|
||||
uint64_t shard() const { return be64toh(shard_be); }
|
||||
uint64_t seqno() const { return be64toh(seqno_be); }
|
||||
|
||||
private:
|
||||
uint64_t shard_be;
|
||||
uint64_t seqno_be;
|
||||
} __attribute__((__packed__));
|
||||
|
||||
struct Reader;
|
||||
struct Writer;
|
||||
|
||||
struct ShardState {
|
||||
char pad1[128] __attribute__((__unused__));
|
||||
std::atomic<uint64_t> last_written{0};
|
||||
Writer* writer;
|
||||
Reader* reader;
|
||||
char pad2[128] __attribute__((__unused__));
|
||||
std::atomic<uint64_t> last_read{0};
|
||||
std::unique_ptr<rocksdb::Iterator> it;
|
||||
std::unique_ptr<rocksdb::Iterator> it_cacheonly;
|
||||
Key upper_bound;
|
||||
rocksdb::Slice upper_bound_slice;
|
||||
char pad3[128] __attribute__((__unused__));
|
||||
};
|
||||
|
||||
struct Reader {
|
||||
public:
|
||||
explicit Reader(std::vector<ShardState>* shard_states, rocksdb::DB* db)
|
||||
: shard_states_(shard_states), db_(db) {
|
||||
sem_init(&sem_, 0, 0);
|
||||
thread_ = std::thread(&Reader::run, this);
|
||||
}
|
||||
|
||||
void run() {
|
||||
while (1) {
|
||||
sem_wait(&sem_);
|
||||
if (done_.load()) {
|
||||
break;
|
||||
}
|
||||
|
||||
uint64_t shard;
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(queue_mutex_);
|
||||
assert(!shards_pending_queue_.empty());
|
||||
shard = shards_pending_queue_.front();
|
||||
shards_pending_queue_.pop();
|
||||
shards_pending_set_.reset(shard);
|
||||
}
|
||||
readOnceFromShard(shard);
|
||||
}
|
||||
}
|
||||
|
||||
void readOnceFromShard(uint64_t shard) {
|
||||
ShardState& state = (*shard_states_)[shard];
|
||||
if (!state.it) {
|
||||
// Initialize iterators
|
||||
rocksdb::ReadOptions options;
|
||||
options.tailing = true;
|
||||
if (FLAGS_iterate_upper_bound) {
|
||||
state.upper_bound = Key(shard, std::numeric_limits<uint64_t>::max());
|
||||
state.upper_bound_slice = rocksdb::Slice(
|
||||
(const char*)&state.upper_bound, sizeof(state.upper_bound));
|
||||
options.iterate_upper_bound = &state.upper_bound_slice;
|
||||
}
|
||||
|
||||
state.it.reset(db_->NewIterator(options));
|
||||
|
||||
if (FLAGS_cache_only_first) {
|
||||
options.read_tier = rocksdb::ReadTier::kBlockCacheTier;
|
||||
state.it_cacheonly.reset(db_->NewIterator(options));
|
||||
}
|
||||
}
|
||||
|
||||
const uint64_t upto = state.last_written.load();
|
||||
for (rocksdb::Iterator* it : {state.it_cacheonly.get(), state.it.get()}) {
|
||||
if (it == nullptr) {
|
||||
continue;
|
||||
}
|
||||
if (state.last_read.load() >= upto) {
|
||||
break;
|
||||
}
|
||||
bool need_seek = true;
|
||||
for (uint64_t seq = state.last_read.load() + 1; seq <= upto; ++seq) {
|
||||
if (need_seek) {
|
||||
Key from(shard, state.last_read.load() + 1);
|
||||
it->Seek(rocksdb::Slice((const char*)&from, sizeof(from)));
|
||||
need_seek = false;
|
||||
} else {
|
||||
it->Next();
|
||||
}
|
||||
if (it->status().IsIncomplete()) {
|
||||
++::stats.cache_misses;
|
||||
break;
|
||||
}
|
||||
assert(it->Valid());
|
||||
assert(it->key().size() == sizeof(Key));
|
||||
Key key;
|
||||
memcpy(&key, it->key().data(), it->key().size());
|
||||
// fprintf(stderr, "Expecting (%ld, %ld) read (%ld, %ld)\n",
|
||||
// shard, seq, key.shard(), key.seqno());
|
||||
assert(key.shard() == shard);
|
||||
assert(key.seqno() == seq);
|
||||
state.last_read.store(seq);
|
||||
++::stats.read;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void onWrite(uint64_t shard) {
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(queue_mutex_);
|
||||
if (!shards_pending_set_.test(shard)) {
|
||||
shards_pending_queue_.push(shard);
|
||||
shards_pending_set_.set(shard);
|
||||
sem_post(&sem_);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
~Reader() {
|
||||
done_.store(true);
|
||||
sem_post(&sem_);
|
||||
thread_.join();
|
||||
}
|
||||
|
||||
private:
|
||||
char pad1[128] __attribute__((__unused__));
|
||||
std::vector<ShardState>* shard_states_;
|
||||
rocksdb::DB* db_;
|
||||
std::thread thread_;
|
||||
sem_t sem_;
|
||||
std::mutex queue_mutex_;
|
||||
std::bitset<MAX_SHARDS + 1> shards_pending_set_;
|
||||
std::queue<uint64_t> shards_pending_queue_;
|
||||
std::atomic<bool> done_{false};
|
||||
char pad2[128] __attribute__((__unused__));
|
||||
};
|
||||
|
||||
struct Writer {
|
||||
explicit Writer(std::vector<ShardState>* shard_states, rocksdb::DB* db)
|
||||
: shard_states_(shard_states), db_(db) {}
|
||||
|
||||
void start() { thread_ = std::thread(&Writer::run, this); }
|
||||
|
||||
void run() {
|
||||
std::queue<std::chrono::steady_clock::time_point> workq;
|
||||
std::chrono::steady_clock::time_point deadline(
|
||||
std::chrono::steady_clock::now() +
|
||||
std::chrono::nanoseconds((uint64_t)(1000000000 * FLAGS_runtime)));
|
||||
std::vector<uint64_t> my_shards;
|
||||
for (int i = 1; i <= FLAGS_shards; ++i) {
|
||||
if ((*shard_states_)[i].writer == this) {
|
||||
my_shards.push_back(i);
|
||||
}
|
||||
}
|
||||
|
||||
std::mt19937 rng{std::random_device()()};
|
||||
std::uniform_int_distribution<int> shard_dist(
|
||||
0, static_cast<int>(my_shards.size()) - 1);
|
||||
std::string value(FLAGS_value_size, '*');
|
||||
|
||||
while (1) {
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
if (FLAGS_runtime >= 0 && now >= deadline) {
|
||||
break;
|
||||
}
|
||||
if (workq.empty()) {
|
||||
for (int i = 0; i < FLAGS_rate; i += FLAGS_writers) {
|
||||
std::chrono::nanoseconds offset(1000000000LL * i / FLAGS_rate);
|
||||
workq.push(now + offset);
|
||||
}
|
||||
}
|
||||
while (!workq.empty() && workq.front() < now) {
|
||||
workq.pop();
|
||||
uint64_t shard = my_shards[shard_dist(rng)];
|
||||
ShardState& state = (*shard_states_)[shard];
|
||||
uint64_t seqno = state.last_written.load() + 1;
|
||||
Key key(shard, seqno);
|
||||
// fprintf(stderr, "Writing (%ld, %ld)\n", shard, seqno);
|
||||
rocksdb::Status status =
|
||||
db_->Put(rocksdb::WriteOptions(),
|
||||
rocksdb::Slice((const char*)&key, sizeof(key)),
|
||||
rocksdb::Slice(value));
|
||||
assert(status.ok());
|
||||
state.last_written.store(seqno);
|
||||
state.reader->onWrite(shard);
|
||||
++::stats.written;
|
||||
}
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
}
|
||||
// fprintf(stderr, "Writer done\n");
|
||||
}
|
||||
|
||||
~Writer() { thread_.join(); }
|
||||
|
||||
private:
|
||||
char pad1[128] __attribute__((__unused__));
|
||||
std::vector<ShardState>* shard_states_;
|
||||
rocksdb::DB* db_;
|
||||
std::thread thread_;
|
||||
char pad2[128] __attribute__((__unused__));
|
||||
};
|
||||
|
||||
struct StatsThread {
|
||||
explicit StatsThread(rocksdb::DB* db)
|
||||
: db_(db), thread_(&StatsThread::run, this) {}
|
||||
|
||||
void run() {
|
||||
// using namespace std::chrono;
|
||||
auto tstart = std::chrono::steady_clock::now(), tlast = tstart;
|
||||
uint64_t wlast = 0, rlast = 0;
|
||||
while (!done_.load()) {
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(cvm_);
|
||||
cv_.wait_for(lock, std::chrono::seconds(1));
|
||||
}
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
double elapsed =
|
||||
std::chrono::duration_cast<std::chrono::duration<double> >(
|
||||
now - tlast).count();
|
||||
uint64_t w = ::stats.written.load();
|
||||
uint64_t r = ::stats.read.load();
|
||||
fprintf(stderr,
|
||||
"%s elapsed %4lds | written %10ld | w/s %10.0f | read %10ld | "
|
||||
"r/s %10.0f | cache misses %10ld\n",
|
||||
db_->GetEnv()->TimeToString(time(nullptr)).c_str(),
|
||||
std::chrono::duration_cast<std::chrono::seconds>(now - tstart)
|
||||
.count(),
|
||||
w, (w - wlast) / elapsed, r, (r - rlast) / elapsed,
|
||||
::stats.cache_misses.load());
|
||||
wlast = w;
|
||||
rlast = r;
|
||||
tlast = now;
|
||||
}
|
||||
}
|
||||
|
||||
~StatsThread() {
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(cvm_);
|
||||
done_.store(true);
|
||||
}
|
||||
cv_.notify_all();
|
||||
thread_.join();
|
||||
}
|
||||
|
||||
private:
|
||||
rocksdb::DB* db_;
|
||||
std::mutex cvm_;
|
||||
std::condition_variable cv_;
|
||||
std::thread thread_;
|
||||
std::atomic<bool> done_{false};
|
||||
};
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
GFLAGS::ParseCommandLineFlags(&argc, &argv, true);
|
||||
|
||||
std::mt19937 rng{std::random_device()()};
|
||||
rocksdb::Status status;
|
||||
std::string path = rocksdb::test::TmpDir() + "/forward_iterator_test";
|
||||
fprintf(stderr, "db path is %s\n", path.c_str());
|
||||
rocksdb::Options options;
|
||||
options.create_if_missing = true;
|
||||
options.compression = rocksdb::CompressionType::kNoCompression;
|
||||
options.compaction_style = rocksdb::CompactionStyle::kCompactionStyleNone;
|
||||
options.level0_slowdown_writes_trigger = 99999;
|
||||
options.level0_stop_writes_trigger = 99999;
|
||||
options.allow_os_buffer = false;
|
||||
options.write_buffer_size = FLAGS_memtable_size;
|
||||
rocksdb::BlockBasedTableOptions table_options;
|
||||
table_options.block_cache = rocksdb::NewLRUCache(FLAGS_block_cache_size);
|
||||
table_options.block_size = FLAGS_block_size;
|
||||
options.table_factory.reset(
|
||||
rocksdb::NewBlockBasedTableFactory(table_options));
|
||||
|
||||
status = rocksdb::DestroyDB(path, options);
|
||||
assert(status.ok());
|
||||
rocksdb::DB* db_raw;
|
||||
status = rocksdb::DB::Open(options, path, &db_raw);
|
||||
assert(status.ok());
|
||||
std::unique_ptr<rocksdb::DB> db(db_raw);
|
||||
|
||||
std::vector<ShardState> shard_states(FLAGS_shards + 1);
|
||||
std::deque<Reader> readers;
|
||||
while (static_cast<int>(readers.size()) < FLAGS_readers) {
|
||||
readers.emplace_back(&shard_states, db_raw);
|
||||
}
|
||||
std::deque<Writer> writers;
|
||||
while (static_cast<int>(writers.size()) < FLAGS_writers) {
|
||||
writers.emplace_back(&shard_states, db_raw);
|
||||
}
|
||||
|
||||
// Each shard gets a random reader and random writer assigned to it
|
||||
for (int i = 1; i <= FLAGS_shards; ++i) {
|
||||
std::uniform_int_distribution<int> reader_dist(0, FLAGS_readers - 1);
|
||||
std::uniform_int_distribution<int> writer_dist(0, FLAGS_writers - 1);
|
||||
shard_states[i].reader = &readers[reader_dist(rng)];
|
||||
shard_states[i].writer = &writers[writer_dist(rng)];
|
||||
}
|
||||
|
||||
StatsThread stats_thread(db_raw);
|
||||
for (Writer& w : writers) {
|
||||
w.start();
|
||||
}
|
||||
|
||||
writers.clear();
|
||||
readers.clear();
|
||||
}
|
||||
#endif // !defined(GFLAGS) || defined(ROCKSDB_LITE)
|
||||
@@ -0,0 +1,657 @@
|
||||
// Copyright (c) 2013, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the root directory of this source tree. An additional
|
||||
// grant of patent rights can be found in the PATENTS file in the same
|
||||
// directory.
|
||||
//
|
||||
// Copyright (c) 2011 The LevelDB Authors. All rights reserved. Use of
|
||||
// this source code is governed by a BSD-style license that can be found
|
||||
// in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
//
|
||||
// InlineSkipList is derived from SkipList (skiplist.h), but it optimizes
|
||||
// the memory layout by requiring that the key storage be allocated through
|
||||
// the skip list instance. For the common case of SkipList<const char*,
|
||||
// Cmp> this saves 1 pointer per skip list node and gives better cache
|
||||
// locality, at the expense of wasted padding from using AllocateAligned
|
||||
// instead of Allocate for the keys. The unused padding will be from
|
||||
// 0 to sizeof(void*)-1 bytes, and the space savings are sizeof(void*)
|
||||
// bytes, so despite the padding the space used is always less than
|
||||
// SkipList<const char*, ..>.
|
||||
//
|
||||
// Thread safety -------------
|
||||
//
|
||||
// Writes via Insert require external synchronization, most likely a mutex.
|
||||
// InsertConcurrently can be safely called concurrently with reads and
|
||||
// with other concurrent inserts. Reads require a guarantee that the
|
||||
// InlineSkipList will not be destroyed while the read is in progress.
|
||||
// Apart from that, reads progress without any internal locking or
|
||||
// synchronization.
|
||||
//
|
||||
// Invariants:
|
||||
//
|
||||
// (1) Allocated nodes are never deleted until the InlineSkipList is
|
||||
// destroyed. This is trivially guaranteed by the code since we never
|
||||
// delete any skip list nodes.
|
||||
//
|
||||
// (2) The contents of a Node except for the next/prev pointers are
|
||||
// immutable after the Node has been linked into the InlineSkipList.
|
||||
// Only Insert() modifies the list, and it is careful to initialize a
|
||||
// node and use release-stores to publish the nodes in one or more lists.
|
||||
//
|
||||
// ... prev vs. next pointer ordering ...
|
||||
//
|
||||
|
||||
#pragma once
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
#include <atomic>
|
||||
#include "port/port.h"
|
||||
#include "util/allocator.h"
|
||||
#include "util/random.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
template <class Comparator>
|
||||
class InlineSkipList {
|
||||
private:
|
||||
struct Node;
|
||||
|
||||
public:
|
||||
// Create a new InlineSkipList object that will use "cmp" for comparing
|
||||
// keys, and will allocate memory using "*allocator". Objects allocated
|
||||
// in the allocator must remain allocated for the lifetime of the
|
||||
// skiplist object.
|
||||
explicit InlineSkipList(Comparator cmp, Allocator* allocator,
|
||||
int32_t max_height = 12,
|
||||
int32_t branching_factor = 4);
|
||||
|
||||
// Allocates a key and a skip-list node, returning a pointer to the key
|
||||
// portion of the node. This method is thread-safe if the allocator
|
||||
// is thread-safe.
|
||||
char* AllocateKey(size_t key_size);
|
||||
|
||||
// Inserts a key allocated by AllocateKey, after the actual key value
|
||||
// has been filled in.
|
||||
//
|
||||
// REQUIRES: nothing that compares equal to key is currently in the list.
|
||||
// REQUIRES: no concurrent calls to INSERT
|
||||
void Insert(const char* key);
|
||||
|
||||
// Like Insert, but external synchronization is not required.
|
||||
void InsertConcurrently(const char* key);
|
||||
|
||||
// Returns true iff an entry that compares equal to key is in the list.
|
||||
bool Contains(const char* key) const;
|
||||
|
||||
// Return estimated number of entries smaller than `key`.
|
||||
uint64_t EstimateCount(const char* key) const;
|
||||
|
||||
// Iteration over the contents of a skip list
|
||||
class Iterator {
|
||||
public:
|
||||
// Initialize an iterator over the specified list.
|
||||
// The returned iterator is not valid.
|
||||
explicit Iterator(const InlineSkipList* list);
|
||||
|
||||
// Change the underlying skiplist used for this iterator
|
||||
// This enables us not changing the iterator without deallocating
|
||||
// an old one and then allocating a new one
|
||||
void SetList(const InlineSkipList* list);
|
||||
|
||||
// Returns true iff the iterator is positioned at a valid node.
|
||||
bool Valid() const;
|
||||
|
||||
// Returns the key at the current position.
|
||||
// REQUIRES: Valid()
|
||||
const char* key() const;
|
||||
|
||||
// Advances to the next position.
|
||||
// REQUIRES: Valid()
|
||||
void Next();
|
||||
|
||||
// Advances to the previous position.
|
||||
// REQUIRES: Valid()
|
||||
void Prev();
|
||||
|
||||
// Advance to the first entry with a key >= target
|
||||
void Seek(const char* target);
|
||||
|
||||
// Position at the first entry in list.
|
||||
// Final state of iterator is Valid() iff list is not empty.
|
||||
void SeekToFirst();
|
||||
|
||||
// Position at the last entry in list.
|
||||
// Final state of iterator is Valid() iff list is not empty.
|
||||
void SeekToLast();
|
||||
|
||||
private:
|
||||
const InlineSkipList* list_;
|
||||
Node* node_;
|
||||
// Intentionally copyable
|
||||
};
|
||||
|
||||
private:
|
||||
enum MaxPossibleHeightEnum : uint16_t { kMaxPossibleHeight = 32 };
|
||||
|
||||
const uint16_t kMaxHeight_;
|
||||
const uint16_t kBranching_;
|
||||
const uint32_t kScaledInverseBranching_;
|
||||
|
||||
// Immutable after construction
|
||||
Comparator const compare_;
|
||||
Allocator* const allocator_; // Allocator used for allocations of nodes
|
||||
|
||||
Node* const head_;
|
||||
|
||||
// Modified only by Insert(). Read racily by readers, but stale
|
||||
// values are ok.
|
||||
std::atomic<int> max_height_; // Height of the entire list
|
||||
|
||||
// Used for optimizing sequential insert patterns. Tricky. prev_height_
|
||||
// of zero means prev_ is undefined. Otherwise: prev_[i] for i up
|
||||
// to max_height_ - 1 (inclusive) is the predecessor of prev_[0], and
|
||||
// prev_height_ is the height of prev_[0]. prev_[0] can only be equal
|
||||
// to head when max_height_ and prev_height_ are both 1.
|
||||
Node** prev_;
|
||||
std::atomic<int32_t> prev_height_;
|
||||
|
||||
inline int GetMaxHeight() const {
|
||||
return max_height_.load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
int RandomHeight();
|
||||
|
||||
Node* AllocateNode(size_t key_size, int height);
|
||||
|
||||
bool Equal(const char* a, const char* b) const {
|
||||
return (compare_(a, b) == 0);
|
||||
}
|
||||
|
||||
// Return true if key is greater than the data stored in "n". Null n
|
||||
// is considered infinite.
|
||||
bool KeyIsAfterNode(const char* key, Node* n) const;
|
||||
|
||||
// Returns the earliest node with a key >= key.
|
||||
// Return nullptr if there is no such node.
|
||||
Node* FindGreaterOrEqual(const char* key) const;
|
||||
|
||||
// Return the latest node with a key < key.
|
||||
// Return head_ if there is no such node.
|
||||
// Fills prev[level] with pointer to previous node at "level" for every
|
||||
// level in [0..max_height_-1], if prev is non-null.
|
||||
Node* FindLessThan(const char* key, Node** prev = nullptr) const;
|
||||
|
||||
// Return the last node in the list.
|
||||
// Return head_ if list is empty.
|
||||
Node* FindLast() const;
|
||||
|
||||
// Traverses a single level of the list, setting *out_prev to the last
|
||||
// node before the key and *out_next to the first node after. Assumes
|
||||
// that the key is not present in the skip list. On entry, before should
|
||||
// point to a node that is before the key, and after should point to
|
||||
// a node that is after the key. after should be nullptr if a good after
|
||||
// node isn't conveniently available.
|
||||
void FindLevelSplice(const char* key, Node* before, Node* after, int level,
|
||||
Node** out_prev, Node** out_next);
|
||||
|
||||
// No copying allowed
|
||||
InlineSkipList(const InlineSkipList&);
|
||||
InlineSkipList& operator=(const InlineSkipList&);
|
||||
};
|
||||
|
||||
// Implementation details follow
|
||||
|
||||
// The Node data type is more of a pointer into custom-managed memory than
|
||||
// a traditional C++ struct. The key is stored in the bytes immediately
|
||||
// after the struct, and the next_ pointers for nodes with height > 1 are
|
||||
// stored immediately _before_ the struct. This avoids the need to include
|
||||
// any pointer or sizing data, which reduces per-node memory overheads.
|
||||
template <class Comparator>
|
||||
struct InlineSkipList<Comparator>::Node {
|
||||
// Stores the height of the node in the memory location normally used for
|
||||
// next_[0]. This is used for passing data from AllocateKey to Insert.
|
||||
void StashHeight(const int height) {
|
||||
assert(sizeof(int) <= sizeof(next_[0]));
|
||||
memcpy(&next_[0], &height, sizeof(int));
|
||||
}
|
||||
|
||||
// Retrieves the value passed to StashHeight. Undefined after a call
|
||||
// to SetNext or NoBarrier_SetNext.
|
||||
int UnstashHeight() const {
|
||||
int rv;
|
||||
memcpy(&rv, &next_[0], sizeof(int));
|
||||
return rv;
|
||||
}
|
||||
|
||||
const char* Key() const { return reinterpret_cast<const char*>(&next_[1]); }
|
||||
|
||||
// Accessors/mutators for links. Wrapped in methods so we can add
|
||||
// the appropriate barriers as necessary, and perform the necessary
|
||||
// addressing trickery for storing links below the Node in memory.
|
||||
Node* Next(int n) {
|
||||
assert(n >= 0);
|
||||
// Use an 'acquire load' so that we observe a fully initialized
|
||||
// version of the returned Node.
|
||||
return (next_[-n].load(std::memory_order_acquire));
|
||||
}
|
||||
|
||||
void SetNext(int n, Node* x) {
|
||||
assert(n >= 0);
|
||||
// Use a 'release store' so that anybody who reads through this
|
||||
// pointer observes a fully initialized version of the inserted node.
|
||||
next_[-n].store(x, std::memory_order_release);
|
||||
}
|
||||
|
||||
bool CASNext(int n, Node* expected, Node* x) {
|
||||
assert(n >= 0);
|
||||
return next_[-n].compare_exchange_strong(expected, x);
|
||||
}
|
||||
|
||||
// No-barrier variants that can be safely used in a few locations.
|
||||
Node* NoBarrier_Next(int n) {
|
||||
assert(n >= 0);
|
||||
return next_[-n].load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
void NoBarrier_SetNext(int n, Node* x) {
|
||||
assert(n >= 0);
|
||||
next_[-n].store(x, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
private:
|
||||
// next_[0] is the lowest level link (level 0). Higher levels are
|
||||
// stored _earlier_, so level 1 is at next_[-1].
|
||||
std::atomic<Node*> next_[1];
|
||||
};
|
||||
|
||||
template <class Comparator>
|
||||
inline InlineSkipList<Comparator>::Iterator::Iterator(
|
||||
const InlineSkipList* list) {
|
||||
SetList(list);
|
||||
}
|
||||
|
||||
template <class Comparator>
|
||||
inline void InlineSkipList<Comparator>::Iterator::SetList(
|
||||
const InlineSkipList* list) {
|
||||
list_ = list;
|
||||
node_ = nullptr;
|
||||
}
|
||||
|
||||
template <class Comparator>
|
||||
inline bool InlineSkipList<Comparator>::Iterator::Valid() const {
|
||||
return node_ != nullptr;
|
||||
}
|
||||
|
||||
template <class Comparator>
|
||||
inline const char* InlineSkipList<Comparator>::Iterator::key() const {
|
||||
assert(Valid());
|
||||
return node_->Key();
|
||||
}
|
||||
|
||||
template <class Comparator>
|
||||
inline void InlineSkipList<Comparator>::Iterator::Next() {
|
||||
assert(Valid());
|
||||
node_ = node_->Next(0);
|
||||
}
|
||||
|
||||
template <class Comparator>
|
||||
inline void InlineSkipList<Comparator>::Iterator::Prev() {
|
||||
// Instead of using explicit "prev" links, we just search for the
|
||||
// last node that falls before key.
|
||||
assert(Valid());
|
||||
node_ = list_->FindLessThan(node_->Key());
|
||||
if (node_ == list_->head_) {
|
||||
node_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
template <class Comparator>
|
||||
inline void InlineSkipList<Comparator>::Iterator::Seek(const char* target) {
|
||||
node_ = list_->FindGreaterOrEqual(target);
|
||||
}
|
||||
|
||||
template <class Comparator>
|
||||
inline void InlineSkipList<Comparator>::Iterator::SeekToFirst() {
|
||||
node_ = list_->head_->Next(0);
|
||||
}
|
||||
|
||||
template <class Comparator>
|
||||
inline void InlineSkipList<Comparator>::Iterator::SeekToLast() {
|
||||
node_ = list_->FindLast();
|
||||
if (node_ == list_->head_) {
|
||||
node_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
template <class Comparator>
|
||||
int InlineSkipList<Comparator>::RandomHeight() {
|
||||
auto rnd = Random::GetTLSInstance();
|
||||
|
||||
// Increase height with probability 1 in kBranching
|
||||
int height = 1;
|
||||
while (height < kMaxHeight_ && height < kMaxPossibleHeight &&
|
||||
rnd->Next() < kScaledInverseBranching_) {
|
||||
height++;
|
||||
}
|
||||
assert(height > 0);
|
||||
assert(height <= kMaxHeight_);
|
||||
assert(height <= kMaxPossibleHeight);
|
||||
return height;
|
||||
}
|
||||
|
||||
template <class Comparator>
|
||||
bool InlineSkipList<Comparator>::KeyIsAfterNode(const char* key,
|
||||
Node* n) const {
|
||||
// nullptr n is considered infinite
|
||||
return (n != nullptr) && (compare_(n->Key(), key) < 0);
|
||||
}
|
||||
|
||||
template <class Comparator>
|
||||
typename InlineSkipList<Comparator>::Node*
|
||||
InlineSkipList<Comparator>::FindGreaterOrEqual(const char* key) const {
|
||||
// Note: It looks like we could reduce duplication by implementing
|
||||
// this function as FindLessThan(key)->Next(0), but we wouldn't be able
|
||||
// to exit early on equality and the result wouldn't even be correct.
|
||||
// A concurrent insert might occur after FindLessThan(key) but before
|
||||
// we get a chance to call Next(0).
|
||||
Node* x = head_;
|
||||
int level = GetMaxHeight() - 1;
|
||||
Node* last_bigger = nullptr;
|
||||
while (true) {
|
||||
Node* next = x->Next(level);
|
||||
// Make sure the lists are sorted
|
||||
assert(x == head_ || next == nullptr || KeyIsAfterNode(next->Key(), x));
|
||||
// Make sure we haven't overshot during our search
|
||||
assert(x == head_ || KeyIsAfterNode(key, x));
|
||||
int cmp = (next == nullptr || next == last_bigger)
|
||||
? 1
|
||||
: compare_(next->Key(), key);
|
||||
if (cmp == 0 || (cmp > 0 && level == 0)) {
|
||||
return next;
|
||||
} else if (cmp < 0) {
|
||||
// Keep searching in this list
|
||||
x = next;
|
||||
} else {
|
||||
// Switch to next list, reuse compare_() result
|
||||
last_bigger = next;
|
||||
level--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <class Comparator>
|
||||
typename InlineSkipList<Comparator>::Node*
|
||||
InlineSkipList<Comparator>::FindLessThan(const char* key, Node** prev) const {
|
||||
Node* x = head_;
|
||||
int level = GetMaxHeight() - 1;
|
||||
// KeyIsAfter(key, last_not_after) is definitely false
|
||||
Node* last_not_after = nullptr;
|
||||
while (true) {
|
||||
Node* next = x->Next(level);
|
||||
assert(x == head_ || next == nullptr || KeyIsAfterNode(next->Key(), x));
|
||||
assert(x == head_ || KeyIsAfterNode(key, x));
|
||||
if (next != last_not_after && KeyIsAfterNode(key, next)) {
|
||||
// Keep searching in this list
|
||||
x = next;
|
||||
} else {
|
||||
if (prev != nullptr) {
|
||||
prev[level] = x;
|
||||
}
|
||||
if (level == 0) {
|
||||
return x;
|
||||
} else {
|
||||
// Switch to next list, reuse KeyIUsAfterNode() result
|
||||
last_not_after = next;
|
||||
level--;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <class Comparator>
|
||||
typename InlineSkipList<Comparator>::Node*
|
||||
InlineSkipList<Comparator>::FindLast() const {
|
||||
Node* x = head_;
|
||||
int level = GetMaxHeight() - 1;
|
||||
while (true) {
|
||||
Node* next = x->Next(level);
|
||||
if (next == nullptr) {
|
||||
if (level == 0) {
|
||||
return x;
|
||||
} else {
|
||||
// Switch to next list
|
||||
level--;
|
||||
}
|
||||
} else {
|
||||
x = next;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <class Comparator>
|
||||
uint64_t InlineSkipList<Comparator>::EstimateCount(const char* key) const {
|
||||
uint64_t count = 0;
|
||||
|
||||
Node* x = head_;
|
||||
int level = GetMaxHeight() - 1;
|
||||
while (true) {
|
||||
assert(x == head_ || compare_(x->Key(), key) < 0);
|
||||
Node* next = x->Next(level);
|
||||
if (next == nullptr || compare_(next->Key(), key) >= 0) {
|
||||
if (level == 0) {
|
||||
return count;
|
||||
} else {
|
||||
// Switch to next list
|
||||
count *= kBranching_;
|
||||
level--;
|
||||
}
|
||||
} else {
|
||||
x = next;
|
||||
count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <class Comparator>
|
||||
InlineSkipList<Comparator>::InlineSkipList(const Comparator cmp,
|
||||
Allocator* allocator,
|
||||
int32_t max_height,
|
||||
int32_t branching_factor)
|
||||
: kMaxHeight_(max_height),
|
||||
kBranching_(branching_factor),
|
||||
kScaledInverseBranching_((Random::kMaxNext + 1) / kBranching_),
|
||||
compare_(cmp),
|
||||
allocator_(allocator),
|
||||
head_(AllocateNode(0, max_height)),
|
||||
max_height_(1),
|
||||
prev_height_(1) {
|
||||
assert(max_height > 0 && kMaxHeight_ == static_cast<uint32_t>(max_height));
|
||||
assert(branching_factor > 1 &&
|
||||
kBranching_ == static_cast<uint32_t>(branching_factor));
|
||||
assert(kScaledInverseBranching_ > 0);
|
||||
// Allocate the prev_ Node* array, directly from the passed-in allocator.
|
||||
// prev_ does not need to be freed, as its life cycle is tied up with
|
||||
// the allocator as a whole.
|
||||
prev_ = reinterpret_cast<Node**>(
|
||||
allocator_->AllocateAligned(sizeof(Node*) * kMaxHeight_));
|
||||
for (int i = 0; i < kMaxHeight_; i++) {
|
||||
head_->SetNext(i, nullptr);
|
||||
prev_[i] = head_;
|
||||
}
|
||||
}
|
||||
|
||||
template <class Comparator>
|
||||
char* InlineSkipList<Comparator>::AllocateKey(size_t key_size) {
|
||||
return const_cast<char*>(AllocateNode(key_size, RandomHeight())->Key());
|
||||
}
|
||||
|
||||
template <class Comparator>
|
||||
typename InlineSkipList<Comparator>::Node*
|
||||
InlineSkipList<Comparator>::AllocateNode(size_t key_size, int height) {
|
||||
auto prefix = sizeof(std::atomic<Node*>) * (height - 1);
|
||||
|
||||
// prefix is space for the height - 1 pointers that we store before
|
||||
// the Node instance (next_[-(height - 1) .. -1]). Node starts at
|
||||
// raw + prefix, and holds the bottom-mode (level 0) skip list pointer
|
||||
// next_[0]. key_size is the bytes for the key, which comes just after
|
||||
// the Node.
|
||||
char* raw = allocator_->AllocateAligned(prefix + sizeof(Node) + key_size);
|
||||
Node* x = reinterpret_cast<Node*>(raw + prefix);
|
||||
|
||||
// Once we've linked the node into the skip list we don't actually need
|
||||
// to know its height, because we can implicitly use the fact that we
|
||||
// traversed into a node at level h to known that h is a valid level
|
||||
// for that node. We need to convey the height to the Insert step,
|
||||
// however, so that it can perform the proper links. Since we're not
|
||||
// using the pointers at the moment, StashHeight temporarily borrow
|
||||
// storage from next_[0] for that purpose.
|
||||
x->StashHeight(height);
|
||||
return x;
|
||||
}
|
||||
|
||||
template <class Comparator>
|
||||
void InlineSkipList<Comparator>::Insert(const char* key) {
|
||||
// InsertConcurrently often can't maintain the prev_ invariants, so
|
||||
// it just sets prev_height_ to zero, letting us know that we should
|
||||
// ignore it. A relaxed load suffices here because write thread
|
||||
// synchronization separates Insert calls from InsertConcurrently calls.
|
||||
auto prev_height = prev_height_.load(std::memory_order_relaxed);
|
||||
|
||||
// fast path for sequential insertion
|
||||
if (prev_height > 0 && !KeyIsAfterNode(key, prev_[0]->NoBarrier_Next(0)) &&
|
||||
(prev_[0] == head_ || KeyIsAfterNode(key, prev_[0]))) {
|
||||
assert(prev_[0] != head_ || (prev_height == 1 && GetMaxHeight() == 1));
|
||||
|
||||
// Outside of this method prev_[1..max_height_] is the predecessor
|
||||
// of prev_[0], and prev_height_ refers to prev_[0]. Inside Insert
|
||||
// prev_[0..max_height - 1] is the predecessor of key. Switch from
|
||||
// the external state to the internal
|
||||
for (int i = 1; i < prev_height; i++) {
|
||||
prev_[i] = prev_[0];
|
||||
}
|
||||
} else {
|
||||
// TODO(opt): we could use a NoBarrier predecessor search as an
|
||||
// optimization for architectures where memory_order_acquire needs
|
||||
// a synchronization instruction. Doesn't matter on x86
|
||||
FindLessThan(key, prev_);
|
||||
}
|
||||
|
||||
// Our data structure does not allow duplicate insertion
|
||||
assert(prev_[0]->Next(0) == nullptr || !Equal(key, prev_[0]->Next(0)->Key()));
|
||||
|
||||
// Find the Node that we placed before the key in AllocateKey
|
||||
Node* x = reinterpret_cast<Node*>(const_cast<char*>(key)) - 1;
|
||||
int height = x->UnstashHeight();
|
||||
assert(height >= 1 && height <= kMaxHeight_);
|
||||
|
||||
if (height > GetMaxHeight()) {
|
||||
for (int i = GetMaxHeight(); i < height; i++) {
|
||||
prev_[i] = head_;
|
||||
}
|
||||
|
||||
// It is ok to mutate max_height_ without any synchronization
|
||||
// with concurrent readers. A concurrent reader that observes
|
||||
// the new value of max_height_ will see either the old value of
|
||||
// new level pointers from head_ (nullptr), or a new value set in
|
||||
// the loop below. In the former case the reader will
|
||||
// immediately drop to the next level since nullptr sorts after all
|
||||
// keys. In the latter case the reader will use the new node.
|
||||
max_height_.store(height, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
for (int i = 0; i < height; i++) {
|
||||
// NoBarrier_SetNext() suffices since we will add a barrier when
|
||||
// we publish a pointer to "x" in prev[i].
|
||||
x->NoBarrier_SetNext(i, prev_[i]->NoBarrier_Next(i));
|
||||
prev_[i]->SetNext(i, x);
|
||||
}
|
||||
prev_[0] = x;
|
||||
prev_height_.store(height, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
template <class Comparator>
|
||||
void InlineSkipList<Comparator>::FindLevelSplice(const char* key, Node* before,
|
||||
Node* after, int level,
|
||||
Node** out_prev,
|
||||
Node** out_next) {
|
||||
while (true) {
|
||||
Node* next = before->Next(level);
|
||||
assert(before == head_ || next == nullptr ||
|
||||
KeyIsAfterNode(next->Key(), before));
|
||||
assert(before == head_ || KeyIsAfterNode(key, before));
|
||||
if (next == after || !KeyIsAfterNode(key, next)) {
|
||||
// found it
|
||||
*out_prev = before;
|
||||
*out_next = next;
|
||||
return;
|
||||
}
|
||||
before = next;
|
||||
}
|
||||
}
|
||||
|
||||
template <class Comparator>
|
||||
void InlineSkipList<Comparator>::InsertConcurrently(const char* key) {
|
||||
Node* x = reinterpret_cast<Node*>(const_cast<char*>(key)) - 1;
|
||||
int height = x->UnstashHeight();
|
||||
assert(height >= 1 && height <= kMaxHeight_);
|
||||
|
||||
// We don't have a lock-free algorithm for updating prev_, but we do have
|
||||
// the option of invalidating the entire sequential-insertion cache.
|
||||
// prev_'s invariant is that prev_[i] (i > 0) is the predecessor of
|
||||
// prev_[0] at that level. We're only going to violate that if height
|
||||
// > 1 and key lands after prev_[height - 1] but before prev_[0].
|
||||
// Comparisons are pretty expensive, so an easier version is to just
|
||||
// clear the cache if height > 1. We only write to prev_height_ if the
|
||||
// nobody else has, to avoid invalidating the root of the skip list in
|
||||
// all of the other CPU caches.
|
||||
if (height > 1 && prev_height_.load(std::memory_order_relaxed) != 0) {
|
||||
prev_height_.store(0, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
int max_height = max_height_.load(std::memory_order_relaxed);
|
||||
while (height > max_height) {
|
||||
if (max_height_.compare_exchange_strong(max_height, height)) {
|
||||
// successfully updated it
|
||||
max_height = height;
|
||||
break;
|
||||
}
|
||||
// else retry, possibly exiting the loop because somebody else
|
||||
// increased it
|
||||
}
|
||||
assert(max_height <= kMaxPossibleHeight);
|
||||
|
||||
Node* prev[kMaxPossibleHeight + 1];
|
||||
Node* next[kMaxPossibleHeight + 1];
|
||||
prev[max_height] = head_;
|
||||
next[max_height] = nullptr;
|
||||
for (int i = max_height - 1; i >= 0; --i) {
|
||||
FindLevelSplice(key, prev[i + 1], next[i + 1], i, &prev[i], &next[i]);
|
||||
}
|
||||
for (int i = 0; i < height; ++i) {
|
||||
while (true) {
|
||||
x->NoBarrier_SetNext(i, next[i]);
|
||||
if (prev[i]->CASNext(i, next[i], x)) {
|
||||
// success
|
||||
break;
|
||||
}
|
||||
// CAS failed, we need to recompute prev and next. It is unlikely
|
||||
// to be helpful to try to use a different level as we redo the
|
||||
// search, because it should be unlikely that lots of nodes have
|
||||
// been inserted between prev[i] and next[i]. No point in using
|
||||
// next[i] as the after hint, because we know it is stale.
|
||||
FindLevelSplice(key, prev[i], nullptr, i, &prev[i], &next[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <class Comparator>
|
||||
bool InlineSkipList<Comparator>::Contains(const char* key) const {
|
||||
Node* x = FindGreaterOrEqual(key);
|
||||
if (x != nullptr && Equal(key, x->Key())) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
@@ -0,0 +1,475 @@
|
||||
// Copyright (c) 2013, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the root directory of this source tree. An additional grant
|
||||
// of patent rights can be found in the PATENTS file in the same directory.
|
||||
//
|
||||
// Copyright (c) 2011 The LevelDB Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
|
||||
#include "db/inlineskiplist.h"
|
||||
#include <set>
|
||||
#include "rocksdb/env.h"
|
||||
#include "util/concurrent_arena.h"
|
||||
#include "util/hash.h"
|
||||
#include "util/random.h"
|
||||
#include "util/testharness.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
// Our test skip list stores 8-byte unsigned integers
|
||||
typedef uint64_t Key;
|
||||
|
||||
static const char* Encode(const uint64_t* key) {
|
||||
return reinterpret_cast<const char*>(key);
|
||||
}
|
||||
|
||||
static Key Decode(const char* key) {
|
||||
Key rv;
|
||||
memcpy(&rv, key, sizeof(Key));
|
||||
return rv;
|
||||
}
|
||||
|
||||
struct TestComparator {
|
||||
int operator()(const char* a, const char* b) const {
|
||||
if (Decode(a) < Decode(b)) {
|
||||
return -1;
|
||||
} else if (Decode(a) > Decode(b)) {
|
||||
return +1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class InlineSkipTest : public testing::Test {};
|
||||
|
||||
TEST_F(InlineSkipTest, Empty) {
|
||||
Arena arena;
|
||||
TestComparator cmp;
|
||||
InlineSkipList<TestComparator> list(cmp, &arena);
|
||||
Key key = 10;
|
||||
ASSERT_TRUE(!list.Contains(Encode(&key)));
|
||||
|
||||
InlineSkipList<TestComparator>::Iterator iter(&list);
|
||||
ASSERT_TRUE(!iter.Valid());
|
||||
iter.SeekToFirst();
|
||||
ASSERT_TRUE(!iter.Valid());
|
||||
key = 100;
|
||||
iter.Seek(Encode(&key));
|
||||
ASSERT_TRUE(!iter.Valid());
|
||||
iter.SeekToLast();
|
||||
ASSERT_TRUE(!iter.Valid());
|
||||
}
|
||||
|
||||
TEST_F(InlineSkipTest, InsertAndLookup) {
|
||||
const int N = 2000;
|
||||
const int R = 5000;
|
||||
Random rnd(1000);
|
||||
std::set<Key> keys;
|
||||
ConcurrentArena arena;
|
||||
TestComparator cmp;
|
||||
InlineSkipList<TestComparator> list(cmp, &arena);
|
||||
for (int i = 0; i < N; i++) {
|
||||
Key key = rnd.Next() % R;
|
||||
if (keys.insert(key).second) {
|
||||
char* buf = list.AllocateKey(sizeof(Key));
|
||||
memcpy(buf, &key, sizeof(Key));
|
||||
list.Insert(buf);
|
||||
}
|
||||
}
|
||||
|
||||
for (Key i = 0; i < R; i++) {
|
||||
if (list.Contains(Encode(&i))) {
|
||||
ASSERT_EQ(keys.count(i), 1U);
|
||||
} else {
|
||||
ASSERT_EQ(keys.count(i), 0U);
|
||||
}
|
||||
}
|
||||
|
||||
// Simple iterator tests
|
||||
{
|
||||
InlineSkipList<TestComparator>::Iterator iter(&list);
|
||||
ASSERT_TRUE(!iter.Valid());
|
||||
|
||||
uint64_t zero = 0;
|
||||
iter.Seek(Encode(&zero));
|
||||
ASSERT_TRUE(iter.Valid());
|
||||
ASSERT_EQ(*(keys.begin()), Decode(iter.key()));
|
||||
|
||||
iter.SeekToFirst();
|
||||
ASSERT_TRUE(iter.Valid());
|
||||
ASSERT_EQ(*(keys.begin()), Decode(iter.key()));
|
||||
|
||||
iter.SeekToLast();
|
||||
ASSERT_TRUE(iter.Valid());
|
||||
ASSERT_EQ(*(keys.rbegin()), Decode(iter.key()));
|
||||
}
|
||||
|
||||
// Forward iteration test
|
||||
for (Key i = 0; i < R; i++) {
|
||||
InlineSkipList<TestComparator>::Iterator iter(&list);
|
||||
iter.Seek(Encode(&i));
|
||||
|
||||
// Compare against model iterator
|
||||
std::set<Key>::iterator model_iter = keys.lower_bound(i);
|
||||
for (int j = 0; j < 3; j++) {
|
||||
if (model_iter == keys.end()) {
|
||||
ASSERT_TRUE(!iter.Valid());
|
||||
break;
|
||||
} else {
|
||||
ASSERT_TRUE(iter.Valid());
|
||||
ASSERT_EQ(*model_iter, Decode(iter.key()));
|
||||
++model_iter;
|
||||
iter.Next();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Backward iteration test
|
||||
{
|
||||
InlineSkipList<TestComparator>::Iterator iter(&list);
|
||||
iter.SeekToLast();
|
||||
|
||||
// Compare against model iterator
|
||||
for (std::set<Key>::reverse_iterator model_iter = keys.rbegin();
|
||||
model_iter != keys.rend(); ++model_iter) {
|
||||
ASSERT_TRUE(iter.Valid());
|
||||
ASSERT_EQ(*model_iter, Decode(iter.key()));
|
||||
iter.Prev();
|
||||
}
|
||||
ASSERT_TRUE(!iter.Valid());
|
||||
}
|
||||
}
|
||||
|
||||
// We want to make sure that with a single writer and multiple
|
||||
// concurrent readers (with no synchronization other than when a
|
||||
// reader's iterator is created), the reader always observes all the
|
||||
// data that was present in the skip list when the iterator was
|
||||
// constructor. Because insertions are happening concurrently, we may
|
||||
// also observe new values that were inserted since the iterator was
|
||||
// constructed, but we should never miss any values that were present
|
||||
// at iterator construction time.
|
||||
//
|
||||
// We generate multi-part keys:
|
||||
// <key,gen,hash>
|
||||
// where:
|
||||
// key is in range [0..K-1]
|
||||
// gen is a generation number for key
|
||||
// hash is hash(key,gen)
|
||||
//
|
||||
// The insertion code picks a random key, sets gen to be 1 + the last
|
||||
// generation number inserted for that key, and sets hash to Hash(key,gen).
|
||||
//
|
||||
// At the beginning of a read, we snapshot the last inserted
|
||||
// generation number for each key. We then iterate, including random
|
||||
// calls to Next() and Seek(). For every key we encounter, we
|
||||
// check that it is either expected given the initial snapshot or has
|
||||
// been concurrently added since the iterator started.
|
||||
class ConcurrentTest {
|
||||
public:
|
||||
static const uint32_t K = 8;
|
||||
|
||||
private:
|
||||
static uint64_t key(Key key) { return (key >> 40); }
|
||||
static uint64_t gen(Key key) { return (key >> 8) & 0xffffffffu; }
|
||||
static uint64_t hash(Key key) { return key & 0xff; }
|
||||
|
||||
static uint64_t HashNumbers(uint64_t k, uint64_t g) {
|
||||
uint64_t data[2] = {k, g};
|
||||
return Hash(reinterpret_cast<char*>(data), sizeof(data), 0);
|
||||
}
|
||||
|
||||
static Key MakeKey(uint64_t k, uint64_t g) {
|
||||
assert(sizeof(Key) == sizeof(uint64_t));
|
||||
assert(k <= K); // We sometimes pass K to seek to the end of the skiplist
|
||||
assert(g <= 0xffffffffu);
|
||||
return ((k << 40) | (g << 8) | (HashNumbers(k, g) & 0xff));
|
||||
}
|
||||
|
||||
static bool IsValidKey(Key k) {
|
||||
return hash(k) == (HashNumbers(key(k), gen(k)) & 0xff);
|
||||
}
|
||||
|
||||
static Key RandomTarget(Random* rnd) {
|
||||
switch (rnd->Next() % 10) {
|
||||
case 0:
|
||||
// Seek to beginning
|
||||
return MakeKey(0, 0);
|
||||
case 1:
|
||||
// Seek to end
|
||||
return MakeKey(K, 0);
|
||||
default:
|
||||
// Seek to middle
|
||||
return MakeKey(rnd->Next() % K, 0);
|
||||
}
|
||||
}
|
||||
|
||||
// Per-key generation
|
||||
struct State {
|
||||
std::atomic<int> generation[K];
|
||||
void Set(int k, int v) {
|
||||
generation[k].store(v, std::memory_order_release);
|
||||
}
|
||||
int Get(int k) { return generation[k].load(std::memory_order_acquire); }
|
||||
|
||||
State() {
|
||||
for (unsigned int k = 0; k < K; k++) {
|
||||
Set(k, 0);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Current state of the test
|
||||
State current_;
|
||||
|
||||
ConcurrentArena arena_;
|
||||
|
||||
// InlineSkipList is not protected by mu_. We just use a single writer
|
||||
// thread to modify it.
|
||||
InlineSkipList<TestComparator> list_;
|
||||
|
||||
public:
|
||||
ConcurrentTest() : list_(TestComparator(), &arena_) {}
|
||||
|
||||
// REQUIRES: No concurrent calls to WriteStep or ConcurrentWriteStep
|
||||
void WriteStep(Random* rnd) {
|
||||
const uint32_t k = rnd->Next() % K;
|
||||
const int g = current_.Get(k) + 1;
|
||||
const Key new_key = MakeKey(k, g);
|
||||
char* buf = list_.AllocateKey(sizeof(Key));
|
||||
memcpy(buf, &new_key, sizeof(Key));
|
||||
list_.Insert(buf);
|
||||
current_.Set(k, g);
|
||||
}
|
||||
|
||||
// REQUIRES: No concurrent calls for the same k
|
||||
void ConcurrentWriteStep(uint32_t k) {
|
||||
const int g = current_.Get(k) + 1;
|
||||
const Key new_key = MakeKey(k, g);
|
||||
char* buf = list_.AllocateKey(sizeof(Key));
|
||||
memcpy(buf, &new_key, sizeof(Key));
|
||||
list_.InsertConcurrently(buf);
|
||||
ASSERT_EQ(g, current_.Get(k) + 1);
|
||||
current_.Set(k, g);
|
||||
}
|
||||
|
||||
void ReadStep(Random* rnd) {
|
||||
// Remember the initial committed state of the skiplist.
|
||||
State initial_state;
|
||||
for (unsigned int k = 0; k < K; k++) {
|
||||
initial_state.Set(k, current_.Get(k));
|
||||
}
|
||||
|
||||
Key pos = RandomTarget(rnd);
|
||||
InlineSkipList<TestComparator>::Iterator iter(&list_);
|
||||
iter.Seek(Encode(&pos));
|
||||
while (true) {
|
||||
Key current;
|
||||
if (!iter.Valid()) {
|
||||
current = MakeKey(K, 0);
|
||||
} else {
|
||||
current = Decode(iter.key());
|
||||
ASSERT_TRUE(IsValidKey(current)) << current;
|
||||
}
|
||||
ASSERT_LE(pos, current) << "should not go backwards";
|
||||
|
||||
// Verify that everything in [pos,current) was not present in
|
||||
// initial_state.
|
||||
while (pos < current) {
|
||||
ASSERT_LT(key(pos), K) << pos;
|
||||
|
||||
// Note that generation 0 is never inserted, so it is ok if
|
||||
// <*,0,*> is missing.
|
||||
ASSERT_TRUE((gen(pos) == 0U) ||
|
||||
(gen(pos) > static_cast<uint64_t>(initial_state.Get(
|
||||
static_cast<int>(key(pos))))))
|
||||
<< "key: " << key(pos) << "; gen: " << gen(pos)
|
||||
<< "; initgen: " << initial_state.Get(static_cast<int>(key(pos)));
|
||||
|
||||
// Advance to next key in the valid key space
|
||||
if (key(pos) < key(current)) {
|
||||
pos = MakeKey(key(pos) + 1, 0);
|
||||
} else {
|
||||
pos = MakeKey(key(pos), gen(pos) + 1);
|
||||
}
|
||||
}
|
||||
|
||||
if (!iter.Valid()) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (rnd->Next() % 2) {
|
||||
iter.Next();
|
||||
pos = MakeKey(key(pos), gen(pos) + 1);
|
||||
} else {
|
||||
Key new_target = RandomTarget(rnd);
|
||||
if (new_target > pos) {
|
||||
pos = new_target;
|
||||
iter.Seek(Encode(&new_target));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
const uint32_t ConcurrentTest::K;
|
||||
|
||||
// Simple test that does single-threaded testing of the ConcurrentTest
|
||||
// scaffolding.
|
||||
TEST_F(InlineSkipTest, ConcurrentReadWithoutThreads) {
|
||||
ConcurrentTest test;
|
||||
Random rnd(test::RandomSeed());
|
||||
for (int i = 0; i < 10000; i++) {
|
||||
test.ReadStep(&rnd);
|
||||
test.WriteStep(&rnd);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(InlineSkipTest, ConcurrentInsertWithoutThreads) {
|
||||
ConcurrentTest test;
|
||||
Random rnd(test::RandomSeed());
|
||||
for (int i = 0; i < 10000; i++) {
|
||||
test.ReadStep(&rnd);
|
||||
uint32_t base = rnd.Next();
|
||||
for (int j = 0; j < 4; ++j) {
|
||||
test.ConcurrentWriteStep((base + j) % ConcurrentTest::K);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class TestState {
|
||||
public:
|
||||
ConcurrentTest t_;
|
||||
int seed_;
|
||||
std::atomic<bool> quit_flag_;
|
||||
std::atomic<uint32_t> next_writer_;
|
||||
|
||||
enum ReaderState { STARTING, RUNNING, DONE };
|
||||
|
||||
explicit TestState(int s)
|
||||
: seed_(s),
|
||||
quit_flag_(false),
|
||||
state_(STARTING),
|
||||
pending_writers_(0),
|
||||
state_cv_(&mu_) {}
|
||||
|
||||
void Wait(ReaderState s) {
|
||||
mu_.Lock();
|
||||
while (state_ != s) {
|
||||
state_cv_.Wait();
|
||||
}
|
||||
mu_.Unlock();
|
||||
}
|
||||
|
||||
void Change(ReaderState s) {
|
||||
mu_.Lock();
|
||||
state_ = s;
|
||||
state_cv_.Signal();
|
||||
mu_.Unlock();
|
||||
}
|
||||
|
||||
void AdjustPendingWriters(int delta) {
|
||||
mu_.Lock();
|
||||
pending_writers_ += delta;
|
||||
if (pending_writers_ == 0) {
|
||||
state_cv_.Signal();
|
||||
}
|
||||
mu_.Unlock();
|
||||
}
|
||||
|
||||
void WaitForPendingWriters() {
|
||||
mu_.Lock();
|
||||
while (pending_writers_ != 0) {
|
||||
state_cv_.Wait();
|
||||
}
|
||||
mu_.Unlock();
|
||||
}
|
||||
|
||||
private:
|
||||
port::Mutex mu_;
|
||||
ReaderState state_;
|
||||
int pending_writers_;
|
||||
port::CondVar state_cv_;
|
||||
};
|
||||
|
||||
static void ConcurrentReader(void* arg) {
|
||||
TestState* state = reinterpret_cast<TestState*>(arg);
|
||||
Random rnd(state->seed_);
|
||||
int64_t reads = 0;
|
||||
state->Change(TestState::RUNNING);
|
||||
while (!state->quit_flag_.load(std::memory_order_acquire)) {
|
||||
state->t_.ReadStep(&rnd);
|
||||
++reads;
|
||||
}
|
||||
state->Change(TestState::DONE);
|
||||
}
|
||||
|
||||
static void ConcurrentWriter(void* arg) {
|
||||
TestState* state = reinterpret_cast<TestState*>(arg);
|
||||
uint32_t k = state->next_writer_++ % ConcurrentTest::K;
|
||||
state->t_.ConcurrentWriteStep(k);
|
||||
state->AdjustPendingWriters(-1);
|
||||
}
|
||||
|
||||
static void RunConcurrentRead(int run) {
|
||||
const int seed = test::RandomSeed() + (run * 100);
|
||||
Random rnd(seed);
|
||||
const int N = 1000;
|
||||
const int kSize = 1000;
|
||||
for (int i = 0; i < N; i++) {
|
||||
if ((i % 100) == 0) {
|
||||
fprintf(stderr, "Run %d of %d\n", i, N);
|
||||
}
|
||||
TestState state(seed + 1);
|
||||
Env::Default()->Schedule(ConcurrentReader, &state);
|
||||
state.Wait(TestState::RUNNING);
|
||||
for (int k = 0; k < kSize; ++k) {
|
||||
state.t_.WriteStep(&rnd);
|
||||
}
|
||||
state.quit_flag_.store(true, std::memory_order_release);
|
||||
state.Wait(TestState::DONE);
|
||||
}
|
||||
}
|
||||
|
||||
static void RunConcurrentInsert(int run, int write_parallelism = 4) {
|
||||
Env::Default()->SetBackgroundThreads(1 + write_parallelism,
|
||||
Env::Priority::LOW);
|
||||
const int seed = test::RandomSeed() + (run * 100);
|
||||
Random rnd(seed);
|
||||
const int N = 1000;
|
||||
const int kSize = 1000;
|
||||
for (int i = 0; i < N; i++) {
|
||||
if ((i % 100) == 0) {
|
||||
fprintf(stderr, "Run %d of %d\n", i, N);
|
||||
}
|
||||
TestState state(seed + 1);
|
||||
Env::Default()->Schedule(ConcurrentReader, &state);
|
||||
state.Wait(TestState::RUNNING);
|
||||
for (int k = 0; k < kSize; k += write_parallelism) {
|
||||
state.next_writer_ = rnd.Next();
|
||||
state.AdjustPendingWriters(write_parallelism);
|
||||
for (int p = 0; p < write_parallelism; ++p) {
|
||||
Env::Default()->Schedule(ConcurrentWriter, &state);
|
||||
}
|
||||
state.WaitForPendingWriters();
|
||||
}
|
||||
state.quit_flag_.store(true, std::memory_order_release);
|
||||
state.Wait(TestState::DONE);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(InlineSkipTest, ConcurrentRead1) { RunConcurrentRead(1); }
|
||||
TEST_F(InlineSkipTest, ConcurrentRead2) { RunConcurrentRead(2); }
|
||||
TEST_F(InlineSkipTest, ConcurrentRead3) { RunConcurrentRead(3); }
|
||||
TEST_F(InlineSkipTest, ConcurrentRead4) { RunConcurrentRead(4); }
|
||||
TEST_F(InlineSkipTest, ConcurrentRead5) { RunConcurrentRead(5); }
|
||||
TEST_F(InlineSkipTest, ConcurrentInsert1) { RunConcurrentInsert(1); }
|
||||
TEST_F(InlineSkipTest, ConcurrentInsert2) { RunConcurrentInsert(2); }
|
||||
TEST_F(InlineSkipTest, ConcurrentInsert3) { RunConcurrentInsert(3); }
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
+34
-42
@@ -35,8 +35,7 @@ void PrintLevelStatsHeader(char* buf, size_t len, const std::string& cf_name) {
|
||||
"\n** Compaction Stats [%s] **\n"
|
||||
"Level Files Size(MB) Score Read(GB) Rn(GB) Rnp1(GB) "
|
||||
"Write(GB) Wnew(GB) Moved(GB) W-Amp Rd(MB/s) Wr(MB/s) "
|
||||
"Comp(sec) Comp(cnt) Avg(sec) "
|
||||
"Stall(cnt) KeyIn KeyDrop\n"
|
||||
"Comp(sec) Comp(cnt) Avg(sec) KeyIn KeyDrop\n"
|
||||
"--------------------------------------------------------------------"
|
||||
"-----------------------------------------------------------"
|
||||
"--------------------------------------\n",
|
||||
@@ -44,9 +43,9 @@ void PrintLevelStatsHeader(char* buf, size_t len, const std::string& cf_name) {
|
||||
}
|
||||
|
||||
void PrintLevelStats(char* buf, size_t len, const std::string& name,
|
||||
int num_files, int being_compacted, double total_file_size, double score,
|
||||
double w_amp, uint64_t stalls,
|
||||
const InternalStats::CompactionStats& stats) {
|
||||
int num_files, int being_compacted, double total_file_size,
|
||||
double score, double w_amp,
|
||||
const InternalStats::CompactionStats& stats) {
|
||||
uint64_t bytes_read =
|
||||
stats.bytes_read_non_output_levels + stats.bytes_read_output_level;
|
||||
int64_t bytes_new =
|
||||
@@ -70,8 +69,6 @@ void PrintLevelStats(char* buf, size_t len, const std::string& name,
|
||||
"%9.0f " /* Comp(sec) */
|
||||
"%9d " /* Comp(cnt) */
|
||||
"%8.3f " /* Avg(sec) */
|
||||
"%10" PRIu64
|
||||
" " /* Stall(cnt) */
|
||||
"%7s " /* KeyIn */
|
||||
"%6s\n", /* KeyDrop */
|
||||
name.c_str(),
|
||||
@@ -82,7 +79,7 @@ void PrintLevelStats(char* buf, size_t len, const std::string& name,
|
||||
bytes_read / kMB / elapsed, stats.bytes_written / kMB / elapsed,
|
||||
stats.micros / kMicrosInSec, stats.count,
|
||||
stats.count == 0 ? 0 : stats.micros / kMicrosInSec / stats.count,
|
||||
stalls, num_input_records.c_str(), num_dropped_records.c_str());
|
||||
num_input_records.c_str(), num_dropped_records.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -488,14 +485,14 @@ void InternalStats::DumpDBStats(std::string* value) {
|
||||
seconds_up, interval_seconds_up);
|
||||
value->append(buf);
|
||||
// Cumulative
|
||||
uint64_t user_bytes_written = db_stats_[InternalStats::BYTES_WRITTEN];
|
||||
uint64_t num_keys_written = db_stats_[InternalStats::NUMBER_KEYS_WRITTEN];
|
||||
uint64_t write_other = db_stats_[InternalStats::WRITE_DONE_BY_OTHER];
|
||||
uint64_t write_self = db_stats_[InternalStats::WRITE_DONE_BY_SELF];
|
||||
uint64_t wal_bytes = db_stats_[InternalStats::WAL_FILE_BYTES];
|
||||
uint64_t wal_synced = db_stats_[InternalStats::WAL_FILE_SYNCED];
|
||||
uint64_t write_with_wal = db_stats_[InternalStats::WRITE_WITH_WAL];
|
||||
uint64_t write_stall_micros = db_stats_[InternalStats::WRITE_STALL_MICROS];
|
||||
uint64_t user_bytes_written = GetDBStats(InternalStats::BYTES_WRITTEN);
|
||||
uint64_t num_keys_written = GetDBStats(InternalStats::NUMBER_KEYS_WRITTEN);
|
||||
uint64_t write_other = GetDBStats(InternalStats::WRITE_DONE_BY_OTHER);
|
||||
uint64_t write_self = GetDBStats(InternalStats::WRITE_DONE_BY_SELF);
|
||||
uint64_t wal_bytes = GetDBStats(InternalStats::WAL_FILE_BYTES);
|
||||
uint64_t wal_synced = GetDBStats(InternalStats::WAL_FILE_SYNCED);
|
||||
uint64_t write_with_wal = GetDBStats(InternalStats::WRITE_WITH_WAL);
|
||||
uint64_t write_stall_micros = GetDBStats(InternalStats::WRITE_STALL_MICROS);
|
||||
uint64_t compact_bytes_read = 0;
|
||||
uint64_t compact_bytes_write = 0;
|
||||
uint64_t compact_micros = 0;
|
||||
@@ -677,45 +674,38 @@ void InternalStats::DumpCFStats(std::string* value) {
|
||||
int total_files = 0;
|
||||
int total_files_being_compacted = 0;
|
||||
double total_file_size = 0;
|
||||
uint64_t total_slowdown_count_soft = 0;
|
||||
uint64_t total_slowdown_count_hard = 0;
|
||||
uint64_t total_stall_count = 0;
|
||||
for (int level = 0; level < number_levels_; level++) {
|
||||
int files = vstorage->NumLevelFiles(level);
|
||||
total_files += files;
|
||||
total_files_being_compacted += files_being_compacted[level];
|
||||
if (comp_stats_[level].micros > 0 || files > 0) {
|
||||
uint64_t stalls =
|
||||
level == 0 ? (cf_stats_count_[LEVEL0_SLOWDOWN_TOTAL] +
|
||||
cf_stats_count_[LEVEL0_NUM_FILES_TOTAL] +
|
||||
cf_stats_count_[HARD_PENDING_COMPACTION_BYTES_LIMIT] +
|
||||
cf_stats_count_[MEMTABLE_COMPACTION])
|
||||
: (stall_leveln_slowdown_count_soft_[level] +
|
||||
stall_leveln_slowdown_count_hard_[level]);
|
||||
|
||||
stats_sum.Add(comp_stats_[level]);
|
||||
total_file_size += vstorage->NumLevelBytes(level);
|
||||
total_stall_count += stalls;
|
||||
total_slowdown_count_soft += stall_leveln_slowdown_count_soft_[level];
|
||||
total_slowdown_count_hard += stall_leveln_slowdown_count_hard_[level];
|
||||
double w_amp =
|
||||
(comp_stats_[level].bytes_read_non_output_levels == 0) ? 0.0
|
||||
: static_cast<double>(comp_stats_[level].bytes_written) /
|
||||
comp_stats_[level].bytes_read_non_output_levels;
|
||||
PrintLevelStats(buf, sizeof(buf), "L" + ToString(level), files,
|
||||
files_being_compacted[level],
|
||||
vstorage->NumLevelBytes(level), compaction_score[level],
|
||||
w_amp, stalls, comp_stats_[level]);
|
||||
static_cast<double>(vstorage->NumLevelBytes(level)),
|
||||
compaction_score[level],
|
||||
w_amp, comp_stats_[level]);
|
||||
value->append(buf);
|
||||
}
|
||||
}
|
||||
uint64_t curr_ingest = cf_stats_value_[BYTES_FLUSHED];
|
||||
// Cumulative summary
|
||||
double w_amp = stats_sum.bytes_written / static_cast<double>(curr_ingest + 1);
|
||||
uint64_t total_stall_count =
|
||||
cf_stats_count_[LEVEL0_SLOWDOWN_TOTAL] +
|
||||
cf_stats_count_[LEVEL0_NUM_FILES_TOTAL] +
|
||||
cf_stats_count_[SOFT_PENDING_COMPACTION_BYTES_LIMIT] +
|
||||
cf_stats_count_[HARD_PENDING_COMPACTION_BYTES_LIMIT] +
|
||||
cf_stats_count_[MEMTABLE_COMPACTION] + cf_stats_count_[MEMTABLE_SLOWDOWN];
|
||||
// Stats summary across levels
|
||||
PrintLevelStats(buf, sizeof(buf), "Sum", total_files,
|
||||
total_files_being_compacted, total_file_size, 0, w_amp,
|
||||
total_stall_count, stats_sum);
|
||||
total_files_being_compacted, total_file_size, 0, w_amp,
|
||||
stats_sum);
|
||||
value->append(buf);
|
||||
// Interval summary
|
||||
uint64_t interval_ingest =
|
||||
@@ -723,9 +713,7 @@ void InternalStats::DumpCFStats(std::string* value) {
|
||||
CompactionStats interval_stats(stats_sum);
|
||||
interval_stats.Subtract(cf_stats_snapshot_.comp_stats);
|
||||
w_amp = interval_stats.bytes_written / static_cast<double>(interval_ingest);
|
||||
PrintLevelStats(buf, sizeof(buf), "Int", 0, 0, 0, 0,
|
||||
w_amp, total_stall_count - cf_stats_snapshot_.stall_count,
|
||||
interval_stats);
|
||||
PrintLevelStats(buf, sizeof(buf), "Int", 0, 0, 0, 0, w_amp, interval_stats);
|
||||
value->append(buf);
|
||||
|
||||
snprintf(buf, sizeof(buf),
|
||||
@@ -742,19 +730,23 @@ void InternalStats::DumpCFStats(std::string* value) {
|
||||
"%" PRIu64
|
||||
" level0_numfiles_with_compaction, "
|
||||
"%" PRIu64
|
||||
" pending_compaction_bytes, "
|
||||
" stop for pending_compaction_bytes, "
|
||||
"%" PRIu64
|
||||
" slowdown for pending_compaction_bytes, "
|
||||
"%" PRIu64
|
||||
" memtable_compaction, "
|
||||
"%" PRIu64
|
||||
" leveln_slowdown_soft, "
|
||||
"%" PRIu64 " leveln_slowdown_hard\n",
|
||||
" memtable_slowdown, "
|
||||
"interval %" PRIu64 " total count\n",
|
||||
cf_stats_count_[LEVEL0_SLOWDOWN_TOTAL],
|
||||
cf_stats_count_[LEVEL0_SLOWDOWN_WITH_COMPACTION],
|
||||
cf_stats_count_[LEVEL0_NUM_FILES_TOTAL],
|
||||
cf_stats_count_[LEVEL0_NUM_FILES_WITH_COMPACTION],
|
||||
cf_stats_count_[HARD_PENDING_COMPACTION_BYTES_LIMIT],
|
||||
cf_stats_count_[MEMTABLE_COMPACTION], total_slowdown_count_soft,
|
||||
total_slowdown_count_hard);
|
||||
cf_stats_count_[SOFT_PENDING_COMPACTION_BYTES_LIMIT],
|
||||
cf_stats_count_[MEMTABLE_COMPACTION],
|
||||
cf_stats_count_[MEMTABLE_SLOWDOWN],
|
||||
total_stall_count - cf_stats_snapshot_.stall_count);
|
||||
value->append(buf);
|
||||
|
||||
cf_stats_snapshot_.ingest_bytes = curr_ingest;
|
||||
|
||||
+18
-35
@@ -86,8 +86,10 @@ class InternalStats {
|
||||
LEVEL0_SLOWDOWN_TOTAL,
|
||||
LEVEL0_SLOWDOWN_WITH_COMPACTION,
|
||||
MEMTABLE_COMPACTION,
|
||||
MEMTABLE_SLOWDOWN,
|
||||
LEVEL0_NUM_FILES_TOTAL,
|
||||
LEVEL0_NUM_FILES_WITH_COMPACTION,
|
||||
SOFT_PENDING_COMPACTION_BYTES_LIMIT,
|
||||
HARD_PENDING_COMPACTION_BYTES_LIMIT,
|
||||
WRITE_STALLS_ENUM_MAX,
|
||||
BYTES_FLUSHED,
|
||||
@@ -107,30 +109,16 @@ class InternalStats {
|
||||
};
|
||||
|
||||
InternalStats(int num_levels, Env* env, ColumnFamilyData* cfd)
|
||||
: db_stats_(INTERNAL_DB_STATS_ENUM_MAX),
|
||||
cf_stats_value_(INTERNAL_CF_STATS_ENUM_MAX),
|
||||
cf_stats_count_(INTERNAL_CF_STATS_ENUM_MAX),
|
||||
: db_stats_{},
|
||||
cf_stats_value_{},
|
||||
cf_stats_count_{},
|
||||
comp_stats_(num_levels),
|
||||
stall_leveln_slowdown_count_hard_(num_levels),
|
||||
stall_leveln_slowdown_count_soft_(num_levels),
|
||||
file_read_latency_(num_levels),
|
||||
bg_error_count_(0),
|
||||
number_levels_(num_levels),
|
||||
env_(env),
|
||||
cfd_(cfd),
|
||||
started_at_(env->NowMicros()) {
|
||||
for (int i = 0; i< INTERNAL_DB_STATS_ENUM_MAX; ++i) {
|
||||
db_stats_[i] = 0;
|
||||
}
|
||||
for (int i = 0; i< INTERNAL_CF_STATS_ENUM_MAX; ++i) {
|
||||
cf_stats_value_[i] = 0;
|
||||
cf_stats_count_[i] = 0;
|
||||
}
|
||||
for (int i = 0; i < num_levels; ++i) {
|
||||
stall_leveln_slowdown_count_hard_[i] = 0;
|
||||
stall_leveln_slowdown_count_soft_[i] = 0;
|
||||
}
|
||||
}
|
||||
started_at_(env->NowMicros()) {}
|
||||
|
||||
// Per level compaction stats. comp_stats_[level] stores the stats for
|
||||
// compactions that produced data for the specified "level".
|
||||
@@ -237,21 +225,19 @@ class InternalStats {
|
||||
comp_stats_[level].bytes_moved += amount;
|
||||
}
|
||||
|
||||
void RecordLevelNSlowdown(int level, bool soft) {
|
||||
if (soft) {
|
||||
++stall_leveln_slowdown_count_soft_[level];
|
||||
} else {
|
||||
++stall_leveln_slowdown_count_hard_[level];
|
||||
}
|
||||
}
|
||||
|
||||
void AddCFStats(InternalCFStatsType type, uint64_t value) {
|
||||
cf_stats_value_[type] += value;
|
||||
++cf_stats_count_[type];
|
||||
}
|
||||
|
||||
void AddDBStats(InternalDBStatsType type, uint64_t value) {
|
||||
db_stats_[type] += value;
|
||||
auto& v = db_stats_[type];
|
||||
v.store(v.load(std::memory_order_relaxed) + value,
|
||||
std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
uint64_t GetDBStats(InternalDBStatsType type) {
|
||||
return db_stats_[type].load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
HistogramImpl* GetFileReadHist(int level) {
|
||||
@@ -276,15 +262,12 @@ class InternalStats {
|
||||
void DumpCFStats(std::string* value);
|
||||
|
||||
// Per-DB stats
|
||||
std::vector<uint64_t> db_stats_;
|
||||
std::atomic<uint64_t> db_stats_[INTERNAL_DB_STATS_ENUM_MAX];
|
||||
// Per-ColumnFamily stats
|
||||
std::vector<uint64_t> cf_stats_value_;
|
||||
std::vector<uint64_t> cf_stats_count_;
|
||||
uint64_t cf_stats_value_[INTERNAL_CF_STATS_ENUM_MAX];
|
||||
uint64_t cf_stats_count_[INTERNAL_CF_STATS_ENUM_MAX];
|
||||
// Per-ColumnFamily/level compaction stats
|
||||
std::vector<CompactionStats> comp_stats_;
|
||||
// These count the number of microseconds for which MakeRoomForWrite stalls.
|
||||
std::vector<uint64_t> stall_leveln_slowdown_count_hard_;
|
||||
std::vector<uint64_t> stall_leveln_slowdown_count_soft_;
|
||||
std::vector<HistogramImpl> file_read_latency_;
|
||||
|
||||
// Used to compute per-interval statistics
|
||||
@@ -359,8 +342,10 @@ class InternalStats {
|
||||
LEVEL0_SLOWDOWN_TOTAL,
|
||||
LEVEL0_SLOWDOWN_WITH_COMPACTION,
|
||||
MEMTABLE_COMPACTION,
|
||||
MEMTABLE_SLOWDOWN,
|
||||
LEVEL0_NUM_FILES_TOTAL,
|
||||
LEVEL0_NUM_FILES_WITH_COMPACTION,
|
||||
SOFT_PENDING_COMPACTION_BYTES_LIMIT,
|
||||
HARD_PENDING_COMPACTION_BYTES_LIMIT,
|
||||
WRITE_STALLS_ENUM_MAX,
|
||||
BYTES_FLUSHED,
|
||||
@@ -407,8 +392,6 @@ class InternalStats {
|
||||
|
||||
void IncBytesMoved(int level, uint64_t amount) {}
|
||||
|
||||
void RecordLevelNSlowdown(int level, bool soft) {}
|
||||
|
||||
void AddCFStats(InternalCFStatsType type, uint64_t value) {}
|
||||
|
||||
void AddDBStats(InternalDBStatsType type, uint64_t value) {}
|
||||
|
||||
+174
-121
@@ -4,6 +4,7 @@
|
||||
// of patent rights can be found in the PATENTS file in the same directory.
|
||||
|
||||
#include "db/db_impl.h"
|
||||
#include "db/db_test_util.h"
|
||||
#include "db/dbformat.h"
|
||||
#include "db/filename.h"
|
||||
#include "db/version_set.h"
|
||||
@@ -37,119 +38,11 @@
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
class EventListenerTest : public testing::Test {
|
||||
class EventListenerTest : public DBTestBase {
|
||||
public:
|
||||
EventListenerTest() {
|
||||
dbname_ = test::TmpDir() + "/listener_test";
|
||||
EXPECT_OK(DestroyDB(dbname_, Options()));
|
||||
db_ = nullptr;
|
||||
Reopen();
|
||||
}
|
||||
|
||||
~EventListenerTest() {
|
||||
Close();
|
||||
Options options;
|
||||
options.db_paths.emplace_back(dbname_, 0);
|
||||
options.db_paths.emplace_back(dbname_ + "_2", 0);
|
||||
options.db_paths.emplace_back(dbname_ + "_3", 0);
|
||||
options.db_paths.emplace_back(dbname_ + "_4", 0);
|
||||
EXPECT_OK(DestroyDB(dbname_, options));
|
||||
}
|
||||
|
||||
void CreateColumnFamilies(const std::vector<std::string>& cfs,
|
||||
const ColumnFamilyOptions* options = nullptr) {
|
||||
ColumnFamilyOptions cf_opts;
|
||||
cf_opts = ColumnFamilyOptions(Options());
|
||||
size_t cfi = handles_.size();
|
||||
handles_.resize(cfi + cfs.size());
|
||||
for (auto cf : cfs) {
|
||||
ASSERT_OK(db_->CreateColumnFamily(cf_opts, cf, &handles_[cfi++]));
|
||||
}
|
||||
}
|
||||
|
||||
void Close() {
|
||||
for (auto h : handles_) {
|
||||
delete h;
|
||||
}
|
||||
handles_.clear();
|
||||
delete db_;
|
||||
db_ = nullptr;
|
||||
}
|
||||
|
||||
void ReopenWithColumnFamilies(const std::vector<std::string>& cfs,
|
||||
const Options* options = nullptr) {
|
||||
ASSERT_OK(TryReopenWithColumnFamilies(cfs, options));
|
||||
}
|
||||
|
||||
Status TryReopenWithColumnFamilies(const std::vector<std::string>& cfs,
|
||||
const Options* options = nullptr) {
|
||||
Close();
|
||||
Options opts = (options == nullptr) ? Options() : *options;
|
||||
std::vector<const Options*> v_opts(cfs.size(), &opts);
|
||||
return TryReopenWithColumnFamilies(cfs, v_opts);
|
||||
}
|
||||
|
||||
Status TryReopenWithColumnFamilies(
|
||||
const std::vector<std::string>& cfs,
|
||||
const std::vector<const Options*>& options) {
|
||||
Close();
|
||||
EXPECT_EQ(cfs.size(), options.size());
|
||||
std::vector<ColumnFamilyDescriptor> column_families;
|
||||
for (size_t i = 0; i < cfs.size(); ++i) {
|
||||
column_families.push_back(ColumnFamilyDescriptor(cfs[i], *options[i]));
|
||||
}
|
||||
DBOptions db_opts = DBOptions(*options[0]);
|
||||
return DB::Open(db_opts, dbname_, column_families, &handles_, &db_);
|
||||
}
|
||||
|
||||
Status TryReopen(Options* options = nullptr) {
|
||||
Close();
|
||||
Options opts;
|
||||
if (options != nullptr) {
|
||||
opts = *options;
|
||||
} else {
|
||||
opts.create_if_missing = true;
|
||||
}
|
||||
|
||||
return DB::Open(opts, dbname_, &db_);
|
||||
}
|
||||
|
||||
void Reopen(Options* options = nullptr) {
|
||||
ASSERT_OK(TryReopen(options));
|
||||
}
|
||||
|
||||
void CreateAndReopenWithCF(const std::vector<std::string>& cfs,
|
||||
const Options* options = nullptr) {
|
||||
CreateColumnFamilies(cfs, options);
|
||||
std::vector<std::string> cfs_plus_default = cfs;
|
||||
cfs_plus_default.insert(cfs_plus_default.begin(), kDefaultColumnFamilyName);
|
||||
ReopenWithColumnFamilies(cfs_plus_default, options);
|
||||
}
|
||||
|
||||
DBImpl* dbfull() {
|
||||
return reinterpret_cast<DBImpl*>(db_);
|
||||
}
|
||||
|
||||
Status Put(int cf, const Slice& k, const Slice& v,
|
||||
WriteOptions wo = WriteOptions()) {
|
||||
return db_->Put(wo, handles_[cf], k, v);
|
||||
}
|
||||
|
||||
Status Flush(size_t cf = 0) {
|
||||
FlushOptions opt = FlushOptions();
|
||||
opt.wait = true;
|
||||
if (cf == 0) {
|
||||
return db_->Flush(opt);
|
||||
} else {
|
||||
return db_->Flush(opt, handles_[cf]);
|
||||
}
|
||||
}
|
||||
EventListenerTest() : DBTestBase("/listener_test") {}
|
||||
|
||||
const size_t k110KB = 110 << 10;
|
||||
|
||||
DB* db_;
|
||||
std::string dbname_;
|
||||
std::vector<ColumnFamilyHandle*> handles_;
|
||||
};
|
||||
|
||||
struct TestPropertiesCollector : public rocksdb::TablePropertiesCollector {
|
||||
@@ -238,7 +131,7 @@ TEST_F(EventListenerTest, OnSingleDBCompactionTest) {
|
||||
std::vector<std::string> cf_names = {
|
||||
"pikachu", "ilya", "muromec", "dobrynia",
|
||||
"nikitich", "alyosha", "popovich"};
|
||||
CreateAndReopenWithCF(cf_names, &options);
|
||||
CreateAndReopenWithCF(cf_names, options);
|
||||
ASSERT_OK(Put(1, "pikachu", std::string(90000, 'p')));
|
||||
ASSERT_OK(Put(2, "ilya", std::string(90000, 'i')));
|
||||
ASSERT_OK(Put(3, "muromec", std::string(90000, 'm')));
|
||||
@@ -246,12 +139,12 @@ TEST_F(EventListenerTest, OnSingleDBCompactionTest) {
|
||||
ASSERT_OK(Put(5, "nikitich", std::string(90000, 'n')));
|
||||
ASSERT_OK(Put(6, "alyosha", std::string(90000, 'a')));
|
||||
ASSERT_OK(Put(7, "popovich", std::string(90000, 'p')));
|
||||
for (size_t i = 1; i < 8; ++i) {
|
||||
for (int i = 1; i < 8; ++i) {
|
||||
ASSERT_OK(Flush(i));
|
||||
const Slice kStart = "a";
|
||||
const Slice kEnd = "z";
|
||||
const Slice kRangeStart = "a";
|
||||
const Slice kRangeEnd = "z";
|
||||
ASSERT_OK(dbfull()->CompactRange(CompactRangeOptions(), handles_[i],
|
||||
&kStart, &kEnd));
|
||||
&kRangeStart, &kRangeEnd));
|
||||
dbfull()->TEST_WaitForFlushMemTable();
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
}
|
||||
@@ -349,7 +242,7 @@ TEST_F(EventListenerTest, OnSingleDBFlushTest) {
|
||||
"nikitich", "alyosha", "popovich"};
|
||||
options.table_properties_collector_factories.push_back(
|
||||
std::make_shared<TestPropertiesCollectorFactory>());
|
||||
CreateAndReopenWithCF(cf_names, &options);
|
||||
CreateAndReopenWithCF(cf_names, options);
|
||||
|
||||
ASSERT_OK(Put(1, "pikachu", std::string(90000, 'p')));
|
||||
ASSERT_OK(Put(2, "ilya", std::string(90000, 'i')));
|
||||
@@ -358,7 +251,7 @@ TEST_F(EventListenerTest, OnSingleDBFlushTest) {
|
||||
ASSERT_OK(Put(5, "nikitich", std::string(90000, 'n')));
|
||||
ASSERT_OK(Put(6, "alyosha", std::string(90000, 'a')));
|
||||
ASSERT_OK(Put(7, "popovich", std::string(90000, 'p')));
|
||||
for (size_t i = 1; i < 8; ++i) {
|
||||
for (int i = 1; i < 8; ++i) {
|
||||
ASSERT_OK(Flush(i));
|
||||
dbfull()->TEST_WaitForFlushMemTable();
|
||||
ASSERT_EQ(listener->flushed_dbs_.size(), i);
|
||||
@@ -385,7 +278,7 @@ TEST_F(EventListenerTest, MultiCF) {
|
||||
std::vector<std::string> cf_names = {
|
||||
"pikachu", "ilya", "muromec", "dobrynia",
|
||||
"nikitich", "alyosha", "popovich"};
|
||||
CreateAndReopenWithCF(cf_names, &options);
|
||||
CreateAndReopenWithCF(cf_names, options);
|
||||
|
||||
ASSERT_OK(Put(1, "pikachu", std::string(90000, 'p')));
|
||||
ASSERT_OK(Put(2, "ilya", std::string(90000, 'i')));
|
||||
@@ -394,7 +287,7 @@ TEST_F(EventListenerTest, MultiCF) {
|
||||
ASSERT_OK(Put(5, "nikitich", std::string(90000, 'n')));
|
||||
ASSERT_OK(Put(6, "alyosha", std::string(90000, 'a')));
|
||||
ASSERT_OK(Put(7, "popovich", std::string(90000, 'p')));
|
||||
for (size_t i = 1; i < 8; ++i) {
|
||||
for (int i = 1; i < 8; ++i) {
|
||||
ASSERT_OK(Flush(i));
|
||||
ASSERT_EQ(listener->flushed_dbs_.size(), i);
|
||||
ASSERT_EQ(listener->flushed_column_family_names_.size(), i);
|
||||
@@ -511,7 +404,7 @@ TEST_F(EventListenerTest, DisableBGCompaction) {
|
||||
options.table_properties_collector_factories.push_back(
|
||||
std::make_shared<TestPropertiesCollectorFactory>());
|
||||
|
||||
CreateAndReopenWithCF({"pikachu"}, &options);
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
ColumnFamilyMetaData cf_meta;
|
||||
db_->GetColumnFamilyMetaData(handles_[1], &cf_meta);
|
||||
|
||||
@@ -525,6 +418,167 @@ TEST_F(EventListenerTest, DisableBGCompaction) {
|
||||
ASSERT_GE(listener->slowdown_count, kSlowdownTrigger * 9);
|
||||
}
|
||||
|
||||
class TestCompactionReasonListener : public EventListener {
|
||||
public:
|
||||
void OnCompactionCompleted(DB* db, const CompactionJobInfo& ci) override {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
compaction_reasons_.push_back(ci.compaction_reason);
|
||||
}
|
||||
|
||||
std::vector<CompactionReason> compaction_reasons_;
|
||||
std::mutex mutex_;
|
||||
};
|
||||
|
||||
TEST_F(EventListenerTest, CompactionReasonLevel) {
|
||||
Options options;
|
||||
options.create_if_missing = true;
|
||||
options.memtable_factory.reset(
|
||||
new SpecialSkipListFactory(DBTestBase::kNumKeysByGenerateNewRandomFile));
|
||||
|
||||
TestCompactionReasonListener* listener = new TestCompactionReasonListener();
|
||||
options.listeners.emplace_back(listener);
|
||||
|
||||
options.level0_file_num_compaction_trigger = 4;
|
||||
options.compaction_style = kCompactionStyleLevel;
|
||||
|
||||
DestroyAndReopen(options);
|
||||
Random rnd(301);
|
||||
|
||||
// Write 4 files in L0
|
||||
for (int i = 0; i < 4; i++) {
|
||||
GenerateNewRandomFile(&rnd);
|
||||
}
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
|
||||
ASSERT_EQ(listener->compaction_reasons_.size(), 1);
|
||||
ASSERT_EQ(listener->compaction_reasons_[0],
|
||||
CompactionReason::kLevelL0FilesNum);
|
||||
|
||||
DestroyAndReopen(options);
|
||||
|
||||
// Write 3 non-overlapping files in L0
|
||||
for (int k = 1; k <= 30; k++) {
|
||||
ASSERT_OK(Put(Key(k), Key(k)));
|
||||
if (k % 10 == 0) {
|
||||
Flush();
|
||||
}
|
||||
}
|
||||
|
||||
// Do a trivial move from L0 -> L1
|
||||
db_->CompactRange(CompactRangeOptions(), nullptr, nullptr);
|
||||
|
||||
options.max_bytes_for_level_base = 1;
|
||||
Close();
|
||||
listener->compaction_reasons_.clear();
|
||||
Reopen(options);
|
||||
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
ASSERT_GT(listener->compaction_reasons_.size(), 1);
|
||||
|
||||
for (auto compaction_reason : listener->compaction_reasons_) {
|
||||
ASSERT_EQ(compaction_reason, CompactionReason::kLevelMaxLevelSize);
|
||||
}
|
||||
|
||||
options.disable_auto_compactions = true;
|
||||
Close();
|
||||
listener->compaction_reasons_.clear();
|
||||
Reopen(options);
|
||||
|
||||
db_->CompactRange(CompactRangeOptions(), nullptr, nullptr);
|
||||
ASSERT_GT(listener->compaction_reasons_.size(), 0);
|
||||
for (auto compaction_reason : listener->compaction_reasons_) {
|
||||
ASSERT_EQ(compaction_reason, CompactionReason::kManualCompaction);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(EventListenerTest, CompactionReasonUniversal) {
|
||||
Options options;
|
||||
options.create_if_missing = true;
|
||||
options.memtable_factory.reset(
|
||||
new SpecialSkipListFactory(DBTestBase::kNumKeysByGenerateNewRandomFile));
|
||||
|
||||
TestCompactionReasonListener* listener = new TestCompactionReasonListener();
|
||||
options.listeners.emplace_back(listener);
|
||||
|
||||
options.compaction_style = kCompactionStyleUniversal;
|
||||
|
||||
Random rnd(301);
|
||||
|
||||
options.level0_file_num_compaction_trigger = 8;
|
||||
options.compaction_options_universal.max_size_amplification_percent = 100000;
|
||||
options.compaction_options_universal.size_ratio = 100000;
|
||||
DestroyAndReopen(options);
|
||||
listener->compaction_reasons_.clear();
|
||||
|
||||
// Write 8 files in L0
|
||||
for (int i = 0; i < 8; i++) {
|
||||
GenerateNewRandomFile(&rnd);
|
||||
}
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
|
||||
ASSERT_GT(listener->compaction_reasons_.size(), 0);
|
||||
for (auto compaction_reason : listener->compaction_reasons_) {
|
||||
ASSERT_EQ(compaction_reason, CompactionReason::kUniversalSortedRunNum);
|
||||
}
|
||||
|
||||
options.level0_file_num_compaction_trigger = 8;
|
||||
options.compaction_options_universal.max_size_amplification_percent = 1;
|
||||
options.compaction_options_universal.size_ratio = 100000;
|
||||
|
||||
DestroyAndReopen(options);
|
||||
listener->compaction_reasons_.clear();
|
||||
|
||||
// Write 8 files in L0
|
||||
for (int i = 0; i < 8; i++) {
|
||||
GenerateNewRandomFile(&rnd);
|
||||
}
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
|
||||
ASSERT_GT(listener->compaction_reasons_.size(), 0);
|
||||
for (auto compaction_reason : listener->compaction_reasons_) {
|
||||
ASSERT_EQ(compaction_reason, CompactionReason::kUniversalSizeAmplification);
|
||||
}
|
||||
|
||||
options.disable_auto_compactions = true;
|
||||
Close();
|
||||
listener->compaction_reasons_.clear();
|
||||
Reopen(options);
|
||||
|
||||
db_->CompactRange(CompactRangeOptions(), nullptr, nullptr);
|
||||
|
||||
ASSERT_GT(listener->compaction_reasons_.size(), 0);
|
||||
for (auto compaction_reason : listener->compaction_reasons_) {
|
||||
ASSERT_EQ(compaction_reason, CompactionReason::kManualCompaction);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(EventListenerTest, CompactionReasonFIFO) {
|
||||
Options options;
|
||||
options.create_if_missing = true;
|
||||
options.memtable_factory.reset(
|
||||
new SpecialSkipListFactory(DBTestBase::kNumKeysByGenerateNewRandomFile));
|
||||
|
||||
TestCompactionReasonListener* listener = new TestCompactionReasonListener();
|
||||
options.listeners.emplace_back(listener);
|
||||
|
||||
options.level0_file_num_compaction_trigger = 4;
|
||||
options.compaction_style = kCompactionStyleFIFO;
|
||||
options.compaction_options_fifo.max_table_files_size = 1;
|
||||
|
||||
DestroyAndReopen(options);
|
||||
Random rnd(301);
|
||||
|
||||
// Write 4 files in L0
|
||||
for (int i = 0; i < 4; i++) {
|
||||
GenerateNewRandomFile(&rnd);
|
||||
}
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
|
||||
ASSERT_GT(listener->compaction_reasons_.size(), 0);
|
||||
for (auto compaction_reason : listener->compaction_reasons_) {
|
||||
ASSERT_EQ(compaction_reason, CompactionReason::kFIFOMaxSize);
|
||||
}
|
||||
}
|
||||
} // namespace rocksdb
|
||||
|
||||
#endif // ROCKSDB_LITE
|
||||
@@ -533,4 +587,3 @@ int main(int argc, char** argv) {
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
|
||||
+2
-2
@@ -47,7 +47,7 @@ Status Writer::AddRecord(const Slice& slice) {
|
||||
Status s;
|
||||
bool begin = true;
|
||||
do {
|
||||
const int leftover = kBlockSize - block_offset_;
|
||||
const int64_t leftover = kBlockSize - block_offset_;
|
||||
assert(leftover >= 0);
|
||||
if (leftover < header_size) {
|
||||
// Switch to a new block
|
||||
@@ -62,7 +62,7 @@ Status Writer::AddRecord(const Slice& slice) {
|
||||
}
|
||||
|
||||
// Invariant: we never leave < header_size bytes in a block.
|
||||
assert(static_cast<int>(kBlockSize) - block_offset_ >= header_size);
|
||||
assert(static_cast<int64_t>(kBlockSize - block_offset_) >= header_size);
|
||||
|
||||
const size_t avail = kBlockSize - block_offset_ - header_size;
|
||||
const size_t fragment_length = (left < avail) ? left : avail;
|
||||
|
||||
+1
-1
@@ -83,7 +83,7 @@ class Writer {
|
||||
|
||||
private:
|
||||
unique_ptr<WritableFileWriter> dest_;
|
||||
int block_offset_; // Current offset in block
|
||||
size_t block_offset_; // Current offset in block
|
||||
uint64_t log_number_;
|
||||
bool recycle_log_files_;
|
||||
|
||||
|
||||
+92
-34
@@ -60,7 +60,7 @@ MemTable::MemTable(const InternalKeyComparator& cmp,
|
||||
moptions_(ioptions, mutable_cf_options),
|
||||
refs_(0),
|
||||
kArenaBlockSize(OptimizeBlockSize(moptions_.arena_block_size)),
|
||||
arena_(moptions_.arena_block_size),
|
||||
arena_(moptions_.arena_block_size, 0),
|
||||
allocator_(&arena_, write_buffer),
|
||||
table_(ioptions.memtable_factory->CreateMemTableRep(
|
||||
comparator_, &allocator_, ioptions.prefix_extractor,
|
||||
@@ -78,12 +78,12 @@ MemTable::MemTable(const InternalKeyComparator& cmp,
|
||||
? moptions_.inplace_update_num_locks
|
||||
: 0),
|
||||
prefix_extractor_(ioptions.prefix_extractor),
|
||||
should_flush_(ShouldFlushNow()),
|
||||
flush_scheduled_(false),
|
||||
flush_state_(FLUSH_NOT_REQUESTED),
|
||||
env_(ioptions.env) {
|
||||
// if should_flush_ == true without an entry inserted, something must have
|
||||
// gone wrong already.
|
||||
assert(!should_flush_);
|
||||
UpdateFlushState();
|
||||
// something went wrong if we need to flush before inserting anything
|
||||
assert(!ShouldScheduleFlush());
|
||||
|
||||
if (prefix_extractor_ && moptions_.memtable_prefix_bloom_bits > 0) {
|
||||
prefix_bloom_.reset(new DynamicBloom(
|
||||
&allocator_,
|
||||
@@ -167,6 +167,17 @@ bool MemTable::ShouldFlushNow() const {
|
||||
return arena_.AllocatedAndUnused() < kArenaBlockSize / 4;
|
||||
}
|
||||
|
||||
void MemTable::UpdateFlushState() {
|
||||
auto state = flush_state_.load(std::memory_order_relaxed);
|
||||
if (state == FLUSH_NOT_REQUESTED && ShouldFlushNow()) {
|
||||
// ignore CAS failure, because that means somebody else requested
|
||||
// a flush
|
||||
flush_state_.compare_exchange_strong(state, FLUSH_REQUESTED,
|
||||
std::memory_order_relaxed,
|
||||
std::memory_order_relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
int MemTable::KeyComparator::operator()(const char* prefix_len_key1,
|
||||
const char* prefix_len_key2) const {
|
||||
// Internal keys are encoded as length-prefixed strings.
|
||||
@@ -274,6 +285,21 @@ class MemTableIterator : public InternalIterator {
|
||||
|
||||
virtual Status status() const override { return Status::OK(); }
|
||||
|
||||
virtual Status PinData() override {
|
||||
// memtable data is always pinned
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
virtual Status ReleasePinnedData() override {
|
||||
// memtable data is always pinned
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
virtual bool IsKeyPinned() const override {
|
||||
// memtable data is always pinned
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
DynamicBloom* bloom_;
|
||||
const SliceTransform* const prefix_extractor_;
|
||||
@@ -320,7 +346,7 @@ uint64_t MemTable::ApproximateSize(const Slice& start_ikey,
|
||||
|
||||
void MemTable::Add(SequenceNumber s, ValueType type,
|
||||
const Slice& key, /* user key */
|
||||
const Slice& value) {
|
||||
const Slice& value, bool allow_concurrent) {
|
||||
// Format of an entry is concatenation of:
|
||||
// key_size : varint32 of internal_key.size()
|
||||
// key bytes : char[internal_key.size()]
|
||||
@@ -334,7 +360,7 @@ void MemTable::Add(SequenceNumber s, ValueType type,
|
||||
val_size;
|
||||
char* buf = nullptr;
|
||||
KeyHandle handle = table_->Allocate(encoded_len, &buf);
|
||||
assert(buf != nullptr);
|
||||
|
||||
char* p = EncodeVarint32(buf, internal_key_size);
|
||||
memcpy(p, key.data(), key_size);
|
||||
p += key_size;
|
||||
@@ -344,32 +370,64 @@ void MemTable::Add(SequenceNumber s, ValueType type,
|
||||
p = EncodeVarint32(p, val_size);
|
||||
memcpy(p, value.data(), val_size);
|
||||
assert((unsigned)(p + val_size - buf) == (unsigned)encoded_len);
|
||||
table_->Insert(handle);
|
||||
num_entries_.store(num_entries_.load(std::memory_order_relaxed) + 1,
|
||||
if (!allow_concurrent) {
|
||||
table_->Insert(handle);
|
||||
|
||||
// this is a bit ugly, but is the way to avoid locked instructions
|
||||
// when incrementing an atomic
|
||||
num_entries_.store(num_entries_.load(std::memory_order_relaxed) + 1,
|
||||
std::memory_order_relaxed);
|
||||
data_size_.store(data_size_.load(std::memory_order_relaxed) + encoded_len,
|
||||
std::memory_order_relaxed);
|
||||
data_size_.store(data_size_.load(std::memory_order_relaxed) + encoded_len,
|
||||
std::memory_order_relaxed);
|
||||
if (type == kTypeDeletion) {
|
||||
num_deletes_++;
|
||||
}
|
||||
|
||||
if (prefix_bloom_) {
|
||||
assert(prefix_extractor_);
|
||||
prefix_bloom_->Add(prefix_extractor_->Transform(key));
|
||||
}
|
||||
|
||||
// The first sequence number inserted into the memtable
|
||||
assert(first_seqno_ == 0 || s > first_seqno_);
|
||||
if (first_seqno_ == 0) {
|
||||
first_seqno_ = s;
|
||||
|
||||
if (earliest_seqno_ == kMaxSequenceNumber) {
|
||||
earliest_seqno_ = first_seqno_;
|
||||
if (type == kTypeDeletion) {
|
||||
num_deletes_.store(num_deletes_.load(std::memory_order_relaxed) + 1,
|
||||
std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
if (prefix_bloom_) {
|
||||
assert(prefix_extractor_);
|
||||
prefix_bloom_->Add(prefix_extractor_->Transform(key));
|
||||
}
|
||||
|
||||
// The first sequence number inserted into the memtable
|
||||
assert(first_seqno_ == 0 || s > first_seqno_);
|
||||
if (first_seqno_ == 0) {
|
||||
first_seqno_.store(s, std::memory_order_relaxed);
|
||||
|
||||
if (earliest_seqno_ == kMaxSequenceNumber) {
|
||||
earliest_seqno_.store(GetFirstSequenceNumber(),
|
||||
std::memory_order_relaxed);
|
||||
}
|
||||
assert(first_seqno_.load() >= earliest_seqno_.load());
|
||||
}
|
||||
} else {
|
||||
table_->InsertConcurrently(handle);
|
||||
|
||||
num_entries_.fetch_add(1, std::memory_order_relaxed);
|
||||
data_size_.fetch_add(encoded_len, std::memory_order_relaxed);
|
||||
if (type == kTypeDeletion) {
|
||||
num_deletes_.fetch_add(1, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
if (prefix_bloom_) {
|
||||
assert(prefix_extractor_);
|
||||
prefix_bloom_->AddConcurrently(prefix_extractor_->Transform(key));
|
||||
}
|
||||
|
||||
// atomically update first_seqno_ and earliest_seqno_.
|
||||
uint64_t cur_seq_num = first_seqno_.load(std::memory_order_relaxed);
|
||||
while ((cur_seq_num == 0 || s < cur_seq_num) &&
|
||||
!first_seqno_.compare_exchange_weak(cur_seq_num, s)) {
|
||||
}
|
||||
uint64_t cur_earliest_seqno =
|
||||
earliest_seqno_.load(std::memory_order_relaxed);
|
||||
while (
|
||||
(cur_earliest_seqno == kMaxSequenceNumber || s < cur_earliest_seqno) &&
|
||||
!first_seqno_.compare_exchange_weak(cur_earliest_seqno, s)) {
|
||||
}
|
||||
assert(first_seqno_ >= earliest_seqno_);
|
||||
}
|
||||
|
||||
should_flush_ = ShouldFlushNow();
|
||||
UpdateFlushState();
|
||||
}
|
||||
|
||||
// Callback from MemTable::Get()
|
||||
@@ -442,7 +500,7 @@ static bool SaveValue(void* arg, const char* entry) {
|
||||
*(s->status) =
|
||||
Status::Corruption("Error: Could not perform merge.");
|
||||
}
|
||||
} else {
|
||||
} else if (s->value != nullptr) {
|
||||
s->value->assign(v.data(), v.size());
|
||||
}
|
||||
if (s->inplace_update_support) {
|
||||
@@ -670,16 +728,16 @@ bool MemTable::UpdateCallback(SequenceNumber seq,
|
||||
}
|
||||
}
|
||||
RecordTick(moptions_.statistics, NUMBER_KEYS_UPDATED);
|
||||
should_flush_ = ShouldFlushNow();
|
||||
UpdateFlushState();
|
||||
return true;
|
||||
} else if (status == UpdateStatus::UPDATED) {
|
||||
Add(seq, kTypeValue, key, Slice(str_value));
|
||||
RecordTick(moptions_.statistics, NUMBER_KEYS_WRITTEN);
|
||||
should_flush_ = ShouldFlushNow();
|
||||
UpdateFlushState();
|
||||
return true;
|
||||
} else if (status == UpdateStatus::UPDATE_FAILED) {
|
||||
// No action required. Return.
|
||||
should_flush_ = ShouldFlushNow();
|
||||
UpdateFlushState();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
+45
-29
@@ -8,10 +8,11 @@
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
|
||||
#pragma once
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <functional>
|
||||
#include <atomic>
|
||||
#include <deque>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include "db/dbformat.h"
|
||||
#include "db/skiplist.h"
|
||||
@@ -21,8 +22,9 @@
|
||||
#include "rocksdb/memtablerep.h"
|
||||
#include "rocksdb/immutable_options.h"
|
||||
#include "db/memtable_allocator.h"
|
||||
#include "util/arena.h"
|
||||
#include "util/concurrent_arena.h"
|
||||
#include "util/dynamic_bloom.h"
|
||||
#include "util/instrumented_mutex.h"
|
||||
#include "util/mutable_cf_options.h"
|
||||
|
||||
namespace rocksdb {
|
||||
@@ -124,10 +126,17 @@ class MemTable {
|
||||
// This method heuristically determines if the memtable should continue to
|
||||
// host more data.
|
||||
bool ShouldScheduleFlush() const {
|
||||
return flush_scheduled_ == false && should_flush_;
|
||||
return flush_state_.load(std::memory_order_relaxed) == FLUSH_REQUESTED;
|
||||
}
|
||||
|
||||
void MarkFlushScheduled() { flush_scheduled_ = true; }
|
||||
// Returns true if a flush should be scheduled and the caller should
|
||||
// be the one to schedule it
|
||||
bool MarkFlushScheduled() {
|
||||
auto before = FLUSH_REQUESTED;
|
||||
return flush_state_.compare_exchange_strong(before, FLUSH_SCHEDULED,
|
||||
std::memory_order_relaxed,
|
||||
std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
// Return an iterator that yields the contents of the memtable.
|
||||
//
|
||||
@@ -147,11 +156,10 @@ class MemTable {
|
||||
// specified sequence number and with the specified type.
|
||||
// Typically value will be empty if type==kTypeDeletion.
|
||||
//
|
||||
// REQUIRES: external synchronization to prevent simultaneous
|
||||
// operations on the same MemTable.
|
||||
void Add(SequenceNumber seq, ValueType type,
|
||||
const Slice& key,
|
||||
const Slice& value);
|
||||
// REQUIRES: if allow_concurrent = false, external synchronization to prevent
|
||||
// simultaneous operations on the same MemTable.
|
||||
void Add(SequenceNumber seq, ValueType type, const Slice& key,
|
||||
const Slice& value, bool allow_concurrent = false);
|
||||
|
||||
// If memtable contains a value for key, store it in *value and return true.
|
||||
// If memtable contains a deletion for key, store a NotFound() error
|
||||
@@ -189,7 +197,7 @@ class MemTable {
|
||||
const Slice& key,
|
||||
const Slice& value);
|
||||
|
||||
// If prev_value for key exits, attempts to update it inplace.
|
||||
// If prev_value for key exists, attempts to update it inplace.
|
||||
// else returns false
|
||||
// Pseudocode
|
||||
// if key exists in current memtable && prev_value is of type kTypeValue
|
||||
@@ -220,7 +228,9 @@ class MemTable {
|
||||
// Get total number of deletes in the mem table.
|
||||
// REQUIRES: external synchronization to prevent simultaneous
|
||||
// operations on the same MemTable (unless this Memtable is immutable).
|
||||
uint64_t num_deletes() const { return num_deletes_; }
|
||||
uint64_t num_deletes() const {
|
||||
return num_deletes_.load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
// Returns the edits area that is needed for flushing the memtable
|
||||
VersionEdit* GetEdits() { return &edit_; }
|
||||
@@ -234,7 +244,9 @@ class MemTable {
|
||||
// into the memtable.
|
||||
// REQUIRES: external synchronization to prevent simultaneous
|
||||
// operations on the same MemTable (unless this Memtable is immutable).
|
||||
SequenceNumber GetFirstSequenceNumber() { return first_seqno_; }
|
||||
SequenceNumber GetFirstSequenceNumber() {
|
||||
return first_seqno_.load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
// Returns the sequence number that is guaranteed to be smaller than or equal
|
||||
// to the sequence number of any key that could be inserted into this
|
||||
@@ -243,7 +255,9 @@ class MemTable {
|
||||
//
|
||||
// If the earliest sequence number could not be determined,
|
||||
// kMaxSequenceNumber will be returned.
|
||||
SequenceNumber GetEarliestSequenceNumber() { return earliest_seqno_; }
|
||||
SequenceNumber GetEarliestSequenceNumber() {
|
||||
return earliest_seqno_.load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
// Returns the next active logfile number when this memtable is about to
|
||||
// be flushed to storage
|
||||
@@ -290,8 +304,7 @@ class MemTable {
|
||||
const MemTableOptions* GetMemTableOptions() const { return &moptions_; }
|
||||
|
||||
private:
|
||||
// Dynamically check if we can add more incoming entries
|
||||
bool ShouldFlushNow() const;
|
||||
enum FlushStateEnum { FLUSH_NOT_REQUESTED, FLUSH_REQUESTED, FLUSH_SCHEDULED };
|
||||
|
||||
friend class MemTableIterator;
|
||||
friend class MemTableBackwardIterator;
|
||||
@@ -301,14 +314,14 @@ class MemTable {
|
||||
const MemTableOptions moptions_;
|
||||
int refs_;
|
||||
const size_t kArenaBlockSize;
|
||||
Arena arena_;
|
||||
ConcurrentArena arena_;
|
||||
MemTableAllocator allocator_;
|
||||
unique_ptr<MemTableRep> table_;
|
||||
|
||||
// Total data size of all data inserted
|
||||
std::atomic<uint64_t> data_size_;
|
||||
std::atomic<uint64_t> num_entries_;
|
||||
uint64_t num_deletes_;
|
||||
std::atomic<uint64_t> num_deletes_;
|
||||
|
||||
// These are used to manage memtable flushes to storage
|
||||
bool flush_in_progress_; // started the flush
|
||||
@@ -320,11 +333,11 @@ class MemTable {
|
||||
VersionEdit edit_;
|
||||
|
||||
// The sequence number of the kv that was inserted first
|
||||
SequenceNumber first_seqno_;
|
||||
std::atomic<SequenceNumber> first_seqno_;
|
||||
|
||||
// The db sequence number at the time of creation or kMaxSequenceNumber
|
||||
// if not set.
|
||||
SequenceNumber earliest_seqno_;
|
||||
std::atomic<SequenceNumber> earliest_seqno_;
|
||||
|
||||
// The log files earlier than this number can be deleted.
|
||||
uint64_t mem_next_logfile_number_;
|
||||
@@ -332,19 +345,22 @@ class MemTable {
|
||||
// rw locks for inplace updates
|
||||
std::vector<port::RWMutex> locks_;
|
||||
|
||||
// No copying allowed
|
||||
MemTable(const MemTable&);
|
||||
void operator=(const MemTable&);
|
||||
|
||||
const SliceTransform* const prefix_extractor_;
|
||||
std::unique_ptr<DynamicBloom> prefix_bloom_;
|
||||
|
||||
// a flag indicating if a memtable has met the criteria to flush
|
||||
bool should_flush_;
|
||||
std::atomic<FlushStateEnum> flush_state_;
|
||||
|
||||
// a flag indicating if flush has been scheduled
|
||||
bool flush_scheduled_;
|
||||
Env* env_;
|
||||
|
||||
// Returns a heuristic flush decision
|
||||
bool ShouldFlushNow() const;
|
||||
|
||||
// Updates flush_state_ using ShouldFlushNow()
|
||||
void UpdateFlushState();
|
||||
|
||||
// No copying allowed
|
||||
MemTable(const MemTable&);
|
||||
MemTable& operator=(const MemTable&);
|
||||
};
|
||||
|
||||
extern const char* EncodeKey(std::string* scratch, const Slice& target);
|
||||
|
||||
+12
-16
@@ -7,46 +7,42 @@
|
||||
// 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 <assert.h>
|
||||
|
||||
#include "db/memtable_allocator.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include "db/writebuffer.h"
|
||||
#include "util/arena.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
MemTableAllocator::MemTableAllocator(Arena* arena, WriteBuffer* write_buffer)
|
||||
: arena_(arena), write_buffer_(write_buffer), bytes_allocated_(0) {
|
||||
}
|
||||
MemTableAllocator::MemTableAllocator(Allocator* allocator,
|
||||
WriteBuffer* write_buffer)
|
||||
: allocator_(allocator), write_buffer_(write_buffer), bytes_allocated_(0) {}
|
||||
|
||||
MemTableAllocator::~MemTableAllocator() {
|
||||
DoneAllocating();
|
||||
}
|
||||
MemTableAllocator::~MemTableAllocator() { DoneAllocating(); }
|
||||
|
||||
char* MemTableAllocator::Allocate(size_t bytes) {
|
||||
assert(write_buffer_ != nullptr);
|
||||
bytes_allocated_ += bytes;
|
||||
bytes_allocated_.fetch_add(bytes, std::memory_order_relaxed);
|
||||
write_buffer_->ReserveMem(bytes);
|
||||
return arena_->Allocate(bytes);
|
||||
return allocator_->Allocate(bytes);
|
||||
}
|
||||
|
||||
char* MemTableAllocator::AllocateAligned(size_t bytes, size_t huge_page_size,
|
||||
Logger* logger) {
|
||||
assert(write_buffer_ != nullptr);
|
||||
bytes_allocated_ += bytes;
|
||||
bytes_allocated_.fetch_add(bytes, std::memory_order_relaxed);
|
||||
write_buffer_->ReserveMem(bytes);
|
||||
return arena_->AllocateAligned(bytes, huge_page_size, logger);
|
||||
return allocator_->AllocateAligned(bytes, huge_page_size, logger);
|
||||
}
|
||||
|
||||
void MemTableAllocator::DoneAllocating() {
|
||||
if (write_buffer_ != nullptr) {
|
||||
write_buffer_->FreeMem(bytes_allocated_);
|
||||
write_buffer_->FreeMem(bytes_allocated_.load(std::memory_order_relaxed));
|
||||
write_buffer_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
size_t MemTableAllocator::BlockSize() const {
|
||||
return arena_->BlockSize();
|
||||
}
|
||||
size_t MemTableAllocator::BlockSize() const { return allocator_->BlockSize(); }
|
||||
|
||||
} // namespace rocksdb
|
||||
@@ -11,17 +11,18 @@
|
||||
// to WriteBuffer so we can track and enforce overall write buffer limits.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include "util/allocator.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
class Arena;
|
||||
class Logger;
|
||||
class WriteBuffer;
|
||||
|
||||
class MemTableAllocator : public Allocator {
|
||||
public:
|
||||
explicit MemTableAllocator(Arena* arena, WriteBuffer* write_buffer);
|
||||
explicit MemTableAllocator(Allocator* allocator, WriteBuffer* write_buffer);
|
||||
~MemTableAllocator();
|
||||
|
||||
// Allocator interface
|
||||
@@ -35,9 +36,9 @@ class MemTableAllocator : public Allocator {
|
||||
void DoneAllocating();
|
||||
|
||||
private:
|
||||
Arena* arena_;
|
||||
Allocator* allocator_;
|
||||
WriteBuffer* write_buffer_;
|
||||
size_t bytes_allocated_;
|
||||
std::atomic<size_t> bytes_allocated_;
|
||||
|
||||
// No copying allowed
|
||||
MemTableAllocator(const MemTableAllocator&);
|
||||
|
||||
+30
-33
@@ -20,7 +20,6 @@
|
||||
#include "utilities/merge_operators.h"
|
||||
#include "util/testharness.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace rocksdb;
|
||||
|
||||
namespace {
|
||||
@@ -76,7 +75,7 @@ class CountMergeOperator : public AssociativeMergeOperator {
|
||||
};
|
||||
|
||||
namespace {
|
||||
std::shared_ptr<DB> OpenDb(const string& dbname, const bool ttl = false,
|
||||
std::shared_ptr<DB> OpenDb(const std::string& dbname, const bool ttl = false,
|
||||
const size_t max_successive_merges = 0,
|
||||
const uint32_t min_partial_merge_operands = 2) {
|
||||
DB* db;
|
||||
@@ -90,7 +89,7 @@ std::shared_ptr<DB> OpenDb(const string& dbname, const bool ttl = false,
|
||||
// DBWithTTL is not supported in ROCKSDB_LITE
|
||||
#ifndef ROCKSDB_LITE
|
||||
if (ttl) {
|
||||
cout << "Opening database with TTL\n";
|
||||
std::cout << "Opening database with TTL\n";
|
||||
DBWithTTL* db_with_ttl;
|
||||
s = DBWithTTL::Open(options, dbname, &db_with_ttl);
|
||||
db = db_with_ttl;
|
||||
@@ -102,7 +101,7 @@ std::shared_ptr<DB> OpenDb(const string& dbname, const bool ttl = false,
|
||||
s = DB::Open(options, dbname, &db);
|
||||
#endif // !ROCKSDB_LITE
|
||||
if (!s.ok()) {
|
||||
cerr << s.ToString() << endl;
|
||||
std::cerr << s.ToString() << std::endl;
|
||||
assert(false);
|
||||
}
|
||||
return std::shared_ptr<DB>(db);
|
||||
@@ -142,7 +141,7 @@ class Counters {
|
||||
// if the underlying level db operation failed.
|
||||
|
||||
// mapped to a levedb Put
|
||||
bool set(const string& key, uint64_t value) {
|
||||
bool set(const std::string& key, uint64_t value) {
|
||||
// just treat the internal rep of int64 as the string
|
||||
Slice slice((char *)&value, sizeof(value));
|
||||
auto s = db_->Put(put_option_, key, slice);
|
||||
@@ -150,26 +149,26 @@ class Counters {
|
||||
if (s.ok()) {
|
||||
return true;
|
||||
} else {
|
||||
cerr << s.ToString() << endl;
|
||||
std::cerr << s.ToString() << std::endl;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// mapped to a rocksdb Delete
|
||||
bool remove(const string& key) {
|
||||
bool remove(const std::string& key) {
|
||||
auto s = db_->Delete(delete_option_, key);
|
||||
|
||||
if (s.ok()) {
|
||||
return true;
|
||||
} else {
|
||||
cerr << s.ToString() << std::endl;
|
||||
std::cerr << s.ToString() << std::endl;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// mapped to a rocksdb Get
|
||||
bool get(const string& key, uint64_t *value) {
|
||||
string str;
|
||||
bool get(const std::string& key, uint64_t* value) {
|
||||
std::string str;
|
||||
auto s = db_->Get(get_option_, key, &str);
|
||||
|
||||
if (s.IsNotFound()) {
|
||||
@@ -179,35 +178,33 @@ class Counters {
|
||||
} else if (s.ok()) {
|
||||
// deserialization
|
||||
if (str.size() != sizeof(uint64_t)) {
|
||||
cerr << "value corruption\n";
|
||||
std::cerr << "value corruption\n";
|
||||
return false;
|
||||
}
|
||||
*value = DecodeFixed64(&str[0]);
|
||||
return true;
|
||||
} else {
|
||||
cerr << s.ToString() << std::endl;
|
||||
std::cerr << s.ToString() << std::endl;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 'add' is implemented as get -> modify -> set
|
||||
// An alternative is a single merge operation, see MergeBasedCounters
|
||||
virtual bool add(const string& key, uint64_t value) {
|
||||
virtual bool add(const std::string& key, uint64_t value) {
|
||||
uint64_t base = default_;
|
||||
return get(key, &base) && set(key, base + value);
|
||||
}
|
||||
|
||||
|
||||
// convenience functions for testing
|
||||
void assert_set(const string& key, uint64_t value) {
|
||||
void assert_set(const std::string& key, uint64_t value) {
|
||||
assert(set(key, value));
|
||||
}
|
||||
|
||||
void assert_remove(const string& key) {
|
||||
assert(remove(key));
|
||||
}
|
||||
void assert_remove(const std::string& key) { assert(remove(key)); }
|
||||
|
||||
uint64_t assert_get(const string& key) {
|
||||
uint64_t assert_get(const std::string& key) {
|
||||
uint64_t value = default_;
|
||||
int result = get(key, &value);
|
||||
assert(result);
|
||||
@@ -215,7 +212,7 @@ class Counters {
|
||||
return value;
|
||||
}
|
||||
|
||||
void assert_add(const string& key, uint64_t value) {
|
||||
void assert_add(const std::string& key, uint64_t value) {
|
||||
int result = add(key, value);
|
||||
assert(result);
|
||||
if (result == 0) exit(1); // Disable unused variable warning.
|
||||
@@ -234,7 +231,7 @@ class MergeBasedCounters : public Counters {
|
||||
}
|
||||
|
||||
// mapped to a rocksdb Merge operation
|
||||
virtual bool add(const string& key, uint64_t value) override {
|
||||
virtual bool add(const std::string& key, uint64_t value) override {
|
||||
char encoded[sizeof(uint64_t)];
|
||||
EncodeFixed64(encoded, value);
|
||||
Slice slice(encoded, sizeof(uint64_t));
|
||||
@@ -243,7 +240,7 @@ class MergeBasedCounters : public Counters {
|
||||
if (s.ok()) {
|
||||
return true;
|
||||
} else {
|
||||
cerr << s.ToString() << endl;
|
||||
std::cerr << s.ToString() << std::endl;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -254,7 +251,7 @@ void dumpDb(DB* db) {
|
||||
auto it = unique_ptr<Iterator>(db->NewIterator(ReadOptions()));
|
||||
for (it->SeekToFirst(); it->Valid(); it->Next()) {
|
||||
uint64_t value = DecodeFixed64(it->value().data());
|
||||
cout << it->key().ToString() << ": " << value << endl;
|
||||
std::cout << it->key().ToString() << ": " << value << std::endl;
|
||||
}
|
||||
assert(it->status().ok()); // Check for any errors found during the scan
|
||||
}
|
||||
@@ -302,9 +299,9 @@ void testCounters(Counters& counters, DB* db, bool test_compaction) {
|
||||
if (test_compaction) {
|
||||
db->Flush(o);
|
||||
|
||||
cout << "Compaction started ...\n";
|
||||
std::cout << "Compaction started ...\n";
|
||||
db->CompactRange(CompactRangeOptions(), nullptr, nullptr);
|
||||
cout << "Compaction ended\n";
|
||||
std::cout << "Compaction ended\n";
|
||||
|
||||
dumpDb(db);
|
||||
|
||||
@@ -400,7 +397,7 @@ void testSingleBatchSuccessiveMerge(DB* db, size_t max_num_merges,
|
||||
|
||||
// Get the value
|
||||
resetNumMergeOperatorCalls();
|
||||
string get_value_str;
|
||||
std::string get_value_str;
|
||||
{
|
||||
Status s = db->Get(ReadOptions(), key, &get_value_str);
|
||||
assert(s.ok());
|
||||
@@ -412,24 +409,24 @@ void testSingleBatchSuccessiveMerge(DB* db, size_t max_num_merges,
|
||||
static_cast<size_t>((num_merges % (max_num_merges + 1))));
|
||||
}
|
||||
|
||||
void runTest(int argc, const string& dbname, const bool use_ttl = false) {
|
||||
void runTest(int argc, const std::string& dbname, const bool use_ttl = false) {
|
||||
bool compact = false;
|
||||
if (argc > 1) {
|
||||
compact = true;
|
||||
cout << "Turn on Compaction\n";
|
||||
std::cout << "Turn on Compaction\n";
|
||||
}
|
||||
|
||||
{
|
||||
auto db = OpenDb(dbname, use_ttl);
|
||||
|
||||
{
|
||||
cout << "Test read-modify-write counters... \n";
|
||||
std::cout << "Test read-modify-write counters... \n";
|
||||
Counters counters(db, 0);
|
||||
testCounters(counters, db.get(), true);
|
||||
}
|
||||
|
||||
{
|
||||
cout << "Test merge-based counters... \n";
|
||||
std::cout << "Test merge-based counters... \n";
|
||||
MergeBasedCounters counters(db, 0);
|
||||
testCounters(counters, db.get(), compact);
|
||||
}
|
||||
@@ -438,7 +435,7 @@ void runTest(int argc, const string& dbname, const bool use_ttl = false) {
|
||||
DestroyDB(dbname, Options());
|
||||
|
||||
{
|
||||
cout << "Test merge in memtable... \n";
|
||||
std::cout << "Test merge in memtable... \n";
|
||||
size_t max_merge = 5;
|
||||
auto db = OpenDb(dbname, use_ttl, max_merge);
|
||||
MergeBasedCounters counters(db, 0);
|
||||
@@ -449,7 +446,7 @@ void runTest(int argc, const string& dbname, const bool use_ttl = false) {
|
||||
}
|
||||
|
||||
{
|
||||
cout << "Test Partial-Merge\n";
|
||||
std::cout << "Test Partial-Merge\n";
|
||||
size_t max_merge = 100;
|
||||
for (uint32_t min_merge = 5; min_merge < 25; min_merge += 5) {
|
||||
for (uint32_t count = min_merge - 1; count <= min_merge + 1; count++) {
|
||||
@@ -469,7 +466,7 @@ void runTest(int argc, const string& dbname, const bool use_ttl = false) {
|
||||
}
|
||||
|
||||
{
|
||||
cout << "Test merge-operator not set after reopen\n";
|
||||
std::cout << "Test merge-operator not set after reopen\n";
|
||||
{
|
||||
auto db = OpenDb(dbname);
|
||||
MergeBasedCounters counters(db, 0);
|
||||
@@ -489,7 +486,7 @@ void runTest(int argc, const string& dbname, const bool use_ttl = false) {
|
||||
|
||||
/* Temporary remove this test
|
||||
{
|
||||
cout << "Test merge-operator not set after reopen (recovery case)\n";
|
||||
std::cout << "Test merge-operator not set after reopen (recovery case)\n";
|
||||
{
|
||||
auto db = OpenDb(dbname);
|
||||
MergeBasedCounters counters(db, 0);
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
// Copyright (c) 2015, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the root directory of this source tree. An additional grant
|
||||
// of patent rights can be found in the PATENTS file in the same directory.
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
#include <string>
|
||||
|
||||
#include "db/db_impl.h"
|
||||
#include "db/db_test_util.h"
|
||||
#include "rocksdb/options.h"
|
||||
#include "rocksdb/table.h"
|
||||
#include "util/testharness.h"
|
||||
|
||||
namespace rocksdb {
|
||||
class OptionsFileTest : public testing::Test {
|
||||
public:
|
||||
OptionsFileTest() : dbname_(test::TmpDir() + "/options_file_test") {}
|
||||
|
||||
std::string dbname_;
|
||||
};
|
||||
|
||||
namespace {
|
||||
void UpdateOptionsFiles(DB* db,
|
||||
std::unordered_set<std::string>* filename_history,
|
||||
int* options_files_count) {
|
||||
std::vector<std::string> filenames;
|
||||
db->GetEnv()->GetChildren(db->GetName(), &filenames);
|
||||
uint64_t number;
|
||||
FileType type;
|
||||
*options_files_count = 0;
|
||||
for (auto filename : filenames) {
|
||||
if (ParseFileName(filename, &number, &type) && type == kOptionsFile) {
|
||||
filename_history->insert(filename);
|
||||
(*options_files_count)++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Verify whether the current Options Files are the latest ones.
|
||||
void VerifyOptionsFileName(
|
||||
DB* db, const std::unordered_set<std::string>& past_filenames) {
|
||||
std::vector<std::string> filenames;
|
||||
std::unordered_set<std::string> current_filenames;
|
||||
db->GetEnv()->GetChildren(db->GetName(), &filenames);
|
||||
uint64_t number;
|
||||
FileType type;
|
||||
for (auto filename : filenames) {
|
||||
if (ParseFileName(filename, &number, &type) && type == kOptionsFile) {
|
||||
current_filenames.insert(filename);
|
||||
}
|
||||
}
|
||||
for (auto past_filename : past_filenames) {
|
||||
if (current_filenames.find(past_filename) != current_filenames.end()) {
|
||||
continue;
|
||||
}
|
||||
for (auto filename : current_filenames) {
|
||||
ASSERT_GT(filename, past_filename);
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST_F(OptionsFileTest, NumberOfOptionsFiles) {
|
||||
const int kReopenCount = 20;
|
||||
Options opt;
|
||||
opt.create_if_missing = true;
|
||||
DestroyDB(dbname_, opt);
|
||||
std::unordered_set<std::string> filename_history;
|
||||
DB* db;
|
||||
for (int i = 0; i < kReopenCount; ++i) {
|
||||
ASSERT_OK(DB::Open(opt, dbname_, &db));
|
||||
int num_options_files = 0;
|
||||
UpdateOptionsFiles(db, &filename_history, &num_options_files);
|
||||
ASSERT_GT(num_options_files, 0);
|
||||
ASSERT_LE(num_options_files, 2);
|
||||
// Make sure we always keep the latest option files.
|
||||
VerifyOptionsFileName(db, filename_history);
|
||||
delete db;
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(OptionsFileTest, OptionsFileName) {
|
||||
const uint64_t kOptionsFileNum = 12345;
|
||||
uint64_t number;
|
||||
FileType type;
|
||||
|
||||
auto options_file_name = OptionsFileName("", kOptionsFileNum);
|
||||
ASSERT_TRUE(ParseFileName(options_file_name, &number, &type, nullptr));
|
||||
ASSERT_EQ(type, kOptionsFile);
|
||||
ASSERT_EQ(number, kOptionsFileNum);
|
||||
|
||||
const uint64_t kTempOptionsFileNum = 54352;
|
||||
auto temp_options_file_name = TempOptionsFileName("", kTempOptionsFileNum);
|
||||
ASSERT_TRUE(ParseFileName(temp_options_file_name, &number, &type, nullptr));
|
||||
ASSERT_NE(temp_options_file_name.find(kTempFileNameSuffix),
|
||||
std::string::npos);
|
||||
ASSERT_EQ(type, kTempFile);
|
||||
ASSERT_EQ(number, kTempOptionsFileNum);
|
||||
}
|
||||
} // namespace rocksdb
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
#if !(defined NDEBUG) || !defined(OS_WIN)
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
#else
|
||||
return 0;
|
||||
#endif // !(defined NDEBUG) || !defined(OS_WIN)
|
||||
}
|
||||
#else
|
||||
|
||||
#include <cstdio>
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
printf("Skipped as Options file is not supported in RocksDBLite.\n");
|
||||
return 0;
|
||||
}
|
||||
#endif // !ROCKSDB_LITE
|
||||
@@ -29,6 +29,7 @@
|
||||
#include "table/bloom_block.h"
|
||||
#include "table/table_builder.h"
|
||||
#include "table/plain_table_factory.h"
|
||||
#include "table/plain_table_key_coding.h"
|
||||
#include "table/plain_table_reader.h"
|
||||
#include "util/hash.h"
|
||||
#include "util/logging.h"
|
||||
@@ -41,6 +42,59 @@
|
||||
using std::unique_ptr;
|
||||
|
||||
namespace rocksdb {
|
||||
class PlainTableKeyDecoderTest : public testing::Test {};
|
||||
|
||||
TEST_F(PlainTableKeyDecoderTest, ReadNonMmap) {
|
||||
std::string tmp;
|
||||
Random rnd(301);
|
||||
const uint32_t kLength = 2222;
|
||||
Slice contents = test::RandomString(&rnd, kLength, &tmp);
|
||||
test::StringSource* string_source =
|
||||
new test::StringSource(contents, 0, false);
|
||||
|
||||
unique_ptr<RandomAccessFileReader> file_reader(
|
||||
test::GetRandomAccessFileReader(string_source));
|
||||
unique_ptr<PlainTableReaderFileInfo> file_info(new PlainTableReaderFileInfo(
|
||||
std::move(file_reader), EnvOptions(), kLength));
|
||||
|
||||
{
|
||||
PlainTableFileReader reader(file_info.get());
|
||||
|
||||
const uint32_t kReadSize = 77;
|
||||
for (uint32_t pos = 0; pos < kLength; pos += kReadSize) {
|
||||
uint32_t read_size = std::min(kLength - pos, kReadSize);
|
||||
Slice out;
|
||||
ASSERT_TRUE(reader.Read(pos, read_size, &out));
|
||||
ASSERT_EQ(0, out.compare(tmp.substr(pos, read_size)));
|
||||
}
|
||||
|
||||
ASSERT_LT(uint32_t(string_source->total_reads()), kLength / kReadSize / 2);
|
||||
}
|
||||
|
||||
std::vector<std::vector<std::pair<uint32_t, uint32_t>>> reads = {
|
||||
{{600, 30}, {590, 30}, {600, 20}, {600, 40}},
|
||||
{{800, 20}, {100, 20}, {500, 20}, {1500, 20}, {100, 20}, {80, 20}},
|
||||
{{1000, 20}, {500, 20}, {1000, 50}},
|
||||
{{1000, 20}, {500, 20}, {500, 20}},
|
||||
{{1000, 20}, {500, 20}, {200, 20}, {500, 20}},
|
||||
{{1000, 20}, {500, 20}, {200, 20}, {1000, 50}},
|
||||
{{600, 500}, {610, 20}, {100, 20}},
|
||||
{{500, 100}, {490, 100}, {550, 50}},
|
||||
};
|
||||
|
||||
std::vector<int> num_file_reads = {2, 6, 2, 2, 4, 3, 2, 2};
|
||||
|
||||
for (size_t i = 0; i < reads.size(); i++) {
|
||||
string_source->set_total_reads(0);
|
||||
PlainTableFileReader reader(file_info.get());
|
||||
for (auto p : reads[i]) {
|
||||
Slice out;
|
||||
ASSERT_TRUE(reader.Read(p.first, p.second, &out));
|
||||
ASSERT_EQ(0, out.compare(tmp.substr(p.first, p.second)));
|
||||
}
|
||||
ASSERT_EQ(num_file_reads[i], string_source->total_reads());
|
||||
}
|
||||
}
|
||||
|
||||
class PlainTableDBTest : public testing::Test,
|
||||
public testing::WithParamInterface<bool> {
|
||||
|
||||
+6
-4
@@ -165,7 +165,7 @@ class Repairer {
|
||||
Status FindFiles() {
|
||||
std::vector<std::string> filenames;
|
||||
bool found_file = false;
|
||||
for (uint32_t path_id = 0; path_id < options_.db_paths.size(); path_id++) {
|
||||
for (size_t path_id = 0; path_id < options_.db_paths.size(); path_id++) {
|
||||
Status status =
|
||||
env_->GetChildren(options_.db_paths[path_id].path, &filenames);
|
||||
if (!status.ok()) {
|
||||
@@ -190,7 +190,8 @@ class Repairer {
|
||||
assert(path_id == 0);
|
||||
logs_.push_back(number);
|
||||
} else if (type == kTableFile) {
|
||||
table_fds_.emplace_back(number, path_id, 0);
|
||||
table_fds_.emplace_back(number, static_cast<uint32_t>(path_id),
|
||||
0);
|
||||
} else {
|
||||
// Ignore other files
|
||||
}
|
||||
@@ -270,7 +271,7 @@ class Repairer {
|
||||
continue;
|
||||
}
|
||||
WriteBatchInternal::SetContents(&batch, record);
|
||||
status = WriteBatchInternal::InsertInto(&batch, cf_mems_default);
|
||||
status = WriteBatchInternal::InsertInto(&batch, cf_mems_default, nullptr);
|
||||
if (status.ok()) {
|
||||
counter += WriteBatchInternal::Count(&batch);
|
||||
} else {
|
||||
@@ -294,7 +295,8 @@ class Repairer {
|
||||
dbname_, env_, ioptions_, env_options_, table_cache_, iter.get(),
|
||||
&meta, icmp_, &int_tbl_prop_collector_factories_,
|
||||
TablePropertiesCollectorFactory::Context::kUnknownColumnFamily, {},
|
||||
kNoCompression, CompressionOptions(), false, nullptr);
|
||||
kMaxSequenceNumber, kNoCompression, CompressionOptions(), false,
|
||||
nullptr);
|
||||
}
|
||||
delete mem->Unref();
|
||||
delete cf_mems_default;
|
||||
|
||||
+12
-10
@@ -107,8 +107,9 @@ class SkipList {
|
||||
};
|
||||
|
||||
private:
|
||||
const int32_t kMaxHeight_;
|
||||
const int32_t kBranching_;
|
||||
const uint16_t kMaxHeight_;
|
||||
const uint16_t kBranching_;
|
||||
const uint32_t kScaledInverseBranching_;
|
||||
|
||||
// Immutable after construction
|
||||
Comparator const compare_;
|
||||
@@ -131,9 +132,6 @@ class SkipList {
|
||||
return max_height_.load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
// Read/written only by Insert().
|
||||
Random rnd_;
|
||||
|
||||
Node* NewNode(const Key& key, int height);
|
||||
int RandomHeight();
|
||||
bool Equal(const Key& a, const Key& b) const { return (compare_(a, b) == 0); }
|
||||
@@ -264,9 +262,11 @@ inline void SkipList<Key, Comparator>::Iterator::SeekToLast() {
|
||||
|
||||
template<typename Key, class Comparator>
|
||||
int SkipList<Key, Comparator>::RandomHeight() {
|
||||
auto rnd = Random::GetTLSInstance();
|
||||
|
||||
// Increase height with probability 1 in kBranching
|
||||
int height = 1;
|
||||
while (height < kMaxHeight_ && ((rnd_.Next() % kBranching_) == 0)) {
|
||||
while (height < kMaxHeight_ && rnd->Next() < kScaledInverseBranching_) {
|
||||
height++;
|
||||
}
|
||||
assert(height > 0);
|
||||
@@ -391,14 +391,16 @@ SkipList<Key, Comparator>::SkipList(const Comparator cmp, Allocator* allocator,
|
||||
int32_t branching_factor)
|
||||
: kMaxHeight_(max_height),
|
||||
kBranching_(branching_factor),
|
||||
kScaledInverseBranching_((Random::kMaxNext + 1) / kBranching_),
|
||||
compare_(cmp),
|
||||
allocator_(allocator),
|
||||
head_(NewNode(0 /* any key will do */, max_height)),
|
||||
max_height_(1),
|
||||
prev_height_(1),
|
||||
rnd_(0xdeadbeef) {
|
||||
assert(kMaxHeight_ > 0);
|
||||
assert(kBranching_ > 0);
|
||||
prev_height_(1) {
|
||||
assert(max_height > 0 && kMaxHeight_ == static_cast<uint32_t>(max_height));
|
||||
assert(branching_factor > 0 &&
|
||||
kBranching_ == static_cast<uint32_t>(branching_factor));
|
||||
assert(kScaledInverseBranching_ > 0);
|
||||
// Allocate the prev_ Node* array, directly from the passed-in allocator.
|
||||
// prev_ does not need to be freed, as its life cycle is tied up with
|
||||
// the allocator as a whole.
|
||||
|
||||
@@ -12,6 +12,9 @@ namespace rocksdb {
|
||||
ManagedSnapshot::ManagedSnapshot(DB* db) : db_(db),
|
||||
snapshot_(db->GetSnapshot()) {}
|
||||
|
||||
ManagedSnapshot::ManagedSnapshot(DB* db, const Snapshot* _snapshot)
|
||||
: db_(db), snapshot_(_snapshot) {}
|
||||
|
||||
ManagedSnapshot::~ManagedSnapshot() {
|
||||
if (snapshot_) {
|
||||
db_->ReleaseSnapshot(snapshot_);
|
||||
|
||||
+21
-2
@@ -34,6 +34,9 @@ class SnapshotImpl : public Snapshot {
|
||||
SnapshotList* list_; // just for sanity checks
|
||||
|
||||
int64_t unix_time_;
|
||||
|
||||
// Will this snapshot be used by a Transaction to do write-conflict checking?
|
||||
bool is_write_conflict_boundary_;
|
||||
};
|
||||
|
||||
class SnapshotList {
|
||||
@@ -50,9 +53,10 @@ class SnapshotList {
|
||||
SnapshotImpl* newest() const { assert(!empty()); return list_.prev_; }
|
||||
|
||||
const SnapshotImpl* New(SnapshotImpl* s, SequenceNumber seq,
|
||||
uint64_t unix_time) {
|
||||
uint64_t unix_time, bool is_write_conflict_boundary) {
|
||||
s->number_ = seq;
|
||||
s->unix_time_ = unix_time;
|
||||
s->is_write_conflict_boundary_ = is_write_conflict_boundary;
|
||||
s->list_ = this;
|
||||
s->next_ = &list_;
|
||||
s->prev_ = list_.prev_;
|
||||
@@ -71,14 +75,29 @@ class SnapshotList {
|
||||
}
|
||||
|
||||
// retrieve all snapshot numbers. They are sorted in ascending order.
|
||||
std::vector<SequenceNumber> GetAll() {
|
||||
std::vector<SequenceNumber> GetAll(
|
||||
SequenceNumber* oldest_write_conflict_snapshot = nullptr) {
|
||||
std::vector<SequenceNumber> ret;
|
||||
|
||||
if (oldest_write_conflict_snapshot != nullptr) {
|
||||
*oldest_write_conflict_snapshot = kMaxSequenceNumber;
|
||||
}
|
||||
|
||||
if (empty()) {
|
||||
return ret;
|
||||
}
|
||||
SnapshotImpl* s = &list_;
|
||||
while (s->next_ != &list_) {
|
||||
ret.push_back(s->next_->number_);
|
||||
|
||||
if (oldest_write_conflict_snapshot != nullptr &&
|
||||
*oldest_write_conflict_snapshot == kMaxSequenceNumber &&
|
||||
s->next_->is_write_conflict_boundary_) {
|
||||
// If this is the first write-conflict boundary snapshot in the list,
|
||||
// it is the oldest
|
||||
*oldest_write_conflict_snapshot = s->next_->number_;
|
||||
}
|
||||
|
||||
s = s->next_;
|
||||
}
|
||||
return ret;
|
||||
|
||||
+19
-14
@@ -88,7 +88,7 @@ Status TableCache::GetTableReader(
|
||||
const EnvOptions& env_options,
|
||||
const InternalKeyComparator& internal_comparator, const FileDescriptor& fd,
|
||||
bool sequential_mode, bool record_read_stats, HistogramImpl* file_read_hist,
|
||||
unique_ptr<TableReader>* table_reader) {
|
||||
unique_ptr<TableReader>* table_reader, bool skip_filters) {
|
||||
std::string fname =
|
||||
TableFileName(ioptions_.db_paths, fd.GetNumber(), fd.GetPathId());
|
||||
unique_ptr<RandomAccessFile> file;
|
||||
@@ -108,7 +108,8 @@ Status TableCache::GetTableReader(
|
||||
ioptions_.statistics, record_read_stats,
|
||||
file_read_hist));
|
||||
s = ioptions_.table_factory->NewTableReader(
|
||||
TableReaderOptions(ioptions_, env_options, internal_comparator),
|
||||
TableReaderOptions(ioptions_, env_options, internal_comparator,
|
||||
skip_filters),
|
||||
std::move(file_reader), fd.GetFileSize(), table_reader);
|
||||
TEST_SYNC_POINT("TableCache::GetTableReader:0");
|
||||
}
|
||||
@@ -119,7 +120,7 @@ Status TableCache::FindTable(const EnvOptions& env_options,
|
||||
const InternalKeyComparator& internal_comparator,
|
||||
const FileDescriptor& fd, Cache::Handle** handle,
|
||||
const bool no_io, bool record_read_stats,
|
||||
HistogramImpl* file_read_hist) {
|
||||
HistogramImpl* file_read_hist, bool skip_filters) {
|
||||
PERF_TIMER_GUARD(find_table_nanos);
|
||||
Status s;
|
||||
uint64_t number = fd.GetNumber();
|
||||
@@ -135,7 +136,7 @@ Status TableCache::FindTable(const EnvOptions& env_options,
|
||||
unique_ptr<TableReader> table_reader;
|
||||
s = GetTableReader(env_options, internal_comparator, fd,
|
||||
false /* sequential mode */, record_read_stats,
|
||||
file_read_hist, &table_reader);
|
||||
file_read_hist, &table_reader, skip_filters);
|
||||
if (!s.ok()) {
|
||||
assert(table_reader == nullptr);
|
||||
RecordTick(ioptions_.statistics, NO_FILE_ERRORS);
|
||||
@@ -153,7 +154,7 @@ InternalIterator* TableCache::NewIterator(
|
||||
const ReadOptions& options, const EnvOptions& env_options,
|
||||
const InternalKeyComparator& icomparator, const FileDescriptor& fd,
|
||||
TableReader** table_reader_ptr, HistogramImpl* file_read_hist,
|
||||
bool for_compaction, Arena* arena) {
|
||||
bool for_compaction, Arena* arena, bool skip_filters) {
|
||||
PERF_TIMER_GUARD(new_table_iterator_nanos);
|
||||
|
||||
if (table_reader_ptr != nullptr) {
|
||||
@@ -176,10 +177,10 @@ InternalIterator* TableCache::NewIterator(
|
||||
} else {
|
||||
table_reader = fd.table_reader;
|
||||
if (table_reader == nullptr) {
|
||||
Status s =
|
||||
FindTable(env_options, icomparator, fd, &handle,
|
||||
options.read_tier == kBlockCacheTier /* no_io */,
|
||||
!for_compaction /* record read_stats */, file_read_hist);
|
||||
Status s = FindTable(env_options, icomparator, fd, &handle,
|
||||
options.read_tier == kBlockCacheTier /* no_io */,
|
||||
!for_compaction /* record read_stats */,
|
||||
file_read_hist, skip_filters);
|
||||
if (!s.ok()) {
|
||||
return NewErrorInternalIterator(s, arena);
|
||||
}
|
||||
@@ -187,7 +188,8 @@ InternalIterator* TableCache::NewIterator(
|
||||
}
|
||||
}
|
||||
|
||||
InternalIterator* result = table_reader->NewIterator(options, arena);
|
||||
InternalIterator* result =
|
||||
table_reader->NewIterator(options, arena, skip_filters);
|
||||
|
||||
if (create_new_table_reader) {
|
||||
assert(handle == nullptr);
|
||||
@@ -209,7 +211,8 @@ InternalIterator* TableCache::NewIterator(
|
||||
Status TableCache::Get(const ReadOptions& options,
|
||||
const InternalKeyComparator& internal_comparator,
|
||||
const FileDescriptor& fd, const Slice& k,
|
||||
GetContext* get_context, HistogramImpl* file_read_hist) {
|
||||
GetContext* get_context, HistogramImpl* file_read_hist,
|
||||
bool skip_filters) {
|
||||
TableReader* t = fd.table_reader;
|
||||
Status s;
|
||||
Cache::Handle* handle = nullptr;
|
||||
@@ -219,7 +222,9 @@ Status TableCache::Get(const ReadOptions& options,
|
||||
IterKey row_cache_key;
|
||||
std::string row_cache_entry_buffer;
|
||||
|
||||
if (ioptions_.row_cache) {
|
||||
// Check row cache if enabled. Since row cache does not currently store
|
||||
// sequence numbers, we cannot use it if we need to fetch the sequence.
|
||||
if (ioptions_.row_cache && !get_context->NeedToReadSequence()) {
|
||||
uint64_t fd_number = fd.GetNumber();
|
||||
auto user_key = ExtractUserKey(k);
|
||||
// We use the user key as cache key instead of the internal key,
|
||||
@@ -256,14 +261,14 @@ Status TableCache::Get(const ReadOptions& options,
|
||||
if (!t) {
|
||||
s = FindTable(env_options_, internal_comparator, fd, &handle,
|
||||
options.read_tier == kBlockCacheTier /* no_io */,
|
||||
true /* record_read_stats */, file_read_hist);
|
||||
true /* record_read_stats */, file_read_hist, skip_filters);
|
||||
if (s.ok()) {
|
||||
t = GetTableReaderFromHandle(handle);
|
||||
}
|
||||
}
|
||||
if (s.ok()) {
|
||||
get_context->SetReplayLog(row_cache_entry); // nullptr if no cache.
|
||||
s = t->Get(options, k, get_context);
|
||||
s = t->Get(options, k, get_context, skip_filters);
|
||||
get_context->SetReplayLog(nullptr);
|
||||
if (handle != nullptr) {
|
||||
ReleaseHandle(handle);
|
||||
|
||||
+10
-4
@@ -44,30 +44,35 @@ class TableCache {
|
||||
// the returned iterator. The returned "*tableptr" object is owned by
|
||||
// the cache and should not be deleted, and is valid for as long as the
|
||||
// returned iterator is live.
|
||||
// @param skip_filters Disables loading/accessing the filter block
|
||||
InternalIterator* NewIterator(
|
||||
const ReadOptions& options, const EnvOptions& toptions,
|
||||
const InternalKeyComparator& internal_comparator,
|
||||
const FileDescriptor& file_fd, TableReader** table_reader_ptr = nullptr,
|
||||
HistogramImpl* file_read_hist = nullptr, bool for_compaction = false,
|
||||
Arena* arena = nullptr);
|
||||
Arena* arena = nullptr, bool skip_filters = false);
|
||||
|
||||
// If a seek to internal key "k" in specified file finds an entry,
|
||||
// call (*handle_result)(arg, found_key, found_value) repeatedly until
|
||||
// it returns false.
|
||||
// @param skip_filters Disables loading/accessing the filter block
|
||||
Status Get(const ReadOptions& options,
|
||||
const InternalKeyComparator& internal_comparator,
|
||||
const FileDescriptor& file_fd, const Slice& k,
|
||||
GetContext* get_context, HistogramImpl* file_read_hist = nullptr);
|
||||
GetContext* get_context, HistogramImpl* file_read_hist = nullptr,
|
||||
bool skip_filters = false);
|
||||
|
||||
// Evict any entry for the specified file number
|
||||
static void Evict(Cache* cache, uint64_t file_number);
|
||||
|
||||
// Find table reader
|
||||
// @param skip_filters Disables loading/accessing the filter block
|
||||
Status FindTable(const EnvOptions& toptions,
|
||||
const InternalKeyComparator& internal_comparator,
|
||||
const FileDescriptor& file_fd, Cache::Handle**,
|
||||
const bool no_io = false, bool record_read_stats = true,
|
||||
HistogramImpl* file_read_hist = nullptr);
|
||||
HistogramImpl* file_read_hist = nullptr,
|
||||
bool skip_filters = false);
|
||||
|
||||
// Get TableReader from a cache handle.
|
||||
TableReader* GetTableReaderFromHandle(Cache::Handle* handle);
|
||||
@@ -100,7 +105,8 @@ class TableCache {
|
||||
const InternalKeyComparator& internal_comparator,
|
||||
const FileDescriptor& fd, bool sequential_mode,
|
||||
bool record_read_stats, HistogramImpl* file_read_hist,
|
||||
unique_ptr<TableReader>* table_reader);
|
||||
unique_ptr<TableReader>* table_reader,
|
||||
bool skip_filters = false);
|
||||
|
||||
const ImmutableCFOptions& ioptions_;
|
||||
const EnvOptions& env_options_;
|
||||
|
||||
@@ -165,7 +165,7 @@ class VersionBuilder::Rep {
|
||||
for (int l = 0; !found && l < base_vstorage_->num_levels(); l++) {
|
||||
const std::vector<FileMetaData*>& base_files =
|
||||
base_vstorage_->LevelFiles(l);
|
||||
for (unsigned int i = 0; i < base_files.size(); i++) {
|
||||
for (size_t i = 0; i < base_files.size(); i++) {
|
||||
FileMetaData* f = base_files[i];
|
||||
if (f->fd.GetNumber() == number) {
|
||||
found = true;
|
||||
@@ -338,7 +338,8 @@ class VersionBuilder::Rep {
|
||||
|
||||
void MaybeAddFile(VersionStorageInfo* vstorage, int level, FileMetaData* f) {
|
||||
if (levels_[level].deleted_files.count(f->fd.GetNumber()) > 0) {
|
||||
// File is deleted: do nothing
|
||||
// f is to-be-delected table file
|
||||
vstorage->RemoveCurrentStats(f);
|
||||
} else {
|
||||
vstorage->AddFile(level, f, info_log_);
|
||||
}
|
||||
|
||||
+2
-1
@@ -55,7 +55,8 @@ struct FileDescriptor {
|
||||
return packed_number_and_path_id & kFileNumberMask;
|
||||
}
|
||||
uint32_t GetPathId() const {
|
||||
return packed_number_and_path_id / (kFileNumberMask + 1);
|
||||
return static_cast<uint32_t>(
|
||||
packed_number_and_path_id / (kFileNumberMask + 1));
|
||||
}
|
||||
uint64_t GetFileSize() const { return file_size; }
|
||||
};
|
||||
|
||||
+76
-40
@@ -475,19 +475,21 @@ class LevelFileNumIterator : public InternalIterator {
|
||||
|
||||
class LevelFileIteratorState : public TwoLevelIteratorState {
|
||||
public:
|
||||
// @param skip_filters Disables loading/accessing the filter block
|
||||
LevelFileIteratorState(TableCache* table_cache,
|
||||
const ReadOptions& read_options,
|
||||
const EnvOptions& env_options,
|
||||
const InternalKeyComparator& icomparator,
|
||||
HistogramImpl* file_read_hist, bool for_compaction,
|
||||
bool prefix_enabled)
|
||||
bool prefix_enabled, bool skip_filters)
|
||||
: TwoLevelIteratorState(prefix_enabled),
|
||||
table_cache_(table_cache),
|
||||
read_options_(read_options),
|
||||
env_options_(env_options),
|
||||
icomparator_(icomparator),
|
||||
file_read_hist_(file_read_hist),
|
||||
for_compaction_(for_compaction) {}
|
||||
for_compaction_(for_compaction),
|
||||
skip_filters_(skip_filters) {}
|
||||
|
||||
InternalIterator* NewSecondaryIterator(const Slice& meta_handle) override {
|
||||
if (meta_handle.size() != sizeof(FileDescriptor)) {
|
||||
@@ -499,7 +501,7 @@ class LevelFileIteratorState : public TwoLevelIteratorState {
|
||||
return table_cache_->NewIterator(
|
||||
read_options_, env_options_, icomparator_, *fd,
|
||||
nullptr /* don't need reference to table*/, file_read_hist_,
|
||||
for_compaction_);
|
||||
for_compaction_, nullptr /* arena */, skip_filters_);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -514,6 +516,7 @@ class LevelFileIteratorState : public TwoLevelIteratorState {
|
||||
const InternalKeyComparator& icomparator_;
|
||||
HistogramImpl* file_read_hist_;
|
||||
bool for_compaction_;
|
||||
bool skip_filters_;
|
||||
};
|
||||
|
||||
// A wrapper of version builder which references the current version in
|
||||
@@ -738,24 +741,27 @@ uint64_t VersionStorageInfo::GetEstimatedActiveKeys() const {
|
||||
// (2) keys are directly overwritten
|
||||
// (3) deletion on non-existing keys
|
||||
// (4) low number of samples
|
||||
if (num_samples_ == 0) {
|
||||
if (current_num_samples_ == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (accumulated_num_non_deletions_ <= accumulated_num_deletions_) {
|
||||
if (current_num_non_deletions_ <= current_num_deletions_) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint64_t est = accumulated_num_non_deletions_ - accumulated_num_deletions_;
|
||||
uint64_t est = current_num_non_deletions_ - current_num_deletions_;
|
||||
|
||||
uint64_t file_count = 0;
|
||||
for (int level = 0; level < num_levels_; ++level) {
|
||||
file_count += files_[level].size();
|
||||
}
|
||||
|
||||
if (num_samples_ < file_count) {
|
||||
if (current_num_samples_ < file_count) {
|
||||
// casting to avoid overflowing
|
||||
return (est * static_cast<double>(file_count) / num_samples_);
|
||||
return
|
||||
static_cast<uint64_t>(
|
||||
(est * static_cast<double>(file_count) / current_num_samples_)
|
||||
);
|
||||
} else {
|
||||
return est;
|
||||
}
|
||||
@@ -792,7 +798,8 @@ void Version::AddIterators(const ReadOptions& read_options,
|
||||
cfd_->internal_comparator(),
|
||||
cfd_->internal_stats()->GetFileReadHist(level),
|
||||
false /* for_compaction */,
|
||||
cfd_->ioptions()->prefix_extractor != nullptr);
|
||||
cfd_->ioptions()->prefix_extractor != nullptr,
|
||||
IsFilterSkipped(level));
|
||||
mem = arena->AllocateAligned(sizeof(LevelFileNumIterator));
|
||||
auto* first_level_iter = new (mem) LevelFileNumIterator(
|
||||
cfd_->internal_comparator(), &storage_info_.LevelFilesBrief(level));
|
||||
@@ -826,7 +833,9 @@ VersionStorageInfo::VersionStorageInfo(
|
||||
accumulated_raw_value_size_(0),
|
||||
accumulated_num_non_deletions_(0),
|
||||
accumulated_num_deletions_(0),
|
||||
num_samples_(0),
|
||||
current_num_non_deletions_(0),
|
||||
current_num_deletions_(0),
|
||||
current_num_samples_(0),
|
||||
estimated_compaction_needed_bytes_(0),
|
||||
finalized_(false) {
|
||||
if (ref_vstorage != nullptr) {
|
||||
@@ -836,7 +845,9 @@ VersionStorageInfo::VersionStorageInfo(
|
||||
accumulated_num_non_deletions_ =
|
||||
ref_vstorage->accumulated_num_non_deletions_;
|
||||
accumulated_num_deletions_ = ref_vstorage->accumulated_num_deletions_;
|
||||
num_samples_ = ref_vstorage->num_samples_;
|
||||
current_num_non_deletions_ = ref_vstorage->current_num_non_deletions_;
|
||||
current_num_deletions_ = ref_vstorage->current_num_deletions_;
|
||||
current_num_samples_ = ref_vstorage->current_num_samples_;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -864,21 +875,24 @@ Version::Version(ColumnFamilyData* column_family_data, VersionSet* vset,
|
||||
refs_(0),
|
||||
version_number_(version_number) {}
|
||||
|
||||
void Version::Get(const ReadOptions& read_options,
|
||||
const LookupKey& k,
|
||||
std::string* value,
|
||||
Status* status,
|
||||
MergeContext* merge_context,
|
||||
bool* value_found) {
|
||||
void Version::Get(const ReadOptions& read_options, const LookupKey& k,
|
||||
std::string* value, Status* status,
|
||||
MergeContext* merge_context, bool* value_found,
|
||||
bool* key_exists, SequenceNumber* seq) {
|
||||
Slice ikey = k.internal_key();
|
||||
Slice user_key = k.user_key();
|
||||
|
||||
assert(status->ok() || status->IsMergeInProgress());
|
||||
|
||||
if (key_exists != nullptr) {
|
||||
// will falsify below if not found
|
||||
*key_exists = true;
|
||||
}
|
||||
|
||||
GetContext get_context(
|
||||
user_comparator(), merge_operator_, info_log_, db_statistics_,
|
||||
status->ok() ? GetContext::kNotFound : GetContext::kMerge, user_key,
|
||||
value, value_found, merge_context, this->env_);
|
||||
value, value_found, merge_context, this->env_, seq);
|
||||
|
||||
FilePicker fp(
|
||||
storage_info_.files_, user_key, ikey, &storage_info_.level_files_brief_,
|
||||
@@ -888,7 +902,8 @@ void Version::Get(const ReadOptions& read_options,
|
||||
while (f != nullptr) {
|
||||
*status = table_cache_->Get(
|
||||
read_options, *internal_comparator(), f->fd, ikey, &get_context,
|
||||
cfd_->internal_stats()->GetFileReadHist(fp.GetHitFileLevel()));
|
||||
cfd_->internal_stats()->GetFileReadHist(fp.GetHitFileLevel()),
|
||||
IsFilterSkipped(static_cast<int>(fp.GetHitFileLevel())));
|
||||
// TODO: examine the behavior for corrupted key
|
||||
if (!status->ok()) {
|
||||
return;
|
||||
@@ -938,10 +953,20 @@ void Version::Get(const ReadOptions& read_options,
|
||||
user_key);
|
||||
}
|
||||
} else {
|
||||
if (key_exists != nullptr) {
|
||||
*key_exists = false;
|
||||
}
|
||||
*status = Status::NotFound(); // Use an empty error message for speed
|
||||
}
|
||||
}
|
||||
|
||||
bool Version::IsFilterSkipped(int level) {
|
||||
// Reaching the bottom level implies misses at all upper levels, so we'll
|
||||
// skip checking the filters when we predict a hit.
|
||||
return cfd_->ioptions()->optimize_filters_for_hits && level > 0 &&
|
||||
level == storage_info_.num_non_empty_levels() - 1;
|
||||
}
|
||||
|
||||
void VersionStorageInfo::GenerateLevelFilesBrief() {
|
||||
level_files_brief_.resize(num_non_empty_levels_);
|
||||
for (int level = 0; level < num_non_empty_levels_; level++) {
|
||||
@@ -993,7 +1018,20 @@ void VersionStorageInfo::UpdateAccumulatedStats(FileMetaData* file_meta) {
|
||||
accumulated_num_non_deletions_ +=
|
||||
file_meta->num_entries - file_meta->num_deletions;
|
||||
accumulated_num_deletions_ += file_meta->num_deletions;
|
||||
num_samples_++;
|
||||
|
||||
current_num_non_deletions_ +=
|
||||
file_meta->num_entries - file_meta->num_deletions;
|
||||
current_num_deletions_ += file_meta->num_deletions;
|
||||
current_num_samples_++;
|
||||
}
|
||||
|
||||
void VersionStorageInfo::RemoveCurrentStats(FileMetaData* file_meta) {
|
||||
if (file_meta->init_stats_from_file) {
|
||||
current_num_non_deletions_ -=
|
||||
file_meta->num_entries - file_meta->num_deletions;
|
||||
current_num_deletions_ -= file_meta->num_deletions;
|
||||
current_num_samples_--;
|
||||
}
|
||||
}
|
||||
|
||||
void Version::UpdateAccumulatedStats(bool update_stats) {
|
||||
@@ -1084,6 +1122,7 @@ void VersionStorageInfo::EstimateCompactionBytesNeeded(
|
||||
const MutableCFOptions& mutable_cf_options) {
|
||||
// Only implemented for level-based compaction
|
||||
if (compaction_style_ != kCompactionStyleLevel) {
|
||||
estimated_compaction_needed_bytes_ = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1139,9 +1178,6 @@ void VersionStorageInfo::EstimateCompactionBytesNeeded(
|
||||
void VersionStorageInfo::ComputeCompactionScore(
|
||||
const MutableCFOptions& mutable_cf_options,
|
||||
const CompactionOptionsFIFO& compaction_options_fifo) {
|
||||
double max_score = 0;
|
||||
int max_score_level = 0;
|
||||
|
||||
for (int level = 0; level <= MaxInputLevel(); level++) {
|
||||
double score;
|
||||
if (level == 0) {
|
||||
@@ -1192,19 +1228,11 @@ void VersionStorageInfo::ComputeCompactionScore(
|
||||
}
|
||||
score = static_cast<double>(level_bytes_no_compacting) /
|
||||
MaxBytesForLevel(level);
|
||||
if (max_score < score) {
|
||||
max_score = score;
|
||||
max_score_level = level;
|
||||
}
|
||||
}
|
||||
compaction_level_[level] = level;
|
||||
compaction_score_[level] = score;
|
||||
}
|
||||
|
||||
// update the max compaction score in levels 1 to n-1
|
||||
max_compaction_score_ = max_score;
|
||||
max_compaction_score_level_ = max_score_level;
|
||||
|
||||
// sort all the levels based on their score. Higher scores get listed
|
||||
// first. Use bubble sort because the number of entries are small.
|
||||
for (int i = 0; i < num_levels() - 2; i++) {
|
||||
@@ -1250,7 +1278,7 @@ namespace {
|
||||
|
||||
// used to sort files by size
|
||||
struct Fsize {
|
||||
int index;
|
||||
size_t index;
|
||||
FileMetaData* file;
|
||||
};
|
||||
|
||||
@@ -1358,7 +1386,7 @@ void VersionStorageInfo::UpdateFilesByCompactionPri(
|
||||
|
||||
// populate a temp vector for sorting based on size
|
||||
std::vector<Fsize> temp(files.size());
|
||||
for (unsigned int i = 0; i < files.size(); i++) {
|
||||
for (size_t i = 0; i < files.size(); i++) {
|
||||
temp[i].index = i;
|
||||
temp[i].file = files[i];
|
||||
}
|
||||
@@ -1369,24 +1397,30 @@ void VersionStorageInfo::UpdateFilesByCompactionPri(
|
||||
num = temp.size();
|
||||
}
|
||||
switch (mutable_cf_options.compaction_pri) {
|
||||
case kCompactionPriByCompensatedSize:
|
||||
case kByCompensatedSize:
|
||||
std::partial_sort(temp.begin(), temp.begin() + num, temp.end(),
|
||||
CompareCompensatedSizeDescending);
|
||||
break;
|
||||
case kCompactionPriByLargestSeq:
|
||||
case kOldestLargestSeqFirst:
|
||||
std::sort(temp.begin(), temp.end(),
|
||||
[this](const Fsize& f1, const Fsize& f2) -> bool {
|
||||
return f1.file->largest_seqno < f2.file->largest_seqno;
|
||||
});
|
||||
break;
|
||||
case kOldestSmallestSeqFirst:
|
||||
std::sort(temp.begin(), temp.end(),
|
||||
[this](const Fsize& f1, const Fsize& f2) -> bool {
|
||||
return f1.file->smallest_seqno < f2.file->smallest_seqno;
|
||||
});
|
||||
break;
|
||||
default:
|
||||
assert(false);
|
||||
}
|
||||
assert(temp.size() == files.size());
|
||||
|
||||
// initialize files_by_compaction_pri_
|
||||
for (unsigned int i = 0; i < temp.size(); i++) {
|
||||
files_by_compaction_pri.push_back(temp[i].index);
|
||||
for (size_t i = 0; i < temp.size(); i++) {
|
||||
files_by_compaction_pri.push_back(static_cast<int>(temp[i].index));
|
||||
}
|
||||
next_file_to_compact_by_size_[level] = 0;
|
||||
assert(files_[level].size() == files_by_compaction_pri_[level].size());
|
||||
@@ -2106,7 +2140,8 @@ Status VersionSet::LogAndApply(ColumnFamilyData* column_family_data,
|
||||
// unlimited table cache. Pre-load table handle now.
|
||||
// Need to do it out of the mutex.
|
||||
builder_guard->version_builder()->LoadTableHandlers(
|
||||
column_family_data->internal_stats());
|
||||
column_family_data->internal_stats(),
|
||||
column_family_data->ioptions()->optimize_filters_for_hits);
|
||||
}
|
||||
|
||||
// This is fine because everything inside of this block is serialized --
|
||||
@@ -3252,7 +3287,8 @@ InternalIterator* VersionSet::MakeInputIterator(Compaction* c) {
|
||||
cfd->table_cache(), read_options, env_options_,
|
||||
cfd->internal_comparator(),
|
||||
nullptr /* no per level latency histogram */,
|
||||
true /* for_compaction */, false /* prefix enabled */),
|
||||
true /* for_compaction */, false /* prefix enabled */,
|
||||
false /* skip_filters */),
|
||||
new LevelFileNumIterator(cfd->internal_comparator(),
|
||||
c->input_levels(which)));
|
||||
}
|
||||
@@ -3298,7 +3334,7 @@ bool VersionSet::VerifyCompactionFileConsistency(Compaction* c) {
|
||||
for (size_t i = 0; i < c->num_input_files(input); ++i) {
|
||||
uint64_t number = c->input(input, i)->fd.GetNumber();
|
||||
bool found = false;
|
||||
for (unsigned int j = 0; j < vstorage->files_[level].size(); j++) {
|
||||
for (size_t j = 0; j < vstorage->files_[level].size(); j++) {
|
||||
FileMetaData* f = vstorage->files_[level][j];
|
||||
if (f->fd.GetNumber() == number) {
|
||||
found = true;
|
||||
|
||||
+34
-12
@@ -111,6 +111,9 @@ class VersionStorageInfo {
|
||||
// Update the accumulated stats from a file-meta.
|
||||
void UpdateAccumulatedStats(FileMetaData* file_meta);
|
||||
|
||||
// Decrease the current stat form a to-be-delected file-meta
|
||||
void RemoveCurrentStats(FileMetaData* file_meta);
|
||||
|
||||
void ComputeCompensatedSizes();
|
||||
|
||||
// Updates internal structures that keep track of compaction scores
|
||||
@@ -143,12 +146,6 @@ class VersionStorageInfo {
|
||||
|
||||
int MaxInputLevel() const;
|
||||
|
||||
// Returns the maxmimum compaction score for levels 1 to max
|
||||
double max_compaction_score() const { return max_compaction_score_; }
|
||||
|
||||
// See field declaration
|
||||
int max_compaction_score_level() const { return max_compaction_score_level_; }
|
||||
|
||||
// Return level number that has idx'th highest score
|
||||
int CompactionScoreLevel(int idx) const { return compaction_level_[idx]; }
|
||||
|
||||
@@ -328,6 +325,10 @@ class VersionStorageInfo {
|
||||
return estimated_compaction_needed_bytes_;
|
||||
}
|
||||
|
||||
void TEST_set_estimated_compaction_needed_bytes(uint64_t v) {
|
||||
estimated_compaction_needed_bytes_ = v;
|
||||
}
|
||||
|
||||
private:
|
||||
const InternalKeyComparator* internal_comparator_;
|
||||
const Comparator* user_comparator_;
|
||||
@@ -384,8 +385,6 @@ class VersionStorageInfo {
|
||||
// These are used to pick the best compaction level
|
||||
std::vector<double> compaction_score_;
|
||||
std::vector<int> compaction_level_;
|
||||
double max_compaction_score_ = 0.0; // max score in l1 to ln-1
|
||||
int max_compaction_score_level_ = 0; // level on which max score occurs
|
||||
int l0_delay_trigger_count_ = 0; // Count used to trigger slow down and stop
|
||||
// for number of L0 files.
|
||||
|
||||
@@ -400,8 +399,12 @@ class VersionStorageInfo {
|
||||
uint64_t accumulated_num_non_deletions_;
|
||||
// total number of deletion entries
|
||||
uint64_t accumulated_num_deletions_;
|
||||
// the number of samples
|
||||
uint64_t num_samples_;
|
||||
// current number of non_deletion entries
|
||||
uint64_t current_num_non_deletions_;
|
||||
// current number of delection entries
|
||||
uint64_t current_num_deletions_;
|
||||
// current number of file samples
|
||||
uint64_t current_num_samples_;
|
||||
// Estimated bytes needed to be compacted until all levels' size is down to
|
||||
// target sizes.
|
||||
uint64_t estimated_compaction_needed_bytes_;
|
||||
@@ -425,11 +428,24 @@ class Version {
|
||||
|
||||
// Lookup the value for key. If found, store it in *val and
|
||||
// return OK. Else return a non-OK status.
|
||||
// Uses *operands to store merge_operator operations to apply later
|
||||
// Uses *operands to store merge_operator operations to apply later.
|
||||
//
|
||||
// If the ReadOptions.read_tier is set to do a read-only fetch, then
|
||||
// *value_found will be set to false if it cannot be determined whether
|
||||
// this value exists without doing IO.
|
||||
//
|
||||
// If the key is Deleted, *status will be set to NotFound and
|
||||
// *key_exists will be set to true.
|
||||
// If no key was found, *status will be set to NotFound and
|
||||
// *key_exists will be set to false.
|
||||
// If seq is non-null, *seq will be set to the sequence number found
|
||||
// for the key if a key was found.
|
||||
//
|
||||
// REQUIRES: lock is not held
|
||||
void Get(const ReadOptions&, const LookupKey& key, std::string* val,
|
||||
Status* status, MergeContext* merge_context,
|
||||
bool* value_found = nullptr);
|
||||
bool* value_found = nullptr, bool* key_exists = nullptr,
|
||||
SequenceNumber* seq = nullptr);
|
||||
|
||||
// Loads some stats information from files. Call without mutex held. It needs
|
||||
// to be called before applying the version to the version set.
|
||||
@@ -510,6 +526,12 @@ class Version {
|
||||
InternalIterator* level_iter,
|
||||
const Slice& internal_prefix) const;
|
||||
|
||||
// Returns true if the filter blocks in the specified level will not be
|
||||
// checked during read operations. In certain cases (trivial move or preload),
|
||||
// the filter block may already be cached, but we still do not access it such
|
||||
// that it eventually expires from the cache.
|
||||
bool IsFilterSkipped(int level);
|
||||
|
||||
// The helper function of UpdateAccumulatedStats, which may fill the missing
|
||||
// fields of file_mata from its associated TableProperties.
|
||||
// Returns true if it does initialize FileMetaData.
|
||||
|
||||
@@ -23,7 +23,7 @@ class GenerateLevelFilesBriefTest : public testing::Test {
|
||||
GenerateLevelFilesBriefTest() { }
|
||||
|
||||
~GenerateLevelFilesBriefTest() {
|
||||
for (unsigned int i = 0; i < files_.size(); i++) {
|
||||
for (size_t i = 0; i < files_.size(); i++) {
|
||||
delete files_[i];
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -329,8 +329,8 @@ Status WalManager::GetSortedWalsOfType(const std::string& path,
|
||||
return s;
|
||||
}
|
||||
|
||||
log_files.push_back(std::move(std::unique_ptr<LogFile>(
|
||||
new LogFileImpl(number, log_type, sequence, size_bytes))));
|
||||
log_files.push_back(std::unique_ptr<LogFile>(
|
||||
new LogFileImpl(number, log_type, sequence, size_bytes)));
|
||||
}
|
||||
}
|
||||
CompareLogByPointer compare_log_files;
|
||||
|
||||
@@ -95,7 +95,7 @@ class WalManagerTest : public testing::Test {
|
||||
Status status = wal_manager_->GetUpdatesSince(
|
||||
seq, &iter, TransactionLogIterator::ReadOptions(), versions_.get());
|
||||
EXPECT_OK(status);
|
||||
return std::move(iter);
|
||||
return iter;
|
||||
}
|
||||
|
||||
std::unique_ptr<MockEnv> env_;
|
||||
@@ -184,7 +184,7 @@ std::vector<std::uint64_t> ListSpecificFiles(
|
||||
}
|
||||
}
|
||||
}
|
||||
return std::move(file_numbers);
|
||||
return file_numbers;
|
||||
}
|
||||
|
||||
int CountRecords(TransactionLogIterator* iter) {
|
||||
|
||||
+254
-81
@@ -28,10 +28,12 @@
|
||||
|
||||
#include <stack>
|
||||
#include <stdexcept>
|
||||
#include <vector>
|
||||
|
||||
#include "db/column_family.h"
|
||||
#include "db/db_impl.h"
|
||||
#include "db/dbformat.h"
|
||||
#include "db/flush_scheduler.h"
|
||||
#include "db/memtable.h"
|
||||
#include "db/snapshot_impl.h"
|
||||
#include "db/write_batch_internal.h"
|
||||
@@ -42,30 +44,95 @@
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
// anon namespace for file-local types
|
||||
namespace {
|
||||
|
||||
enum ContentFlags : uint32_t {
|
||||
DEFERRED = 1,
|
||||
HAS_PUT = 2,
|
||||
HAS_DELETE = 4,
|
||||
HAS_SINGLE_DELETE = 8,
|
||||
HAS_MERGE = 16,
|
||||
};
|
||||
|
||||
struct BatchContentClassifier : public WriteBatch::Handler {
|
||||
uint32_t content_flags = 0;
|
||||
|
||||
Status PutCF(uint32_t, const Slice&, const Slice&) override {
|
||||
content_flags |= ContentFlags::HAS_PUT;
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status DeleteCF(uint32_t, const Slice&) override {
|
||||
content_flags |= ContentFlags::HAS_DELETE;
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status SingleDeleteCF(uint32_t, const Slice&) override {
|
||||
content_flags |= ContentFlags::HAS_SINGLE_DELETE;
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status MergeCF(uint32_t, const Slice&, const Slice&) override {
|
||||
content_flags |= ContentFlags::HAS_MERGE;
|
||||
return Status::OK();
|
||||
}
|
||||
};
|
||||
|
||||
} // anon namespace
|
||||
|
||||
// WriteBatch header has an 8-byte sequence number followed by a 4-byte count.
|
||||
static const size_t kHeader = 12;
|
||||
|
||||
struct SavePoint {
|
||||
size_t size; // size of rep_
|
||||
int count; // count of elements in rep_
|
||||
SavePoint(size_t s, int c) : size(s), count(c) {}
|
||||
uint32_t content_flags;
|
||||
};
|
||||
|
||||
struct SavePoints {
|
||||
std::stack<SavePoint> stack;
|
||||
};
|
||||
|
||||
WriteBatch::WriteBatch(size_t reserved_bytes) : save_points_(nullptr) {
|
||||
WriteBatch::WriteBatch(size_t reserved_bytes)
|
||||
: save_points_(nullptr), content_flags_(0), rep_() {
|
||||
rep_.reserve((reserved_bytes > kHeader) ? reserved_bytes : kHeader);
|
||||
Clear();
|
||||
rep_.resize(kHeader);
|
||||
}
|
||||
|
||||
WriteBatch::~WriteBatch() {
|
||||
if (save_points_ != nullptr) {
|
||||
delete save_points_;
|
||||
WriteBatch::WriteBatch(const std::string& rep)
|
||||
: save_points_(nullptr),
|
||||
content_flags_(ContentFlags::DEFERRED),
|
||||
rep_(rep) {}
|
||||
|
||||
WriteBatch::WriteBatch(const WriteBatch& src)
|
||||
: save_points_(src.save_points_),
|
||||
content_flags_(src.content_flags_.load(std::memory_order_relaxed)),
|
||||
rep_(src.rep_) {}
|
||||
|
||||
WriteBatch::WriteBatch(WriteBatch&& src)
|
||||
: save_points_(std::move(src.save_points_)),
|
||||
content_flags_(src.content_flags_.load(std::memory_order_relaxed)),
|
||||
rep_(std::move(src.rep_)) {}
|
||||
|
||||
WriteBatch& WriteBatch::operator=(const WriteBatch& src) {
|
||||
if (&src != this) {
|
||||
this->~WriteBatch();
|
||||
new (this) WriteBatch(src);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
WriteBatch& WriteBatch::operator=(WriteBatch&& src) {
|
||||
if (&src != this) {
|
||||
this->~WriteBatch();
|
||||
new (this) WriteBatch(std::move(src));
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
WriteBatch::~WriteBatch() { delete save_points_; }
|
||||
|
||||
WriteBatch::Handler::~Handler() { }
|
||||
|
||||
void WriteBatch::Handler::LogData(const Slice& blob) {
|
||||
@@ -81,6 +148,8 @@ void WriteBatch::Clear() {
|
||||
rep_.clear();
|
||||
rep_.resize(kHeader);
|
||||
|
||||
content_flags_.store(0, std::memory_order_relaxed);
|
||||
|
||||
if (save_points_ != nullptr) {
|
||||
while (!save_points_->stack.empty()) {
|
||||
save_points_->stack.pop();
|
||||
@@ -92,6 +161,38 @@ int WriteBatch::Count() const {
|
||||
return WriteBatchInternal::Count(this);
|
||||
}
|
||||
|
||||
uint32_t WriteBatch::ComputeContentFlags() const {
|
||||
auto rv = content_flags_.load(std::memory_order_relaxed);
|
||||
if ((rv & ContentFlags::DEFERRED) != 0) {
|
||||
BatchContentClassifier classifier;
|
||||
Iterate(&classifier);
|
||||
rv = classifier.content_flags;
|
||||
|
||||
// this method is conceptually const, because it is performing a lazy
|
||||
// computation that doesn't affect the abstract state of the batch.
|
||||
// content_flags_ is marked mutable so that we can perform the
|
||||
// following assignment
|
||||
content_flags_.store(rv, std::memory_order_relaxed);
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
bool WriteBatch::HasPut() const {
|
||||
return (ComputeContentFlags() & ContentFlags::HAS_PUT) != 0;
|
||||
}
|
||||
|
||||
bool WriteBatch::HasDelete() const {
|
||||
return (ComputeContentFlags() & ContentFlags::HAS_DELETE) != 0;
|
||||
}
|
||||
|
||||
bool WriteBatch::HasSingleDelete() const {
|
||||
return (ComputeContentFlags() & ContentFlags::HAS_SINGLE_DELETE) != 0;
|
||||
}
|
||||
|
||||
bool WriteBatch::HasMerge() const {
|
||||
return (ComputeContentFlags() & ContentFlags::HAS_MERGE) != 0;
|
||||
}
|
||||
|
||||
Status ReadRecordFromWriteBatch(Slice* input, char* tag,
|
||||
uint32_t* column_family, Slice* key,
|
||||
Slice* value, Slice* blob) {
|
||||
@@ -169,21 +270,29 @@ Status WriteBatch::Iterate(Handler* handler) const {
|
||||
switch (tag) {
|
||||
case kTypeColumnFamilyValue:
|
||||
case kTypeValue:
|
||||
assert(content_flags_.load(std::memory_order_relaxed) &
|
||||
(ContentFlags::DEFERRED | ContentFlags::HAS_PUT));
|
||||
s = handler->PutCF(column_family, key, value);
|
||||
found++;
|
||||
break;
|
||||
case kTypeColumnFamilyDeletion:
|
||||
case kTypeDeletion:
|
||||
assert(content_flags_.load(std::memory_order_relaxed) &
|
||||
(ContentFlags::DEFERRED | ContentFlags::HAS_DELETE));
|
||||
s = handler->DeleteCF(column_family, key);
|
||||
found++;
|
||||
break;
|
||||
case kTypeColumnFamilySingleDeletion:
|
||||
case kTypeSingleDeletion:
|
||||
assert(content_flags_.load(std::memory_order_relaxed) &
|
||||
(ContentFlags::DEFERRED | ContentFlags::HAS_SINGLE_DELETE));
|
||||
s = handler->SingleDeleteCF(column_family, key);
|
||||
found++;
|
||||
break;
|
||||
case kTypeColumnFamilyMerge:
|
||||
case kTypeMerge:
|
||||
assert(content_flags_.load(std::memory_order_relaxed) &
|
||||
(ContentFlags::DEFERRED | ContentFlags::HAS_MERGE));
|
||||
s = handler->MergeCF(column_family, key, value);
|
||||
found++;
|
||||
break;
|
||||
@@ -233,6 +342,9 @@ void WriteBatchInternal::Put(WriteBatch* b, uint32_t column_family_id,
|
||||
}
|
||||
PutLengthPrefixedSlice(&b->rep_, key);
|
||||
PutLengthPrefixedSlice(&b->rep_, value);
|
||||
b->content_flags_.store(
|
||||
b->content_flags_.load(std::memory_order_relaxed) | ContentFlags::HAS_PUT,
|
||||
std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
void WriteBatch::Put(ColumnFamilyHandle* column_family, const Slice& key,
|
||||
@@ -251,6 +363,9 @@ void WriteBatchInternal::Put(WriteBatch* b, uint32_t column_family_id,
|
||||
}
|
||||
PutLengthPrefixedSliceParts(&b->rep_, key);
|
||||
PutLengthPrefixedSliceParts(&b->rep_, value);
|
||||
b->content_flags_.store(
|
||||
b->content_flags_.load(std::memory_order_relaxed) | ContentFlags::HAS_PUT,
|
||||
std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
void WriteBatch::Put(ColumnFamilyHandle* column_family, const SliceParts& key,
|
||||
@@ -268,6 +383,9 @@ void WriteBatchInternal::Delete(WriteBatch* b, uint32_t column_family_id,
|
||||
PutVarint32(&b->rep_, column_family_id);
|
||||
}
|
||||
PutLengthPrefixedSlice(&b->rep_, key);
|
||||
b->content_flags_.store(b->content_flags_.load(std::memory_order_relaxed) |
|
||||
ContentFlags::HAS_DELETE,
|
||||
std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
void WriteBatch::Delete(ColumnFamilyHandle* column_family, const Slice& key) {
|
||||
@@ -284,6 +402,9 @@ void WriteBatchInternal::Delete(WriteBatch* b, uint32_t column_family_id,
|
||||
PutVarint32(&b->rep_, column_family_id);
|
||||
}
|
||||
PutLengthPrefixedSliceParts(&b->rep_, key);
|
||||
b->content_flags_.store(b->content_flags_.load(std::memory_order_relaxed) |
|
||||
ContentFlags::HAS_DELETE,
|
||||
std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
void WriteBatch::Delete(ColumnFamilyHandle* column_family,
|
||||
@@ -301,6 +422,9 @@ void WriteBatchInternal::SingleDelete(WriteBatch* b, uint32_t column_family_id,
|
||||
PutVarint32(&b->rep_, column_family_id);
|
||||
}
|
||||
PutLengthPrefixedSlice(&b->rep_, key);
|
||||
b->content_flags_.store(b->content_flags_.load(std::memory_order_relaxed) |
|
||||
ContentFlags::HAS_SINGLE_DELETE,
|
||||
std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
void WriteBatch::SingleDelete(ColumnFamilyHandle* column_family,
|
||||
@@ -318,6 +442,9 @@ void WriteBatchInternal::SingleDelete(WriteBatch* b, uint32_t column_family_id,
|
||||
PutVarint32(&b->rep_, column_family_id);
|
||||
}
|
||||
PutLengthPrefixedSliceParts(&b->rep_, key);
|
||||
b->content_flags_.store(b->content_flags_.load(std::memory_order_relaxed) |
|
||||
ContentFlags::HAS_SINGLE_DELETE,
|
||||
std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
void WriteBatch::SingleDelete(ColumnFamilyHandle* column_family,
|
||||
@@ -336,6 +463,9 @@ void WriteBatchInternal::Merge(WriteBatch* b, uint32_t column_family_id,
|
||||
}
|
||||
PutLengthPrefixedSlice(&b->rep_, key);
|
||||
PutLengthPrefixedSlice(&b->rep_, value);
|
||||
b->content_flags_.store(b->content_flags_.load(std::memory_order_relaxed) |
|
||||
ContentFlags::HAS_MERGE,
|
||||
std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
void WriteBatch::Merge(ColumnFamilyHandle* column_family, const Slice& key,
|
||||
@@ -355,6 +485,9 @@ void WriteBatchInternal::Merge(WriteBatch* b, uint32_t column_family_id,
|
||||
}
|
||||
PutLengthPrefixedSliceParts(&b->rep_, key);
|
||||
PutLengthPrefixedSliceParts(&b->rep_, value);
|
||||
b->content_flags_.store(b->content_flags_.load(std::memory_order_relaxed) |
|
||||
ContentFlags::HAS_MERGE,
|
||||
std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
void WriteBatch::Merge(ColumnFamilyHandle* column_family,
|
||||
@@ -374,7 +507,8 @@ void WriteBatch::SetSavePoint() {
|
||||
save_points_ = new SavePoints();
|
||||
}
|
||||
// Record length and count of current batch of writes.
|
||||
save_points_->stack.push(SavePoint(GetDataSize(), Count()));
|
||||
save_points_->stack.push(SavePoint{
|
||||
GetDataSize(), Count(), content_flags_.load(std::memory_order_relaxed)});
|
||||
}
|
||||
|
||||
Status WriteBatch::RollbackToSavePoint() {
|
||||
@@ -387,6 +521,7 @@ Status WriteBatch::RollbackToSavePoint() {
|
||||
save_points_->stack.pop();
|
||||
|
||||
assert(savepoint.size <= rep_.size());
|
||||
assert(savepoint.count <= Count());
|
||||
|
||||
if (savepoint.size == rep_.size()) {
|
||||
// No changes to rollback
|
||||
@@ -396,41 +531,49 @@ Status WriteBatch::RollbackToSavePoint() {
|
||||
} else {
|
||||
rep_.resize(savepoint.size);
|
||||
WriteBatchInternal::SetCount(this, savepoint.count);
|
||||
content_flags_.store(savepoint.content_flags, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
namespace {
|
||||
// This class can *only* be used from a single-threaded write thread, because it
|
||||
// calls ColumnFamilyMemTablesImpl::Seek()
|
||||
class MemTableInserter : public WriteBatch::Handler {
|
||||
public:
|
||||
SequenceNumber sequence_;
|
||||
ColumnFamilyMemTables* cf_mems_;
|
||||
bool ignore_missing_column_families_;
|
||||
uint64_t log_number_;
|
||||
ColumnFamilyMemTables* const cf_mems_;
|
||||
FlushScheduler* const flush_scheduler_;
|
||||
const bool ignore_missing_column_families_;
|
||||
const uint64_t log_number_;
|
||||
DBImpl* db_;
|
||||
const bool dont_filter_deletes_;
|
||||
const bool concurrent_memtable_writes_;
|
||||
|
||||
// cf_mems should not be shared with concurrent inserters
|
||||
MemTableInserter(SequenceNumber sequence, ColumnFamilyMemTables* cf_mems,
|
||||
FlushScheduler* flush_scheduler,
|
||||
bool ignore_missing_column_families, uint64_t log_number,
|
||||
DB* db, const bool dont_filter_deletes)
|
||||
DB* db, const bool dont_filter_deletes,
|
||||
bool concurrent_memtable_writes)
|
||||
: sequence_(sequence),
|
||||
cf_mems_(cf_mems),
|
||||
flush_scheduler_(flush_scheduler),
|
||||
ignore_missing_column_families_(ignore_missing_column_families),
|
||||
log_number_(log_number),
|
||||
db_(reinterpret_cast<DBImpl*>(db)),
|
||||
dont_filter_deletes_(dont_filter_deletes) {
|
||||
assert(cf_mems);
|
||||
dont_filter_deletes_(dont_filter_deletes),
|
||||
concurrent_memtable_writes_(concurrent_memtable_writes) {
|
||||
assert(cf_mems_);
|
||||
if (!dont_filter_deletes_) {
|
||||
assert(db_);
|
||||
}
|
||||
}
|
||||
|
||||
bool SeekToColumnFamily(uint32_t column_family_id, Status* s) {
|
||||
// We are only allowed to call this from a single-threaded write thread
|
||||
// (or while holding DB mutex)
|
||||
// If we are in a concurrent mode, it is the caller's responsibility
|
||||
// to clone the original ColumnFamilyMemTables so that each thread
|
||||
// has its own instance. Otherwise, it must be guaranteed that there
|
||||
// is no concurrent access
|
||||
bool found = cf_mems_->Seek(column_family_id);
|
||||
if (!found) {
|
||||
if (ignore_missing_column_families_) {
|
||||
@@ -453,6 +596,7 @@ class MemTableInserter : public WriteBatch::Handler {
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual Status PutCF(uint32_t column_family_id, const Slice& key,
|
||||
const Slice& value) override {
|
||||
Status seek_status;
|
||||
@@ -463,11 +607,13 @@ class MemTableInserter : public WriteBatch::Handler {
|
||||
MemTable* mem = cf_mems_->GetMemTable();
|
||||
auto* moptions = mem->GetMemTableOptions();
|
||||
if (!moptions->inplace_update_support) {
|
||||
mem->Add(sequence_, kTypeValue, key, value);
|
||||
mem->Add(sequence_, kTypeValue, key, value, concurrent_memtable_writes_);
|
||||
} else if (moptions->inplace_callback == nullptr) {
|
||||
assert(!concurrent_memtable_writes_);
|
||||
mem->Update(sequence_, key, value);
|
||||
RecordTick(moptions->statistics, NUMBER_KEYS_UPDATED);
|
||||
} else {
|
||||
assert(!concurrent_memtable_writes_);
|
||||
if (mem->UpdateCallback(sequence_, key, value)) {
|
||||
} else {
|
||||
// key not found in memtable. Do sst get, update, add
|
||||
@@ -505,72 +651,54 @@ class MemTableInserter : public WriteBatch::Handler {
|
||||
// sequence number. Even if the update eventually fails and does not result
|
||||
// in memtable add/update.
|
||||
sequence_++;
|
||||
cf_mems_->CheckMemtableFull();
|
||||
CheckMemtableFull();
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status DeleteImpl(uint32_t column_family_id, const Slice& key,
|
||||
ValueType delete_type) {
|
||||
Status seek_status;
|
||||
if (!SeekToColumnFamily(column_family_id, &seek_status)) {
|
||||
++sequence_;
|
||||
return seek_status;
|
||||
}
|
||||
MemTable* mem = cf_mems_->GetMemTable();
|
||||
auto* moptions = mem->GetMemTableOptions();
|
||||
if (!dont_filter_deletes_ && moptions->filter_deletes) {
|
||||
assert(!concurrent_memtable_writes_);
|
||||
SnapshotImpl read_from_snapshot;
|
||||
read_from_snapshot.number_ = sequence_;
|
||||
ReadOptions ropts;
|
||||
ropts.snapshot = &read_from_snapshot;
|
||||
std::string value;
|
||||
auto cf_handle = cf_mems_->GetColumnFamilyHandle();
|
||||
if (cf_handle == nullptr) {
|
||||
cf_handle = db_->DefaultColumnFamily();
|
||||
}
|
||||
if (!db_->KeyMayExist(ropts, cf_handle, key, &value)) {
|
||||
RecordTick(moptions->statistics, NUMBER_FILTERED_DELETES);
|
||||
return Status::OK();
|
||||
}
|
||||
}
|
||||
mem->Add(sequence_, delete_type, key, Slice(), concurrent_memtable_writes_);
|
||||
sequence_++;
|
||||
CheckMemtableFull();
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
virtual Status DeleteCF(uint32_t column_family_id,
|
||||
const Slice& key) override {
|
||||
Status seek_status;
|
||||
if (!SeekToColumnFamily(column_family_id, &seek_status)) {
|
||||
++sequence_;
|
||||
return seek_status;
|
||||
}
|
||||
MemTable* mem = cf_mems_->GetMemTable();
|
||||
auto* moptions = mem->GetMemTableOptions();
|
||||
if (!dont_filter_deletes_ && moptions->filter_deletes) {
|
||||
SnapshotImpl read_from_snapshot;
|
||||
read_from_snapshot.number_ = sequence_;
|
||||
ReadOptions ropts;
|
||||
ropts.snapshot = &read_from_snapshot;
|
||||
std::string value;
|
||||
auto cf_handle = cf_mems_->GetColumnFamilyHandle();
|
||||
if (cf_handle == nullptr) {
|
||||
cf_handle = db_->DefaultColumnFamily();
|
||||
}
|
||||
if (!db_->KeyMayExist(ropts, cf_handle, key, &value)) {
|
||||
RecordTick(moptions->statistics, NUMBER_FILTERED_DELETES);
|
||||
return Status::OK();
|
||||
}
|
||||
}
|
||||
mem->Add(sequence_, kTypeDeletion, key, Slice());
|
||||
sequence_++;
|
||||
cf_mems_->CheckMemtableFull();
|
||||
return Status::OK();
|
||||
return DeleteImpl(column_family_id, key, kTypeDeletion);
|
||||
}
|
||||
|
||||
virtual Status SingleDeleteCF(uint32_t column_family_id,
|
||||
const Slice& key) override {
|
||||
Status seek_status;
|
||||
if (!SeekToColumnFamily(column_family_id, &seek_status)) {
|
||||
++sequence_;
|
||||
return seek_status;
|
||||
}
|
||||
MemTable* mem = cf_mems_->GetMemTable();
|
||||
auto* moptions = mem->GetMemTableOptions();
|
||||
if (!dont_filter_deletes_ && moptions->filter_deletes) {
|
||||
SnapshotImpl read_from_snapshot;
|
||||
read_from_snapshot.number_ = sequence_;
|
||||
ReadOptions ropts;
|
||||
ropts.snapshot = &read_from_snapshot;
|
||||
std::string value;
|
||||
auto cf_handle = cf_mems_->GetColumnFamilyHandle();
|
||||
if (cf_handle == nullptr) {
|
||||
cf_handle = db_->DefaultColumnFamily();
|
||||
}
|
||||
if (!db_->KeyMayExist(ropts, cf_handle, key, &value)) {
|
||||
RecordTick(moptions->statistics, NUMBER_FILTERED_DELETES);
|
||||
return Status::OK();
|
||||
}
|
||||
}
|
||||
mem->Add(sequence_, kTypeSingleDeletion, key, Slice());
|
||||
sequence_++;
|
||||
cf_mems_->CheckMemtableFull();
|
||||
return Status::OK();
|
||||
return DeleteImpl(column_family_id, key, kTypeSingleDeletion);
|
||||
}
|
||||
|
||||
virtual Status MergeCF(uint32_t column_family_id, const Slice& key,
|
||||
const Slice& value) override {
|
||||
assert(!concurrent_memtable_writes_);
|
||||
Status seek_status;
|
||||
if (!SeekToColumnFamily(column_family_id, &seek_status)) {
|
||||
++sequence_;
|
||||
@@ -645,37 +773,82 @@ class MemTableInserter : public WriteBatch::Handler {
|
||||
}
|
||||
|
||||
sequence_++;
|
||||
cf_mems_->CheckMemtableFull();
|
||||
CheckMemtableFull();
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
void CheckMemtableFull() {
|
||||
if (flush_scheduler_ != nullptr) {
|
||||
auto* cfd = cf_mems_->current();
|
||||
assert(cfd != nullptr);
|
||||
if (cfd->mem()->ShouldScheduleFlush() &&
|
||||
cfd->mem()->MarkFlushScheduled()) {
|
||||
// MarkFlushScheduled only returns true if we are the one that
|
||||
// should take action, so no need to dedup further
|
||||
flush_scheduler_->ScheduleFlush(cfd);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
} // namespace
|
||||
|
||||
// This function can only be called in these conditions:
|
||||
// 1) During Recovery()
|
||||
// 2) during Write(), in a single-threaded write thread
|
||||
// The reason is that it calles ColumnFamilyMemTablesImpl::Seek(), which needs
|
||||
// to be called from a single-threaded write thread (or while holding DB mutex)
|
||||
Status WriteBatchInternal::InsertInto(const WriteBatch* b,
|
||||
// 2) During Write(), in a single-threaded write thread
|
||||
// 3) During Write(), in a concurrent context where memtables has been cloned
|
||||
// The reason is that it calls memtables->Seek(), which has a stateful cache
|
||||
Status WriteBatchInternal::InsertInto(
|
||||
const autovector<WriteBatch*>& batches, SequenceNumber sequence,
|
||||
ColumnFamilyMemTables* memtables, FlushScheduler* flush_scheduler,
|
||||
bool ignore_missing_column_families, uint64_t log_number, DB* db,
|
||||
const bool dont_filter_deletes, bool concurrent_memtable_writes) {
|
||||
MemTableInserter inserter(sequence, memtables, flush_scheduler,
|
||||
ignore_missing_column_families, log_number, db,
|
||||
dont_filter_deletes, concurrent_memtable_writes);
|
||||
Status rv = Status::OK();
|
||||
for (size_t i = 0; i < batches.size() && rv.ok(); ++i) {
|
||||
rv = batches[i]->Iterate(&inserter);
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
Status WriteBatchInternal::InsertInto(const WriteBatch* batch,
|
||||
ColumnFamilyMemTables* memtables,
|
||||
FlushScheduler* flush_scheduler,
|
||||
bool ignore_missing_column_families,
|
||||
uint64_t log_number, DB* db,
|
||||
const bool dont_filter_deletes) {
|
||||
MemTableInserter inserter(WriteBatchInternal::Sequence(b), memtables,
|
||||
ignore_missing_column_families, log_number, db,
|
||||
dont_filter_deletes);
|
||||
return b->Iterate(&inserter);
|
||||
const bool dont_filter_deletes,
|
||||
bool concurrent_memtable_writes) {
|
||||
MemTableInserter inserter(WriteBatchInternal::Sequence(batch), memtables,
|
||||
flush_scheduler, ignore_missing_column_families,
|
||||
log_number, db, dont_filter_deletes,
|
||||
concurrent_memtable_writes);
|
||||
return batch->Iterate(&inserter);
|
||||
}
|
||||
|
||||
void WriteBatchInternal::SetContents(WriteBatch* b, const Slice& contents) {
|
||||
assert(contents.size() >= kHeader);
|
||||
b->rep_.assign(contents.data(), contents.size());
|
||||
b->content_flags_.store(ContentFlags::DEFERRED, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
void WriteBatchInternal::Append(WriteBatch* dst, const WriteBatch* src) {
|
||||
SetCount(dst, Count(dst) + Count(src));
|
||||
assert(src->rep_.size() >= kHeader);
|
||||
dst->rep_.append(src->rep_.data() + kHeader, src->rep_.size() - kHeader);
|
||||
dst->content_flags_.store(
|
||||
dst->content_flags_.load(std::memory_order_relaxed) |
|
||||
src->content_flags_.load(std::memory_order_relaxed),
|
||||
std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
size_t WriteBatchInternal::AppendedByteSize(size_t leftByteSize,
|
||||
size_t rightByteSize) {
|
||||
if (leftByteSize == 0 || rightByteSize == 0) {
|
||||
return leftByteSize + rightByteSize;
|
||||
} else {
|
||||
return leftByteSize + rightByteSize - kHeader;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
+41
-14
@@ -8,14 +8,18 @@
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
|
||||
#pragma once
|
||||
#include <vector>
|
||||
#include "rocksdb/types.h"
|
||||
#include "rocksdb/write_batch.h"
|
||||
#include "rocksdb/db.h"
|
||||
#include "rocksdb/options.h"
|
||||
#include "util/autovector.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
class MemTable;
|
||||
class FlushScheduler;
|
||||
class ColumnFamilyData;
|
||||
|
||||
class ColumnFamilyMemTables {
|
||||
public:
|
||||
@@ -27,7 +31,7 @@ class ColumnFamilyMemTables {
|
||||
virtual uint64_t GetLogNumber() const = 0;
|
||||
virtual MemTable* GetMemTable() const = 0;
|
||||
virtual ColumnFamilyHandle* GetColumnFamilyHandle() = 0;
|
||||
virtual void CheckMemtableFull() = 0;
|
||||
virtual ColumnFamilyData* current() { return nullptr; }
|
||||
};
|
||||
|
||||
class ColumnFamilyMemTablesDefault : public ColumnFamilyMemTables {
|
||||
@@ -49,8 +53,6 @@ class ColumnFamilyMemTablesDefault : public ColumnFamilyMemTables {
|
||||
|
||||
ColumnFamilyHandle* GetColumnFamilyHandle() override { return nullptr; }
|
||||
|
||||
void CheckMemtableFull() override {}
|
||||
|
||||
private:
|
||||
bool ok_;
|
||||
MemTable* mem_;
|
||||
@@ -112,24 +114,49 @@ class WriteBatchInternal {
|
||||
|
||||
static void SetContents(WriteBatch* batch, const Slice& contents);
|
||||
|
||||
// Inserts batch entries into memtable
|
||||
// If dont_filter_deletes is false AND options.filter_deletes is true,
|
||||
// then --> Drops deletes in batch if db->KeyMayExist returns false
|
||||
// If ignore_missing_column_families == true. WriteBatch referencing
|
||||
// non-existing column family should be ignored.
|
||||
// However, if ignore_missing_column_families == false, any WriteBatch
|
||||
// referencing non-existing column family will return a InvalidArgument()
|
||||
// failure.
|
||||
// Inserts batches[i] into memtable, for i in 0..num_batches-1 inclusive.
|
||||
//
|
||||
// If dont_filter_deletes is false AND options.filter_deletes is true
|
||||
// AND db->KeyMayExist is false, then a Delete won't modify the memtable.
|
||||
//
|
||||
// If ignore_missing_column_families == true. WriteBatch
|
||||
// referencing non-existing column family will be ignored.
|
||||
// If ignore_missing_column_families == false, processing of the
|
||||
// batches will be stopped if a reference is found to a non-existing
|
||||
// column family and InvalidArgument() will be returned. The writes
|
||||
// in batches may be only partially applied at that point.
|
||||
//
|
||||
// If log_number is non-zero, the memtable will be updated only if
|
||||
// memtables->GetLogNumber() >= log_number
|
||||
static Status InsertInto(const WriteBatch* batch,
|
||||
// memtables->GetLogNumber() >= log_number.
|
||||
//
|
||||
// If flush_scheduler is non-null, it will be invoked if the memtable
|
||||
// should be flushed.
|
||||
//
|
||||
// Under concurrent use, the caller is responsible for making sure that
|
||||
// the memtables object itself is thread-local.
|
||||
static Status InsertInto(const autovector<WriteBatch*>& batches,
|
||||
SequenceNumber sequence,
|
||||
ColumnFamilyMemTables* memtables,
|
||||
FlushScheduler* flush_scheduler,
|
||||
bool ignore_missing_column_families = false,
|
||||
uint64_t log_number = 0, DB* db = nullptr,
|
||||
const bool dont_filter_deletes = true);
|
||||
const bool dont_filter_deletes = true,
|
||||
bool concurrent_memtable_writes = false);
|
||||
|
||||
// Convenience form of InsertInto when you have only one batch
|
||||
static Status InsertInto(const WriteBatch* batch,
|
||||
ColumnFamilyMemTables* memtables,
|
||||
FlushScheduler* flush_scheduler,
|
||||
bool ignore_missing_column_families = false,
|
||||
uint64_t log_number = 0, DB* db = nullptr,
|
||||
const bool dont_filter_deletes = true,
|
||||
bool concurrent_memtable_writes = false);
|
||||
|
||||
static void Append(WriteBatch* dst, const WriteBatch* src);
|
||||
|
||||
// Returns the byte size of appending a WriteBatch with ByteSize
|
||||
// leftByteSize and a WriteBatch with ByteSize rightByteSize
|
||||
static size_t AppendedByteSize(size_t leftByteSize, size_t rightByteSize);
|
||||
};
|
||||
|
||||
} // namespace rocksdb
|
||||
+13
-1
@@ -37,8 +37,12 @@ static std::string PrintContents(WriteBatch* b) {
|
||||
mem->Ref();
|
||||
std::string state;
|
||||
ColumnFamilyMemTablesDefault cf_mems_default(mem);
|
||||
Status s = WriteBatchInternal::InsertInto(b, &cf_mems_default);
|
||||
Status s = WriteBatchInternal::InsertInto(b, &cf_mems_default, nullptr);
|
||||
int count = 0;
|
||||
int put_count = 0;
|
||||
int delete_count = 0;
|
||||
int single_delete_count = 0;
|
||||
int merge_count = 0;
|
||||
Arena arena;
|
||||
ScopedArenaIterator iter(mem->NewIterator(ReadOptions(), &arena));
|
||||
for (iter->SeekToFirst(); iter->Valid(); iter->Next()) {
|
||||
@@ -53,18 +57,21 @@ static std::string PrintContents(WriteBatch* b) {
|
||||
state.append(iter->value().ToString());
|
||||
state.append(")");
|
||||
count++;
|
||||
put_count++;
|
||||
break;
|
||||
case kTypeDeletion:
|
||||
state.append("Delete(");
|
||||
state.append(ikey.user_key.ToString());
|
||||
state.append(")");
|
||||
count++;
|
||||
delete_count++;
|
||||
break;
|
||||
case kTypeSingleDeletion:
|
||||
state.append("SingleDelete(");
|
||||
state.append(ikey.user_key.ToString());
|
||||
state.append(")");
|
||||
count++;
|
||||
single_delete_count++;
|
||||
break;
|
||||
case kTypeMerge:
|
||||
state.append("Merge(");
|
||||
@@ -73,6 +80,7 @@ static std::string PrintContents(WriteBatch* b) {
|
||||
state.append(iter->value().ToString());
|
||||
state.append(")");
|
||||
count++;
|
||||
merge_count++;
|
||||
break;
|
||||
default:
|
||||
assert(false);
|
||||
@@ -81,6 +89,10 @@ static std::string PrintContents(WriteBatch* b) {
|
||||
state.append("@");
|
||||
state.append(NumberToString(ikey.sequence));
|
||||
}
|
||||
EXPECT_EQ(b->HasPut(), put_count > 0);
|
||||
EXPECT_EQ(b->HasDelete(), delete_count > 0);
|
||||
EXPECT_EQ(b->HasSingleDelete(), single_delete_count > 0);
|
||||
EXPECT_EQ(b->HasMerge(), merge_count > 0);
|
||||
if (!s.ok()) {
|
||||
state.append(s.ToString());
|
||||
} else if (count != WriteBatchInternal::Count(b)) {
|
||||
|
||||
@@ -16,16 +16,18 @@ std::unique_ptr<WriteControllerToken> WriteController::GetStopToken() {
|
||||
return std::unique_ptr<WriteControllerToken>(new StopWriteToken(this));
|
||||
}
|
||||
|
||||
std::unique_ptr<WriteControllerToken> WriteController::GetDelayToken() {
|
||||
if (total_delayed_++ == 0) {
|
||||
last_refill_time_ = 0;
|
||||
bytes_left_ = 0;
|
||||
}
|
||||
std::unique_ptr<WriteControllerToken> WriteController::GetDelayToken(
|
||||
uint64_t write_rate) {
|
||||
total_delayed_++;
|
||||
// Reset counters.
|
||||
last_refill_time_ = 0;
|
||||
bytes_left_ = 0;
|
||||
set_delayed_write_rate(write_rate);
|
||||
return std::unique_ptr<WriteControllerToken>(new DelayWriteToken(this));
|
||||
}
|
||||
|
||||
bool WriteController::IsStopped() const { return total_stopped_ > 0; }
|
||||
// Tihs is inside DB mutex, so we can't sleep and need to minimize
|
||||
// This is inside DB mutex, so we can't sleep and need to minimize
|
||||
// frequency to get time.
|
||||
// If it turns out to be a performance issue, we can redesign the thread
|
||||
// synchronization model here.
|
||||
|
||||
+10
-8
@@ -20,12 +20,12 @@ class WriteControllerToken;
|
||||
// to be called while holding DB mutex
|
||||
class WriteController {
|
||||
public:
|
||||
explicit WriteController(uint64_t delayed_write_rate = 1024u * 1024u * 32u)
|
||||
explicit WriteController(uint64_t _delayed_write_rate = 1024u * 1024u * 32u)
|
||||
: total_stopped_(0),
|
||||
total_delayed_(0),
|
||||
bytes_left_(0),
|
||||
last_refill_time_(0) {
|
||||
set_delayed_write_rate(delayed_write_rate);
|
||||
set_delayed_write_rate(_delayed_write_rate);
|
||||
}
|
||||
~WriteController() = default;
|
||||
|
||||
@@ -36,7 +36,8 @@ class WriteController {
|
||||
// writes to the DB will be controlled under the delayed write rate. Every
|
||||
// write needs to call GetDelay() with number of bytes writing to the DB,
|
||||
// which returns number of microseconds to sleep.
|
||||
std::unique_ptr<WriteControllerToken> GetDelayToken();
|
||||
std::unique_ptr<WriteControllerToken> GetDelayToken(
|
||||
uint64_t delayed_write_rate);
|
||||
|
||||
// these two metods are querying the state of the WriteController
|
||||
bool IsStopped() const;
|
||||
@@ -45,13 +46,14 @@ class WriteController {
|
||||
// num_bytes: how many number of bytes to put into the DB.
|
||||
// Prerequisite: DB mutex held.
|
||||
uint64_t GetDelay(Env* env, uint64_t num_bytes);
|
||||
void set_delayed_write_rate(uint64_t delayed_write_rate) {
|
||||
delayed_write_rate_ = delayed_write_rate;
|
||||
if (delayed_write_rate_ == 0) {
|
||||
// avoid divide 0
|
||||
delayed_write_rate_ = 1U;
|
||||
void set_delayed_write_rate(uint64_t write_rate) {
|
||||
// avoid divide 0
|
||||
if (write_rate == 0) {
|
||||
write_rate = 1u;
|
||||
}
|
||||
delayed_write_rate_ = write_rate;
|
||||
}
|
||||
uint64_t delayed_write_rate() const { return delayed_write_rate_; }
|
||||
|
||||
private:
|
||||
friend class WriteControllerToken;
|
||||
|
||||
@@ -19,6 +19,28 @@ class TimeSetEnv : public EnvWrapper {
|
||||
virtual uint64_t NowMicros() override { return now_micros_; }
|
||||
};
|
||||
|
||||
TEST_F(WriteControllerTest, ChangeDelayRateTest) {
|
||||
TimeSetEnv env;
|
||||
WriteController controller(10000000u);
|
||||
auto delay_token_0 =
|
||||
controller.GetDelayToken(controller.delayed_write_rate());
|
||||
ASSERT_EQ(static_cast<uint64_t>(2000000),
|
||||
controller.GetDelay(&env, 20000000u));
|
||||
auto delay_token_1 = controller.GetDelayToken(2000000u);
|
||||
ASSERT_EQ(static_cast<uint64_t>(10000000),
|
||||
controller.GetDelay(&env, 20000000u));
|
||||
auto delay_token_2 = controller.GetDelayToken(1000000u);
|
||||
ASSERT_EQ(static_cast<uint64_t>(20000000),
|
||||
controller.GetDelay(&env, 20000000u));
|
||||
auto delay_token_3 = controller.GetDelayToken(20000000u);
|
||||
ASSERT_EQ(static_cast<uint64_t>(1000000),
|
||||
controller.GetDelay(&env, 20000000u));
|
||||
auto delay_token_4 =
|
||||
controller.GetDelayToken(controller.delayed_write_rate() * 2);
|
||||
ASSERT_EQ(static_cast<uint64_t>(500000),
|
||||
controller.GetDelay(&env, 20000000u));
|
||||
}
|
||||
|
||||
TEST_F(WriteControllerTest, SanityTest) {
|
||||
WriteController controller(10000000u);
|
||||
auto stop_token_1 = controller.GetStopToken();
|
||||
@@ -32,12 +54,19 @@ TEST_F(WriteControllerTest, SanityTest) {
|
||||
|
||||
TimeSetEnv env;
|
||||
|
||||
auto delay_token_1 = controller.GetDelayToken();
|
||||
auto delay_token_1 = controller.GetDelayToken(10000000u);
|
||||
ASSERT_EQ(static_cast<uint64_t>(2000000),
|
||||
controller.GetDelay(&env, 20000000u));
|
||||
|
||||
env.now_micros_ += 1999900u; // sleep debt 1000
|
||||
auto delay_token_2 = controller.GetDelayToken();
|
||||
|
||||
auto delay_token_2 = controller.GetDelayToken(10000000u);
|
||||
// Rate reset after changing the token.
|
||||
ASSERT_EQ(static_cast<uint64_t>(2000000),
|
||||
controller.GetDelay(&env, 20000000u));
|
||||
|
||||
env.now_micros_ += 1999900u; // sleep debt 1000
|
||||
|
||||
// One refill: 10240 bytes allowed, 1000 used, 9240 left
|
||||
ASSERT_EQ(static_cast<uint64_t>(1124), controller.GetDelay(&env, 1000u));
|
||||
env.now_micros_ += 1124u; // sleep debt 0
|
||||
|
||||
+269
-30
@@ -4,33 +4,197 @@
|
||||
// of patent rights can be found in the PATENTS file in the same directory.
|
||||
|
||||
#include "db/write_thread.h"
|
||||
#include <chrono>
|
||||
#include <limits>
|
||||
#include <thread>
|
||||
#include "db/column_family.h"
|
||||
#include "port/port.h"
|
||||
#include "util/sync_point.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
void WriteThread::Await(Writer* w) {
|
||||
std::unique_lock<std::mutex> guard(w->JoinMutex());
|
||||
w->JoinCV().wait(guard, [w] { return w->joined; });
|
||||
WriteThread::WriteThread(uint64_t max_yield_usec, uint64_t slow_yield_usec)
|
||||
: max_yield_usec_(max_yield_usec),
|
||||
slow_yield_usec_(slow_yield_usec),
|
||||
newest_writer_(nullptr) {}
|
||||
|
||||
uint8_t WriteThread::BlockingAwaitState(Writer* w, uint8_t goal_mask) {
|
||||
// We're going to block. Lazily create the mutex. We guarantee
|
||||
// propagation of this construction to the waker via the
|
||||
// STATE_LOCKED_WAITING state. The waker won't try to touch the mutex
|
||||
// or the condvar unless they CAS away the STATE_LOCKED_WAITING that
|
||||
// we install below.
|
||||
w->CreateMutex();
|
||||
|
||||
auto state = w->state.load(std::memory_order_acquire);
|
||||
assert(state != STATE_LOCKED_WAITING);
|
||||
if ((state & goal_mask) == 0 &&
|
||||
w->state.compare_exchange_strong(state, STATE_LOCKED_WAITING)) {
|
||||
// we have permission (and an obligation) to use StateMutex
|
||||
std::unique_lock<std::mutex> guard(w->StateMutex());
|
||||
w->StateCV().wait(guard, [w] {
|
||||
return w->state.load(std::memory_order_relaxed) != STATE_LOCKED_WAITING;
|
||||
});
|
||||
state = w->state.load(std::memory_order_relaxed);
|
||||
}
|
||||
// else tricky. Goal is met or CAS failed. In the latter case the waker
|
||||
// must have changed the state, and compare_exchange_strong has updated
|
||||
// our local variable with the new one. At the moment WriteThread never
|
||||
// waits for a transition across intermediate states, so we know that
|
||||
// since a state change has occurred the goal must have been met.
|
||||
assert((state & goal_mask) != 0);
|
||||
return state;
|
||||
}
|
||||
|
||||
void WriteThread::MarkJoined(Writer* w) {
|
||||
std::lock_guard<std::mutex> guard(w->JoinMutex());
|
||||
assert(!w->joined);
|
||||
w->joined = true;
|
||||
w->JoinCV().notify_one();
|
||||
uint8_t WriteThread::AwaitState(Writer* w, uint8_t goal_mask,
|
||||
AdaptationContext* ctx) {
|
||||
uint8_t state;
|
||||
|
||||
// On a modern Xeon each loop takes about 7 nanoseconds (most of which
|
||||
// is the effect of the pause instruction), so 200 iterations is a bit
|
||||
// more than a microsecond. This is long enough that waits longer than
|
||||
// this can amortize the cost of accessing the clock and yielding.
|
||||
for (uint32_t tries = 0; tries < 200; ++tries) {
|
||||
state = w->state.load(std::memory_order_acquire);
|
||||
if ((state & goal_mask) != 0) {
|
||||
return state;
|
||||
}
|
||||
port::AsmVolatilePause();
|
||||
}
|
||||
|
||||
// If we're only going to end up waiting a short period of time,
|
||||
// it can be a lot more efficient to call std::this_thread::yield()
|
||||
// in a loop than to block in StateMutex(). For reference, on my 4.0
|
||||
// SELinux test server with support for syscall auditing enabled, the
|
||||
// minimum latency between FUTEX_WAKE to returning from FUTEX_WAIT is
|
||||
// 2.7 usec, and the average is more like 10 usec. That can be a big
|
||||
// drag on RockDB's single-writer design. Of course, spinning is a
|
||||
// bad idea if other threads are waiting to run or if we're going to
|
||||
// wait for a long time. How do we decide?
|
||||
//
|
||||
// We break waiting into 3 categories: short-uncontended,
|
||||
// short-contended, and long. If we had an oracle, then we would always
|
||||
// spin for short-uncontended, always block for long, and our choice for
|
||||
// short-contended might depend on whether we were trying to optimize
|
||||
// RocksDB throughput or avoid being greedy with system resources.
|
||||
//
|
||||
// Bucketing into short or long is easy by measuring elapsed time.
|
||||
// Differentiating short-uncontended from short-contended is a bit
|
||||
// trickier, but not too bad. We could look for involuntary context
|
||||
// switches using getrusage(RUSAGE_THREAD, ..), but it's less work
|
||||
// (portability code and CPU) to just look for yield calls that take
|
||||
// longer than we expect. sched_yield() doesn't actually result in any
|
||||
// context switch overhead if there are no other runnable processes
|
||||
// on the current core, in which case it usually takes less than
|
||||
// a microsecond.
|
||||
//
|
||||
// There are two primary tunables here: the threshold between "short"
|
||||
// and "long" waits, and the threshold at which we suspect that a yield
|
||||
// is slow enough to indicate we should probably block. If these
|
||||
// thresholds are chosen well then CPU-bound workloads that don't
|
||||
// have more threads than cores will experience few context switches
|
||||
// (voluntary or involuntary), and the total number of context switches
|
||||
// (voluntary and involuntary) will not be dramatically larger (maybe
|
||||
// 2x) than the number of voluntary context switches that occur when
|
||||
// --max_yield_wait_micros=0.
|
||||
//
|
||||
// There's another constant, which is the number of slow yields we will
|
||||
// tolerate before reversing our previous decision. Solitary slow
|
||||
// yields are pretty common (low-priority small jobs ready to run),
|
||||
// so this should be at least 2. We set this conservatively to 3 so
|
||||
// that we can also immediately schedule a ctx adaptation, rather than
|
||||
// waiting for the next update_ctx.
|
||||
|
||||
const size_t kMaxSlowYieldsWhileSpinning = 3;
|
||||
|
||||
bool update_ctx = false;
|
||||
bool would_spin_again = false;
|
||||
|
||||
if (max_yield_usec_ > 0) {
|
||||
update_ctx = Random::GetTLSInstance()->OneIn(256);
|
||||
|
||||
if (update_ctx || ctx->value.load(std::memory_order_relaxed) >= 0) {
|
||||
// we're updating the adaptation statistics, or spinning has >
|
||||
// 50% chance of being shorter than max_yield_usec_ and causing no
|
||||
// involuntary context switches
|
||||
auto spin_begin = std::chrono::steady_clock::now();
|
||||
|
||||
// this variable doesn't include the final yield (if any) that
|
||||
// causes the goal to be met
|
||||
size_t slow_yield_count = 0;
|
||||
|
||||
auto iter_begin = spin_begin;
|
||||
while ((iter_begin - spin_begin) <=
|
||||
std::chrono::microseconds(max_yield_usec_)) {
|
||||
std::this_thread::yield();
|
||||
|
||||
state = w->state.load(std::memory_order_acquire);
|
||||
if ((state & goal_mask) != 0) {
|
||||
// success
|
||||
would_spin_again = true;
|
||||
break;
|
||||
}
|
||||
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
if (now == iter_begin ||
|
||||
now - iter_begin >= std::chrono::microseconds(slow_yield_usec_)) {
|
||||
// conservatively count it as a slow yield if our clock isn't
|
||||
// accurate enough to measure the yield duration
|
||||
++slow_yield_count;
|
||||
if (slow_yield_count >= kMaxSlowYieldsWhileSpinning) {
|
||||
// Not just one ivcsw, but several. Immediately update ctx
|
||||
// and fall back to blocking
|
||||
update_ctx = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
iter_begin = now;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ((state & goal_mask) == 0) {
|
||||
state = BlockingAwaitState(w, goal_mask);
|
||||
}
|
||||
|
||||
if (update_ctx) {
|
||||
auto v = ctx->value.load(std::memory_order_relaxed);
|
||||
// fixed point exponential decay with decay constant 1/1024, with +1
|
||||
// and -1 scaled to avoid overflow for int32_t
|
||||
v = v + (v / 1024) + (would_spin_again ? 1 : -1) * 16384;
|
||||
ctx->value.store(v, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
assert((state & goal_mask) != 0);
|
||||
return state;
|
||||
}
|
||||
|
||||
void WriteThread::LinkOne(Writer* w, bool* wait_needed) {
|
||||
assert(!w->joined && !w->done);
|
||||
void WriteThread::SetState(Writer* w, uint8_t new_state) {
|
||||
auto state = w->state.load(std::memory_order_acquire);
|
||||
if (state == STATE_LOCKED_WAITING ||
|
||||
!w->state.compare_exchange_strong(state, new_state)) {
|
||||
assert(state == STATE_LOCKED_WAITING);
|
||||
|
||||
std::lock_guard<std::mutex> guard(w->StateMutex());
|
||||
assert(w->state.load(std::memory_order_relaxed) != new_state);
|
||||
w->state.store(new_state, std::memory_order_relaxed);
|
||||
w->StateCV().notify_one();
|
||||
}
|
||||
}
|
||||
|
||||
void WriteThread::LinkOne(Writer* w, bool* linked_as_leader) {
|
||||
assert(w->state == STATE_INIT);
|
||||
|
||||
Writer* writers = newest_writer_.load(std::memory_order_relaxed);
|
||||
while (true) {
|
||||
w->link_older = writers;
|
||||
if (writers != nullptr) {
|
||||
w->CreateMutex();
|
||||
}
|
||||
if (newest_writer_.compare_exchange_strong(writers, w)) {
|
||||
// Success.
|
||||
*wait_needed = (writers != nullptr);
|
||||
if (writers == nullptr) {
|
||||
// this isn't part of the WriteThread machinery, but helps with
|
||||
// debugging and is checked by an assert in WriteImpl
|
||||
w->state.store(STATE_GROUP_LEADER, std::memory_order_relaxed);
|
||||
}
|
||||
*linked_as_leader = (writers == nullptr);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -49,11 +213,15 @@ void WriteThread::CreateMissingNewerLinks(Writer* head) {
|
||||
}
|
||||
|
||||
void WriteThread::JoinBatchGroup(Writer* w) {
|
||||
static AdaptationContext ctx("JoinBatchGroup");
|
||||
|
||||
assert(w->batch != nullptr);
|
||||
bool wait_needed;
|
||||
LinkOne(w, &wait_needed);
|
||||
if (wait_needed) {
|
||||
Await(w);
|
||||
bool linked_as_leader;
|
||||
LinkOne(w, &linked_as_leader);
|
||||
if (!linked_as_leader) {
|
||||
AwaitState(w,
|
||||
STATE_GROUP_LEADER | STATE_PARALLEL_FOLLOWER | STATE_COMPLETED,
|
||||
&ctx);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,7 +255,7 @@ size_t WriteThread::EnterAsBatchGroupLeader(
|
||||
// This is safe regardless of any db mutex status of the caller. Previous
|
||||
// calls to ExitAsGroupLeader either didn't call CreateMissingNewerLinks
|
||||
// (they emptied the list and then we added ourself as leader) or had to
|
||||
// explicitly wake up us (the list was non-empty when we added ourself,
|
||||
// explicitly wake us up (the list was non-empty when we added ourself,
|
||||
// so we have already received our MarkJoined).
|
||||
CreateMissingNewerLinks(newest_writer);
|
||||
|
||||
@@ -134,6 +302,73 @@ size_t WriteThread::EnterAsBatchGroupLeader(
|
||||
return size;
|
||||
}
|
||||
|
||||
void WriteThread::LaunchParallelFollowers(ParallelGroup* pg,
|
||||
SequenceNumber sequence) {
|
||||
// EnterAsBatchGroupLeader already created the links from leader to
|
||||
// newer writers in the group
|
||||
|
||||
pg->leader->parallel_group = pg;
|
||||
|
||||
Writer* w = pg->leader;
|
||||
w->sequence = sequence;
|
||||
|
||||
while (w != pg->last_writer) {
|
||||
sequence += WriteBatchInternal::Count(w->batch);
|
||||
w = w->link_newer;
|
||||
|
||||
w->sequence = sequence;
|
||||
w->parallel_group = pg;
|
||||
SetState(w, STATE_PARALLEL_FOLLOWER);
|
||||
}
|
||||
}
|
||||
|
||||
bool WriteThread::CompleteParallelWorker(Writer* w) {
|
||||
static AdaptationContext ctx("CompleteParallelWorker");
|
||||
|
||||
auto* pg = w->parallel_group;
|
||||
if (!w->status.ok()) {
|
||||
std::lock_guard<std::mutex> guard(w->StateMutex());
|
||||
pg->status = w->status;
|
||||
}
|
||||
auto leader = pg->leader;
|
||||
auto early_exit_allowed = pg->early_exit_allowed;
|
||||
|
||||
if (pg->running.load(std::memory_order_acquire) > 1 && pg->running-- > 1) {
|
||||
// we're not the last one
|
||||
AwaitState(w, STATE_COMPLETED, &ctx);
|
||||
|
||||
// Caller only needs to perform exit duties if early exit doesn't
|
||||
// apply and this is the leader. Can't touch pg here. Whoever set
|
||||
// our state to STATE_COMPLETED copied pg->status to w.status for us.
|
||||
return w == leader && !(early_exit_allowed && w->status.ok());
|
||||
}
|
||||
// else we're the last parallel worker
|
||||
|
||||
if (w == leader || (early_exit_allowed && pg->status.ok())) {
|
||||
// this thread should perform exit duties
|
||||
w->status = pg->status;
|
||||
return true;
|
||||
} else {
|
||||
// We're the last parallel follower but early commit is not
|
||||
// applicable. Wake up the leader and then wait for it to exit.
|
||||
assert(w->state == STATE_PARALLEL_FOLLOWER);
|
||||
SetState(leader, STATE_COMPLETED);
|
||||
AwaitState(w, STATE_COMPLETED, &ctx);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void WriteThread::EarlyExitParallelGroup(Writer* w) {
|
||||
auto* pg = w->parallel_group;
|
||||
|
||||
assert(w->state == STATE_PARALLEL_FOLLOWER);
|
||||
assert(pg->status.ok());
|
||||
ExitAsBatchGroupLeader(pg->leader, pg->last_writer, pg->status);
|
||||
assert(w->state == STATE_COMPLETED);
|
||||
assert(w->status.ok());
|
||||
SetState(pg->leader, STATE_COMPLETED);
|
||||
}
|
||||
|
||||
void WriteThread::ExitAsBatchGroupLeader(Writer* leader, Writer* last_writer,
|
||||
Status status) {
|
||||
assert(leader->link_older == nullptr);
|
||||
@@ -165,30 +400,34 @@ void WriteThread::ExitAsBatchGroupLeader(Writer* leader, Writer* last_writer,
|
||||
// nullptr when they enqueued (we were definitely enqueued before them
|
||||
// and are still in the list). That means leader handoff occurs when
|
||||
// we call MarkJoined
|
||||
MarkJoined(last_writer->link_newer);
|
||||
SetState(last_writer->link_newer, STATE_GROUP_LEADER);
|
||||
}
|
||||
// else nobody else was waiting, although there might already be a new
|
||||
// leader now
|
||||
|
||||
while (last_writer != leader) {
|
||||
last_writer->status = status;
|
||||
last_writer->done = true;
|
||||
// We must read link_older before calling MarkJoined, because as
|
||||
// soon as it is marked the other thread's AwaitJoined may return
|
||||
// and deallocate the Writer.
|
||||
|
||||
// we need to read link_older before calling SetState, because as soon
|
||||
// as it is marked committed the other thread's Await may return and
|
||||
// deallocate the Writer.
|
||||
auto next = last_writer->link_older;
|
||||
MarkJoined(last_writer);
|
||||
SetState(last_writer, STATE_COMPLETED);
|
||||
|
||||
last_writer = next;
|
||||
}
|
||||
}
|
||||
|
||||
void WriteThread::EnterUnbatched(Writer* w, InstrumentedMutex* mu) {
|
||||
static AdaptationContext ctx("EnterUnbatched");
|
||||
|
||||
assert(w->batch == nullptr);
|
||||
bool wait_needed;
|
||||
LinkOne(w, &wait_needed);
|
||||
if (wait_needed) {
|
||||
bool linked_as_leader;
|
||||
LinkOne(w, &linked_as_leader);
|
||||
if (!linked_as_leader) {
|
||||
mu->Unlock();
|
||||
Await(w);
|
||||
TEST_SYNC_POINT("WriteThread::EnterUnbatched:Wait");
|
||||
AwaitState(w, STATE_GROUP_LEADER, &ctx);
|
||||
mu->Lock();
|
||||
}
|
||||
}
|
||||
|
||||
+132
-37
@@ -8,11 +8,13 @@
|
||||
#include <assert.h>
|
||||
#include <stdint.h>
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <condition_variable>
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
#include <type_traits>
|
||||
#include "rocksdb/status.h"
|
||||
#include "db/write_batch_internal.h"
|
||||
#include "rocksdb/status.h"
|
||||
#include "util/autovector.h"
|
||||
#include "util/instrumented_mutex.h"
|
||||
|
||||
@@ -20,19 +22,69 @@ namespace rocksdb {
|
||||
|
||||
class WriteThread {
|
||||
public:
|
||||
enum State : uint8_t {
|
||||
// The initial state of a writer. This is a Writer that is
|
||||
// waiting in JoinBatchGroup. This state can be left when another
|
||||
// thread informs the waiter that it has become a group leader
|
||||
// (-> STATE_GROUP_LEADER), when a leader that has chosen to be
|
||||
// non-parallel informs a follower that its writes have been committed
|
||||
// (-> STATE_COMPLETED), or when a leader that has chosen to perform
|
||||
// updates in parallel and needs this Writer to apply its batch (->
|
||||
// STATE_PARALLEL_FOLLOWER).
|
||||
STATE_INIT = 1,
|
||||
|
||||
// The state used to inform a waiting Writer that it has become the
|
||||
// leader, and it should now build a write batch group. Tricky:
|
||||
// this state is not used if newest_writer_ is empty when a writer
|
||||
// enqueues itself, because there is no need to wait (or even to
|
||||
// create the mutex and condvar used to wait) in that case. This is
|
||||
// a terminal state unless the leader chooses to make this a parallel
|
||||
// batch, in which case the last parallel worker to finish will move
|
||||
// the leader to STATE_COMPLETED.
|
||||
STATE_GROUP_LEADER = 2,
|
||||
|
||||
// A Writer that has returned as a follower in a parallel group.
|
||||
// It should apply its batch to the memtable and then call
|
||||
// CompleteParallelWorker. When someone calls ExitAsBatchGroupLeader
|
||||
// or EarlyExitParallelGroup this state will get transitioned to
|
||||
// STATE_COMPLETED.
|
||||
STATE_PARALLEL_FOLLOWER = 4,
|
||||
|
||||
// A follower whose writes have been applied, or a parallel leader
|
||||
// whose followers have all finished their work. This is a terminal
|
||||
// state.
|
||||
STATE_COMPLETED = 8,
|
||||
|
||||
// A state indicating that the thread may be waiting using StateMutex()
|
||||
// and StateCondVar()
|
||||
STATE_LOCKED_WAITING = 16,
|
||||
};
|
||||
|
||||
struct Writer;
|
||||
|
||||
struct ParallelGroup {
|
||||
Writer* leader;
|
||||
Writer* last_writer;
|
||||
bool early_exit_allowed;
|
||||
// before running goes to zero, status needs leader->StateMutex()
|
||||
Status status;
|
||||
std::atomic<uint32_t> running;
|
||||
};
|
||||
|
||||
// Information kept for every waiting writer.
|
||||
struct Writer {
|
||||
WriteBatch* batch;
|
||||
bool sync;
|
||||
bool disableWAL;
|
||||
bool in_batch_group;
|
||||
bool done;
|
||||
bool has_callback;
|
||||
bool made_waitable; // records lazy construction of mutex and cv
|
||||
std::atomic<uint8_t> state; // write under StateMutex() or pre-link
|
||||
ParallelGroup* parallel_group;
|
||||
SequenceNumber sequence; // the sequence number to use
|
||||
Status status;
|
||||
bool made_waitable; // records lazy construction of mutex and cv
|
||||
bool joined; // read/write only under JoinMutex() (or pre-link)
|
||||
std::aligned_storage<sizeof(std::mutex)>::type join_mutex_bytes;
|
||||
std::aligned_storage<sizeof(std::condition_variable)>::type join_cv_bytes;
|
||||
std::aligned_storage<sizeof(std::mutex)>::type state_mutex_bytes;
|
||||
std::aligned_storage<sizeof(std::condition_variable)>::type state_cv_bytes;
|
||||
Writer* link_older; // read/write only before linking, or as leader
|
||||
Writer* link_newer; // lazy, read/write only before linking, or as leader
|
||||
|
||||
@@ -41,44 +93,45 @@ class WriteThread {
|
||||
sync(false),
|
||||
disableWAL(false),
|
||||
in_batch_group(false),
|
||||
done(false),
|
||||
has_callback(false),
|
||||
made_waitable(false),
|
||||
joined(false),
|
||||
state(STATE_INIT),
|
||||
link_older(nullptr),
|
||||
link_newer(nullptr) {}
|
||||
|
||||
~Writer() {
|
||||
if (made_waitable) {
|
||||
JoinMutex().~mutex();
|
||||
JoinCV().~condition_variable();
|
||||
StateMutex().~mutex();
|
||||
StateCV().~condition_variable();
|
||||
}
|
||||
}
|
||||
|
||||
void CreateMutex() {
|
||||
assert(!joined);
|
||||
if (!made_waitable) {
|
||||
// Note that made_waitable is tracked separately from state
|
||||
// transitions, because we can't atomically create the mutex and
|
||||
// link into the list.
|
||||
made_waitable = true;
|
||||
new (&join_mutex_bytes) std::mutex;
|
||||
new (&join_cv_bytes) std::condition_variable;
|
||||
new (&state_mutex_bytes) std::mutex;
|
||||
new (&state_cv_bytes) std::condition_variable;
|
||||
}
|
||||
}
|
||||
|
||||
// No other mutexes may be acquired while holding JoinMutex(), it is
|
||||
// No other mutexes may be acquired while holding StateMutex(), it is
|
||||
// always last in the order
|
||||
std::mutex& JoinMutex() {
|
||||
std::mutex& StateMutex() {
|
||||
assert(made_waitable);
|
||||
return *static_cast<std::mutex*>(static_cast<void*>(&join_mutex_bytes));
|
||||
return *static_cast<std::mutex*>(static_cast<void*>(&state_mutex_bytes));
|
||||
}
|
||||
|
||||
std::condition_variable& JoinCV() {
|
||||
std::condition_variable& StateCV() {
|
||||
assert(made_waitable);
|
||||
return *static_cast<std::condition_variable*>(
|
||||
static_cast<void*>(&join_cv_bytes));
|
||||
static_cast<void*>(&state_cv_bytes));
|
||||
}
|
||||
};
|
||||
|
||||
WriteThread() : newest_writer_(nullptr) {}
|
||||
WriteThread(uint64_t max_yield_usec, uint64_t slow_yield_usec);
|
||||
|
||||
// IMPORTANT: None of the methods in this class rely on the db mutex
|
||||
// for correctness. All of the methods except JoinBatchGroup and
|
||||
@@ -86,13 +139,16 @@ class WriteThread {
|
||||
// Correctness is maintained by ensuring that only a single thread is
|
||||
// a leader at a time.
|
||||
|
||||
// Registers w as ready to become part of a batch group, and blocks
|
||||
// until some other thread has completed the write (in which case
|
||||
// w->done will be set to true) or this write has become the leader
|
||||
// of a batch group (w->done will remain unset). The db mutex SHOULD
|
||||
// NOT be held when calling this function, because it will block.
|
||||
// If !w->done then JoinBatchGroup should be followed by a call to
|
||||
// EnterAsBatchGroupLeader and ExitAsBatchGroupLeader.
|
||||
// Registers w as ready to become part of a batch group, waits until the
|
||||
// caller should perform some work, and returns the current state of the
|
||||
// writer. If w has become the leader of a write batch group, returns
|
||||
// STATE_GROUP_LEADER. If w has been made part of a sequential batch
|
||||
// group and the leader has performed the write, returns STATE_DONE.
|
||||
// If w has been made part of a parallel batch group and is reponsible
|
||||
// for updating the memtable, returns STATE_PARALLEL_FOLLOWER.
|
||||
//
|
||||
// The db mutex SHOULD NOT be held when calling this function, because
|
||||
// it will block.
|
||||
//
|
||||
// Writer* w: Writer to be executed as part of a batch group
|
||||
void JoinBatchGroup(Writer* w);
|
||||
@@ -100,15 +156,35 @@ class WriteThread {
|
||||
// Constructs a write batch group led by leader, which should be a
|
||||
// Writer passed to JoinBatchGroup on the current thread.
|
||||
//
|
||||
// Writer* leader: Writer passed to JoinBatchGroup, but !done
|
||||
// Writer** last_writer: Out-param for use by ExitAsBatchGroupLeader
|
||||
// Writer* leader: Writer that is STATE_GROUP_LEADER
|
||||
// Writer** last_writer: Out-param that identifies the last follower
|
||||
// autovector<WriteBatch*>* write_batch_group: Out-param of group members
|
||||
// returns: Total batch group size
|
||||
// returns: Total batch group byte size
|
||||
size_t EnterAsBatchGroupLeader(Writer* leader, Writer** last_writer,
|
||||
autovector<WriteBatch*>* write_batch_group);
|
||||
|
||||
// Unlinks the Writer-s in a batch group, wakes up the non-leaders, and
|
||||
// wakes up the next leader (if any).
|
||||
// Causes JoinBatchGroup to return STATE_PARALLEL_FOLLOWER for all of the
|
||||
// non-leader members of this write batch group. Sets Writer::sequence
|
||||
// before waking them up.
|
||||
//
|
||||
// ParallalGroup* pg: Extra state used to coordinate the parallel add
|
||||
// SequenceNumber sequence: Starting sequence number to assign to Writer-s
|
||||
void LaunchParallelFollowers(ParallelGroup* pg, SequenceNumber sequence);
|
||||
|
||||
// Reports the completion of w's batch to the parallel group leader, and
|
||||
// waits for the rest of the parallel batch to complete. Returns true
|
||||
// if this thread is the last to complete, and hence should advance
|
||||
// the sequence number and then call EarlyExitParallelGroup, false if
|
||||
// someone else has already taken responsibility for that.
|
||||
bool CompleteParallelWorker(Writer* w);
|
||||
|
||||
// This method performs an early completion of a parallel write group,
|
||||
// where the cleanup work of the leader is performed by a follower who
|
||||
// happens to be the last parallel worker to complete.
|
||||
void EarlyExitParallelGroup(Writer* w);
|
||||
|
||||
// Unlinks the Writer-s in a batch group, wakes up the non-leaders,
|
||||
// and wakes up the next leader (if any).
|
||||
//
|
||||
// Writer* leader: From EnterAsBatchGroupLeader
|
||||
// Writer* last_writer: Value of out-param of EnterAsBatchGroupLeader
|
||||
@@ -128,18 +204,37 @@ class WriteThread {
|
||||
// writers.
|
||||
void ExitUnbatched(Writer* w);
|
||||
|
||||
struct AdaptationContext {
|
||||
const char* name;
|
||||
std::atomic<int32_t> value;
|
||||
|
||||
explicit AdaptationContext(const char* name0) : name(name0), value(0) {}
|
||||
};
|
||||
|
||||
private:
|
||||
uint64_t max_yield_usec_;
|
||||
uint64_t slow_yield_usec_;
|
||||
|
||||
// Points to the newest pending Writer. Only leader can remove
|
||||
// elements, adding can be done lock-free by anybody
|
||||
std::atomic<Writer*> newest_writer_;
|
||||
|
||||
void Await(Writer* w);
|
||||
void MarkJoined(Writer* w);
|
||||
// Waits for w->state & goal_mask using w->StateMutex(). Returns
|
||||
// the state that satisfies goal_mask.
|
||||
uint8_t BlockingAwaitState(Writer* w, uint8_t goal_mask);
|
||||
|
||||
// Links w into the newest_writer_ list. Sets *wait_needed to false
|
||||
// if w was linked directly into the leader position, true otherwise.
|
||||
// Safe to call from multiple threads without external locking.
|
||||
void LinkOne(Writer* w, bool* wait_needed);
|
||||
// Blocks until w->state & goal_mask, returning the state value
|
||||
// that satisfied the predicate. Uses ctx to adaptively use
|
||||
// std::this_thread::yield() to avoid mutex overheads. ctx should be
|
||||
// a context-dependent static.
|
||||
uint8_t AwaitState(Writer* w, uint8_t goal_mask, AdaptationContext* ctx);
|
||||
|
||||
void SetState(Writer* w, uint8_t new_state);
|
||||
|
||||
// Links w into the newest_writer_ list. Sets *linked_as_leader to
|
||||
// true if w was linked directly into the leader position. Safe to
|
||||
// call from multiple threads without external locking.
|
||||
void LinkOne(Writer* w, bool* linked_as_leader);
|
||||
|
||||
// Computes any missing link_newer links. Should not be called
|
||||
// concurrently with itself.
|
||||
|
||||
Loaded 100 of 271 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user