mirror of
https://github.com/facebook/rocksdb.git
synced 2026-11-06 11:02:36 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a683d4aba9 | ||
|
|
53a4bd8a69 | ||
|
|
3e86869616 | ||
|
|
b2973eaaeb | ||
|
|
5b197bff41 | ||
|
|
281fbdddc2 | ||
|
|
2ae15b2d50 | ||
|
|
00a0587257 | ||
|
|
5091dfc1ed | ||
|
|
edc764e91b | ||
|
|
8ff59b2b40 | ||
|
|
1ba452226f | ||
|
|
972c895c35 | ||
|
|
a73b26f601 | ||
|
|
098da83483 | ||
|
|
54db29b8f0 | ||
|
|
d755c62f92 | ||
|
|
fda098461b | ||
|
|
0fee896841 | ||
|
|
10d46b9c8f | ||
|
|
774a6aa296 | ||
|
|
3070ed9021 | ||
|
|
e42dc9192e | ||
|
|
5647fa427c | ||
|
|
0d65acec0c | ||
|
|
19dd5a61cd | ||
|
|
5aca977be8 | ||
|
|
1147e5b05a | ||
|
|
6e6622abb9 | ||
|
|
e532877940 | ||
|
|
a791a2cf2d | ||
|
|
af0c9ac01d | ||
|
|
02ec8154e5 | ||
|
|
630b732cb3 | ||
|
|
62d5480984 | ||
|
|
842958651f | ||
|
|
88acd932f6 | ||
|
|
3a276b0cbe | ||
|
|
8cf0f86d39 | ||
|
|
27ad170712 | ||
|
|
936973d145 | ||
|
|
21c047ab49 | ||
|
|
71c7eed91c | ||
|
|
5d85fdb2c5 | ||
|
|
c40c4cae14 | ||
|
|
590e2617ee | ||
|
|
8dfa980cba | ||
|
|
f62fbd2c85 | ||
|
|
57461fba8c | ||
|
|
0e20000171 | ||
|
|
472c06e903 | ||
|
|
157e0633e7 | ||
|
|
23d4cf483c | ||
|
|
345fd73faf | ||
|
|
8fc75de327 | ||
|
|
9dd50d9902 | ||
|
|
5d660258e7 | ||
|
|
d379d110ee | ||
|
|
21f847eda5 | ||
|
|
99765ed855 | ||
|
|
def2f7bd0e | ||
|
|
f89caa127b | ||
|
|
8f1214531e | ||
|
|
11f329bd40 | ||
|
|
2b2a898e0b | ||
|
|
34df1c94d5 | ||
|
|
7947aba68c | ||
|
|
847e471db6 | ||
|
|
4e7e41ba77 | ||
|
|
1d725ca51d | ||
|
|
0e77246ba9 | ||
|
|
2073cf3775 | ||
|
|
26adaad438 | ||
|
|
bb98ca3c80 | ||
|
|
1f2dca0eaa | ||
|
|
f6e404c20a | ||
|
|
7383b64b3a | ||
|
|
0e665c3998 | ||
|
|
3c69f77c67 | ||
|
|
533cda90ce | ||
|
|
c70a9335de | ||
|
|
05c5c39a7c | ||
|
|
a6254f2bd4 | ||
|
|
2ead115116 | ||
|
|
1f0142ce19 | ||
|
|
43afd72bee | ||
|
|
bac3be7c46 | ||
|
|
f6aedb62c0 | ||
|
|
a08c8c851a | ||
|
|
5c06e0814c | ||
|
|
aab91b8d8f | ||
|
|
a400336398 | ||
|
|
fa3536d202 | ||
|
|
40123b3805 | ||
|
|
49815e3841 | ||
|
|
c1af07ce84 | ||
|
|
e61ba052b3 | ||
|
|
560358dc93 | ||
|
|
5c1c904877 | ||
|
|
c27061dae7 | ||
|
|
a657ee9a9c | ||
|
|
8a66c85e90 | ||
|
|
1b8a2e8fdd | ||
|
|
0460e9dcce | ||
|
|
f548da33e8 | ||
|
|
d86f9b9c3f | ||
|
|
4b31723433 | ||
|
|
bfb6b1b8a8 | ||
|
|
258459ed54 | ||
|
|
fca5aa6fcc | ||
|
|
e1951b6f28 | ||
|
|
730f7e2e21 | ||
|
|
a9b3c47c8e | ||
|
|
3f16a836a4 | ||
|
|
269f6b2e2d | ||
|
|
04dec2a359 | ||
|
|
72c73cdc8b | ||
|
|
4d02bfa3a6 | ||
|
|
8f65feafc0 | ||
|
|
0d590d9991 | ||
|
|
7ccb8d6ef3 | ||
|
|
e3c6ba37dd | ||
|
|
967476eaee | ||
|
|
9790b94c92 | ||
|
|
a4ea345b04 | ||
|
|
24a24f013d | ||
|
|
ff4b3fb5b4 | ||
|
|
cba752d588 | ||
|
|
6e801b0bd1 | ||
|
|
1b166928c7 | ||
|
|
b8cf9130f8 | ||
|
|
21441c09bd | ||
|
|
4ea6e051ee | ||
|
|
f3bb024fd6 | ||
|
|
6e56a114be |
No files matched your search
+16
-4
@@ -153,7 +153,9 @@ set(SOURCES
|
||||
memtable/skiplistrep.cc
|
||||
memtable/vectorrep.cc
|
||||
port/stack_trace.cc
|
||||
port/win/io_win.cc
|
||||
port/win/env_win.cc
|
||||
port/win/env_default.cc
|
||||
port/win/port_win.cc
|
||||
port/win/win_logger.cc
|
||||
port/win/xpress_win.cc
|
||||
@@ -164,7 +166,6 @@ set(SOURCES
|
||||
table/block_based_table_factory.cc
|
||||
table/block_based_table_reader.cc
|
||||
table/block_builder.cc
|
||||
table/block_hash_index.cc
|
||||
table/block_prefix_index.cc
|
||||
table/bloom_block.cc
|
||||
table/cuckoo_table_builder.cc
|
||||
@@ -183,6 +184,7 @@ set(SOURCES
|
||||
table/plain_table_index.cc
|
||||
table/plain_table_key_coding.cc
|
||||
table/plain_table_reader.cc
|
||||
table/persistent_cache_helper.cc
|
||||
table/table_properties.cc
|
||||
table/two_level_iterator.cc
|
||||
tools/sst_dump_tool.cc
|
||||
@@ -200,6 +202,7 @@ set(SOURCES
|
||||
util/delete_scheduler.cc
|
||||
util/dynamic_bloom.cc
|
||||
util/env.cc
|
||||
util/env_chroot.cc
|
||||
util/env_hdfs.cc
|
||||
util/event_logger.cc
|
||||
util/file_util.cc
|
||||
@@ -219,7 +222,6 @@ set(SOURCES
|
||||
util/murmurhash.cc
|
||||
util/mutable_cf_options.cc
|
||||
util/options.cc
|
||||
util/options_builder.cc
|
||||
util/options_helper.cc
|
||||
util/options_parser.cc
|
||||
util/options_sanity_check.cc
|
||||
@@ -236,6 +238,7 @@ set(SOURCES
|
||||
util/testharness.cc
|
||||
util/testutil.cc
|
||||
util/thread_local.cc
|
||||
util/threadpool.cc
|
||||
util/thread_status_impl.cc
|
||||
util/thread_status_updater.cc
|
||||
util/thread_status_util.cc
|
||||
@@ -250,6 +253,7 @@ set(SOURCES
|
||||
utilities/document/json_document.cc
|
||||
utilities/document/json_document_builder.cc
|
||||
utilities/env_mirror.cc
|
||||
utilities/env_registry.cc
|
||||
utilities/flashcache/flashcache.cc
|
||||
utilities/geodb/geodb_impl.cc
|
||||
utilities/leveldb_options/leveldb_options.cc
|
||||
@@ -257,8 +261,11 @@ set(SOURCES
|
||||
utilities/merge_operators/string_append/stringappend.cc
|
||||
utilities/merge_operators/string_append/stringappend2.cc
|
||||
utilities/merge_operators/put.cc
|
||||
utilities/merge_operators/max.cc
|
||||
utilities/merge_operators/uint64add.cc
|
||||
utilities/options/options_util.cc
|
||||
utilities/persistent_cache/persistent_cache_tier.cc
|
||||
utilities/persistent_cache/volatile_tier_impl.cc
|
||||
utilities/redis/redis_lists.cc
|
||||
utilities/spatialdb/spatial_db.cc
|
||||
utilities/table_properties_collectors/compact_on_deletion_collector.cc
|
||||
@@ -281,6 +288,7 @@ set(TESTUTIL_SOURCE
|
||||
db/db_test_util.cc
|
||||
table/mock_table.cc
|
||||
util/mock_env.cc
|
||||
util/fault_injection_test_env.cc
|
||||
util/thread_status_updater_debug.cc
|
||||
)
|
||||
|
||||
@@ -316,6 +324,7 @@ set(APPS
|
||||
tools/dump/rocksdb_dump.cc
|
||||
tools/dump/rocksdb_undump.cc
|
||||
util/cache_bench.cc
|
||||
utilities/persistent_cache/hash_table_bench.cc
|
||||
)
|
||||
|
||||
set(C_TESTS db/c_test.c)
|
||||
@@ -375,8 +384,8 @@ set(TESTS
|
||||
db/write_batch_test.cc
|
||||
db/write_callback_test.cc
|
||||
db/write_controller_test.cc
|
||||
db/db_io_failure_test.cc
|
||||
table/block_based_filter_block_test.cc
|
||||
table/block_hash_index_test.cc
|
||||
table/block_test.cc
|
||||
table/cuckoo_table_builder_test.cc
|
||||
table/cuckoo_table_reader_test.cc
|
||||
@@ -395,6 +404,7 @@ set(TESTS
|
||||
util/crc32c_test.cc
|
||||
util/delete_scheduler_test.cc
|
||||
util/dynamic_bloom_test.cc
|
||||
util/env_basic_test.cc
|
||||
util/env_test.cc
|
||||
util/event_logger_test.cc
|
||||
util/filelock_test.cc
|
||||
@@ -402,7 +412,6 @@ set(TESTS
|
||||
util/heap_test.cc
|
||||
util/histogram_test.cc
|
||||
util/iostats_context_test.cc
|
||||
util/memenv_test.cc
|
||||
util/mock_env_test.cc
|
||||
util/options_settable_test.cc
|
||||
util/options_test.cc
|
||||
@@ -414,10 +423,13 @@ set(TESTS
|
||||
utilities/checkpoint/checkpoint_test.cc
|
||||
utilities/document/document_db_test.cc
|
||||
utilities/document/json_document_test.cc
|
||||
utilities/env_registry_test.cc
|
||||
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/persistent_cache/hash_table_test.cc
|
||||
utilities/persistent_cache/persistent_cache_test.cc
|
||||
utilities/redis/redis_lists_test.cc
|
||||
utilities/spatialdb/spatial_db_test.cc
|
||||
utilities/table_properties_collectors/compact_on_deletion_collector_test.cc
|
||||
|
||||
+16
-2
@@ -1,10 +1,23 @@
|
||||
# Rocksdb Change Log
|
||||
## Unreleased
|
||||
|
||||
## 4.9.0 (6/9/2016)
|
||||
### Public API changes
|
||||
* Add bottommost_compression option, This option can be used to set a specific compression algorithm for the bottommost level (Last level containing files in the DB).
|
||||
* Introduce CompactionJobInfo::compression, This field state the compression algorithm used to generate the output files of the compaction.
|
||||
* Deprecate BlockBaseTableOptions.hash_index_allow_collision=false
|
||||
* Deprecate options builder (GetOptions()).
|
||||
|
||||
### New Features
|
||||
* Introduce NewSimCache() in rocksdb/utilities/sim_cache.h. This function creates a block cache that is able to give simulation results (mainly hit rate) of simulating block behavior with a configurable cache size.
|
||||
|
||||
## 4.8.0 (5/2/2016)
|
||||
### Public API Change
|
||||
* Allow preset compression dictionary for improved compression of block-based tables. This is supported for zlib, zstd, and lz4. The compression dictionary's size is configurable via CompressionOptions::max_dict_bytes.
|
||||
* Delete deprecated classes for creating backups (BackupableDB) and restoring from backups (RestoreBackupableDB). Now, BackupEngine should be used for creating backups, and BackupEngineReadOnly should be used for restorations. For more details, see https://github.com/facebook/rocksdb/wiki/How-to-backup-RocksDB%3F
|
||||
* Expose estimate of per-level compression ratio via DB property: "rocksdb.compression-ratio-at-levelN".
|
||||
* Added EventListener::OnTableFileCreationStarted. EventListener::OnTableFileCreated will be called on failure case. User can check creation status via TableFileCreationInfo::status.
|
||||
|
||||
### New Features
|
||||
* Add ReadOptions::readahead_size. If non-zero, NewIterator will create a new table reader which performs reads of the given size.
|
||||
|
||||
@@ -17,8 +30,9 @@
|
||||
### Public API Changes
|
||||
* Change default of BlockBasedTableOptions.format_version to 2. It means default DB created by 4.6 or up cannot be opened by RocksDB version 3.9 or earlier.
|
||||
* Added strict_capacity_limit option to NewLRUCache. If the flag is set to true, insert to cache will fail if no enough capacity can be free. Signiture of Cache::Insert() is updated accordingly.
|
||||
* Tickers [NUMBER_DB_NEXT, NUMBER_DB_PREV, NUMBER_DB_NEXT_FOUND, NUMBER_DB_PREV_FOUND, ITER_BYTES_READ] are not updated immediately. The are updated when the Iterator is deleted.
|
||||
* Tickers [NUMBER_DB_NEXT, NUMBER_DB_PREV, NUMBER_DB_NEXT_FOUND, NUMBER_DB_PREV_FOUND, ITER_BYTES_READ] are not updated immediately. The are updated when the Iterator is deleted.
|
||||
* Add monotonically increasing counter (DB property "rocksdb.current-super-version-number") that increments upon any change to the LSM tree.
|
||||
|
||||
### New Features
|
||||
* Add CompactionPri::kMinOverlappingRatio, a compaction picking mode friendly to write amplification.
|
||||
* Deprecate Iterator::IsKeyPinned() and replace it with Iterator::GetProperty() with prop_name="rocksdb.iterator.is.key.pinned"
|
||||
@@ -32,7 +46,7 @@
|
||||
### New Features
|
||||
* ldb tool now supports operations to non-default column families.
|
||||
* Add kPersistedTier to ReadTier. This option allows Get and MultiGet to read only the persited data and skip mem-tables if writes were done with disableWAL = true.
|
||||
* Add DBOptions::sst_file_manager. Use NewSstFileManager() in include/rocksdb/sst_file_manager.h to create a SstFileManager that can be used to track the total size of SST files and control the SST files deletion rate.
|
||||
* Add DBOptions::sst_file_manager. Use NewSstFileManager() in include/rocksdb/sst_file_manager.h to create a SstFileManager that can be used to track the total size of SST files and control the SST files deletion rate.
|
||||
|
||||
## 4.4.0 (1/14/2016)
|
||||
### Public API Changes
|
||||
|
||||
@@ -271,9 +271,9 @@ TESTS = \
|
||||
db_tailing_iter_test \
|
||||
db_universal_compaction_test \
|
||||
db_wal_test \
|
||||
db_io_failure_test \
|
||||
db_properties_test \
|
||||
db_table_properties_test \
|
||||
block_hash_index_test \
|
||||
autovector_test \
|
||||
column_family_test \
|
||||
table_properties_collector_test \
|
||||
@@ -290,6 +290,7 @@ TESTS = \
|
||||
crc32c_test \
|
||||
slice_transform_test \
|
||||
dbformat_test \
|
||||
env_basic_test \
|
||||
env_test \
|
||||
fault_injection_test \
|
||||
filelock_test \
|
||||
@@ -297,17 +298,18 @@ TESTS = \
|
||||
file_reader_writer_test \
|
||||
block_based_filter_block_test \
|
||||
full_filter_block_test \
|
||||
hash_table_test \
|
||||
histogram_test \
|
||||
inlineskiplist_test \
|
||||
log_test \
|
||||
manual_compaction_test \
|
||||
memenv_test \
|
||||
mock_env_test \
|
||||
memtable_list_test \
|
||||
merge_helper_test \
|
||||
memory_test \
|
||||
merge_test \
|
||||
merger_test \
|
||||
util_merge_operators_test \
|
||||
options_file_test \
|
||||
redis_test \
|
||||
reduce_levels_test \
|
||||
@@ -320,6 +322,7 @@ TESTS = \
|
||||
backupable_db_test \
|
||||
document_db_test \
|
||||
json_document_test \
|
||||
sim_cache_test \
|
||||
spatial_db_test \
|
||||
version_edit_test \
|
||||
version_set_test \
|
||||
@@ -358,7 +361,8 @@ TESTS = \
|
||||
compaction_job_stats_test \
|
||||
transaction_test \
|
||||
ldb_cmd_test \
|
||||
iostats_context_test
|
||||
iostats_context_test \
|
||||
persistent_cache_test \
|
||||
|
||||
PARALLEL_TEST = \
|
||||
backupable_db_test \
|
||||
@@ -391,6 +395,9 @@ TOOLS = \
|
||||
rocksdb_dump \
|
||||
rocksdb_undump
|
||||
|
||||
TEST_LIBS = \
|
||||
librocksdb_env_basic_test.a
|
||||
|
||||
# TODO: add back forward_iterator_bench, after making it build in all environemnts.
|
||||
BENCHMARKS = db_bench table_reader_bench cache_bench memtablerep_bench
|
||||
|
||||
@@ -404,6 +411,7 @@ else
|
||||
endif
|
||||
endif
|
||||
LIBRARY = ${LIBNAME}.a
|
||||
TOOLS_LIBRARY = ${LIBNAME}_tools.a
|
||||
|
||||
ROCKSDB_MAJOR = $(shell egrep "ROCKSDB_MAJOR.[0-9]" include/rocksdb/version.h | cut -d ' ' -f 3)
|
||||
ROCKSDB_MINOR = $(shell egrep "ROCKSDB_MINOR.[0-9]" include/rocksdb/version.h | cut -d ' ' -f 3)
|
||||
@@ -455,10 +463,10 @@ endif # PLATFORM_SHARED_EXT
|
||||
.PHONY: blackbox_crash_test check clean coverage crash_test ldb_tests package \
|
||||
release tags valgrind_check whitebox_crash_test format static_lib shared_lib all \
|
||||
dbg rocksdbjavastatic rocksdbjava install install-static install-shared uninstall \
|
||||
analyze tools
|
||||
analyze tools tools_lib
|
||||
|
||||
|
||||
all: $(LIBRARY) $(BENCHMARKS) tools $(TESTS)
|
||||
all: $(LIBRARY) $(BENCHMARKS) tools tools_lib test_libs $(TESTS)
|
||||
|
||||
static_lib: $(LIBRARY)
|
||||
|
||||
@@ -466,6 +474,10 @@ shared_lib: $(SHARED)
|
||||
|
||||
tools: $(TOOLS)
|
||||
|
||||
tools_lib: $(TOOLS_LIBRARY)
|
||||
|
||||
test_libs: $(TEST_LIBS)
|
||||
|
||||
dbg: $(LIBRARY) $(BENCHMARKS) tools $(TESTS)
|
||||
|
||||
# creates static library and programs
|
||||
@@ -795,6 +807,14 @@ $(LIBRARY): $(LIBOBJECTS)
|
||||
$(AM_V_AR)rm -f $@
|
||||
$(AM_V_at)$(AR) $(ARFLAGS) $@ $(LIBOBJECTS)
|
||||
|
||||
$(TOOLS_LIBRARY): $(BENCH_SOURCES:.cc=.o) $(TOOL_SOURCES:.cc=.o) $(LIB_SOURCES:.cc=.o) $(TESTUTIL)
|
||||
$(AM_V_AR)rm -f $@
|
||||
$(AM_V_at)$(AR) $(ARFLAGS) $@ $^
|
||||
|
||||
librocksdb_env_basic_test.a: util/env_basic_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_V_AR)rm -f $@
|
||||
$(AM_V_at)$(AR) $(ARFLAGS) $@ $^
|
||||
|
||||
db_bench: tools/db_bench.o $(BENCHTOOLOBJECTS)
|
||||
$(AM_LINK)
|
||||
|
||||
@@ -804,9 +824,6 @@ cache_bench: util/cache_bench.o $(LIBOBJECTS) $(TESTUTIL)
|
||||
memtablerep_bench: db/memtablerep_bench.o $(LIBOBJECTS) $(TESTUTIL)
|
||||
$(AM_LINK)
|
||||
|
||||
block_hash_index_test: table/block_hash_index_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
db_stress: tools/db_stress.o $(LIBOBJECTS) $(TESTUTIL)
|
||||
$(AM_LINK)
|
||||
|
||||
@@ -852,6 +869,9 @@ stringappend_test: utilities/merge_operators/string_append/stringappend_test.o $
|
||||
redis_test: utilities/redis/redis_lists_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
hash_table_test: utilities/persistent_cache/hash_table_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
histogram_test: util/histogram_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
@@ -912,6 +932,9 @@ db_universal_compaction_test: db/db_universal_compaction_test.o db/db_test_util.
|
||||
db_wal_test: db/db_wal_test.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
db_io_failure_test: db/db_io_failure_test.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
db_properties_test: db/db_properties_test.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
@@ -948,12 +971,18 @@ document_db_test: utilities/document/document_db_test.o $(LIBOBJECTS) $(TESTHARN
|
||||
json_document_test: utilities/document/json_document_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
sim_cache_test: utilities/simulator_cache/sim_cache_test.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
spatial_db_test: utilities/spatialdb/spatial_db_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
env_mirror_test: utilities/env_mirror_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
env_registry_test: utilities/env_registry_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
ttl_test: utilities/ttl/ttl_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
@@ -981,6 +1010,9 @@ wal_manager_test: db/wal_manager_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
dbformat_test: db/dbformat_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
env_basic_test: util/env_basic_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
env_test: util/env_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
@@ -1056,6 +1088,9 @@ merge_test: db/merge_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
merger_test: table/merger_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
util_merge_operators_test: utilities/util_merge_operators_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
options_file_test: db/options_file_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
@@ -1098,15 +1133,15 @@ options_settable_test: util/options_settable_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
options_util_test: utilities/options/options_util_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
db_bench_tool_test: tools/db_bench_tool_test.o $(BENCHTOOLOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
event_logger_test: util/event_logger_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
sst_dump_test: tools/sst_dump_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
memenv_test : util/memenv_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
optimistic_transaction_test: utilities/transactions/optimistic_transaction_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
@@ -1149,6 +1184,9 @@ ldb: tools/ldb.o $(LIBOBJECTS)
|
||||
iostats_context_test: util/iostats_context_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_V_CCLD)$(CXX) $^ $(EXEC_LDFLAGS) -o $@ $(LDFLAGS)
|
||||
|
||||
persistent_cache_test: utilities/persistent_cache/persistent_cache_test.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
#-------------------------------------------------
|
||||
# make install related stuff
|
||||
INSTALL_PATH ?= /usr/local
|
||||
|
||||
@@ -56,3 +56,6 @@ Pinterest's Object Retrieval System uses RocksDB for storage: https://www.youtub
|
||||
## quasardb
|
||||
[quasardb](https://www.quasardb.net) is a high-performance, distributed, transactional key-value database that integrates well with in-memory analytics engines such as Apache Spark.
|
||||
quasardb uses a heavily tuned RocksDB as its persistence layer.
|
||||
|
||||
## Netflix
|
||||
[Netflix](http://techblog.netflix.com/2016/05/application-data-caching-using-ssds.html) uses RocksDB on spinning disks to cache application data.
|
||||
+3
-2
@@ -2,7 +2,7 @@ version: 1.0.{build}
|
||||
before_build:
|
||||
- md %APPVEYOR_BUILD_FOLDER%\build
|
||||
- cd %APPVEYOR_BUILD_FOLDER%\build
|
||||
- cmake -G "Visual Studio 12 Win64" -DOPTDBG=1 ..
|
||||
- cmake -G "Visual Studio 12 Win64" -DOPTDBG=1 -DXPRESS=1 ..
|
||||
- cd ..
|
||||
build:
|
||||
project: build\rocksdb.sln
|
||||
@@ -10,6 +10,7 @@ build:
|
||||
verbosity: minimal
|
||||
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 -EnableRerun -Run db_test2 -Concurrency 8
|
||||
- ps: build_tools\run_ci_db_test.ps1 -EnableRerun -Run db_test -Exclude DBTest.GroupCommitTest -Concurrency 10
|
||||
- ps: build_tools\run_ci_db_test.ps1 -Run env_test -Concurrency 1
|
||||
|
||||
@@ -131,7 +131,7 @@ class FacebookArcanistConfiguration extends ArcanistConfiguration {
|
||||
} else {
|
||||
// manually list of tests we want to run in sandcastle
|
||||
$tests = array(
|
||||
"unit", "unit_481", "clang_unit", "tsan", "asan", "lite", "valgrind"
|
||||
"unit", "unit_481", "clang_unit", "tsan", "asan", "lite_test", "valgrind"
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
+16
-16
@@ -1,16 +1,16 @@
|
||||
GCC_BASE=/mnt/vol/engshare/fbcode/third-party2/gcc/4.9.x/centos6-native/1317bc4/
|
||||
CLANG_BASE=/mnt/gvfs/third-party2/clang/fc904e50a9266b9d7b98cae1993afa0c5aae1440/3.7.1/centos6-native/9d9ecb9/
|
||||
LIBGCC_BASE=/mnt/gvfs/third-party2/libgcc/f97108c12512b3b0789ac4515d836bdb1eae1142/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/cb6c4880fcb4fee471574ba6af63a3882155a16a/0.5.1/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/d9acac8a9a2c9378eb696e22ffa8bd0e97d9206b/master/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/41bfa3759ce52c071f5fd547ec9ecd2522929f0a/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/71454c53fffcb716a0beb9a90047aff7fb5c984a/2.26/centos6-native/da39a3e
|
||||
VALGRIND_BASE=/mnt/gvfs/third-party2/valgrind/af85c56f424cd5edfc2c97588299b44ecdec96bb/3.10.0/gcc-4.9-glibc-2.20/e9936bf
|
||||
GCC_BASE=/mnt/gvfs/third-party2/gcc/ebc96bc2fb751b5a0300b8d91a95bdf24ac1d88b/4.9.x/centos6-native/108cf83
|
||||
CLANG_BASE=/mnt/gvfs/third-party2/llvm-fb/b91de48a4974ec839946d824402b098d43454cef/stable/centos6-native/7aaccbe
|
||||
LIBGCC_BASE=/mnt/gvfs/third-party2/libgcc/53e0eac8911888a105aa98b9a35fe61cf1d8b278/4.9.x/gcc-4.9-glibc-2.20/024dbc3
|
||||
GLIBC_BASE=/mnt/gvfs/third-party2/glibc/ee36ac9a72dfac4a995f1b215bb4c0fc8a0f6f91/2.20/gcc-4.9-glibc-2.20/500e281
|
||||
SNAPPY_BASE=/mnt/gvfs/third-party2/snappy/8c38a4c1e52b4c2cc8a9cdc31b9c947ed7dbfcb4/1.1.3/gcc-4.9-glibc-2.20/e9936bf
|
||||
ZLIB_BASE=/mnt/gvfs/third-party2/zlib/2b24f1e99454f9ca7b0301720f94836dae1bf71b/1.2.8/gcc-5-glibc-2.23/9bc6787
|
||||
BZIP2_BASE=/mnt/gvfs/third-party2/bzip2/af7c14c9b652cdd5ec34eadd25c3f38a9b306c5d/1.0.6/gcc-5-glibc-2.23/9bc6787
|
||||
LZ4_BASE=/mnt/gvfs/third-party2/lz4/0e790b441e2d9acd68d51e1d2e028f88c6a79ddf/r131/gcc-5-glibc-2.23/9bc6787
|
||||
ZSTD_BASE=/mnt/gvfs/third-party2/zstd/1408484d03b15492aa54b10356104e9dc22e1cc5/0.6.1/gcc-5-glibc-2.23/9bc6787
|
||||
GFLAGS_BASE=/mnt/gvfs/third-party2/gflags/5a5c7a6608cb32f1e1e7f814023d5bdfbd136370/2.1.1/gcc-4.8.1-glibc-2.17/c3f970a
|
||||
JEMALLOC_BASE=/mnt/gvfs/third-party2/jemalloc/a4c2adecffcaa68d5585d06be2252e3efa52555b/master/gcc-5-glibc-2.23/1c32b4b
|
||||
NUMA_BASE=/mnt/gvfs/third-party2/numa/1abc0d3c01743b854676423cf2d3629912f34930/2.0.8/gcc-5-glibc-2.23/9bc6787
|
||||
LIBUNWIND_BASE=/mnt/gvfs/third-party2/libunwind/ad576de2a1ea560c4d3434304f0fc4e079bede42/trunk/gcc-5-glibc-2.23/b1847cb
|
||||
KERNEL_HEADERS_BASE=/mnt/gvfs/third-party2/kernel-headers/f24be37d170e04be6e469af487644d4d62e1c6c1/4.0.9-36_fbk5_2933_gd092e3f/gcc-5-glibc-2.23/da39a3e
|
||||
BINUTILS_BASE=/mnt/gvfs/third-party2/binutils/9d65c666b9adf8f2a989fd4b98a9a5e7d3afa233/2.26/centos6-native/da39a3e
|
||||
VALGRIND_BASE=/mnt/gvfs/third-party2/valgrind/9cee5a3628dc9d4b93897972c58eba865e25b270/3.10.0/gcc-4.9-glibc-2.20/e9936bf
|
||||
@@ -1,16 +1,16 @@
|
||||
GCC_BASE=/mnt/vol/engshare/fbcode/third-party2/gcc/4.8.1/centos6-native/cc6c9dc/
|
||||
CLANG_BASE=/mnt/gvfs/third-party2/clang/fc904e50a9266b9d7b98cae1993afa0c5aae1440/3.7.1/centos6-native/9d9ecb9/
|
||||
LIBGCC_BASE=/mnt/gvfs/third-party2/libgcc/f97108c12512b3b0789ac4515d836bdb1eae1142/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/cb6c4880fcb4fee471574ba6af63a3882155a16a/0.5.1/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/d9acac8a9a2c9378eb696e22ffa8bd0e97d9206b/master/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/41bfa3759ce52c071f5fd547ec9ecd2522929f0a/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/71454c53fffcb716a0beb9a90047aff7fb5c984a/2.26/centos6-native/da39a3e
|
||||
VALGRIND_BASE=/mnt/gvfs/third-party2/valgrind/af85c56f424cd5edfc2c97588299b44ecdec96bb/3.8.1/gcc-4.8.1-glibc-2.17/c3f970a
|
||||
GCC_BASE=/mnt/gvfs/third-party2/gcc/ebc96bc2fb751b5a0300b8d91a95bdf24ac1d88b/4.8.1/centos6-native/cc6c9dc
|
||||
CLANG_BASE=/mnt/gvfs/third-party2/llvm-fb/b91de48a4974ec839946d824402b098d43454cef/stable/centos6-native/7aaccbe
|
||||
LIBGCC_BASE=/mnt/gvfs/third-party2/libgcc/53e0eac8911888a105aa98b9a35fe61cf1d8b278/4.8.1/gcc-4.8.1-glibc-2.17/8aac7fc
|
||||
GLIBC_BASE=/mnt/gvfs/third-party2/glibc/ee36ac9a72dfac4a995f1b215bb4c0fc8a0f6f91/2.17/gcc-4.8.1-glibc-2.17/99df8fc
|
||||
SNAPPY_BASE=/mnt/gvfs/third-party2/snappy/8c38a4c1e52b4c2cc8a9cdc31b9c947ed7dbfcb4/1.1.3/gcc-4.8.1-glibc-2.17/c3f970a
|
||||
ZLIB_BASE=/mnt/gvfs/third-party2/zlib/2b24f1e99454f9ca7b0301720f94836dae1bf71b/1.2.8/gcc-4.8.1-glibc-2.17/c3f970a
|
||||
BZIP2_BASE=/mnt/gvfs/third-party2/bzip2/af7c14c9b652cdd5ec34eadd25c3f38a9b306c5d/1.0.6/gcc-4.8.1-glibc-2.17/c3f970a
|
||||
LZ4_BASE=/mnt/gvfs/third-party2/lz4/0e790b441e2d9acd68d51e1d2e028f88c6a79ddf/r131/gcc-4.8.1-glibc-2.17/c3f970a
|
||||
ZSTD_BASE=/mnt/gvfs/third-party2/zstd/1408484d03b15492aa54b10356104e9dc22e1cc5/0.6.1/gcc-4.8.1-glibc-2.17/c3f970a
|
||||
GFLAGS_BASE=/mnt/gvfs/third-party2/gflags/5a5c7a6608cb32f1e1e7f814023d5bdfbd136370/2.1.1/gcc-4.8.1-glibc-2.17/c3f970a
|
||||
JEMALLOC_BASE=/mnt/gvfs/third-party2/jemalloc/a4c2adecffcaa68d5585d06be2252e3efa52555b/master/gcc-4.8.1-glibc-2.17/8d31e51
|
||||
NUMA_BASE=/mnt/gvfs/third-party2/numa/1abc0d3c01743b854676423cf2d3629912f34930/2.0.8/gcc-4.8.1-glibc-2.17/c3f970a
|
||||
LIBUNWIND_BASE=/mnt/gvfs/third-party2/libunwind/ad576de2a1ea560c4d3434304f0fc4e079bede42/trunk/gcc-4.8.1-glibc-2.17/675d945
|
||||
KERNEL_HEADERS_BASE=/mnt/gvfs/third-party2/kernel-headers/f24be37d170e04be6e469af487644d4d62e1c6c1/4.0.9-36_fbk5_2933_gd092e3f/gcc-4.8.1-glibc-2.17/da39a3e
|
||||
BINUTILS_BASE=/mnt/gvfs/third-party2/binutils/9d65c666b9adf8f2a989fd4b98a9a5e7d3afa233/2.26/centos6-native/da39a3e
|
||||
VALGRIND_BASE=/mnt/gvfs/third-party2/valgrind/9cee5a3628dc9d4b93897972c58eba865e25b270/3.8.1/gcc-4.8.1-glibc-2.17/c3f970a
|
||||
@@ -100,7 +100,7 @@ if [ -z "$USE_CLANG" ]; then
|
||||
JEMALLOC=1
|
||||
else
|
||||
# clang
|
||||
CLANG_INCLUDE="$CLANG_LIB/clang/*/include"
|
||||
CLANG_INCLUDE="$CLANG_LIB/clang/stable/include"
|
||||
CC="$CLANG_BIN/clang"
|
||||
CXX="$CLANG_BIN/clang++"
|
||||
|
||||
@@ -114,6 +114,7 @@ else
|
||||
CFLAGS+=" -isystem $CLANG_INCLUDE"
|
||||
CFLAGS+=" -isystem $KERNEL_HEADERS_INCLUDE/linux "
|
||||
CFLAGS+=" -isystem $KERNEL_HEADERS_INCLUDE "
|
||||
CFLAGS+=" -Wno-expansion-to-defined "
|
||||
CXXFLAGS="-nostdinc++"
|
||||
fi
|
||||
|
||||
|
||||
@@ -65,7 +65,7 @@ SHM="TEST_TMPDIR=/dev/shm/rocksdb"
|
||||
GCC_481="ROCKSDB_FBCODE_BUILD_WITH_481=1"
|
||||
ASAN="COMPILE_WITH_ASAN=1"
|
||||
CLANG="USE_CLANG=1"
|
||||
LITE="OPT=-DROCKSDB_LITE"
|
||||
LITE="OPT=\"-DROCKSDB_LITE -g\""
|
||||
TSAN="COMPILE_WITH_TSAN=1"
|
||||
UBSAN="COMPILE_WITH_UBSAN=1"
|
||||
DISABLE_JEMALLOC="DISABLE_JEMALLOC=1"
|
||||
@@ -257,7 +257,7 @@ LITE_BUILD_COMMANDS="[
|
||||
$CLEANUP_ENV,
|
||||
{
|
||||
'name':'Build RocksDB debug version',
|
||||
'shell':'$LITE $DEBUG make J=1 static_lib',
|
||||
'shell':'$LITE make J=1 static_lib',
|
||||
'user':'root',
|
||||
$PARSER
|
||||
},
|
||||
@@ -277,7 +277,7 @@ LITE_UNIT_TEST_COMMANDS="[
|
||||
$CLEANUP_ENV,
|
||||
{
|
||||
'name':'Build RocksDB debug version',
|
||||
'shell':'$SHM $LITE $DEBUG make J=1 check',
|
||||
'shell':'$SHM $LITE make J=1 check',
|
||||
'user':'root',
|
||||
$PARSER
|
||||
},
|
||||
|
||||
@@ -64,16 +64,16 @@ 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 /mnt/gvfs/third-party2/clang/fc904e50a9266b9d7b98cae1993afa0c5aae1440/3.7.1/centos6-native/*/ | head -n1`
|
||||
GCC_BASE=`readlink -f $TP2_LATEST/gcc/4.9.x/centos6-native/*/`
|
||||
CLANG_BASE=`readlink -f $TP2_LATEST/llvm-fb/stable/centos6-native/*/`
|
||||
|
||||
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 glibc 2.20
|
||||
get_lib_base snappy LATEST gcc-4.9-glibc-2.20
|
||||
get_lib_base zlib LATEST
|
||||
get_lib_base bzip2 LATEST
|
||||
get_lib_base lz4 LATEST
|
||||
@@ -85,7 +85,7 @@ get_lib_base libunwind LATEST
|
||||
|
||||
get_lib_base kernel-headers LATEST
|
||||
get_lib_base binutils LATEST centos6-native
|
||||
get_lib_base valgrind LATEST
|
||||
get_lib_base valgrind 3.10.0 gcc-4.9-glibc-2.20
|
||||
|
||||
git diff $OUTPUT
|
||||
|
||||
@@ -101,8 +101,8 @@ 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 /mnt/gvfs/third-party2/clang/fc904e50a9266b9d7b98cae1993afa0c5aae1440/3.7.1/centos6-native/*/ | head -n1`
|
||||
GCC_BASE=`readlink -f $TP2_LATEST/gcc/4.8.1/centos6-native/*/`
|
||||
CLANG_BASE=`readlink -f $TP2_LATEST/llvm-fb/stable/centos6-native/*/`
|
||||
|
||||
log_variable GCC_BASE
|
||||
log_variable CLANG_BASE
|
||||
|
||||
@@ -6,8 +6,6 @@
|
||||
#include "db/auto_roll_logger.h"
|
||||
#include "util/mutexlock.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
// -- AutoRollLogger
|
||||
@@ -47,7 +45,8 @@ void AutoRollLogger::RollLogFile() {
|
||||
env_->RenameFile(log_fname_, old_fname);
|
||||
}
|
||||
|
||||
string AutoRollLogger::ValistToString(const char* format, va_list args) const {
|
||||
std::string AutoRollLogger::ValistToString(const char* format,
|
||||
va_list args) const {
|
||||
// Any log messages longer than 1024 will get truncated.
|
||||
// The user is responsible for chopping longer messages into multi line log
|
||||
static const int MAXBUFFERSIZE = 1024;
|
||||
@@ -112,7 +111,7 @@ void AutoRollLogger::LogHeader(const char* format, va_list args) {
|
||||
// strings
|
||||
va_list tmp;
|
||||
va_copy(tmp, args);
|
||||
string data = ValistToString(format, tmp);
|
||||
std::string data = ValistToString(format, tmp);
|
||||
va_end(tmp);
|
||||
|
||||
MutexLock l(&mutex_);
|
||||
|
||||
+25
-27
@@ -19,8 +19,6 @@
|
||||
#include <sys/stat.h>
|
||||
#include <errno.h>
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
class AutoRollLoggerTest : public testing::Test {
|
||||
@@ -40,23 +38,22 @@ class AutoRollLoggerTest : public testing::Test {
|
||||
Env::Default()->CreateDir(kTestDir);
|
||||
}
|
||||
|
||||
void RollLogFileBySizeTest(AutoRollLogger* logger,
|
||||
size_t log_max_size,
|
||||
const string& log_message);
|
||||
uint64_t RollLogFileByTimeTest(AutoRollLogger* logger,
|
||||
size_t time,
|
||||
const string& log_message);
|
||||
void RollLogFileBySizeTest(AutoRollLogger* logger, size_t log_max_size,
|
||||
const std::string& log_message);
|
||||
uint64_t RollLogFileByTimeTest(AutoRollLogger* logger, size_t time,
|
||||
const std::string& log_message);
|
||||
|
||||
static const string kSampleMessage;
|
||||
static const string kTestDir;
|
||||
static const string kLogFile;
|
||||
static const std::string kSampleMessage;
|
||||
static const std::string kTestDir;
|
||||
static const std::string kLogFile;
|
||||
static Env* env;
|
||||
};
|
||||
|
||||
const string AutoRollLoggerTest::kSampleMessage(
|
||||
const std::string AutoRollLoggerTest::kSampleMessage(
|
||||
"this is the message to be written to the log file!!");
|
||||
const string AutoRollLoggerTest::kTestDir(test::TmpDir() + "/db_log_test");
|
||||
const string AutoRollLoggerTest::kLogFile(test::TmpDir() + "/db_log_test/LOG");
|
||||
const std::string AutoRollLoggerTest::kTestDir(test::TmpDir() + "/db_log_test");
|
||||
const std::string AutoRollLoggerTest::kLogFile(test::TmpDir() +
|
||||
"/db_log_test/LOG");
|
||||
Env* AutoRollLoggerTest::env = Env::Default();
|
||||
|
||||
// In this test we only want to Log some simple log message with
|
||||
@@ -86,7 +83,7 @@ void GetFileCreateTime(const std::string& fname, uint64_t* file_ctime) {
|
||||
|
||||
void AutoRollLoggerTest::RollLogFileBySizeTest(AutoRollLogger* logger,
|
||||
size_t log_max_size,
|
||||
const string& log_message) {
|
||||
const std::string& log_message) {
|
||||
logger->SetInfoLogLevel(InfoLogLevel::INFO_LEVEL);
|
||||
// measure the size of each message, which is supposed
|
||||
// to be equal or greater than log_message.size()
|
||||
@@ -111,7 +108,7 @@ void AutoRollLoggerTest::RollLogFileBySizeTest(AutoRollLogger* logger,
|
||||
}
|
||||
|
||||
uint64_t AutoRollLoggerTest::RollLogFileByTimeTest(
|
||||
AutoRollLogger* logger, size_t time, const string& log_message) {
|
||||
AutoRollLogger* logger, size_t time, const std::string& log_message) {
|
||||
uint64_t expected_create_time;
|
||||
uint64_t actual_create_time;
|
||||
uint64_t total_log_size;
|
||||
@@ -361,13 +358,13 @@ TEST_F(AutoRollLoggerTest, InfoLogLevel) {
|
||||
|
||||
// Test the logger Header function for roll over logs
|
||||
// We expect the new logs creates as roll over to carry the headers specified
|
||||
static std::vector<string> GetOldFileNames(const string& path) {
|
||||
std::vector<string> ret;
|
||||
static std::vector<std::string> GetOldFileNames(const std::string& path) {
|
||||
std::vector<std::string> ret;
|
||||
|
||||
const string dirname = path.substr(/*start=*/ 0, path.find_last_of("/"));
|
||||
const string fname = path.substr(path.find_last_of("/") + 1);
|
||||
const std::string dirname = path.substr(/*start=*/0, path.find_last_of("/"));
|
||||
const std::string fname = path.substr(path.find_last_of("/") + 1);
|
||||
|
||||
std::vector<string> children;
|
||||
std::vector<std::string> children;
|
||||
Env::Default()->GetChildren(dirname, &children);
|
||||
|
||||
// We know that the old log files are named [path]<something>
|
||||
@@ -382,12 +379,13 @@ static std::vector<string> GetOldFileNames(const string& path) {
|
||||
}
|
||||
|
||||
// Return the number of lines where a given pattern was found in the file
|
||||
static size_t GetLinesCount(const string& fname, const string& pattern) {
|
||||
stringstream ssbuf;
|
||||
string line;
|
||||
static size_t GetLinesCount(const std::string& fname,
|
||||
const std::string& pattern) {
|
||||
std::stringstream ssbuf;
|
||||
std::string line;
|
||||
size_t count = 0;
|
||||
|
||||
ifstream inFile(fname.c_str());
|
||||
std::ifstream inFile(fname.c_str());
|
||||
ssbuf << inFile.rdbuf();
|
||||
|
||||
while (getline(ssbuf, line)) {
|
||||
@@ -426,7 +424,7 @@ TEST_F(AutoRollLoggerTest, LogHeaderTest) {
|
||||
}
|
||||
}
|
||||
|
||||
const string newfname = logger.TEST_log_fname();
|
||||
const std::string newfname = logger.TEST_log_fname();
|
||||
|
||||
// Log enough data to cause a roll over
|
||||
int i = 0;
|
||||
@@ -466,7 +464,7 @@ TEST_F(AutoRollLoggerTest, LogFileExistence) {
|
||||
[](char ch) { return ch == '/'; }, '\\');
|
||||
std::string deleteCmd = "if exist " + testDir + " rd /s /q " + testDir;
|
||||
#else
|
||||
string deleteCmd = "rm -rf " + kTestDir;
|
||||
std::string deleteCmd = "rm -rf " + kTestDir;
|
||||
#endif
|
||||
ASSERT_EQ(system(deleteCmd.c_str()), 0);
|
||||
options.max_log_file_size = 100 * 1024 * 1024;
|
||||
|
||||
+3
-3
@@ -59,8 +59,8 @@ TableBuilder* NewTableBuilder(
|
||||
|
||||
Status BuildTable(
|
||||
const std::string& dbname, Env* env, const ImmutableCFOptions& ioptions,
|
||||
const EnvOptions& env_options, TableCache* table_cache,
|
||||
InternalIterator* iter, FileMetaData* meta,
|
||||
const MutableCFOptions& mutable_cf_options, const EnvOptions& env_options,
|
||||
TableCache* table_cache, InternalIterator* iter, FileMetaData* meta,
|
||||
const InternalKeyComparator& internal_comparator,
|
||||
const std::vector<std::unique_ptr<IntTblPropCollectorFactory>>*
|
||||
int_tbl_prop_collector_factories,
|
||||
@@ -113,7 +113,7 @@ Status BuildTable(
|
||||
|
||||
MergeHelper merge(env, internal_comparator.user_comparator(),
|
||||
ioptions.merge_operator, nullptr, ioptions.info_log,
|
||||
ioptions.min_partial_merge_operands,
|
||||
mutable_cf_options.min_partial_merge_operands,
|
||||
true /* internal key corruption is not ok */,
|
||||
snapshots.empty() ? 0 : snapshots.back());
|
||||
|
||||
|
||||
+3
-2
@@ -19,6 +19,7 @@
|
||||
#include "rocksdb/table_properties.h"
|
||||
#include "rocksdb/types.h"
|
||||
#include "util/event_logger.h"
|
||||
#include "util/mutable_cf_options.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
@@ -61,8 +62,8 @@ TableBuilder* NewTableBuilder(
|
||||
// by column_family_id, or empty string if unknown.
|
||||
extern Status BuildTable(
|
||||
const std::string& dbname, Env* env, const ImmutableCFOptions& options,
|
||||
const EnvOptions& env_options, TableCache* table_cache,
|
||||
InternalIterator* iter, FileMetaData* meta,
|
||||
const MutableCFOptions& mutable_cf_options, const EnvOptions& env_options,
|
||||
TableCache* table_cache, InternalIterator* iter, FileMetaData* meta,
|
||||
const InternalKeyComparator& internal_comparator,
|
||||
const std::vector<std::unique_ptr<IntTblPropCollectorFactory>>*
|
||||
int_tbl_prop_collector_factories,
|
||||
|
||||
@@ -1875,6 +1875,11 @@ void rocksdb_options_set_inplace_update_num_locks(
|
||||
opt->rep.inplace_update_num_locks = v;
|
||||
}
|
||||
|
||||
void rocksdb_options_set_report_bg_io_stats(
|
||||
rocksdb_options_t* opt, int v) {
|
||||
opt->rep.report_bg_io_stats = v;
|
||||
}
|
||||
|
||||
void rocksdb_options_set_compaction_style(rocksdb_options_t *opt, int style) {
|
||||
opt->rep.compaction_style = static_cast<rocksdb::CompactionStyle>(style);
|
||||
}
|
||||
@@ -2122,6 +2127,11 @@ void rocksdb_readoptions_set_tailing(
|
||||
opt->rep.tailing = v;
|
||||
}
|
||||
|
||||
void rocksdb_readoptions_set_readahead_size(
|
||||
rocksdb_readoptions_t* opt, size_t v) {
|
||||
opt->rep.readahead_size = v;
|
||||
}
|
||||
|
||||
rocksdb_writeoptions_t* rocksdb_writeoptions_create() {
|
||||
return new rocksdb_writeoptions_t;
|
||||
}
|
||||
@@ -2163,6 +2173,10 @@ void rocksdb_cache_destroy(rocksdb_cache_t* cache) {
|
||||
delete cache;
|
||||
}
|
||||
|
||||
void rocksdb_cache_set_capacity(rocksdb_cache_t* cache, size_t capacity) {
|
||||
cache->rep->SetCapacity(capacity);
|
||||
}
|
||||
|
||||
rocksdb_env_t* rocksdb_create_default_env() {
|
||||
rocksdb_env_t* result = new rocksdb_env_t;
|
||||
result->rep = Env::Default();
|
||||
@@ -2388,6 +2402,31 @@ void rocksdb_get_options_from_string(const rocksdb_options_t* base_options,
|
||||
&new_options->rep));
|
||||
}
|
||||
|
||||
void rocksdb_delete_file_in_range(rocksdb_t* db, const char* start_key,
|
||||
size_t start_key_len, const char* limit_key,
|
||||
size_t limit_key_len, char** errptr) {
|
||||
Slice a, b;
|
||||
SaveError(
|
||||
errptr,
|
||||
DeleteFilesInRange(
|
||||
db->rep, db->rep->DefaultColumnFamily(),
|
||||
(start_key ? (a = Slice(start_key, start_key_len), &a) : nullptr),
|
||||
(limit_key ? (b = Slice(limit_key, limit_key_len), &b) : nullptr)));
|
||||
}
|
||||
|
||||
void rocksdb_delete_file_in_range_cf(
|
||||
rocksdb_t* db, rocksdb_column_family_handle_t* column_family,
|
||||
const char* start_key, size_t start_key_len, const char* limit_key,
|
||||
size_t limit_key_len, char** errptr) {
|
||||
Slice a, b;
|
||||
SaveError(
|
||||
errptr,
|
||||
DeleteFilesInRange(
|
||||
db->rep, column_family->rep,
|
||||
(start_key ? (a = Slice(start_key, start_key_len), &a) : nullptr),
|
||||
(limit_key ? (b = Slice(limit_key, limit_key_len), &b) : nullptr)));
|
||||
}
|
||||
|
||||
void rocksdb_free(void* ptr) { free(ptr); }
|
||||
|
||||
} // end extern "C"
|
||||
|
||||
+149
-1
@@ -13,6 +13,7 @@
|
||||
#include <thread>
|
||||
|
||||
#include "db/db_impl.h"
|
||||
#include "db/db_test_util.h"
|
||||
#include "rocksdb/db.h"
|
||||
#include "rocksdb/env.h"
|
||||
#include "rocksdb/iterator.h"
|
||||
@@ -26,6 +27,8 @@
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
static const int kValueSize = 1000;
|
||||
|
||||
namespace {
|
||||
std::string RandomString(Random* rnd, int len) {
|
||||
std::string r;
|
||||
@@ -70,6 +73,77 @@ class ColumnFamilyTest : public testing::Test {
|
||||
delete env_;
|
||||
}
|
||||
|
||||
// Return the value to associate with the specified key
|
||||
Slice Value(int k, std::string* 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);
|
||||
}
|
||||
}
|
||||
|
||||
void Build(int base, int n, int flush_every = 0) {
|
||||
std::string key_space, value_space;
|
||||
WriteBatch batch;
|
||||
|
||||
for (int i = 0; i < n; i++) {
|
||||
if (flush_every != 0 && i != 0 && i % flush_every == 0) {
|
||||
DBImpl* dbi = reinterpret_cast<DBImpl*>(db_);
|
||||
dbi->TEST_FlushMemTable();
|
||||
}
|
||||
|
||||
int keyi = base + i;
|
||||
Slice key(DBTestBase::Key(keyi));
|
||||
|
||||
batch.Clear();
|
||||
batch.Put(handles_[0], key, Value(keyi, &value_space));
|
||||
batch.Put(handles_[1], key, Value(keyi, &value_space));
|
||||
batch.Put(handles_[2], key, Value(keyi, &value_space));
|
||||
ASSERT_OK(db_->Write(WriteOptions(), &batch));
|
||||
}
|
||||
}
|
||||
|
||||
void CheckMissed() {
|
||||
uint64_t next_expected = 0;
|
||||
uint64_t missed = 0;
|
||||
int bad_keys = 0;
|
||||
int bad_values = 0;
|
||||
int correct = 0;
|
||||
std::string value_space;
|
||||
for (int cf = 0; cf < 3; cf++) {
|
||||
next_expected = 0;
|
||||
Iterator* iter = db_->NewIterator(ReadOptions(false, true), handles_[cf]);
|
||||
for (iter->SeekToFirst(); iter->Valid(); iter->Next()) {
|
||||
uint64_t key;
|
||||
Slice in(iter->key());
|
||||
in.remove_prefix(3);
|
||||
if (!ConsumeDecimalNumber(&in, &key) || !in.empty() ||
|
||||
key < next_expected) {
|
||||
bad_keys++;
|
||||
continue;
|
||||
}
|
||||
missed += (key - next_expected);
|
||||
next_expected = key + 1;
|
||||
if (iter->value() != Value(static_cast<int>(key), &value_space)) {
|
||||
bad_values++;
|
||||
} else {
|
||||
correct++;
|
||||
}
|
||||
}
|
||||
delete iter;
|
||||
}
|
||||
|
||||
ASSERT_EQ(0, bad_keys);
|
||||
ASSERT_EQ(0, bad_values);
|
||||
ASSERT_EQ(0, missed);
|
||||
(void)correct;
|
||||
}
|
||||
|
||||
void Close() {
|
||||
for (auto h : handles_) {
|
||||
if (h) {
|
||||
@@ -447,7 +521,7 @@ TEST_F(ColumnFamilyTest, AddDrop) {
|
||||
|
||||
std::vector<std::string> families;
|
||||
ASSERT_OK(DB::ListColumnFamilies(db_options_, dbname_, &families));
|
||||
sort(families.begin(), families.end());
|
||||
std::sort(families.begin(), families.end());
|
||||
ASSERT_TRUE(families ==
|
||||
std::vector<std::string>({"default", "four", "three"}));
|
||||
}
|
||||
@@ -2597,6 +2671,80 @@ TEST_F(ColumnFamilyTest, LogSyncConflictFlush) {
|
||||
Close();
|
||||
}
|
||||
#endif
|
||||
|
||||
// this test is placed here, because the infrastructure for Column Family
|
||||
// test is being used to ensure a roll of wal files.
|
||||
// Basic idea is to test that WAL truncation is being detected and not
|
||||
// ignored
|
||||
TEST_F(ColumnFamilyTest, DISABLED_LogTruncationTest) {
|
||||
Open();
|
||||
CreateColumnFamiliesAndReopen({"one", "two"});
|
||||
|
||||
Build(0, 100);
|
||||
|
||||
// Flush the 0th column family to force a roll of the wal log
|
||||
Flush(0);
|
||||
|
||||
// Add some more entries
|
||||
Build(100, 100);
|
||||
|
||||
std::vector<std::string> filenames;
|
||||
ASSERT_OK(env_->GetChildren(dbname_, &filenames));
|
||||
|
||||
// collect wal files
|
||||
std::vector<std::string> logfs;
|
||||
for (size_t i = 0; i < filenames.size(); i++) {
|
||||
uint64_t number;
|
||||
FileType type;
|
||||
if (!(ParseFileName(filenames[i], &number, &type))) continue;
|
||||
|
||||
if (type != kLogFile) continue;
|
||||
|
||||
logfs.push_back(filenames[i]);
|
||||
}
|
||||
|
||||
std::sort(logfs.begin(), logfs.end());
|
||||
ASSERT_GE(logfs.size(), 2);
|
||||
|
||||
// Take the last but one file, and truncate it
|
||||
std::string fpath = dbname_ + "/" + logfs[logfs.size() - 2];
|
||||
std::vector<std::string> names_save = names_;
|
||||
|
||||
uint64_t fsize;
|
||||
ASSERT_OK(env_->GetFileSize(fpath, &fsize));
|
||||
ASSERT_GT(fsize, 0);
|
||||
|
||||
Close();
|
||||
|
||||
std::string backup_logs = dbname_ + "/backup_logs";
|
||||
std::string t_fpath = backup_logs + "/" + logfs[logfs.size() - 2];
|
||||
|
||||
ASSERT_OK(env_->CreateDirIfMissing(backup_logs));
|
||||
// Not sure how easy it is to make this data driven.
|
||||
// need to read back the WAL file and truncate last 10
|
||||
// entries
|
||||
CopyFile(fpath, t_fpath, fsize - 9180);
|
||||
|
||||
ASSERT_OK(env_->DeleteFile(fpath));
|
||||
ASSERT_OK(env_->RenameFile(t_fpath, fpath));
|
||||
|
||||
db_options_.wal_recovery_mode = WALRecoveryMode::kPointInTimeRecovery;
|
||||
|
||||
OpenReadOnly(names_save);
|
||||
|
||||
CheckMissed();
|
||||
|
||||
Close();
|
||||
|
||||
Open(names_save);
|
||||
|
||||
CheckMissed();
|
||||
|
||||
Close();
|
||||
|
||||
// cleanup
|
||||
env_->DeleteDir(backup_logs);
|
||||
}
|
||||
} // namespace rocksdb
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
+10
-10
@@ -16,8 +16,8 @@
|
||||
#include <inttypes.h>
|
||||
#include <vector>
|
||||
|
||||
#include "rocksdb/compaction_filter.h"
|
||||
#include "db/column_family.h"
|
||||
#include "rocksdb/compaction_filter.h"
|
||||
#include "util/logging.h"
|
||||
#include "util/sync_point.h"
|
||||
|
||||
@@ -203,9 +203,11 @@ Compaction::~Compaction() {
|
||||
}
|
||||
|
||||
bool Compaction::InputCompressionMatchesOutput() const {
|
||||
int base_level = input_version_->storage_info()->base_level();
|
||||
bool matches = (GetCompressionType(*cfd_->ioptions(), start_level_,
|
||||
base_level) == output_compression_);
|
||||
VersionStorageInfo* vstorage = input_version_->storage_info();
|
||||
int base_level = vstorage->base_level();
|
||||
bool matches =
|
||||
(GetCompressionType(*cfd_->ioptions(), vstorage, mutable_cf_options_,
|
||||
start_level_, base_level) == output_compression_);
|
||||
if (matches) {
|
||||
TEST_SYNC_POINT("Compaction::InputCompressionMatchesOutput:Matches");
|
||||
return true;
|
||||
@@ -291,8 +293,8 @@ bool Compaction::KeyNotExistsBeyondOutputLevel(
|
||||
void Compaction::MarkFilesBeingCompacted(bool mark_as_compacted) {
|
||||
for (size_t i = 0; i < num_input_levels(); i++) {
|
||||
for (size_t j = 0; j < inputs_[i].size(); j++) {
|
||||
assert(mark_as_compacted ? !inputs_[i][j]->being_compacted :
|
||||
inputs_[i][j]->being_compacted);
|
||||
assert(mark_as_compacted ? !inputs_[i][j]->being_compacted
|
||||
: inputs_[i][j]->being_compacted);
|
||||
inputs_[i][j]->being_compacted = mark_as_compacted;
|
||||
}
|
||||
}
|
||||
@@ -367,10 +369,8 @@ int InputSummary(const std::vector<FileMetaData*>& files, char* output,
|
||||
|
||||
void Compaction::Summary(char* output, int len) {
|
||||
int write =
|
||||
snprintf(output, len, "Base version %" PRIu64
|
||||
" Base level %d, inputs: [",
|
||||
input_version_->GetVersionNumber(),
|
||||
start_level_);
|
||||
snprintf(output, len, "Base version %" PRIu64 " Base level %d, inputs: [",
|
||||
input_version_->GetVersionNumber(), start_level_);
|
||||
if (write < 0 || write >= len) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -471,9 +471,9 @@ void CompactionJob::GenSubcompactionBoundaries() {
|
||||
|
||||
// Group the ranges into subcompactions
|
||||
const double min_file_fill_percent = 4.0 / 5;
|
||||
uint64_t max_output_files = static_cast<uint64_t>(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 /
|
||||
c->mutable_cf_options()->MaxFileSizeForLevel(out_lvl)));
|
||||
uint64_t subcompactions =
|
||||
std::min({static_cast<uint64_t>(ranges.size()),
|
||||
static_cast<uint64_t>(db_options_.max_subcompactions),
|
||||
@@ -704,7 +704,7 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
|
||||
MergeHelper merge(
|
||||
env_, cfd->user_comparator(), cfd->ioptions()->merge_operator,
|
||||
compaction_filter, db_options_.info_log.get(),
|
||||
cfd->ioptions()->min_partial_merge_operands,
|
||||
mutable_cf_options->min_partial_merge_operands,
|
||||
false /* internal key corruption is expected */,
|
||||
existing_snapshots_.empty() ? 0 : existing_snapshots_.back(),
|
||||
compact_->compaction->level(), db_options_.statistics.get());
|
||||
|
||||
@@ -122,6 +122,7 @@ class CompactionJob {
|
||||
const std::string& dbname_;
|
||||
const DBOptions& db_options_;
|
||||
const EnvOptions& env_options_;
|
||||
|
||||
Env* env_;
|
||||
VersionSet* versions_;
|
||||
std::atomic<bool>* shutting_down_;
|
||||
|
||||
+85
-92
@@ -70,7 +70,8 @@ struct UserKeyComparator {
|
||||
};
|
||||
|
||||
typedef std::priority_queue<InputFileInfo, std::vector<InputFileInfo>,
|
||||
UserKeyComparator> SmallestKeyHeap;
|
||||
UserKeyComparator>
|
||||
SmallestKeyHeap;
|
||||
|
||||
// This function creates the heap that is used to find if the files are
|
||||
// overlapping during universal compaction when the allow_trivial_move
|
||||
@@ -109,13 +110,22 @@ SmallestKeyHeap create_level_heap(Compaction* c, const Comparator* ucmp) {
|
||||
// matter what the values of the other two parameters are.
|
||||
// Otherwise, the compression type is determined based on options and level.
|
||||
CompressionType GetCompressionType(const ImmutableCFOptions& ioptions,
|
||||
const VersionStorageInfo* vstorage,
|
||||
const MutableCFOptions& mutable_cf_options,
|
||||
int level, int base_level,
|
||||
const bool enable_compression) {
|
||||
if (!enable_compression) {
|
||||
// disable compression
|
||||
return kNoCompression;
|
||||
}
|
||||
// If the use has specified a different compression level for each level,
|
||||
|
||||
// If bottommost_compression is set and we are compacting to the
|
||||
// bottommost level then we should use it.
|
||||
if (ioptions.bottommost_compression != kDisableCompressionOption &&
|
||||
level > base_level && level >= (vstorage->num_non_empty_levels() - 1)) {
|
||||
return ioptions.bottommost_compression;
|
||||
}
|
||||
// If the user has specified a different compression level for each level,
|
||||
// then pick the compression for that level.
|
||||
if (!ioptions.compression_per_level.empty()) {
|
||||
assert(level == 0 || level >= base_level);
|
||||
@@ -129,7 +139,7 @@ CompressionType GetCompressionType(const ImmutableCFOptions& ioptions,
|
||||
// specified compression levels, use the last value.
|
||||
return ioptions.compression_per_level[std::max(0, std::min(idx, n))];
|
||||
} else {
|
||||
return ioptions.compression;
|
||||
return mutable_cf_options.compression;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -190,10 +200,9 @@ void CompactionPicker::GetRange(const CompactionInputFiles& inputs1,
|
||||
InternalKey smallest1, smallest2, largest1, largest2;
|
||||
GetRange(inputs1, &smallest1, &largest1);
|
||||
GetRange(inputs2, &smallest2, &largest2);
|
||||
*smallest = icmp_->Compare(smallest1, smallest2) < 0 ?
|
||||
smallest1 : smallest2;
|
||||
*largest = icmp_->Compare(largest1, largest2) < 0 ?
|
||||
largest2 : largest1;
|
||||
*smallest =
|
||||
icmp_->Compare(smallest1, smallest2) < 0 ? smallest1 : smallest2;
|
||||
*largest = icmp_->Compare(largest1, largest2) < 0 ? largest2 : largest1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -258,9 +267,9 @@ Compaction* CompactionPicker::FormCompaction(
|
||||
VersionStorageInfo* vstorage, const MutableCFOptions& mutable_cf_options,
|
||||
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();
|
||||
output_level + 1 < vstorage->num_levels()
|
||||
? mutable_cf_options.MaxGrandParentOverlapBytes(output_level + 1)
|
||||
: std::numeric_limits<uint64_t>::max();
|
||||
assert(input_files.size());
|
||||
|
||||
// TODO(rven ): we might be able to run concurrent level 0 compaction
|
||||
@@ -284,8 +293,7 @@ Compaction* CompactionPicker::FormCompaction(
|
||||
|
||||
Status CompactionPicker::GetCompactionInputsFromFileNumbers(
|
||||
std::vector<CompactionInputFiles>* input_files,
|
||||
std::unordered_set<uint64_t>* input_set,
|
||||
const VersionStorageInfo* vstorage,
|
||||
std::unordered_set<uint64_t>* input_set, const VersionStorageInfo* vstorage,
|
||||
const CompactionOptions& compact_options) const {
|
||||
if (input_set->size() == 0U) {
|
||||
return Status::InvalidArgument(
|
||||
@@ -323,8 +331,8 @@ Status CompactionPicker::GetCompactionInputsFromFileNumbers(
|
||||
return Status::InvalidArgument(message);
|
||||
}
|
||||
|
||||
for (int level = first_non_empty_level;
|
||||
level <= last_non_empty_level; ++level) {
|
||||
for (int level = first_non_empty_level; level <= last_non_empty_level;
|
||||
++level) {
|
||||
matched_input_files[level].level = level;
|
||||
input_files->emplace_back(std::move(matched_input_files[level]));
|
||||
}
|
||||
@@ -332,8 +340,6 @@ Status CompactionPicker::GetCompactionInputsFromFileNumbers(
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Returns true if any one of the parent files are being compacted
|
||||
bool CompactionPicker::RangeInCompaction(VersionStorageInfo* vstorage,
|
||||
const InternalKey* smallest,
|
||||
@@ -505,7 +511,8 @@ Compaction* CompactionPicker::CompactRange(
|
||||
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),
|
||||
GetCompressionType(ioptions_, vstorage, mutable_cf_options,
|
||||
output_level, 1),
|
||||
/* grandparents */ {}, /* is manual */ true);
|
||||
if (start_level == 0) {
|
||||
level0_compactions_in_progress_.insert(c);
|
||||
@@ -542,7 +549,7 @@ Compaction* CompactionPicker::CompactRange(
|
||||
// two files overlap.
|
||||
if (input_level > 0) {
|
||||
const uint64_t limit = mutable_cf_options.MaxFileSizeForLevel(input_level) *
|
||||
mutable_cf_options.source_compaction_factor;
|
||||
mutable_cf_options.source_compaction_factor;
|
||||
uint64_t total = 0;
|
||||
for (size_t i = 0; i + 1 < inputs.size(); ++i) {
|
||||
uint64_t s = inputs[i]->compensated_file_size;
|
||||
@@ -606,7 +613,8 @@ Compaction* CompactionPicker::CompactRange(
|
||||
mutable_cf_options.MaxFileSizeForLevel(output_level),
|
||||
mutable_cf_options.MaxGrandParentOverlapBytes(input_level),
|
||||
output_path_id,
|
||||
GetCompressionType(ioptions_, output_level, vstorage->base_level()),
|
||||
GetCompressionType(ioptions_, vstorage, mutable_cf_options, output_level,
|
||||
vstorage->base_level()),
|
||||
std::move(grandparents), /* is manual compaction */ true);
|
||||
|
||||
TEST_SYNC_POINT_CALLBACK("CompactionPicker::CompactRange:Return", compaction);
|
||||
@@ -618,11 +626,7 @@ Compaction* CompactionPicker::CompactRange(
|
||||
// takes running compactions into account (by skipping files that are already
|
||||
// being compacted). Since we just changed compaction score, we recalculate it
|
||||
// here
|
||||
{ // this piece of code recomputes compaction score
|
||||
CompactionOptionsFIFO dummy_compaction_options_fifo;
|
||||
vstorage->ComputeCompactionScore(mutable_cf_options,
|
||||
dummy_compaction_options_fifo);
|
||||
}
|
||||
vstorage->ComputeCompactionScore(mutable_cf_options);
|
||||
|
||||
return compaction;
|
||||
}
|
||||
@@ -630,9 +634,8 @@ Compaction* CompactionPicker::CompactRange(
|
||||
#ifndef ROCKSDB_LITE
|
||||
namespace {
|
||||
// Test whether two files have overlapping key-ranges.
|
||||
bool HaveOverlappingKeyRanges(
|
||||
const Comparator* c,
|
||||
const SstFileMetaData& a, const SstFileMetaData& b) {
|
||||
bool HaveOverlappingKeyRanges(const Comparator* c, const SstFileMetaData& a,
|
||||
const SstFileMetaData& b) {
|
||||
if (c->Compare(a.smallestkey, b.smallestkey) >= 0) {
|
||||
if (c->Compare(a.smallestkey, b.largestkey) <= 0) {
|
||||
// b.smallestkey <= a.smallestkey <= b.largestkey
|
||||
@@ -656,9 +659,8 @@ bool HaveOverlappingKeyRanges(
|
||||
} // namespace
|
||||
|
||||
Status CompactionPicker::SanitizeCompactionInputFilesForAllLevels(
|
||||
std::unordered_set<uint64_t>* input_files,
|
||||
const ColumnFamilyMetaData& cf_meta,
|
||||
const int output_level) const {
|
||||
std::unordered_set<uint64_t>* input_files,
|
||||
const ColumnFamilyMetaData& cf_meta, const int output_level) const {
|
||||
auto& levels = cf_meta.levels;
|
||||
auto comparator = icmp_->user_comparator();
|
||||
|
||||
@@ -711,18 +713,17 @@ Status CompactionPicker::SanitizeCompactionInputFilesForAllLevels(
|
||||
// has overlapping key-range with other non-compaction input
|
||||
// files in the same level.
|
||||
while (first_included > 0) {
|
||||
if (comparator->Compare(
|
||||
current_files[first_included - 1].largestkey,
|
||||
current_files[first_included].smallestkey) < 0) {
|
||||
if (comparator->Compare(current_files[first_included - 1].largestkey,
|
||||
current_files[first_included].smallestkey) <
|
||||
0) {
|
||||
break;
|
||||
}
|
||||
first_included--;
|
||||
}
|
||||
|
||||
while (last_included < static_cast<int>(current_files.size()) - 1) {
|
||||
if (comparator->Compare(
|
||||
current_files[last_included + 1].smallestkey,
|
||||
current_files[last_included].largestkey) > 0) {
|
||||
if (comparator->Compare(current_files[last_included + 1].smallestkey,
|
||||
current_files[last_included].largestkey) > 0) {
|
||||
break;
|
||||
}
|
||||
last_included++;
|
||||
@@ -732,33 +733,31 @@ Status CompactionPicker::SanitizeCompactionInputFilesForAllLevels(
|
||||
// include all files between the first and the last compaction input files.
|
||||
for (int f = first_included; f <= last_included; ++f) {
|
||||
if (current_files[f].being_compacted) {
|
||||
return Status::Aborted(
|
||||
"Necessary compaction input file " + current_files[f].name +
|
||||
" is currently being compacted.");
|
||||
return Status::Aborted("Necessary compaction input file " +
|
||||
current_files[f].name +
|
||||
" is currently being compacted.");
|
||||
}
|
||||
input_files->insert(
|
||||
TableFileNameToNumber(current_files[f].name));
|
||||
input_files->insert(TableFileNameToNumber(current_files[f].name));
|
||||
}
|
||||
|
||||
// update smallest and largest key
|
||||
if (l == 0) {
|
||||
for (int f = first_included; f <= last_included; ++f) {
|
||||
if (comparator->Compare(
|
||||
smallestkey, current_files[f].smallestkey) > 0) {
|
||||
if (comparator->Compare(smallestkey, current_files[f].smallestkey) >
|
||||
0) {
|
||||
smallestkey = current_files[f].smallestkey;
|
||||
}
|
||||
if (comparator->Compare(
|
||||
largestkey, current_files[f].largestkey) < 0) {
|
||||
if (comparator->Compare(largestkey, current_files[f].largestkey) < 0) {
|
||||
largestkey = current_files[f].largestkey;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (comparator->Compare(
|
||||
smallestkey, current_files[first_included].smallestkey) > 0) {
|
||||
if (comparator->Compare(smallestkey,
|
||||
current_files[first_included].smallestkey) > 0) {
|
||||
smallestkey = current_files[first_included].smallestkey;
|
||||
}
|
||||
if (comparator->Compare(
|
||||
largestkey, current_files[last_included].largestkey) < 0) {
|
||||
if (comparator->Compare(largestkey,
|
||||
current_files[last_included].largestkey) < 0) {
|
||||
largestkey = current_files[last_included].largestkey;
|
||||
}
|
||||
}
|
||||
@@ -775,16 +774,15 @@ Status CompactionPicker::SanitizeCompactionInputFilesForAllLevels(
|
||||
// time and not by key
|
||||
for (int m = std::max(l, 1); m <= output_level; ++m) {
|
||||
for (auto& next_lv_file : levels[m].files) {
|
||||
if (HaveOverlappingKeyRanges(
|
||||
comparator, aggregated_file_meta, next_lv_file)) {
|
||||
if (HaveOverlappingKeyRanges(comparator, aggregated_file_meta,
|
||||
next_lv_file)) {
|
||||
if (next_lv_file.being_compacted) {
|
||||
return Status::Aborted(
|
||||
"File " + next_lv_file.name +
|
||||
" that has overlapping key range with one of the compaction "
|
||||
" input file is currently being compacted.");
|
||||
}
|
||||
input_files->insert(
|
||||
TableFileNameToNumber(next_lv_file.name));
|
||||
input_files->insert(TableFileNameToNumber(next_lv_file.name));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -794,28 +792,25 @@ Status CompactionPicker::SanitizeCompactionInputFilesForAllLevels(
|
||||
|
||||
Status CompactionPicker::SanitizeCompactionInputFiles(
|
||||
std::unordered_set<uint64_t>* input_files,
|
||||
const ColumnFamilyMetaData& cf_meta,
|
||||
const int output_level) const {
|
||||
const ColumnFamilyMetaData& cf_meta, const int output_level) const {
|
||||
assert(static_cast<int>(cf_meta.levels.size()) - 1 ==
|
||||
cf_meta.levels[cf_meta.levels.size() - 1].level);
|
||||
if (output_level >= static_cast<int>(cf_meta.levels.size())) {
|
||||
return Status::InvalidArgument(
|
||||
"Output level for column family " + cf_meta.name +
|
||||
" must between [0, " +
|
||||
ToString(cf_meta.levels[cf_meta.levels.size() - 1].level) +
|
||||
"].");
|
||||
ToString(cf_meta.levels[cf_meta.levels.size() - 1].level) + "].");
|
||||
}
|
||||
|
||||
if (output_level > MaxOutputLevel()) {
|
||||
return Status::InvalidArgument(
|
||||
"Exceed the maximum output level defined by "
|
||||
"the current compaction algorithm --- " +
|
||||
ToString(MaxOutputLevel()));
|
||||
ToString(MaxOutputLevel()));
|
||||
}
|
||||
|
||||
if (output_level < 0) {
|
||||
return Status::InvalidArgument(
|
||||
"Output level cannot be negative.");
|
||||
return Status::InvalidArgument("Output level cannot be negative.");
|
||||
}
|
||||
|
||||
if (input_files->size() == 0) {
|
||||
@@ -823,8 +818,8 @@ Status CompactionPicker::SanitizeCompactionInputFiles(
|
||||
"A compaction must contain at least one file.");
|
||||
}
|
||||
|
||||
Status s = SanitizeCompactionInputFilesForAllLevels(
|
||||
input_files, cf_meta, output_level);
|
||||
Status s = SanitizeCompactionInputFilesForAllLevels(input_files, cf_meta,
|
||||
output_level);
|
||||
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
@@ -838,10 +833,9 @@ Status CompactionPicker::SanitizeCompactionInputFiles(
|
||||
for (auto file_meta : level_meta.files) {
|
||||
if (file_num == TableFileNameToNumber(file_meta.name)) {
|
||||
if (file_meta.being_compacted) {
|
||||
return Status::Aborted(
|
||||
"Specified compaction input file " +
|
||||
MakeTableFileName("", file_num) +
|
||||
" is already being compacted.");
|
||||
return Status::Aborted("Specified compaction input file " +
|
||||
MakeTableFileName("", file_num) +
|
||||
" is already being compacted.");
|
||||
}
|
||||
found = true;
|
||||
break;
|
||||
@@ -853,8 +847,7 @@ Status CompactionPicker::SanitizeCompactionInputFiles(
|
||||
}
|
||||
if (!found) {
|
||||
return Status::InvalidArgument(
|
||||
"Specified compaction input file " +
|
||||
MakeTableFileName("", file_num) +
|
||||
"Specified compaction input file " + MakeTableFileName("", file_num) +
|
||||
" does not exist in column family " + cf_meta.name + ".");
|
||||
}
|
||||
}
|
||||
@@ -863,8 +856,8 @@ Status CompactionPicker::SanitizeCompactionInputFiles(
|
||||
}
|
||||
#endif // !ROCKSDB_LITE
|
||||
|
||||
bool LevelCompactionPicker::NeedsCompaction(const VersionStorageInfo* vstorage)
|
||||
const {
|
||||
bool LevelCompactionPicker::NeedsCompaction(
|
||||
const VersionStorageInfo* vstorage) const {
|
||||
if (!vstorage->FilesMarkedForCompaction().empty()) {
|
||||
return true;
|
||||
}
|
||||
@@ -1010,7 +1003,7 @@ Compaction* LevelCompactionPicker::PickCompaction(
|
||||
CompactionInputFiles output_level_inputs;
|
||||
output_level_inputs.level = output_level;
|
||||
if (!SetupOtherInputs(cf_name, mutable_cf_options, vstorage, &inputs,
|
||||
&output_level_inputs, &parent_index, base_index)) {
|
||||
&output_level_inputs, &parent_index, base_index)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@@ -1026,7 +1019,8 @@ Compaction* LevelCompactionPicker::PickCompaction(
|
||||
mutable_cf_options.MaxFileSizeForLevel(output_level),
|
||||
mutable_cf_options.MaxGrandParentOverlapBytes(level),
|
||||
GetPathId(ioptions_, mutable_cf_options, output_level),
|
||||
GetCompressionType(ioptions_, output_level, vstorage->base_level()),
|
||||
GetCompressionType(ioptions_, vstorage, mutable_cf_options, output_level,
|
||||
vstorage->base_level()),
|
||||
std::move(grandparents), is_manual, score,
|
||||
false /* deletion_compaction */, compaction_reason);
|
||||
|
||||
@@ -1040,11 +1034,7 @@ Compaction* LevelCompactionPicker::PickCompaction(
|
||||
// takes running compactions into account (by skipping files that are already
|
||||
// being compacted). Since we just changed compaction score, we recalculate it
|
||||
// here
|
||||
{ // this piece of code recomputes compaction score
|
||||
CompactionOptionsFIFO dummy_compaction_options_fifo;
|
||||
vstorage->ComputeCompactionScore(mutable_cf_options,
|
||||
dummy_compaction_options_fifo);
|
||||
}
|
||||
vstorage->ComputeCompactionScore(mutable_cf_options);
|
||||
|
||||
TEST_SYNC_POINT_CALLBACK("LevelCompactionPicker::PickCompaction:Return", c);
|
||||
|
||||
@@ -1439,8 +1429,9 @@ uint32_t UniversalCompactionPicker::GetPathId(
|
||||
// considered in this algorithm. So the target size can be violated in
|
||||
// that case. We need to improve it.
|
||||
uint64_t accumulated_size = 0;
|
||||
uint64_t future_size = file_size *
|
||||
(100 - ioptions.compaction_options_universal.size_ratio) / 100;
|
||||
uint64_t future_size =
|
||||
file_size * (100 - ioptions.compaction_options_universal.size_ratio) /
|
||||
100;
|
||||
uint32_t p = 0;
|
||||
assert(!ioptions.db_paths.empty());
|
||||
for (; p < ioptions.db_paths.size() - 1; p++) {
|
||||
@@ -1464,17 +1455,17 @@ Compaction* UniversalCompactionPicker::PickCompactionUniversalReadAmp(
|
||||
unsigned int max_number_of_files_to_compact,
|
||||
const std::vector<SortedRun>& sorted_runs, LogBuffer* log_buffer) {
|
||||
unsigned int min_merge_width =
|
||||
ioptions_.compaction_options_universal.min_merge_width;
|
||||
ioptions_.compaction_options_universal.min_merge_width;
|
||||
unsigned int max_merge_width =
|
||||
ioptions_.compaction_options_universal.max_merge_width;
|
||||
ioptions_.compaction_options_universal.max_merge_width;
|
||||
|
||||
const SortedRun* sr = nullptr;
|
||||
bool done = false;
|
||||
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);
|
||||
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
|
||||
@@ -1586,7 +1577,7 @@ Compaction* UniversalCompactionPicker::PickCompactionUniversalReadAmp(
|
||||
uint64_t older_file_size = 0;
|
||||
for (size_t i = sorted_runs.size() - 1; i >= first_index_after; i--) {
|
||||
older_file_size += sorted_runs[i].size;
|
||||
if (older_file_size * 100L >= total_size * (long) ratio_to_compress) {
|
||||
if (older_file_size * 100L >= total_size * (long)ratio_to_compress) {
|
||||
enable_compression = false;
|
||||
break;
|
||||
}
|
||||
@@ -1638,7 +1629,8 @@ Compaction* UniversalCompactionPicker::PickCompactionUniversalReadAmp(
|
||||
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),
|
||||
GetCompressionType(ioptions_, vstorage, mutable_cf_options, start_level,
|
||||
1, enable_compression),
|
||||
/* grandparents */ {}, /* is manual */ false, score,
|
||||
false /* deletion_compaction */, compaction_reason);
|
||||
}
|
||||
@@ -1654,8 +1646,8 @@ Compaction* UniversalCompactionPicker::PickCompactionUniversalSizeAmp(
|
||||
VersionStorageInfo* vstorage, double score,
|
||||
const std::vector<SortedRun>& sorted_runs, LogBuffer* log_buffer) {
|
||||
// percentage flexibilty while reducing size amplification
|
||||
uint64_t ratio = ioptions_.compaction_options_universal.
|
||||
max_size_amplification_percent;
|
||||
uint64_t ratio =
|
||||
ioptions_.compaction_options_universal.max_size_amplification_percent;
|
||||
|
||||
unsigned int candidate_count = 0;
|
||||
uint64_t candidate_size = 0;
|
||||
@@ -1666,7 +1658,7 @@ Compaction* UniversalCompactionPicker::PickCompactionUniversalSizeAmp(
|
||||
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.
|
||||
start_index = loop; // Consider this as the first candidate.
|
||||
break;
|
||||
}
|
||||
char file_num_buf[kFormatFileNumberBufSize];
|
||||
@@ -1678,7 +1670,7 @@ Compaction* UniversalCompactionPicker::PickCompactionUniversalSizeAmp(
|
||||
}
|
||||
|
||||
if (sr == nullptr) {
|
||||
return nullptr; // no candidate files
|
||||
return nullptr; // no candidate files
|
||||
}
|
||||
{
|
||||
char file_num_buf[kFormatFileNumberBufSize];
|
||||
@@ -1763,14 +1755,15 @@ Compaction* UniversalCompactionPicker::PickCompactionUniversalSizeAmp(
|
||||
vstorage->num_levels() - 1,
|
||||
mutable_cf_options.MaxFileSizeForLevel(vstorage->num_levels() - 1),
|
||||
/* max_grandparent_overlap_bytes */ LLONG_MAX, path_id,
|
||||
GetCompressionType(ioptions_, vstorage->num_levels() - 1, 1),
|
||||
GetCompressionType(ioptions_, vstorage, mutable_cf_options,
|
||||
vstorage->num_levels() - 1, 1),
|
||||
/* grandparents */ {}, /* is manual */ false, score,
|
||||
false /* deletion_compaction */,
|
||||
CompactionReason::kUniversalSizeAmplification);
|
||||
}
|
||||
|
||||
bool FIFOCompactionPicker::NeedsCompaction(const VersionStorageInfo* vstorage)
|
||||
const {
|
||||
bool FIFOCompactionPicker::NeedsCompaction(
|
||||
const VersionStorageInfo* vstorage) const {
|
||||
const int kLevel0 = 0;
|
||||
return vstorage->CompactionScore(kLevel0) >= 1;
|
||||
}
|
||||
|
||||
+30
-34
@@ -63,23 +63,20 @@ class CompactionPicker {
|
||||
InternalKey** compaction_end, bool* manual_conflict);
|
||||
|
||||
// The maximum allowed output level. Default value is NumberLevels() - 1.
|
||||
virtual int MaxOutputLevel() const {
|
||||
return NumberLevels() - 1;
|
||||
}
|
||||
virtual int MaxOutputLevel() const { return NumberLevels() - 1; }
|
||||
|
||||
virtual bool NeedsCompaction(const VersionStorageInfo* vstorage) const = 0;
|
||||
|
||||
// Sanitize the input set of compaction input files.
|
||||
// When the input parameters do not describe a valid compaction, the
|
||||
// function will try to fix the input_files by adding necessary
|
||||
// files. If it's not possible to conver an invalid input_files
|
||||
// into a valid one by adding more files, the function will return a
|
||||
// non-ok status with specific reason.
|
||||
// Sanitize the input set of compaction input files.
|
||||
// When the input parameters do not describe a valid compaction, the
|
||||
// function will try to fix the input_files by adding necessary
|
||||
// files. If it's not possible to conver an invalid input_files
|
||||
// into a valid one by adding more files, the function will return a
|
||||
// non-ok status with specific reason.
|
||||
#ifndef ROCKSDB_LITE
|
||||
Status SanitizeCompactionInputFiles(
|
||||
std::unordered_set<uint64_t>* input_files,
|
||||
const ColumnFamilyMetaData& cf_meta,
|
||||
const int output_level) const;
|
||||
Status SanitizeCompactionInputFiles(std::unordered_set<uint64_t>* input_files,
|
||||
const ColumnFamilyMetaData& cf_meta,
|
||||
const int output_level) const;
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
// Free up the files that participated in a compaction
|
||||
@@ -123,15 +120,15 @@ class CompactionPicker {
|
||||
// Stores the minimal range that covers all entries in inputs in
|
||||
// *smallest, *largest.
|
||||
// REQUIRES: inputs is not empty
|
||||
void GetRange(const CompactionInputFiles& inputs,
|
||||
InternalKey* smallest, InternalKey* largest);
|
||||
void GetRange(const CompactionInputFiles& inputs, InternalKey* smallest,
|
||||
InternalKey* largest);
|
||||
|
||||
// Stores the minimal range that covers all entries in inputs1 and inputs2
|
||||
// in *smallest, *largest.
|
||||
// REQUIRES: inputs is not empty
|
||||
void GetRange(const CompactionInputFiles& inputs1,
|
||||
const CompactionInputFiles& inputs2,
|
||||
InternalKey* smallest, InternalKey* largest);
|
||||
const CompactionInputFiles& inputs2, InternalKey* smallest,
|
||||
InternalKey* largest);
|
||||
|
||||
// Add more files to the inputs on "level" to make sure that
|
||||
// no newer version of a key is compacted to "level+1" while leaving an older
|
||||
@@ -166,13 +163,12 @@ class CompactionPicker {
|
||||
|
||||
const ImmutableCFOptions& ioptions_;
|
||||
|
||||
// A helper function to SanitizeCompactionInputFiles() that
|
||||
// sanitizes "input_files" by adding necessary files.
|
||||
// A helper function to SanitizeCompactionInputFiles() that
|
||||
// sanitizes "input_files" by adding necessary files.
|
||||
#ifndef ROCKSDB_LITE
|
||||
virtual Status SanitizeCompactionInputFilesForAllLevels(
|
||||
std::unordered_set<uint64_t>* input_files,
|
||||
const ColumnFamilyMetaData& cf_meta,
|
||||
const int output_level) const;
|
||||
const ColumnFamilyMetaData& cf_meta, const int output_level) const;
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
// Keeps track of all compactions that are running on Level0.
|
||||
@@ -192,8 +188,8 @@ class LevelCompactionPicker : public CompactionPicker {
|
||||
VersionStorageInfo* vstorage,
|
||||
LogBuffer* log_buffer) override;
|
||||
|
||||
virtual bool NeedsCompaction(const VersionStorageInfo* vstorage) const
|
||||
override;
|
||||
virtual bool NeedsCompaction(
|
||||
const VersionStorageInfo* vstorage) const override;
|
||||
|
||||
// Pick a path ID to place a newly generated file, with its level
|
||||
static uint32_t GetPathId(const ImmutableCFOptions& ioptions,
|
||||
@@ -232,8 +228,8 @@ class UniversalCompactionPicker : public CompactionPicker {
|
||||
|
||||
virtual int MaxOutputLevel() const override { return NumberLevels() - 1; }
|
||||
|
||||
virtual bool NeedsCompaction(const VersionStorageInfo* vstorage) const
|
||||
override;
|
||||
virtual bool NeedsCompaction(
|
||||
const VersionStorageInfo* vstorage) const override;
|
||||
|
||||
private:
|
||||
struct SortedRun {
|
||||
@@ -307,19 +303,17 @@ class FIFOCompactionPicker : public CompactionPicker {
|
||||
InternalKey** compaction_end, bool* manual_conflict) override;
|
||||
|
||||
// The maximum allowed output level. Always returns 0.
|
||||
virtual int MaxOutputLevel() const override {
|
||||
return 0;
|
||||
}
|
||||
virtual int MaxOutputLevel() const override { return 0; }
|
||||
|
||||
virtual bool NeedsCompaction(const VersionStorageInfo* vstorage) const
|
||||
override;
|
||||
virtual bool NeedsCompaction(
|
||||
const VersionStorageInfo* vstorage) const override;
|
||||
};
|
||||
|
||||
class NullCompactionPicker : public CompactionPicker {
|
||||
public:
|
||||
NullCompactionPicker(const ImmutableCFOptions& ioptions,
|
||||
const InternalKeyComparator* icmp) :
|
||||
CompactionPicker(ioptions, icmp) {}
|
||||
const InternalKeyComparator* icmp)
|
||||
: CompactionPicker(ioptions, icmp) {}
|
||||
virtual ~NullCompactionPicker() {}
|
||||
|
||||
// Always return "nullptr"
|
||||
@@ -342,14 +336,16 @@ class NullCompactionPicker : public CompactionPicker {
|
||||
}
|
||||
|
||||
// Always returns false.
|
||||
virtual bool NeedsCompaction(const VersionStorageInfo* vstorage) const
|
||||
override {
|
||||
virtual bool NeedsCompaction(
|
||||
const VersionStorageInfo* vstorage) const override {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
#endif // !ROCKSDB_LITE
|
||||
|
||||
CompressionType GetCompressionType(const ImmutableCFOptions& ioptions,
|
||||
const VersionStorageInfo* vstorage,
|
||||
const MutableCFOptions& mutable_cf_options,
|
||||
int level, int base_level,
|
||||
const bool enable_compression = true);
|
||||
|
||||
|
||||
@@ -119,7 +119,7 @@ class CompactionPickerTest : public testing::Test {
|
||||
vstorage_->UpdateNumNonEmptyLevels();
|
||||
vstorage_->GenerateFileIndexer();
|
||||
vstorage_->GenerateLevelFilesBrief();
|
||||
vstorage_->ComputeCompactionScore(mutable_cf_options_, fifo_options_);
|
||||
vstorage_->ComputeCompactionScore(mutable_cf_options_);
|
||||
vstorage_->GenerateLevel0NonOverlapping();
|
||||
vstorage_->SetFinalized();
|
||||
}
|
||||
@@ -467,7 +467,6 @@ TEST_F(CompactionPickerTest, NeedsCompactionFIFO) {
|
||||
fifo_options_.max_table_files_size = kMaxSize;
|
||||
ioptions_.compaction_options_fifo = fifo_options_;
|
||||
FIFOCompactionPicker fifo_compaction_picker(ioptions_, &icmp_);
|
||||
|
||||
UpdateVersionStorageInfo();
|
||||
// must return false when there's no files.
|
||||
ASSERT_EQ(fifo_compaction_picker.NeedsCompaction(vstorage_.get()), false);
|
||||
@@ -776,19 +775,22 @@ TEST_F(CompactionPickerTest, EstimateCompactionBytesNeeded1) {
|
||||
Add(1, 4U, "400", "500", 600);
|
||||
Add(1, 5U, "600", "700", 600);
|
||||
// Level 2 is less than target 10000 even added size of level 1
|
||||
// Size ratio of L2/L1 is 9600 / 1200 = 8
|
||||
Add(2, 6U, "150", "200", 2500);
|
||||
Add(2, 7U, "201", "210", 2000);
|
||||
Add(2, 8U, "300", "310", 2500);
|
||||
Add(2, 8U, "300", "310", 2600);
|
||||
Add(2, 9U, "400", "500", 2500);
|
||||
// Level 3 exceeds target 100,000 of 1000
|
||||
Add(3, 10U, "400", "500", 101000);
|
||||
// Level 4 exceeds target 1,000,000 of 500 after adding size from level 3
|
||||
Add(4, 11U, "400", "500", 999500);
|
||||
Add(5, 11U, "400", "500", 8000000);
|
||||
// Level 4 exceeds target 1,000,000 by 900 after adding size from level 3
|
||||
// Size ratio L4/L3 is 9.9
|
||||
// After merge from L3, L4 size is 1000900
|
||||
Add(4, 11U, "400", "500", 999900);
|
||||
Add(5, 11U, "400", "500", 8007200);
|
||||
|
||||
UpdateVersionStorageInfo();
|
||||
|
||||
ASSERT_EQ(2200u + 11000u + 5500u,
|
||||
ASSERT_EQ(200u * 9u + 10900u + 900u * 9,
|
||||
vstorage_->estimated_compaction_needed_bytes());
|
||||
}
|
||||
|
||||
@@ -804,17 +806,42 @@ TEST_F(CompactionPickerTest, EstimateCompactionBytesNeeded2) {
|
||||
Add(0, 4U, "150", "200", 200);
|
||||
Add(0, 5U, "150", "200", 200);
|
||||
Add(0, 6U, "150", "200", 200);
|
||||
// Level 1 is over target by
|
||||
// Level 1 size will be 1400 after merging with L0
|
||||
Add(1, 7U, "400", "500", 200);
|
||||
Add(1, 8U, "600", "700", 200);
|
||||
// Level 2 is less than target 10000 even added size of level 1
|
||||
Add(2, 9U, "150", "200", 9500);
|
||||
Add(2, 9U, "150", "200", 9100);
|
||||
// Level 3 over the target, but since level 4 is empty, we assume it will be
|
||||
// a trivial move.
|
||||
Add(3, 10U, "400", "500", 101000);
|
||||
|
||||
UpdateVersionStorageInfo();
|
||||
|
||||
ASSERT_EQ(1400u + 4400u + 11000u,
|
||||
vstorage_->estimated_compaction_needed_bytes());
|
||||
// estimated L1->L2 merge: 400 * (9100.0 / 1400.0 + 1.0)
|
||||
ASSERT_EQ(1400u + 3000u, vstorage_->estimated_compaction_needed_bytes());
|
||||
}
|
||||
|
||||
TEST_F(CompactionPickerTest, EstimateCompactionBytesNeeded3) {
|
||||
int num_levels = ioptions_.num_levels;
|
||||
ioptions_.level_compaction_dynamic_level_bytes = false;
|
||||
mutable_cf_options_.level0_file_num_compaction_trigger = 3;
|
||||
mutable_cf_options_.max_bytes_for_level_base = 1000;
|
||||
mutable_cf_options_.max_bytes_for_level_multiplier = 10;
|
||||
NewVersionStorage(num_levels, kCompactionStyleLevel);
|
||||
Add(0, 1U, "150", "200", 2000);
|
||||
Add(0, 2U, "150", "200", 2000);
|
||||
Add(0, 4U, "150", "200", 2000);
|
||||
Add(0, 5U, "150", "200", 2000);
|
||||
Add(0, 6U, "150", "200", 1000);
|
||||
// Level 1 size will be 10000 after merging with L0
|
||||
Add(1, 7U, "400", "500", 500);
|
||||
Add(1, 8U, "600", "700", 500);
|
||||
|
||||
Add(2, 9U, "150", "200", 10000);
|
||||
|
||||
UpdateVersionStorageInfo();
|
||||
|
||||
ASSERT_EQ(10000u + 18000u, vstorage_->estimated_compaction_needed_bytes());
|
||||
}
|
||||
|
||||
TEST_F(CompactionPickerTest, EstimateCompactionBytesNeededDynamicLevel) {
|
||||
@@ -838,12 +865,14 @@ TEST_F(CompactionPickerTest, EstimateCompactionBytesNeededDynamicLevel) {
|
||||
// num_levels - 3 is over target by 100 + 1000
|
||||
Add(num_levels - 3, 7U, "400", "500", 300);
|
||||
Add(num_levels - 3, 8U, "600", "700", 300);
|
||||
// Level 2 is over target by 1100 + 100
|
||||
Add(num_levels - 2, 9U, "150", "200", 5100);
|
||||
// num_levels - 2 is over target by 1100 + 200
|
||||
Add(num_levels - 2, 9U, "150", "200", 5200);
|
||||
|
||||
UpdateVersionStorageInfo();
|
||||
|
||||
ASSERT_EQ(1600u + 12100u + 13200u,
|
||||
// Merging to the second last level: (5200 / 1600 + 1) * 1100
|
||||
// Merging to the last level: (50000 / 6300 + 1) * 1300
|
||||
ASSERT_EQ(1600u + 4675u + 11617u,
|
||||
vstorage_->estimated_compaction_needed_bytes());
|
||||
}
|
||||
|
||||
|
||||
@@ -281,6 +281,47 @@ TEST_F(DBBlockCacheTest, IndexAndFilterBlocksOfNewTableAddedToCache) {
|
||||
TestGetTickerCount(options, BLOCK_CACHE_FILTER_HIT));
|
||||
}
|
||||
|
||||
TEST_F(DBBlockCacheTest, IndexAndFilterBlocksStats) {
|
||||
Options options = CurrentOptions();
|
||||
options.create_if_missing = true;
|
||||
options.statistics = rocksdb::CreateDBStatistics();
|
||||
BlockBasedTableOptions table_options;
|
||||
table_options.cache_index_and_filter_blocks = true;
|
||||
// 200 bytes are enough to hold the first two blocks
|
||||
std::shared_ptr<Cache> cache = NewLRUCache(200, 0, false);
|
||||
table_options.block_cache = cache;
|
||||
table_options.filter_policy.reset(NewBloomFilterPolicy(20));
|
||||
options.table_factory.reset(new BlockBasedTableFactory(table_options));
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
|
||||
ASSERT_OK(Put(1, "key", "val"));
|
||||
// Create a new table
|
||||
ASSERT_OK(Flush(1));
|
||||
size_t index_bytes_insert =
|
||||
TestGetTickerCount(options, BLOCK_CACHE_INDEX_BYTES_INSERT);
|
||||
size_t filter_bytes_insert =
|
||||
TestGetTickerCount(options, BLOCK_CACHE_FILTER_BYTES_INSERT);
|
||||
ASSERT_GT(index_bytes_insert, 0);
|
||||
ASSERT_GT(filter_bytes_insert, 0);
|
||||
ASSERT_EQ(cache->GetUsage(), index_bytes_insert + filter_bytes_insert);
|
||||
// set the cache capacity to the current usage
|
||||
cache->SetCapacity(index_bytes_insert + filter_bytes_insert);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOCK_CACHE_INDEX_BYTES_EVICT), 0);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOCK_CACHE_FILTER_BYTES_EVICT), 0);
|
||||
ASSERT_OK(Put(1, "key2", "val"));
|
||||
// Create a new table
|
||||
ASSERT_OK(Flush(1));
|
||||
// cache evicted old index and block entries
|
||||
ASSERT_GT(TestGetTickerCount(options, BLOCK_CACHE_INDEX_BYTES_INSERT),
|
||||
index_bytes_insert);
|
||||
ASSERT_GT(TestGetTickerCount(options, BLOCK_CACHE_FILTER_BYTES_INSERT),
|
||||
filter_bytes_insert);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOCK_CACHE_INDEX_BYTES_EVICT),
|
||||
index_bytes_insert);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOCK_CACHE_FILTER_BYTES_EVICT),
|
||||
filter_bytes_insert);
|
||||
}
|
||||
|
||||
TEST_F(DBBlockCacheTest, ParanoidFileChecks) {
|
||||
Options options = CurrentOptions();
|
||||
options.create_if_missing = true;
|
||||
|
||||
+42
-32
@@ -125,15 +125,15 @@ TEST_F(DBTestDynamicLevel, DynamicLevelMaxBytesBase2) {
|
||||
|
||||
Options options = CurrentOptions();
|
||||
options.create_if_missing = true;
|
||||
options.db_write_buffer_size = 2048;
|
||||
options.write_buffer_size = 2048;
|
||||
options.db_write_buffer_size = 204800;
|
||||
options.write_buffer_size = 20480;
|
||||
options.max_write_buffer_number = 2;
|
||||
options.level0_file_num_compaction_trigger = 2;
|
||||
options.level0_slowdown_writes_trigger = 9999;
|
||||
options.level0_stop_writes_trigger = 9999;
|
||||
options.target_file_size_base = 2048;
|
||||
options.target_file_size_base = 9102;
|
||||
options.level_compaction_dynamic_level_bytes = true;
|
||||
options.max_bytes_for_level_base = 10240;
|
||||
options.max_bytes_for_level_base = 40960;
|
||||
options.max_bytes_for_level_multiplier = 4;
|
||||
options.max_background_compactions = 2;
|
||||
options.num_levels = 5;
|
||||
@@ -154,10 +154,10 @@ TEST_F(DBTestDynamicLevel, DynamicLevelMaxBytesBase2) {
|
||||
ASSERT_TRUE(db_->GetIntProperty("rocksdb.base-level", &int_prop));
|
||||
ASSERT_EQ(4U, int_prop);
|
||||
|
||||
// Put about 7K to L0
|
||||
// Put about 28K to L0
|
||||
for (int i = 0; i < 70; i++) {
|
||||
ASSERT_OK(Put(Key(static_cast<int>(rnd.Uniform(kMaxKey))),
|
||||
RandomString(&rnd, 80)));
|
||||
RandomString(&rnd, 380)));
|
||||
}
|
||||
ASSERT_OK(dbfull()->SetOptions({
|
||||
{"disable_auto_compactions", "false"},
|
||||
@@ -167,14 +167,14 @@ TEST_F(DBTestDynamicLevel, DynamicLevelMaxBytesBase2) {
|
||||
ASSERT_TRUE(db_->GetIntProperty("rocksdb.base-level", &int_prop));
|
||||
ASSERT_EQ(4U, int_prop);
|
||||
|
||||
// Insert extra about 3.5K to L0. After they are compacted to L4, base level
|
||||
// Insert extra about 28K to L0. After they are compacted to L4, base level
|
||||
// should be changed to L3.
|
||||
ASSERT_OK(dbfull()->SetOptions({
|
||||
{"disable_auto_compactions", "true"},
|
||||
}));
|
||||
for (int i = 0; i < 70; i++) {
|
||||
ASSERT_OK(Put(Key(static_cast<int>(rnd.Uniform(kMaxKey))),
|
||||
RandomString(&rnd, 80)));
|
||||
RandomString(&rnd, 380)));
|
||||
}
|
||||
|
||||
ASSERT_OK(dbfull()->SetOptions({
|
||||
@@ -199,10 +199,10 @@ TEST_F(DBTestDynamicLevel, DynamicLevelMaxBytesBase2) {
|
||||
ASSERT_OK(dbfull()->SetOptions({
|
||||
{"disable_auto_compactions", "true"},
|
||||
}));
|
||||
// Write about 10K more
|
||||
// Write about 40K more
|
||||
for (int i = 0; i < 100; i++) {
|
||||
ASSERT_OK(Put(Key(static_cast<int>(rnd.Uniform(kMaxKey))),
|
||||
RandomString(&rnd, 80)));
|
||||
RandomString(&rnd, 380)));
|
||||
}
|
||||
ASSERT_OK(dbfull()->SetOptions({
|
||||
{"disable_auto_compactions", "false"},
|
||||
@@ -218,15 +218,15 @@ TEST_F(DBTestDynamicLevel, DynamicLevelMaxBytesBase2) {
|
||||
|
||||
// Trigger a condition that the compaction changes base level and L0->Lbase
|
||||
// happens at the same time.
|
||||
// We try to make last levels' targets to be 10K, 40K, 160K, add triggers
|
||||
// We try to make last levels' targets to be 40K, 160K, 640K, add triggers
|
||||
// another compaction from 40K->160K.
|
||||
ASSERT_OK(dbfull()->SetOptions({
|
||||
{"disable_auto_compactions", "true"},
|
||||
}));
|
||||
// Write about 150K more
|
||||
for (int i = 0; i < 1350; i++) {
|
||||
// Write about 600K more
|
||||
for (int i = 0; i < 1500; i++) {
|
||||
ASSERT_OK(Put(Key(static_cast<int>(rnd.Uniform(kMaxKey))),
|
||||
RandomString(&rnd, 80)));
|
||||
RandomString(&rnd, 380)));
|
||||
}
|
||||
ASSERT_OK(dbfull()->SetOptions({
|
||||
{"disable_auto_compactions", "false"},
|
||||
@@ -236,29 +236,39 @@ TEST_F(DBTestDynamicLevel, DynamicLevelMaxBytesBase2) {
|
||||
ASSERT_TRUE(db_->GetIntProperty("rocksdb.base-level", &int_prop));
|
||||
ASSERT_EQ(2U, int_prop);
|
||||
|
||||
// A manual compaction will trigger the base level to become L2
|
||||
// Keep Writing data until base level changed 2->1. There will be L0->L2
|
||||
// compaction going on at the same time.
|
||||
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
||||
for (int attempt = 0; attempt <= 20; attempt++) {
|
||||
// Write about 5K more data with two flushes. It should be flush to level 2
|
||||
// but when it is applied, base level is already 1.
|
||||
for (int i = 0; i < 50; i++) {
|
||||
ASSERT_OK(Put(Key(static_cast<int>(rnd.Uniform(kMaxKey))),
|
||||
RandomString(&rnd, 80)));
|
||||
}
|
||||
Flush();
|
||||
|
||||
ASSERT_TRUE(db_->GetIntProperty("rocksdb.base-level", &int_prop));
|
||||
if (int_prop == 2U) {
|
||||
env_->SleepForMicroseconds(50000);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
||||
rocksdb::SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
|
||||
env_->SleepForMicroseconds(200000);
|
||||
rocksdb::SyncPoint::GetInstance()->LoadDependency({
|
||||
{"CompactionJob::Run():Start", "DynamicLevelMaxBytesBase2:0"},
|
||||
{"DynamicLevelMaxBytesBase2:1", "CompactionJob::Run():End"},
|
||||
{"DynamicLevelMaxBytesBase2:compact_range_finish",
|
||||
"FlushJob::WriteLevel0Table"},
|
||||
});
|
||||
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
std::thread thread([this] {
|
||||
TEST_SYNC_POINT("DynamicLevelMaxBytesBase2:compact_range_start");
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
||||
TEST_SYNC_POINT("DynamicLevelMaxBytesBase2:compact_range_finish");
|
||||
});
|
||||
|
||||
TEST_SYNC_POINT("DynamicLevelMaxBytesBase2:0");
|
||||
for (int i = 0; i < 2; i++) {
|
||||
ASSERT_OK(Put(Key(static_cast<int>(rnd.Uniform(kMaxKey))),
|
||||
RandomString(&rnd, 380)));
|
||||
}
|
||||
TEST_SYNC_POINT("DynamicLevelMaxBytesBase2:1");
|
||||
|
||||
Flush();
|
||||
|
||||
thread.join();
|
||||
|
||||
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
||||
rocksdb::SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
|
||||
ASSERT_TRUE(db_->GetIntProperty("rocksdb.base-level", &int_prop));
|
||||
ASSERT_EQ(1U, int_prop);
|
||||
|
||||
+238
-64
@@ -90,10 +90,10 @@
|
||||
#include "util/log_buffer.h"
|
||||
#include "util/logging.h"
|
||||
#include "util/mutexlock.h"
|
||||
#include "util/sst_file_manager_impl.h"
|
||||
#include "util/options_helper.h"
|
||||
#include "util/options_parser.h"
|
||||
#include "util/perf_context_imp.h"
|
||||
#include "util/sst_file_manager_impl.h"
|
||||
#include "util/stop_watch.h"
|
||||
#include "util/string_util.h"
|
||||
#include "util/sync_point.h"
|
||||
@@ -169,6 +169,17 @@ DBOptions SanitizeOptions(const std::string& dbname, const DBOptions& src) {
|
||||
result.recycle_log_file_num = false;
|
||||
}
|
||||
|
||||
if (result.recycle_log_file_num &&
|
||||
(result.wal_recovery_mode == WALRecoveryMode::kPointInTimeRecovery ||
|
||||
result.wal_recovery_mode == WALRecoveryMode::kAbsoluteConsistency)) {
|
||||
// kPointInTimeRecovery is indistinguishable from
|
||||
// kTolerateCorruptedTailRecords in recycle mode since we define
|
||||
// the "end" of the log as the first corrupt record we encounter.
|
||||
// kAbsoluteConsistency doesn't make sense because even a clean
|
||||
// shutdown leaves old junk at the end of the log file.
|
||||
result.wal_recovery_mode = WALRecoveryMode::kTolerateCorruptedTailRecords;
|
||||
}
|
||||
|
||||
if (result.wal_dir.empty()) {
|
||||
// Use dbname as default
|
||||
result.wal_dir = dbname;
|
||||
@@ -241,7 +252,9 @@ static Status ValidateOptions(
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
CompressionType GetCompressionFlush(const ImmutableCFOptions& ioptions) {
|
||||
CompressionType GetCompressionFlush(
|
||||
const ImmutableCFOptions& ioptions,
|
||||
const MutableCFOptions& mutable_cf_options) {
|
||||
// Compressing memtable flushes might not help unless the sequential load
|
||||
// optimization is used for leveled compaction. Otherwise the CPU and
|
||||
// latency overhead is not offset by saving much space.
|
||||
@@ -258,7 +271,7 @@ CompressionType GetCompressionFlush(const ImmutableCFOptions& ioptions) {
|
||||
}
|
||||
|
||||
if (can_compress) {
|
||||
return ioptions.compression;
|
||||
return mutable_cf_options.compression;
|
||||
} else {
|
||||
return kNoCompression;
|
||||
}
|
||||
@@ -460,6 +473,10 @@ DBImpl::~DBImpl() {
|
||||
// so the cache can be safely destroyed.
|
||||
table_cache_->EraseUnRefEntries();
|
||||
|
||||
for (auto& txn_entry : recovered_transactions_) {
|
||||
delete txn_entry.second;
|
||||
}
|
||||
|
||||
// versions need to be destroyed before table_cache since it can hold
|
||||
// references to table_cache.
|
||||
versions_.reset();
|
||||
@@ -614,6 +631,79 @@ void DBImpl::MaybeDumpStats() {
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t DBImpl::FindMinPrepLogReferencedByMemTable() {
|
||||
uint64_t min_log = 0;
|
||||
|
||||
// we must look through the memtables for two phase transactions
|
||||
// that have been committed but not yet flushed
|
||||
for (auto loop_cfd : *versions_->GetColumnFamilySet()) {
|
||||
if (loop_cfd->IsDropped()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto log = loop_cfd->imm()->GetMinLogContainingPrepSection();
|
||||
|
||||
if (log > 0 && (min_log == 0 || log < min_log)) {
|
||||
min_log = log;
|
||||
}
|
||||
|
||||
log = loop_cfd->mem()->GetMinLogContainingPrepSection();
|
||||
|
||||
if (log > 0 && (min_log == 0 || log < min_log)) {
|
||||
min_log = log;
|
||||
}
|
||||
}
|
||||
|
||||
return min_log;
|
||||
}
|
||||
|
||||
void DBImpl::MarkLogAsHavingPrepSectionFlushed(uint64_t log) {
|
||||
assert(log != 0);
|
||||
std::lock_guard<std::mutex> lock(prep_heap_mutex_);
|
||||
auto it = prepared_section_completed_.find(log);
|
||||
assert(it != prepared_section_completed_.end());
|
||||
it->second += 1;
|
||||
}
|
||||
|
||||
void DBImpl::MarkLogAsContainingPrepSection(uint64_t log) {
|
||||
assert(log != 0);
|
||||
std::lock_guard<std::mutex> lock(prep_heap_mutex_);
|
||||
min_log_with_prep_.push(log);
|
||||
auto it = prepared_section_completed_.find(log);
|
||||
if (it == prepared_section_completed_.end()) {
|
||||
prepared_section_completed_[log] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t DBImpl::FindMinLogContainingOutstandingPrep() {
|
||||
std::lock_guard<std::mutex> lock(prep_heap_mutex_);
|
||||
uint64_t min_log = 0;
|
||||
|
||||
// first we look in the prepared heap where we keep
|
||||
// track of transactions that have been prepared (written to WAL)
|
||||
// but not yet committed.
|
||||
while (!min_log_with_prep_.empty()) {
|
||||
min_log = min_log_with_prep_.top();
|
||||
|
||||
auto it = prepared_section_completed_.find(min_log);
|
||||
|
||||
// value was marked as 'deleted' from heap
|
||||
if (it != prepared_section_completed_.end() && it->second > 0) {
|
||||
it->second -= 1;
|
||||
min_log_with_prep_.pop();
|
||||
|
||||
// back to squere one...
|
||||
min_log = 0;
|
||||
continue;
|
||||
} else {
|
||||
// found a valid value
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return min_log;
|
||||
}
|
||||
|
||||
// * Returns the list of live files in 'sst_live'
|
||||
// If it's doing full scan:
|
||||
// * Returns the list of all files in the filesystem in
|
||||
@@ -671,6 +761,32 @@ void DBImpl::FindObsoleteFiles(JobContext* job_context, bool force,
|
||||
job_context->pending_manifest_file_number =
|
||||
versions_->pending_manifest_file_number();
|
||||
job_context->log_number = versions_->MinLogNumber();
|
||||
|
||||
if (allow_2pc()) {
|
||||
// if are 2pc we must consider logs containing prepared
|
||||
// sections of outstanding transactions.
|
||||
//
|
||||
// We must check min logs with outstanding prep before we check
|
||||
// logs referneces by memtables because a log referenced by the
|
||||
// first data structure could transition to the second under us.
|
||||
//
|
||||
// TODO(horuff): iterating over all column families under db mutex.
|
||||
// should find more optimial solution
|
||||
auto min_log_in_prep_heap = FindMinLogContainingOutstandingPrep();
|
||||
|
||||
if (min_log_in_prep_heap != 0 &&
|
||||
min_log_in_prep_heap < job_context->log_number) {
|
||||
job_context->log_number = min_log_in_prep_heap;
|
||||
}
|
||||
|
||||
auto min_log_refed_by_mem = FindMinPrepLogReferencedByMemTable();
|
||||
|
||||
if (min_log_refed_by_mem != 0 &&
|
||||
min_log_refed_by_mem < job_context->log_number) {
|
||||
job_context->log_number = min_log_refed_by_mem;
|
||||
}
|
||||
}
|
||||
|
||||
job_context->prev_log_number = versions_->prev_log_number();
|
||||
|
||||
versions_->AddLiveFiles(&job_context->sst_live);
|
||||
@@ -708,7 +824,7 @@ void DBImpl::FindObsoleteFiles(JobContext* job_context, bool force,
|
||||
}
|
||||
|
||||
if (!alive_log_files_.empty()) {
|
||||
uint64_t min_log_number = versions_->MinLogNumber();
|
||||
uint64_t min_log_number = job_context->log_number;
|
||||
// find newly obsoleted log files
|
||||
while (alive_log_files_.begin()->number < min_log_number) {
|
||||
auto& earliest = *alive_log_files_.begin();
|
||||
@@ -805,9 +921,11 @@ void DBImpl::PurgeObsoleteFiles(const JobContext& state) {
|
||||
|
||||
// dedup state.candidate_files so we don't try to delete the same
|
||||
// file twice
|
||||
sort(candidate_files.begin(), candidate_files.end(), CompareCandidateFile);
|
||||
candidate_files.erase(unique(candidate_files.begin(), candidate_files.end()),
|
||||
candidate_files.end());
|
||||
std::sort(candidate_files.begin(), candidate_files.end(),
|
||||
CompareCandidateFile);
|
||||
candidate_files.erase(
|
||||
std::unique(candidate_files.begin(), candidate_files.end()),
|
||||
candidate_files.end());
|
||||
|
||||
std::vector<std::string> old_info_log_files;
|
||||
InfoLogPrefix info_log_prefix(!db_options_.db_log_dir.empty(), dbname_);
|
||||
@@ -1083,7 +1201,7 @@ Status DBImpl::Recover(
|
||||
s = CheckConsistency();
|
||||
}
|
||||
if (s.ok()) {
|
||||
SequenceNumber max_sequence(kMaxSequenceNumber);
|
||||
SequenceNumber next_sequence(kMaxSequenceNumber);
|
||||
default_cf_handle_ = new ColumnFamilyHandleImpl(
|
||||
versions_->GetColumnFamilySet()->GetDefault(), this, &mutex_);
|
||||
default_cf_internal_stats_ = default_cf_handle_->cfd()->internal_stats();
|
||||
@@ -1097,8 +1215,6 @@ Status DBImpl::Recover(
|
||||
// Note that prev_log_number() is no longer used, but we pay
|
||||
// attention to it in case we are recovering a database
|
||||
// produced by an older version of rocksdb.
|
||||
const uint64_t min_log = versions_->MinLogNumber();
|
||||
const uint64_t prev_log = versions_->prev_log_number();
|
||||
std::vector<std::string> filenames;
|
||||
s = env_->GetChildren(db_options_.wal_dir, &filenames);
|
||||
if (!s.ok()) {
|
||||
@@ -1115,7 +1231,7 @@ Status DBImpl::Recover(
|
||||
"While creating a new Db, wal_dir contains "
|
||||
"existing log file: ",
|
||||
filenames[i]);
|
||||
} else if ((number >= min_log) || (number == prev_log)) {
|
||||
} else {
|
||||
logs.push_back(number);
|
||||
}
|
||||
}
|
||||
@@ -1145,7 +1261,7 @@ Status DBImpl::Recover(
|
||||
if (!logs.empty()) {
|
||||
// Recover in the order in which the logs were generated
|
||||
std::sort(logs.begin(), logs.end());
|
||||
s = RecoverLogFiles(logs, &max_sequence, read_only);
|
||||
s = RecoverLogFiles(logs, &next_sequence, read_only);
|
||||
if (!s.ok()) {
|
||||
// Clear memtables if recovery failed
|
||||
for (auto cfd : *versions_->GetColumnFamilySet()) {
|
||||
@@ -1170,7 +1286,7 @@ Status DBImpl::Recover(
|
||||
|
||||
// REQUIRES: log_numbers are sorted in ascending order
|
||||
Status DBImpl::RecoverLogFiles(const std::vector<uint64_t>& log_numbers,
|
||||
SequenceNumber* max_sequence, bool read_only) {
|
||||
SequenceNumber* next_sequence, bool read_only) {
|
||||
struct LogReporter : public log::Reader::Reporter {
|
||||
Env* env;
|
||||
Logger* info_log;
|
||||
@@ -1373,15 +1489,19 @@ Status DBImpl::RecoverLogFiles(const std::vector<uint64_t>& log_numbers,
|
||||
}
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
if (*next_sequence == kMaxSequenceNumber) {
|
||||
*next_sequence = WriteBatchInternal::Sequence(&batch);
|
||||
}
|
||||
WriteBatchInternal::SetSequence(&batch, *next_sequence);
|
||||
|
||||
// If column family was not found, it might mean that the WAL write
|
||||
// batch references to the column family that was dropped after the
|
||||
// insert. We don't want to fail the whole write batch in that case --
|
||||
// we just ignore the update.
|
||||
// That's why we set ignore missing column families to true
|
||||
status =
|
||||
WriteBatchInternal::InsertInto(&batch, column_family_memtables_.get(),
|
||||
&flush_scheduler_, true, log_number);
|
||||
|
||||
status = WriteBatchInternal::InsertInto(
|
||||
&batch, column_family_memtables_.get(), &flush_scheduler_, true,
|
||||
log_number, this, true, false, next_sequence);
|
||||
MaybeIgnoreError(&status);
|
||||
if (!status.ok()) {
|
||||
// We are treating this as a failure while reading since we read valid
|
||||
@@ -1390,12 +1510,6 @@ Status DBImpl::RecoverLogFiles(const std::vector<uint64_t>& log_numbers,
|
||||
continue;
|
||||
}
|
||||
|
||||
const SequenceNumber last_seq = WriteBatchInternal::Sequence(&batch) +
|
||||
WriteBatchInternal::Count(&batch) - 1;
|
||||
if ((*max_sequence == kMaxSequenceNumber) || (last_seq > *max_sequence)) {
|
||||
*max_sequence = last_seq;
|
||||
}
|
||||
|
||||
if (!read_only) {
|
||||
// we can do this because this is called before client has access to the
|
||||
// DB and there is only a single thread operating on DB
|
||||
@@ -1417,7 +1531,7 @@ Status DBImpl::RecoverLogFiles(const std::vector<uint64_t>& log_numbers,
|
||||
}
|
||||
|
||||
cfd->CreateNewMemtable(*cfd->GetLatestMutableCFOptions(),
|
||||
*max_sequence);
|
||||
*next_sequence);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1435,7 +1549,7 @@ Status DBImpl::RecoverLogFiles(const std::vector<uint64_t>& log_numbers,
|
||||
|
||||
Log(InfoLogLevel::INFO_LEVEL, db_options_.info_log,
|
||||
"Point in time recovered to log #%" PRIu64 " seq #%" PRIu64,
|
||||
log_number, *max_sequence);
|
||||
log_number, *next_sequence);
|
||||
} else {
|
||||
assert(db_options_.wal_recovery_mode ==
|
||||
WALRecoveryMode::kTolerateCorruptedTailRecords
|
||||
@@ -1446,9 +1560,10 @@ Status DBImpl::RecoverLogFiles(const std::vector<uint64_t>& log_numbers,
|
||||
}
|
||||
|
||||
flush_scheduler_.Clear();
|
||||
if ((*max_sequence != kMaxSequenceNumber) &&
|
||||
(versions_->LastSequence() < *max_sequence)) {
|
||||
versions_->SetLastSequence(*max_sequence);
|
||||
auto last_sequence = *next_sequence - 1;
|
||||
if ((*next_sequence != kMaxSequenceNumber) &&
|
||||
(versions_->LastSequence() <= last_sequence)) {
|
||||
versions_->SetLastSequence(last_sequence);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1480,7 +1595,7 @@ Status DBImpl::RecoverLogFiles(const std::vector<uint64_t>& log_numbers,
|
||||
}
|
||||
|
||||
cfd->CreateNewMemtable(*cfd->GetLatestMutableCFOptions(),
|
||||
*max_sequence);
|
||||
*next_sequence);
|
||||
}
|
||||
|
||||
// write MANIFEST with update
|
||||
@@ -1530,6 +1645,9 @@ Status DBImpl::WriteLevel0TableForRecovery(int job_id, ColumnFamilyData* cfd,
|
||||
" Level-0 table #%" PRIu64 ": started",
|
||||
cfd->GetName().c_str(), meta.fd.GetNumber());
|
||||
|
||||
// Get the latest mutable cf options while the mutex is still locked
|
||||
const MutableCFOptions mutable_cf_options =
|
||||
*cfd->GetLatestMutableCFOptions();
|
||||
bool paranoid_file_checks =
|
||||
cfd->GetLatestMutableCFOptions()->paranoid_file_checks;
|
||||
{
|
||||
@@ -1540,11 +1658,11 @@ Status DBImpl::WriteLevel0TableForRecovery(int job_id, ColumnFamilyData* cfd,
|
||||
snapshots_.GetAll(&earliest_write_conflict_snapshot);
|
||||
|
||||
s = BuildTable(
|
||||
dbname_, env_, *cfd->ioptions(), env_options_, cfd->table_cache(),
|
||||
iter.get(), &meta, cfd->internal_comparator(),
|
||||
dbname_, env_, *cfd->ioptions(), mutable_cf_options, env_options_,
|
||||
cfd->table_cache(), iter.get(), &meta, cfd->internal_comparator(),
|
||||
cfd->int_tbl_prop_collector_factories(), cfd->GetID(), cfd->GetName(),
|
||||
snapshot_seqs, earliest_write_conflict_snapshot,
|
||||
GetCompressionFlush(*cfd->ioptions()),
|
||||
GetCompressionFlush(*cfd->ioptions(), mutable_cf_options),
|
||||
cfd->ioptions()->compression_opts, paranoid_file_checks,
|
||||
cfd->internal_stats(), TableFileCreationReason::kRecovery,
|
||||
&event_logger_, job_id);
|
||||
@@ -1591,13 +1709,13 @@ Status DBImpl::FlushMemTableToOutputFile(
|
||||
std::vector<SequenceNumber> snapshot_seqs =
|
||||
snapshots_.GetAll(&earliest_write_conflict_snapshot);
|
||||
|
||||
FlushJob flush_job(dbname_, cfd, db_options_, mutable_cf_options,
|
||||
env_options_, versions_.get(), &mutex_, &shutting_down_,
|
||||
snapshot_seqs, earliest_write_conflict_snapshot,
|
||||
job_context, log_buffer, directories_.GetDbDir(),
|
||||
directories_.GetDataDir(0U),
|
||||
GetCompressionFlush(*cfd->ioptions()), stats_,
|
||||
&event_logger_, mutable_cf_options.report_bg_io_stats);
|
||||
FlushJob flush_job(
|
||||
dbname_, cfd, db_options_, mutable_cf_options, env_options_,
|
||||
versions_.get(), &mutex_, &shutting_down_, snapshot_seqs,
|
||||
earliest_write_conflict_snapshot, job_context, log_buffer,
|
||||
directories_.GetDbDir(), directories_.GetDataDir(0U),
|
||||
GetCompressionFlush(*cfd->ioptions(), mutable_cf_options), stats_,
|
||||
&event_logger_, mutable_cf_options.report_bg_io_stats);
|
||||
|
||||
FileMetaData file_meta;
|
||||
|
||||
@@ -1964,11 +2082,7 @@ Status DBImpl::CompactFilesImpl(
|
||||
// takes running compactions into account (by skipping files that are already
|
||||
// being compacted). Since we just changed compaction score, we recalculate it
|
||||
// here.
|
||||
{
|
||||
CompactionOptionsFIFO dummy_compaction_options_fifo;
|
||||
version->storage_info()->ComputeCompactionScore(
|
||||
*c->mutable_cf_options(), dummy_compaction_options_fifo);
|
||||
}
|
||||
version->storage_info()->ComputeCompactionScore(*c->mutable_cf_options());
|
||||
|
||||
compaction_job.Prepare();
|
||||
|
||||
@@ -2055,6 +2169,7 @@ void DBImpl::NotifyOnCompactionCompleted(
|
||||
}
|
||||
// release lock while notifying events
|
||||
mutex_.Unlock();
|
||||
TEST_SYNC_POINT("DBImpl::NotifyOnCompactionCompleted::UnlockMutex");
|
||||
{
|
||||
CompactionJobInfo info;
|
||||
info.cf_name = cfd->GetName();
|
||||
@@ -2066,6 +2181,7 @@ void DBImpl::NotifyOnCompactionCompleted(
|
||||
info.stats = compaction_job_stats;
|
||||
info.table_properties = c->GetOutputTableProperties();
|
||||
info.compaction_reason = c->compaction_reason();
|
||||
info.compression = c->output_compression();
|
||||
for (size_t i = 0; i < c->num_input_levels(); ++i) {
|
||||
for (const auto fmd : *c->inputs(i)) {
|
||||
auto fn = TableFileName(db_options_.db_paths, fmd->fd.GetNumber(),
|
||||
@@ -3162,11 +3278,11 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
|
||||
*made_progress = true;
|
||||
}
|
||||
if (c != nullptr) {
|
||||
c->ReleaseCompactionFiles(status);
|
||||
*made_progress = true;
|
||||
NotifyOnCompactionCompleted(
|
||||
c->column_family_data(), c.get(), status,
|
||||
compaction_job_stats, job_context->job_id);
|
||||
c->ReleaseCompactionFiles(status);
|
||||
*made_progress = true;
|
||||
}
|
||||
// this will unref its input_version and column_family_data
|
||||
c.reset();
|
||||
@@ -4257,19 +4373,21 @@ Status DBImpl::SingleDelete(const WriteOptions& write_options,
|
||||
}
|
||||
|
||||
Status DBImpl::Write(const WriteOptions& write_options, WriteBatch* my_batch) {
|
||||
return WriteImpl(write_options, my_batch, nullptr);
|
||||
return WriteImpl(write_options, my_batch, nullptr, nullptr);
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
Status DBImpl::WriteWithCallback(const WriteOptions& write_options,
|
||||
WriteBatch* my_batch,
|
||||
WriteCallback* callback) {
|
||||
return WriteImpl(write_options, my_batch, callback);
|
||||
return WriteImpl(write_options, my_batch, callback, nullptr);
|
||||
}
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
Status DBImpl::WriteImpl(const WriteOptions& write_options,
|
||||
WriteBatch* my_batch, WriteCallback* callback) {
|
||||
WriteBatch* my_batch, WriteCallback* callback,
|
||||
uint64_t* log_used, uint64_t log_ref,
|
||||
bool disable_memtable) {
|
||||
if (my_batch == nullptr) {
|
||||
return Status::Corruption("Batch is nullptr!");
|
||||
}
|
||||
@@ -4294,8 +4412,10 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options,
|
||||
w.batch = my_batch;
|
||||
w.sync = write_options.sync;
|
||||
w.disableWAL = write_options.disableWAL;
|
||||
w.disable_memtable = disable_memtable;
|
||||
w.in_batch_group = false;
|
||||
w.callback = callback;
|
||||
w.log_ref = log_ref;
|
||||
|
||||
if (!write_options.disableWAL) {
|
||||
RecordTick(stats_, WRITE_WITH_WAL);
|
||||
@@ -4308,12 +4428,16 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options,
|
||||
// we are a non-leader in a parallel group
|
||||
PERF_TIMER_GUARD(write_memtable_time);
|
||||
|
||||
if (!w.CallbackFailed()) {
|
||||
if (log_used != nullptr) {
|
||||
*log_used = w.log_used;
|
||||
}
|
||||
|
||||
if (w.ShouldWriteToMemtable()) {
|
||||
ColumnFamilyMemTablesImpl column_family_memtables(
|
||||
versions_->GetColumnFamilySet());
|
||||
WriteBatchInternal::SetSequence(w.batch, w.sequence);
|
||||
w.status = WriteBatchInternal::InsertInto(
|
||||
w.batch, &column_family_memtables, &flush_scheduler_,
|
||||
&w, &column_family_memtables, &flush_scheduler_,
|
||||
write_options.ignore_missing_column_families, 0 /*log_number*/, this,
|
||||
true /*dont_filter_deletes*/, true /*concurrent_memtable_writes*/);
|
||||
}
|
||||
@@ -4331,6 +4455,9 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options,
|
||||
status = w.FinalStatus();
|
||||
}
|
||||
if (w.state == WriteThread::STATE_COMPLETED) {
|
||||
if (log_used != nullptr) {
|
||||
*log_used = w.log_used;
|
||||
}
|
||||
// write is complete and leader has updated sequence
|
||||
RecordTick(stats_, WRITE_DONE_BY_OTHER);
|
||||
return w.FinalStatus();
|
||||
@@ -4488,10 +4615,15 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options,
|
||||
uint64_t total_byte_size = 0;
|
||||
for (auto writer : write_group) {
|
||||
if (writer->CheckCallback(this)) {
|
||||
total_count += WriteBatchInternal::Count(writer->batch);
|
||||
total_byte_size = WriteBatchInternal::AppendedByteSize(
|
||||
total_byte_size, WriteBatchInternal::ByteSize(writer->batch));
|
||||
parallel = parallel && !writer->batch->HasMerge();
|
||||
if (writer->ShouldWriteToMemtable()) {
|
||||
total_count += WriteBatchInternal::Count(writer->batch);
|
||||
parallel = parallel && !writer->batch->HasMerge();
|
||||
}
|
||||
|
||||
if (writer->ShouldWriteToWAL()) {
|
||||
total_byte_size = WriteBatchInternal::AppendedByteSize(
|
||||
total_byte_size, WriteBatchInternal::ByteSize(writer->batch));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4513,22 +4645,27 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options,
|
||||
PERF_TIMER_GUARD(write_wal_time);
|
||||
|
||||
WriteBatch* merged_batch = nullptr;
|
||||
if (write_group.size() == 1 && !write_group[0]->CallbackFailed()) {
|
||||
if (write_group.size() == 1 && write_group[0]->ShouldWriteToWAL()) {
|
||||
merged_batch = write_group[0]->batch;
|
||||
write_group[0]->log_used = logfile_number_;
|
||||
} else {
|
||||
// WAL needs all of the batches flattened into a single batch.
|
||||
// We could avoid copying here with an iov-like AddRecord
|
||||
// interface
|
||||
merged_batch = &tmp_batch_;
|
||||
for (auto writer : write_group) {
|
||||
if (!writer->CallbackFailed()) {
|
||||
if (writer->ShouldWriteToWAL()) {
|
||||
WriteBatchInternal::Append(merged_batch, writer->batch);
|
||||
}
|
||||
writer->log_used = logfile_number_;
|
||||
}
|
||||
}
|
||||
WriteBatchInternal::SetSequence(merged_batch, current_sequence);
|
||||
|
||||
assert(WriteBatchInternal::Count(merged_batch) == total_count);
|
||||
if (log_used != nullptr) {
|
||||
*log_used = logfile_number_;
|
||||
}
|
||||
|
||||
WriteBatchInternal::SetSequence(merged_batch, current_sequence);
|
||||
|
||||
Slice log_entry = WriteBatchInternal::Contents(merged_batch);
|
||||
status = logs_.back().writer->AddRecord(log_entry);
|
||||
@@ -4614,14 +4751,14 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options,
|
||||
std::memory_order_relaxed);
|
||||
write_thread_.LaunchParallelFollowers(&pg, current_sequence);
|
||||
|
||||
if (!w.CallbackFailed()) {
|
||||
if (w.ShouldWriteToMemtable()) {
|
||||
// do leader write
|
||||
ColumnFamilyMemTablesImpl column_family_memtables(
|
||||
versions_->GetColumnFamilySet());
|
||||
assert(w.sequence == current_sequence);
|
||||
WriteBatchInternal::SetSequence(w.batch, w.sequence);
|
||||
w.status = WriteBatchInternal::InsertInto(
|
||||
w.batch, &column_family_memtables, &flush_scheduler_,
|
||||
&w, &column_family_memtables, &flush_scheduler_,
|
||||
write_options.ignore_missing_column_families, 0 /*log_number*/,
|
||||
this, true /*dont_filter_deletes*/,
|
||||
true /*concurrent_memtable_writes*/);
|
||||
@@ -4725,6 +4862,22 @@ Status DBImpl::ScheduleFlushes(WriteContext* context) {
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
void DBImpl::NotifyOnMemTableSealed(ColumnFamilyData* cfd,
|
||||
const MemTableInfo& mem_table_info) {
|
||||
if (db_options_.listeners.size() == 0U) {
|
||||
return;
|
||||
}
|
||||
if (shutting_down_.load(std::memory_order_acquire)) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto listener : db_options_.listeners) {
|
||||
listener->OnMemTableSealed(mem_table_info);
|
||||
}
|
||||
}
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
// REQUIRES: mutex_ is held
|
||||
// REQUIRES: this thread is currently at the front of the writer queue
|
||||
Status DBImpl::SwitchMemtable(ColumnFamilyData* cfd, WriteContext* context) {
|
||||
@@ -4747,6 +4900,19 @@ Status DBImpl::SwitchMemtable(ColumnFamilyData* cfd, WriteContext* context) {
|
||||
creating_new_log ? versions_->NewFileNumber() : logfile_number_;
|
||||
SuperVersion* new_superversion = nullptr;
|
||||
const MutableCFOptions mutable_cf_options = *cfd->GetLatestMutableCFOptions();
|
||||
|
||||
// Set current_memtble_info for memtable sealed callback
|
||||
#ifndef ROCKSDB_LITE
|
||||
MemTableInfo memtable_info;
|
||||
memtable_info.cf_name = cfd->GetName();
|
||||
memtable_info.first_seqno = cfd->mem()->GetFirstSequenceNumber();
|
||||
memtable_info.earliest_seqno = cfd->mem()->GetEarliestSequenceNumber();
|
||||
memtable_info.num_entries = cfd->mem()->num_entries();
|
||||
memtable_info.num_deletes = cfd->mem()->num_deletes();
|
||||
#endif // ROCKSDB_LITE
|
||||
// Log this later after lock release. It may be outdated, e.g., if background
|
||||
// flush happens before logging, but that should be ok.
|
||||
int num_imm_unflushed = cfd->imm()->NumNotFlushed();
|
||||
mutex_.Unlock();
|
||||
Status s;
|
||||
{
|
||||
@@ -4783,10 +4949,18 @@ Status DBImpl::SwitchMemtable(ColumnFamilyData* cfd, WriteContext* context) {
|
||||
new_mem = cfd->ConstructNewMemtable(mutable_cf_options, seq);
|
||||
new_superversion = new SuperVersion();
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
// PLEASE NOTE: We assume that there are no failable operations
|
||||
// after lock is acquired below since we are already notifying
|
||||
// client about mem table becoming immutable.
|
||||
NotifyOnMemTableSealed(cfd, memtable_info);
|
||||
#endif //ROCKSDB_LITE
|
||||
}
|
||||
Log(InfoLogLevel::DEBUG_LEVEL, db_options_.info_log,
|
||||
"[%s] New memtable created with log file: #%" PRIu64 "\n",
|
||||
cfd->GetName().c_str(), new_log_number);
|
||||
Log(InfoLogLevel::INFO_LEVEL, db_options_.info_log,
|
||||
"[%s] New memtable created with log file: #%" PRIu64
|
||||
". Immutable memtables: %d.\n",
|
||||
cfd->GetName().c_str(), new_log_number, num_imm_unflushed);
|
||||
mutex_.Lock();
|
||||
if (!s.ok()) {
|
||||
// how do we fail if we're not creating new log?
|
||||
|
||||
+101
-2
@@ -10,8 +10,10 @@
|
||||
|
||||
#include <atomic>
|
||||
#include <deque>
|
||||
#include <functional>
|
||||
#include <limits>
|
||||
#include <list>
|
||||
#include <queue>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
@@ -55,6 +57,7 @@ class Arena;
|
||||
class WriteCallback;
|
||||
struct JobContext;
|
||||
struct ExternalSstFileInfo;
|
||||
struct MemTableInfo;
|
||||
|
||||
class DBImpl : public DB {
|
||||
public:
|
||||
@@ -296,7 +299,8 @@ class DBImpl : public DB {
|
||||
bool disallow_trivial_move = false);
|
||||
|
||||
// Force current memtable contents to be flushed.
|
||||
Status TEST_FlushMemTable(bool wait = true);
|
||||
Status TEST_FlushMemTable(bool wait = true,
|
||||
ColumnFamilyHandle* cfh = nullptr);
|
||||
|
||||
// Wait for memtable compaction
|
||||
Status TEST_WaitForFlushMemTable(ColumnFamilyHandle* column_family = nullptr);
|
||||
@@ -341,10 +345,17 @@ class DBImpl : public DB {
|
||||
Status TEST_GetAllImmutableCFOptions(
|
||||
std::unordered_map<std::string, const ImmutableCFOptions*>* iopts_map);
|
||||
|
||||
// Return the lastest MutableCFOptions of of a column family
|
||||
Status TEST_GetLatestMutableCFOptions(ColumnFamilyHandle* column_family,
|
||||
MutableCFOptions* mutable_cf_opitons);
|
||||
|
||||
Cache* TEST_table_cache() { return table_cache_.get(); }
|
||||
|
||||
WriteController& TEST_write_controler() { return write_controller_; }
|
||||
|
||||
uint64_t TEST_FindMinLogContainingOutstandingPrep();
|
||||
uint64_t TEST_FindMinPrepLogReferencedByMemTable();
|
||||
|
||||
#endif // NDEBUG
|
||||
|
||||
// Return maximum background compaction alowed to be scheduled based on
|
||||
@@ -421,12 +432,70 @@ class DBImpl : public DB {
|
||||
return num_running_compactions_;
|
||||
}
|
||||
|
||||
// hollow transactions shell used for recovery.
|
||||
// these will then be passed to TransactionDB so that
|
||||
// locks can be reacquired before writing can resume.
|
||||
struct RecoveredTransaction {
|
||||
uint64_t log_number_;
|
||||
std::string name_;
|
||||
WriteBatch* batch_;
|
||||
explicit RecoveredTransaction(const uint64_t log, const std::string& name,
|
||||
WriteBatch* batch)
|
||||
: log_number_(log), name_(name), batch_(batch) {}
|
||||
|
||||
~RecoveredTransaction() { delete batch_; }
|
||||
};
|
||||
|
||||
bool allow_2pc() const { return db_options_.allow_2pc; }
|
||||
|
||||
std::unordered_map<std::string, RecoveredTransaction*>
|
||||
recovered_transactions() {
|
||||
return recovered_transactions_;
|
||||
}
|
||||
|
||||
RecoveredTransaction* GetRecoveredTransaction(const std::string& name) {
|
||||
auto it = recovered_transactions_.find(name);
|
||||
if (it == recovered_transactions_.end()) {
|
||||
return nullptr;
|
||||
} else {
|
||||
return it->second;
|
||||
}
|
||||
}
|
||||
|
||||
void InsertRecoveredTransaction(const uint64_t log, const std::string& name,
|
||||
WriteBatch* batch) {
|
||||
recovered_transactions_[name] = new RecoveredTransaction(log, name, batch);
|
||||
MarkLogAsContainingPrepSection(log);
|
||||
}
|
||||
|
||||
void DeleteRecoveredTransaction(const std::string& name) {
|
||||
auto it = recovered_transactions_.find(name);
|
||||
assert(it != recovered_transactions_.end());
|
||||
auto* trx = it->second;
|
||||
recovered_transactions_.erase(it);
|
||||
MarkLogAsHavingPrepSectionFlushed(trx->log_number_);
|
||||
delete trx;
|
||||
}
|
||||
|
||||
void DeleteAllRecoveredTransactions() {
|
||||
for (auto it = recovered_transactions_.begin();
|
||||
it != recovered_transactions_.end(); it++) {
|
||||
delete it->second;
|
||||
}
|
||||
recovered_transactions_.clear();
|
||||
}
|
||||
|
||||
void MarkLogAsHavingPrepSectionFlushed(uint64_t log);
|
||||
void MarkLogAsContainingPrepSection(uint64_t log);
|
||||
|
||||
protected:
|
||||
Env* const env_;
|
||||
const std::string dbname_;
|
||||
unique_ptr<VersionSet> versions_;
|
||||
const DBOptions db_options_;
|
||||
Statistics* stats_;
|
||||
std::unordered_map<std::string, RecoveredTransaction*>
|
||||
recovered_transactions_;
|
||||
|
||||
InternalIterator* NewInternalIterator(const ReadOptions&,
|
||||
ColumnFamilyData* cfd,
|
||||
@@ -452,6 +521,8 @@ class DBImpl : public DB {
|
||||
Compaction *c, const Status &st,
|
||||
const CompactionJobStats& job_stats,
|
||||
int job_id);
|
||||
void NotifyOnMemTableSealed(ColumnFamilyData* cfd,
|
||||
const MemTableInfo& mem_table_info);
|
||||
|
||||
void NewThreadStatusCfInfo(ColumnFamilyData* cfd) const;
|
||||
|
||||
@@ -460,11 +531,17 @@ class DBImpl : public DB {
|
||||
void EraseThreadStatusDbInfo() const;
|
||||
|
||||
Status WriteImpl(const WriteOptions& options, WriteBatch* updates,
|
||||
WriteCallback* callback);
|
||||
WriteCallback* callback = nullptr,
|
||||
uint64_t* log_used = nullptr, uint64_t log_ref = 0,
|
||||
bool disable_memtable = false);
|
||||
|
||||
uint64_t FindMinLogContainingOutstandingPrep();
|
||||
uint64_t FindMinPrepLogReferencedByMemTable();
|
||||
|
||||
private:
|
||||
friend class DB;
|
||||
friend class InternalStats;
|
||||
friend class TransactionImpl;
|
||||
#ifndef ROCKSDB_LITE
|
||||
friend class ForwardIterator;
|
||||
#endif
|
||||
@@ -854,6 +931,28 @@ class DBImpl : public DB {
|
||||
// Indicate DB was opened successfully
|
||||
bool opened_successfully_;
|
||||
|
||||
// minmum log number still containing prepared data.
|
||||
// this is used by FindObsoleteFiles to determine which
|
||||
// flushed logs we must keep around because they still
|
||||
// contain prepared data which has not been flushed or rolled back
|
||||
std::priority_queue<uint64_t, std::vector<uint64_t>, std::greater<uint64_t>>
|
||||
min_log_with_prep_;
|
||||
|
||||
// to be used in conjunction with min_log_with_prep_.
|
||||
// once a transaction with data in log L is committed or rolled back
|
||||
// rather than removing the value from the heap we add that value
|
||||
// to prepared_section_completed_ which maps LOG -> instance_count
|
||||
// since a log could contain multiple prepared sections
|
||||
//
|
||||
// when trying to determine the minmum log still active we first
|
||||
// consult min_log_with_prep_. while that root value maps to
|
||||
// a value > 0 in prepared_section_completed_ we decrement the
|
||||
// instance_count for that log and pop the root value in
|
||||
// min_log_with_prep_. This will work the same as a min_heap
|
||||
// where we are deleteing arbitrary elements and the up heaping.
|
||||
std::unordered_map<uint64_t, uint64_t> prepared_section_completed_;
|
||||
std::mutex prep_heap_mutex_;
|
||||
|
||||
// No copying allowed
|
||||
DBImpl(const DBImpl&);
|
||||
void operator=(const DBImpl&);
|
||||
|
||||
+26
-2
@@ -74,10 +74,17 @@ Status DBImpl::TEST_CompactRange(int level, const Slice* begin,
|
||||
disallow_trivial_move);
|
||||
}
|
||||
|
||||
Status DBImpl::TEST_FlushMemTable(bool wait) {
|
||||
Status DBImpl::TEST_FlushMemTable(bool wait, ColumnFamilyHandle* cfh) {
|
||||
FlushOptions fo;
|
||||
fo.wait = wait;
|
||||
return FlushMemTable(default_cf_handle_->cfd(), fo);
|
||||
ColumnFamilyData* cfd;
|
||||
if (cfh == nullptr) {
|
||||
cfd = default_cf_handle_->cfd();
|
||||
} else {
|
||||
auto cfhi = reinterpret_cast<ColumnFamilyHandleImpl*>(cfh);
|
||||
cfd = cfhi->cfd();
|
||||
}
|
||||
return FlushMemTable(cfd, fo);
|
||||
}
|
||||
|
||||
Status DBImpl::TEST_WaitForFlushMemTable(ColumnFamilyHandle* column_family) {
|
||||
@@ -154,5 +161,22 @@ Status DBImpl::TEST_GetAllImmutableCFOptions(
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
uint64_t DBImpl::TEST_FindMinLogContainingOutstandingPrep() {
|
||||
return FindMinLogContainingOutstandingPrep();
|
||||
}
|
||||
|
||||
uint64_t DBImpl::TEST_FindMinPrepLogReferencedByMemTable() {
|
||||
return FindMinPrepLogReferencedByMemTable();
|
||||
}
|
||||
|
||||
Status DBImpl::TEST_GetLatestMutableCFOptions(
|
||||
ColumnFamilyHandle* column_family, MutableCFOptions* mutable_cf_options) {
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
|
||||
auto cfh = reinterpret_cast<ColumnFamilyHandleImpl*>(column_family);
|
||||
*mutable_cf_options = *cfh->cfd()->GetLatestMutableCFOptions();
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
#endif // NDEBUG
|
||||
@@ -49,8 +49,7 @@ Status DBImpl::SuggestCompactRange(ColumnFamilyHandle* column_family,
|
||||
}
|
||||
// Since we have some more files to compact, we should also recompute
|
||||
// compaction score
|
||||
vstorage->ComputeCompactionScore(*cfd->GetLatestMutableCFOptions(),
|
||||
CompactionOptionsFIFO());
|
||||
vstorage->ComputeCompactionScore(*cfd->GetLatestMutableCFOptions());
|
||||
SchedulePendingCompaction(cfd);
|
||||
MaybeScheduleFlushOrCompaction();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,259 @@
|
||||
// Copyright (c) 2011-present, 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/db_test_util.h"
|
||||
#include "port/stack_trace.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
class DBIOFailureTest : public DBTestBase {
|
||||
public:
|
||||
DBIOFailureTest() : DBTestBase("/db_io_failure_test") {}
|
||||
};
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
// Check that number of files does not grow when writes are dropped
|
||||
TEST_F(DBIOFailureTest, DropWrites) {
|
||||
do {
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
options.paranoid_checks = false;
|
||||
Reopen(options);
|
||||
|
||||
ASSERT_OK(Put("foo", "v1"));
|
||||
ASSERT_EQ("v1", Get("foo"));
|
||||
Compact("a", "z");
|
||||
const size_t num_files = CountFiles();
|
||||
// Force out-of-space errors
|
||||
env_->drop_writes_.store(true, std::memory_order_release);
|
||||
env_->sleep_counter_.Reset();
|
||||
env_->no_sleep_ = true;
|
||||
for (int i = 0; i < 5; i++) {
|
||||
if (option_config_ != kUniversalCompactionMultiLevel &&
|
||||
option_config_ != kUniversalSubcompactions) {
|
||||
for (int level = 0; level < dbfull()->NumberLevels(); level++) {
|
||||
if (level > 0 && level == dbfull()->NumberLevels() - 1) {
|
||||
break;
|
||||
}
|
||||
dbfull()->TEST_CompactRange(level, nullptr, nullptr, nullptr,
|
||||
true /* disallow trivial move */);
|
||||
}
|
||||
} else {
|
||||
dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
std::string property_value;
|
||||
ASSERT_TRUE(db_->GetProperty("rocksdb.background-errors", &property_value));
|
||||
ASSERT_EQ("5", property_value);
|
||||
|
||||
env_->drop_writes_.store(false, std::memory_order_release);
|
||||
ASSERT_LT(CountFiles(), num_files + 3);
|
||||
|
||||
// Check that compaction attempts slept after errors
|
||||
// TODO @krad: Figure out why ASSERT_EQ 5 keeps failing in certain compiler
|
||||
// versions
|
||||
ASSERT_GE(env_->sleep_counter_.Read(), 4);
|
||||
} while (ChangeCompactOptions());
|
||||
}
|
||||
|
||||
// Check background error counter bumped on flush failures.
|
||||
TEST_F(DBIOFailureTest, DropWritesFlush) {
|
||||
do {
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
options.max_background_flushes = 1;
|
||||
Reopen(options);
|
||||
|
||||
ASSERT_OK(Put("foo", "v1"));
|
||||
// Force out-of-space errors
|
||||
env_->drop_writes_.store(true, std::memory_order_release);
|
||||
|
||||
std::string property_value;
|
||||
// Background error count is 0 now.
|
||||
ASSERT_TRUE(db_->GetProperty("rocksdb.background-errors", &property_value));
|
||||
ASSERT_EQ("0", property_value);
|
||||
|
||||
dbfull()->TEST_FlushMemTable(true);
|
||||
|
||||
ASSERT_TRUE(db_->GetProperty("rocksdb.background-errors", &property_value));
|
||||
ASSERT_EQ("1", property_value);
|
||||
|
||||
env_->drop_writes_.store(false, std::memory_order_release);
|
||||
} while (ChangeCompactOptions());
|
||||
}
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
// Check that CompactRange() returns failure if there is not enough space left
|
||||
// on device
|
||||
TEST_F(DBIOFailureTest, NoSpaceCompactRange) {
|
||||
do {
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
options.disable_auto_compactions = true;
|
||||
Reopen(options);
|
||||
|
||||
// generate 5 tables
|
||||
for (int i = 0; i < 5; ++i) {
|
||||
ASSERT_OK(Put(Key(i), Key(i) + "v"));
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
|
||||
// Force out-of-space errors
|
||||
env_->no_space_.store(true, std::memory_order_release);
|
||||
|
||||
Status s = dbfull()->TEST_CompactRange(0, nullptr, nullptr, nullptr,
|
||||
true /* disallow trivial move */);
|
||||
ASSERT_TRUE(s.IsIOError());
|
||||
|
||||
env_->no_space_.store(false, std::memory_order_release);
|
||||
} while (ChangeCompactOptions());
|
||||
}
|
||||
|
||||
TEST_F(DBIOFailureTest, NonWritableFileSystem) {
|
||||
do {
|
||||
Options options = CurrentOptions();
|
||||
options.write_buffer_size = 4096;
|
||||
options.arena_block_size = 4096;
|
||||
options.env = env_;
|
||||
Reopen(options);
|
||||
ASSERT_OK(Put("foo", "v1"));
|
||||
env_->non_writeable_rate_.store(100);
|
||||
std::string big(100000, 'x');
|
||||
int errors = 0;
|
||||
for (int i = 0; i < 20; i++) {
|
||||
if (!Put("foo", big).ok()) {
|
||||
errors++;
|
||||
env_->SleepForMicroseconds(100000);
|
||||
}
|
||||
}
|
||||
ASSERT_GT(errors, 0);
|
||||
env_->non_writeable_rate_.store(0);
|
||||
} while (ChangeCompactOptions());
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
TEST_F(DBIOFailureTest, ManifestWriteError) {
|
||||
// Test for the following problem:
|
||||
// (a) Compaction produces file F
|
||||
// (b) Log record containing F is written to MANIFEST file, but Sync() fails
|
||||
// (c) GC deletes F
|
||||
// (d) After reopening DB, reads fail since deleted F is named in log record
|
||||
|
||||
// We iterate twice. In the second iteration, everything is the
|
||||
// same except the log record never makes it to the MANIFEST file.
|
||||
for (int iter = 0; iter < 2; iter++) {
|
||||
std::atomic<bool>* error_type = (iter == 0) ? &env_->manifest_sync_error_
|
||||
: &env_->manifest_write_error_;
|
||||
|
||||
// Insert foo=>bar mapping
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
options.create_if_missing = true;
|
||||
options.error_if_exists = false;
|
||||
options.paranoid_checks = true;
|
||||
DestroyAndReopen(options);
|
||||
ASSERT_OK(Put("foo", "bar"));
|
||||
ASSERT_EQ("bar", Get("foo"));
|
||||
|
||||
// Memtable compaction (will succeed)
|
||||
Flush();
|
||||
ASSERT_EQ("bar", Get("foo"));
|
||||
const int last = 2;
|
||||
MoveFilesToLevel(2);
|
||||
ASSERT_EQ(NumTableFilesAtLevel(last), 1); // foo=>bar is now in last level
|
||||
|
||||
// Merging compaction (will fail)
|
||||
error_type->store(true, std::memory_order_release);
|
||||
dbfull()->TEST_CompactRange(last, nullptr, nullptr); // Should fail
|
||||
ASSERT_EQ("bar", Get("foo"));
|
||||
|
||||
error_type->store(false, std::memory_order_release);
|
||||
|
||||
// Since paranoid_checks=true, writes should fail
|
||||
ASSERT_NOK(Put("foo2", "bar2"));
|
||||
|
||||
// Recovery: should not lose data
|
||||
ASSERT_EQ("bar", Get("foo"));
|
||||
|
||||
// Try again with paranoid_checks=false
|
||||
Close();
|
||||
options.paranoid_checks = false;
|
||||
Reopen(options);
|
||||
|
||||
// Merging compaction (will fail)
|
||||
error_type->store(true, std::memory_order_release);
|
||||
dbfull()->TEST_CompactRange(last, nullptr, nullptr); // Should fail
|
||||
ASSERT_EQ("bar", Get("foo"));
|
||||
|
||||
// Recovery: should not lose data
|
||||
error_type->store(false, std::memory_order_release);
|
||||
Reopen(options);
|
||||
ASSERT_EQ("bar", Get("foo"));
|
||||
|
||||
// Since paranoid_checks=false, writes should succeed
|
||||
ASSERT_OK(Put("foo2", "bar2"));
|
||||
ASSERT_EQ("bar", Get("foo"));
|
||||
ASSERT_EQ("bar2", Get("foo2"));
|
||||
}
|
||||
}
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
TEST_F(DBIOFailureTest, PutFailsParanoid) {
|
||||
// Test the following:
|
||||
// (a) A random put fails in paranoid mode (simulate by sync fail)
|
||||
// (b) All other puts have to fail, even if writes would succeed
|
||||
// (c) All of that should happen ONLY if paranoid_checks = true
|
||||
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
options.create_if_missing = true;
|
||||
options.error_if_exists = false;
|
||||
options.paranoid_checks = true;
|
||||
DestroyAndReopen(options);
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
Status s;
|
||||
|
||||
ASSERT_OK(Put(1, "foo", "bar"));
|
||||
ASSERT_OK(Put(1, "foo1", "bar1"));
|
||||
// simulate error
|
||||
env_->log_write_error_.store(true, std::memory_order_release);
|
||||
s = Put(1, "foo2", "bar2");
|
||||
ASSERT_TRUE(!s.ok());
|
||||
env_->log_write_error_.store(false, std::memory_order_release);
|
||||
s = Put(1, "foo3", "bar3");
|
||||
// the next put should fail, too
|
||||
ASSERT_TRUE(!s.ok());
|
||||
// but we're still able to read
|
||||
ASSERT_EQ("bar", Get(1, "foo"));
|
||||
|
||||
// do the same thing with paranoid checks off
|
||||
options.paranoid_checks = false;
|
||||
DestroyAndReopen(options);
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
|
||||
ASSERT_OK(Put(1, "foo", "bar"));
|
||||
ASSERT_OK(Put(1, "foo1", "bar1"));
|
||||
// simulate error
|
||||
env_->log_write_error_.store(true, std::memory_order_release);
|
||||
s = Put(1, "foo2", "bar2");
|
||||
ASSERT_TRUE(!s.ok());
|
||||
env_->log_write_error_.store(false, std::memory_order_release);
|
||||
s = Put(1, "foo3", "bar3");
|
||||
// the next put should NOT fail
|
||||
ASSERT_TRUE(s.ok());
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
rocksdb::port::InstallStackTraceHandler();
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
+6
-10
@@ -754,9 +754,8 @@ TEST_F(DBSSTTest, AddExternalSstFile) {
|
||||
env_->CreateDir(sst_files_folder);
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
const ImmutableCFOptions ioptions(options);
|
||||
|
||||
SstFileWriter sst_file_writer(EnvOptions(), ioptions, options.comparator);
|
||||
SstFileWriter sst_file_writer(EnvOptions(), options, options.comparator);
|
||||
|
||||
// file1.sst (0 => 99)
|
||||
std::string file1 = sst_files_folder + "file1.sst";
|
||||
@@ -934,9 +933,7 @@ TEST_F(DBSSTTest, AddExternalSstFilePurgeObsoleteFilesBug) {
|
||||
env_->CreateDir(sst_files_folder);
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
const ImmutableCFOptions ioptions(options);
|
||||
|
||||
SstFileWriter sst_file_writer(EnvOptions(), ioptions, options.comparator);
|
||||
SstFileWriter sst_file_writer(EnvOptions(), options, options.comparator);
|
||||
|
||||
// file1.sst (0 => 500)
|
||||
std::string sst_file_path = sst_files_folder + "file1.sst";
|
||||
@@ -985,7 +982,7 @@ TEST_F(DBSSTTest, AddExternalSstFileNoCopy) {
|
||||
options.env = env_;
|
||||
const ImmutableCFOptions ioptions(options);
|
||||
|
||||
SstFileWriter sst_file_writer(EnvOptions(), ioptions, options.comparator);
|
||||
SstFileWriter sst_file_writer(EnvOptions(), options, options.comparator);
|
||||
|
||||
// file1.sst (0 => 99)
|
||||
std::string file1 = sst_files_folder + "file1.sst";
|
||||
@@ -1063,7 +1060,6 @@ TEST_F(DBSSTTest, AddExternalSstFileMultiThreaded) {
|
||||
do {
|
||||
env_->CreateDir(sst_files_folder);
|
||||
Options options = CurrentOptions();
|
||||
const ImmutableCFOptions ioptions(options);
|
||||
|
||||
std::atomic<int> thread_num(0);
|
||||
std::function<void()> write_file_func = [&]() {
|
||||
@@ -1071,7 +1067,7 @@ TEST_F(DBSSTTest, AddExternalSstFileMultiThreaded) {
|
||||
int range_start = file_idx * keys_per_file;
|
||||
int range_end = range_start + keys_per_file;
|
||||
|
||||
SstFileWriter sst_file_writer(EnvOptions(), ioptions, options.comparator);
|
||||
SstFileWriter sst_file_writer(EnvOptions(), options, options.comparator);
|
||||
|
||||
ASSERT_OK(sst_file_writer.Open(file_names[file_idx]));
|
||||
|
||||
@@ -1154,8 +1150,8 @@ TEST_F(DBSSTTest, AddExternalSstFileOverlappingRanges) {
|
||||
env_->CreateDir(sst_files_folder);
|
||||
Options options = CurrentOptions();
|
||||
DestroyAndReopen(options);
|
||||
const ImmutableCFOptions ioptions(options);
|
||||
SstFileWriter sst_file_writer(EnvOptions(), ioptions, options.comparator);
|
||||
|
||||
SstFileWriter sst_file_writer(EnvOptions(), options, options.comparator);
|
||||
|
||||
printf("Option config = %d\n", option_config_);
|
||||
std::vector<std::pair<int, int>> key_ranges;
|
||||
|
||||
+87
-258
@@ -10,12 +10,12 @@
|
||||
// Introduction of SyncPoint effectively disabled building and running this test
|
||||
// in Release build.
|
||||
// which is a pity, it is a good test
|
||||
#include <fcntl.h>
|
||||
#include <algorithm>
|
||||
#include <set>
|
||||
#include <thread>
|
||||
#include <unordered_set>
|
||||
#include <utility>
|
||||
#include <fcntl.h>
|
||||
#ifndef OS_WIN
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
@@ -23,10 +23,10 @@
|
||||
#include <alloca.h>
|
||||
#endif
|
||||
|
||||
#include "db/filename.h"
|
||||
#include "db/dbformat.h"
|
||||
#include "db/db_impl.h"
|
||||
#include "db/db_test_util.h"
|
||||
#include "db/dbformat.h"
|
||||
#include "db/filename.h"
|
||||
#include "db/job_context.h"
|
||||
#include "db/version_set.h"
|
||||
#include "db/write_batch_internal.h"
|
||||
@@ -47,27 +47,27 @@
|
||||
#include "rocksdb/table.h"
|
||||
#include "rocksdb/table_properties.h"
|
||||
#include "rocksdb/thread_status.h"
|
||||
#include "rocksdb/utilities/write_batch_with_index.h"
|
||||
#include "rocksdb/utilities/checkpoint.h"
|
||||
#include "rocksdb/utilities/optimistic_transaction_db.h"
|
||||
#include "rocksdb/utilities/write_batch_with_index.h"
|
||||
#include "table/block_based_table_factory.h"
|
||||
#include "table/mock_table.h"
|
||||
#include "table/plain_table_factory.h"
|
||||
#include "table/scoped_arena_iterator.h"
|
||||
#include "util/compression.h"
|
||||
#include "util/file_reader_writer.h"
|
||||
#include "util/hash.h"
|
||||
#include "utilities/merge_operators.h"
|
||||
#include "util/logging.h"
|
||||
#include "util/compression.h"
|
||||
#include "util/mock_env.h"
|
||||
#include "util/mutexlock.h"
|
||||
#include "util/rate_limiter.h"
|
||||
#include "util/string_util.h"
|
||||
#include "util/sync_point.h"
|
||||
#include "util/testharness.h"
|
||||
#include "util/testutil.h"
|
||||
#include "util/mock_env.h"
|
||||
#include "util/string_util.h"
|
||||
#include "util/thread_status_util.h"
|
||||
#include "util/xfunc.h"
|
||||
#include "utilities/merge_operators.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
@@ -125,8 +125,8 @@ TEST_F(DBTest, MockEnvTest) {
|
||||
ASSERT_TRUE(!iterator->Valid());
|
||||
delete iterator;
|
||||
|
||||
// TEST_FlushMemTable() is not supported in ROCKSDB_LITE
|
||||
#ifndef ROCKSDB_LITE
|
||||
// TEST_FlushMemTable() is not supported in ROCKSDB_LITE
|
||||
#ifndef ROCKSDB_LITE
|
||||
DBImpl* dbi = reinterpret_cast<DBImpl*>(db);
|
||||
ASSERT_OK(dbi->TEST_FlushMemTable());
|
||||
|
||||
@@ -135,7 +135,7 @@ TEST_F(DBTest, MockEnvTest) {
|
||||
ASSERT_OK(db->Get(ReadOptions(), keys[i], &res));
|
||||
ASSERT_TRUE(res == vals[i]);
|
||||
}
|
||||
#endif // ROCKSDB_LITE
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
delete db;
|
||||
}
|
||||
@@ -322,7 +322,8 @@ TEST_F(DBTest, CompactedDB) {
|
||||
|
||||
// MultiGet
|
||||
std::vector<std::string> values;
|
||||
std::vector<Status> status_list = dbfull()->MultiGet(ReadOptions(),
|
||||
std::vector<Status> status_list = dbfull()->MultiGet(
|
||||
ReadOptions(),
|
||||
std::vector<Slice>({Slice("aaa"), Slice("ccc"), Slice("eee"),
|
||||
Slice("ggg"), Slice("iii"), Slice("kkk")}),
|
||||
&values);
|
||||
@@ -662,8 +663,8 @@ TEST_F(DBTest, GetFromImmutableLayer) {
|
||||
|
||||
// Block sync calls
|
||||
env_->delay_sstable_sync_.store(true, std::memory_order_release);
|
||||
Put(1, "k1", std::string(100000, 'x')); // Fill memtable
|
||||
Put(1, "k2", std::string(100000, 'y')); // Trigger flush
|
||||
Put(1, "k1", std::string(100000, 'x')); // Fill memtable
|
||||
Put(1, "k2", std::string(100000, 'y')); // Trigger flush
|
||||
ASSERT_EQ("v1", Get(1, "foo"));
|
||||
ASSERT_EQ("NOT_FOUND", Get(0, "foo"));
|
||||
// Release sync calls
|
||||
@@ -2039,239 +2040,6 @@ TEST_F(DBTest, DestroyDBMetaDatabase) {
|
||||
ASSERT_TRUE(!(DB::Open(options, metametadbname, &db)).ok());
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
// Check that number of files does not grow when writes are dropped
|
||||
TEST_F(DBTest, DropWrites) {
|
||||
do {
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
options.paranoid_checks = false;
|
||||
Reopen(options);
|
||||
|
||||
ASSERT_OK(Put("foo", "v1"));
|
||||
ASSERT_EQ("v1", Get("foo"));
|
||||
Compact("a", "z");
|
||||
const size_t num_files = CountFiles();
|
||||
// Force out-of-space errors
|
||||
env_->drop_writes_.store(true, std::memory_order_release);
|
||||
env_->sleep_counter_.Reset();
|
||||
env_->no_sleep_ = true;
|
||||
for (int i = 0; i < 5; i++) {
|
||||
if (option_config_ != kUniversalCompactionMultiLevel &&
|
||||
option_config_ != kUniversalSubcompactions) {
|
||||
for (int level = 0; level < dbfull()->NumberLevels(); level++) {
|
||||
if (level > 0 && level == dbfull()->NumberLevels() - 1) {
|
||||
break;
|
||||
}
|
||||
dbfull()->TEST_CompactRange(level, nullptr, nullptr, nullptr,
|
||||
true /* disallow trivial move */);
|
||||
}
|
||||
} else {
|
||||
dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
std::string property_value;
|
||||
ASSERT_TRUE(db_->GetProperty("rocksdb.background-errors", &property_value));
|
||||
ASSERT_EQ("5", property_value);
|
||||
|
||||
env_->drop_writes_.store(false, std::memory_order_release);
|
||||
ASSERT_LT(CountFiles(), num_files + 3);
|
||||
|
||||
// Check that compaction attempts slept after errors
|
||||
// TODO @krad: Figure out why ASSERT_EQ 5 keeps failing in certain compiler
|
||||
// versions
|
||||
ASSERT_GE(env_->sleep_counter_.Read(), 4);
|
||||
} while (ChangeCompactOptions());
|
||||
}
|
||||
|
||||
// Check background error counter bumped on flush failures.
|
||||
TEST_F(DBTest, DropWritesFlush) {
|
||||
do {
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
options.max_background_flushes = 1;
|
||||
Reopen(options);
|
||||
|
||||
ASSERT_OK(Put("foo", "v1"));
|
||||
// Force out-of-space errors
|
||||
env_->drop_writes_.store(true, std::memory_order_release);
|
||||
|
||||
std::string property_value;
|
||||
// Background error count is 0 now.
|
||||
ASSERT_TRUE(db_->GetProperty("rocksdb.background-errors", &property_value));
|
||||
ASSERT_EQ("0", property_value);
|
||||
|
||||
dbfull()->TEST_FlushMemTable(true);
|
||||
|
||||
ASSERT_TRUE(db_->GetProperty("rocksdb.background-errors", &property_value));
|
||||
ASSERT_EQ("1", property_value);
|
||||
|
||||
env_->drop_writes_.store(false, std::memory_order_release);
|
||||
} while (ChangeCompactOptions());
|
||||
}
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
// Check that CompactRange() returns failure if there is not enough space left
|
||||
// on device
|
||||
TEST_F(DBTest, NoSpaceCompactRange) {
|
||||
do {
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
options.disable_auto_compactions = true;
|
||||
Reopen(options);
|
||||
|
||||
// generate 5 tables
|
||||
for (int i = 0; i < 5; ++i) {
|
||||
ASSERT_OK(Put(Key(i), Key(i) + "v"));
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
|
||||
// Force out-of-space errors
|
||||
env_->no_space_.store(true, std::memory_order_release);
|
||||
|
||||
Status s = dbfull()->TEST_CompactRange(0, nullptr, nullptr, nullptr,
|
||||
true /* disallow trivial move */);
|
||||
ASSERT_TRUE(s.IsIOError());
|
||||
|
||||
env_->no_space_.store(false, std::memory_order_release);
|
||||
} while (ChangeCompactOptions());
|
||||
}
|
||||
|
||||
TEST_F(DBTest, NonWritableFileSystem) {
|
||||
do {
|
||||
Options options = CurrentOptions();
|
||||
options.write_buffer_size = 4096;
|
||||
options.arena_block_size = 4096;
|
||||
options.env = env_;
|
||||
Reopen(options);
|
||||
ASSERT_OK(Put("foo", "v1"));
|
||||
env_->non_writeable_rate_.store(100);
|
||||
std::string big(100000, 'x');
|
||||
int errors = 0;
|
||||
for (int i = 0; i < 20; i++) {
|
||||
if (!Put("foo", big).ok()) {
|
||||
errors++;
|
||||
env_->SleepForMicroseconds(100000);
|
||||
}
|
||||
}
|
||||
ASSERT_GT(errors, 0);
|
||||
env_->non_writeable_rate_.store(0);
|
||||
} while (ChangeCompactOptions());
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
TEST_F(DBTest, ManifestWriteError) {
|
||||
// Test for the following problem:
|
||||
// (a) Compaction produces file F
|
||||
// (b) Log record containing F is written to MANIFEST file, but Sync() fails
|
||||
// (c) GC deletes F
|
||||
// (d) After reopening DB, reads fail since deleted F is named in log record
|
||||
|
||||
// We iterate twice. In the second iteration, everything is the
|
||||
// same except the log record never makes it to the MANIFEST file.
|
||||
for (int iter = 0; iter < 2; iter++) {
|
||||
std::atomic<bool>* error_type = (iter == 0) ? &env_->manifest_sync_error_
|
||||
: &env_->manifest_write_error_;
|
||||
|
||||
// Insert foo=>bar mapping
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
options.create_if_missing = true;
|
||||
options.error_if_exists = false;
|
||||
options.paranoid_checks = true;
|
||||
DestroyAndReopen(options);
|
||||
ASSERT_OK(Put("foo", "bar"));
|
||||
ASSERT_EQ("bar", Get("foo"));
|
||||
|
||||
// Memtable compaction (will succeed)
|
||||
Flush();
|
||||
ASSERT_EQ("bar", Get("foo"));
|
||||
const int last = 2;
|
||||
MoveFilesToLevel(2);
|
||||
ASSERT_EQ(NumTableFilesAtLevel(last), 1); // foo=>bar is now in last level
|
||||
|
||||
// Merging compaction (will fail)
|
||||
error_type->store(true, std::memory_order_release);
|
||||
dbfull()->TEST_CompactRange(last, nullptr, nullptr); // Should fail
|
||||
ASSERT_EQ("bar", Get("foo"));
|
||||
|
||||
error_type->store(false, std::memory_order_release);
|
||||
|
||||
// Since paranoid_checks=true, writes should fail
|
||||
ASSERT_NOK(Put("foo2", "bar2"));
|
||||
|
||||
// Recovery: should not lose data
|
||||
ASSERT_EQ("bar", Get("foo"));
|
||||
|
||||
// Try again with paranoid_checks=false
|
||||
Close();
|
||||
options.paranoid_checks = false;
|
||||
Reopen(options);
|
||||
|
||||
// Merging compaction (will fail)
|
||||
error_type->store(true, std::memory_order_release);
|
||||
dbfull()->TEST_CompactRange(last, nullptr, nullptr); // Should fail
|
||||
ASSERT_EQ("bar", Get("foo"));
|
||||
|
||||
// Recovery: should not lose data
|
||||
error_type->store(false, std::memory_order_release);
|
||||
Reopen(options);
|
||||
ASSERT_EQ("bar", Get("foo"));
|
||||
|
||||
// Since paranoid_checks=false, writes should succeed
|
||||
ASSERT_OK(Put("foo2", "bar2"));
|
||||
ASSERT_EQ("bar", Get("foo"));
|
||||
ASSERT_EQ("bar2", Get("foo2"));
|
||||
}
|
||||
}
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
TEST_F(DBTest, PutFailsParanoid) {
|
||||
// Test the following:
|
||||
// (a) A random put fails in paranoid mode (simulate by sync fail)
|
||||
// (b) All other puts have to fail, even if writes would succeed
|
||||
// (c) All of that should happen ONLY if paranoid_checks = true
|
||||
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
options.create_if_missing = true;
|
||||
options.error_if_exists = false;
|
||||
options.paranoid_checks = true;
|
||||
DestroyAndReopen(options);
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
Status s;
|
||||
|
||||
ASSERT_OK(Put(1, "foo", "bar"));
|
||||
ASSERT_OK(Put(1, "foo1", "bar1"));
|
||||
// simulate error
|
||||
env_->log_write_error_.store(true, std::memory_order_release);
|
||||
s = Put(1, "foo2", "bar2");
|
||||
ASSERT_TRUE(!s.ok());
|
||||
env_->log_write_error_.store(false, std::memory_order_release);
|
||||
s = Put(1, "foo3", "bar3");
|
||||
// the next put should fail, too
|
||||
ASSERT_TRUE(!s.ok());
|
||||
// but we're still able to read
|
||||
ASSERT_EQ("bar", Get(1, "foo"));
|
||||
|
||||
// do the same thing with paranoid checks off
|
||||
options.paranoid_checks = false;
|
||||
DestroyAndReopen(options);
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
|
||||
ASSERT_OK(Put(1, "foo", "bar"));
|
||||
ASSERT_OK(Put(1, "foo1", "bar1"));
|
||||
// simulate error
|
||||
env_->log_write_error_.store(true, std::memory_order_release);
|
||||
s = Put(1, "foo2", "bar2");
|
||||
ASSERT_TRUE(!s.ok());
|
||||
env_->log_write_error_.store(false, std::memory_order_release);
|
||||
s = Put(1, "foo3", "bar3");
|
||||
// the next put should NOT fail
|
||||
ASSERT_TRUE(s.ok());
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
TEST_F(DBTest, SnapshotFiles) {
|
||||
do {
|
||||
@@ -2913,7 +2681,7 @@ TEST_F(DBTest, GroupCommitTest) {
|
||||
for (int i = 0; i < kGCNumThreads * kGCNumKeys; ++i) {
|
||||
expected_db.push_back(ToString(i));
|
||||
}
|
||||
sort(expected_db.begin(), expected_db.end());
|
||||
std::sort(expected_db.begin(), expected_db.end());
|
||||
|
||||
Iterator* itr = db_->NewIterator(ReadOptions());
|
||||
itr->SeekToFirst();
|
||||
@@ -3192,8 +2960,8 @@ class ModelDB : public DB {
|
||||
|
||||
virtual Status GetUpdatesSince(
|
||||
rocksdb::SequenceNumber, unique_ptr<rocksdb::TransactionLogIterator>*,
|
||||
const TransactionLogIterator::ReadOptions&
|
||||
read_options = TransactionLogIterator::ReadOptions()) override {
|
||||
const TransactionLogIterator::ReadOptions& read_options =
|
||||
TransactionLogIterator::ReadOptions()) override {
|
||||
return Status::NotSupported("Not supported in Model DB");
|
||||
}
|
||||
|
||||
@@ -3279,8 +3047,7 @@ static bool CompareIterators(int step, DB* model, DB* db,
|
||||
ok && miter->Valid() && dbiter->Valid(); miter->Next(), dbiter->Next()) {
|
||||
count++;
|
||||
if (miter->key().compare(dbiter->key()) != 0) {
|
||||
fprintf(stderr, "step %d: Key mismatch: '%s' vs. '%s'\n",
|
||||
step,
|
||||
fprintf(stderr, "step %d: Key mismatch: '%s' vs. '%s'\n", step,
|
||||
EscapeString(miter->key()).c_str(),
|
||||
EscapeString(dbiter->key()).c_str());
|
||||
ok = false;
|
||||
@@ -3483,7 +3250,6 @@ TEST_F(DBTest, BlockBasedTablePrefixIndexTest) {
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
options.prefix_extractor.reset(NewFixedPrefixTransform(1));
|
||||
|
||||
|
||||
Reopen(options);
|
||||
ASSERT_OK(Put("k1", "v1"));
|
||||
Flush();
|
||||
@@ -3682,7 +3448,6 @@ TEST_F(DBTest, TableOptionsSanitizeTest) {
|
||||
ASSERT_OK(TryReopen(options));
|
||||
}
|
||||
|
||||
|
||||
// On Windows you can have either memory mapped file or a file
|
||||
// with unbuffered access. So this asserts and does not make
|
||||
// sense to run
|
||||
@@ -4912,6 +4677,48 @@ TEST_F(DBTest, DynamicMiscOptions) {
|
||||
dbfull()->TEST_FlushMemTable(true);
|
||||
// No reseek
|
||||
assert_reseek_count(300, 1);
|
||||
|
||||
MutableCFOptions mutable_cf_options;
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
// Test soft_pending_compaction_bytes_limit,
|
||||
// hard_pending_compaction_bytes_limit
|
||||
ASSERT_OK(dbfull()->SetOptions(
|
||||
handles_[1], {{"soft_pending_compaction_bytes_limit", "200"},
|
||||
{"hard_pending_compaction_bytes_limit", "300"}}));
|
||||
ASSERT_OK(dbfull()->TEST_GetLatestMutableCFOptions(handles_[1],
|
||||
&mutable_cf_options));
|
||||
ASSERT_EQ(200, mutable_cf_options.soft_pending_compaction_bytes_limit);
|
||||
ASSERT_EQ(300, mutable_cf_options.hard_pending_compaction_bytes_limit);
|
||||
// Test report_bg_io_stats
|
||||
ASSERT_OK(
|
||||
dbfull()->SetOptions(handles_[1], {{"report_bg_io_stats", "true"}}));
|
||||
// sanity check
|
||||
ASSERT_OK(dbfull()->TEST_GetLatestMutableCFOptions(handles_[1],
|
||||
&mutable_cf_options));
|
||||
ASSERT_EQ(true, mutable_cf_options.report_bg_io_stats);
|
||||
// Test min_partial_merge_operands
|
||||
ASSERT_OK(
|
||||
dbfull()->SetOptions(handles_[1], {{"min_partial_merge_operands", "4"}}));
|
||||
ASSERT_OK(dbfull()->TEST_GetLatestMutableCFOptions(handles_[1],
|
||||
&mutable_cf_options));
|
||||
ASSERT_EQ(4, mutable_cf_options.min_partial_merge_operands);
|
||||
// Test compression
|
||||
// sanity check
|
||||
ASSERT_OK(dbfull()->SetOptions({{"compression", "kNoCompression"}}));
|
||||
ASSERT_OK(dbfull()->TEST_GetLatestMutableCFOptions(handles_[0],
|
||||
&mutable_cf_options));
|
||||
ASSERT_EQ(CompressionType::kNoCompression, mutable_cf_options.compression);
|
||||
ASSERT_OK(dbfull()->SetOptions({{"compression", "kSnappyCompression"}}));
|
||||
ASSERT_OK(dbfull()->TEST_GetLatestMutableCFOptions(handles_[0],
|
||||
&mutable_cf_options));
|
||||
ASSERT_EQ(CompressionType::kSnappyCompression,
|
||||
mutable_cf_options.compression);
|
||||
// Test paranoid_file_checks already done in db_block_cache_test
|
||||
ASSERT_OK(
|
||||
dbfull()->SetOptions(handles_[1], {{"paranoid_file_checks", "true"}}));
|
||||
ASSERT_OK(dbfull()->TEST_GetLatestMutableCFOptions(handles_[1],
|
||||
&mutable_cf_options));
|
||||
ASSERT_EQ(true, mutable_cf_options.report_bg_io_stats);
|
||||
}
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
@@ -4960,7 +4767,7 @@ TEST_F(DBTest, EncodeDecompressedBlockSizeTest) {
|
||||
// iter 3 -- lz4HC
|
||||
// iter 4 -- xpress
|
||||
CompressionType compressions[] = {kZlibCompression, kBZip2Compression,
|
||||
kLZ4Compression, kLZ4HCCompression,
|
||||
kLZ4Compression, kLZ4HCCompression,
|
||||
kXpressCompression};
|
||||
for (auto comp : compressions) {
|
||||
if (!CompressionTypeSupported(comp)) {
|
||||
@@ -5675,7 +5482,7 @@ TEST_F(DBTest, SoftLimit) {
|
||||
options.level0_slowdown_writes_trigger = 3;
|
||||
options.level0_stop_writes_trigger = 999999;
|
||||
options.delayed_write_rate = 20000; // About 200KB/s limited rate
|
||||
options.soft_pending_compaction_bytes_limit = 200000;
|
||||
options.soft_pending_compaction_bytes_limit = 160000;
|
||||
options.target_file_size_base = 99999999; // All into one file
|
||||
options.max_bytes_for_level_base = 50000;
|
||||
options.max_bytes_for_level_multiplier = 10;
|
||||
@@ -5683,6 +5490,27 @@ TEST_F(DBTest, SoftLimit) {
|
||||
options.compression = kNoCompression;
|
||||
|
||||
Reopen(options);
|
||||
|
||||
// Generating 360KB in Level 3
|
||||
for (int i = 0; i < 72; i++) {
|
||||
Put(Key(i), std::string(5000, 'x'));
|
||||
if (i % 10 == 0) {
|
||||
Flush();
|
||||
}
|
||||
}
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
MoveFilesToLevel(3);
|
||||
|
||||
// Generating 360KB in Level 2
|
||||
for (int i = 0; i < 72; i++) {
|
||||
Put(Key(i), std::string(5000, 'x'));
|
||||
if (i % 10 == 0) {
|
||||
Flush();
|
||||
}
|
||||
}
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
MoveFilesToLevel(2);
|
||||
|
||||
Put(Key(0), "");
|
||||
|
||||
test::SleepingBackgroundTask sleeping_task_low;
|
||||
@@ -5758,7 +5586,8 @@ TEST_F(DBTest, SoftLimit) {
|
||||
sleeping_task_low.WaitUntilSleeping();
|
||||
|
||||
// Now there is one L1 file (around 90KB) which exceeds 50KB base by 40KB
|
||||
// Given level multiplier 10, estimated pending compaction is around 400KB
|
||||
// L2 size is 360KB, so the estimated level fanout 4, estimated pending
|
||||
// compaction is around 200KB
|
||||
// triggerring soft_pending_compaction_bytes_limit
|
||||
ASSERT_EQ(NumTableFilesAtLevel(1), 1);
|
||||
ASSERT_TRUE(dbfull()->TEST_write_controler().NeedsDelay());
|
||||
@@ -5821,7 +5650,7 @@ TEST_F(DBTest, LastWriteBufferDelay) {
|
||||
|
||||
TEST_F(DBTest, FailWhenCompressionNotSupportedTest) {
|
||||
CompressionType compressions[] = {kZlibCompression, kBZip2Compression,
|
||||
kLZ4Compression, kLZ4HCCompression,
|
||||
kLZ4Compression, kLZ4HCCompression,
|
||||
kXpressCompression};
|
||||
for (auto comp : compressions) {
|
||||
if (!CompressionTypeSupported(comp)) {
|
||||
|
||||
+290
@@ -7,8 +7,10 @@
|
||||
// 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 <cstdlib>
|
||||
|
||||
#include "db/db_test_util.h"
|
||||
#include "port/stack_trace.h"
|
||||
#include "rocksdb/persistent_cache.h"
|
||||
#include "rocksdb/wal_filter.h"
|
||||
|
||||
namespace rocksdb {
|
||||
@@ -800,6 +802,170 @@ TEST_F(DBTest2, PresetCompressionDict) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class CompactionCompressionListener : public EventListener {
|
||||
public:
|
||||
explicit CompactionCompressionListener(Options* db_options)
|
||||
: db_options_(db_options) {}
|
||||
|
||||
void OnCompactionCompleted(DB* db, const CompactionJobInfo& ci) override {
|
||||
// Figure out last level with files
|
||||
int bottommost_level = 0;
|
||||
for (int level = 0; level < db->NumberLevels(); level++) {
|
||||
std::string files_at_level;
|
||||
ASSERT_TRUE(
|
||||
db->GetProperty("rocksdb.num-files-at-level" + NumberToString(level),
|
||||
&files_at_level));
|
||||
if (files_at_level != "0") {
|
||||
bottommost_level = level;
|
||||
}
|
||||
}
|
||||
|
||||
if (db_options_->bottommost_compression != kDisableCompressionOption &&
|
||||
ci.output_level == bottommost_level && ci.output_level >= 2) {
|
||||
ASSERT_EQ(ci.compression, db_options_->bottommost_compression);
|
||||
} else if (db_options_->compression_per_level.size() != 0) {
|
||||
ASSERT_EQ(ci.compression,
|
||||
db_options_->compression_per_level[ci.output_level]);
|
||||
} else {
|
||||
ASSERT_EQ(ci.compression, db_options_->compression);
|
||||
}
|
||||
max_level_checked = std::max(max_level_checked, ci.output_level);
|
||||
}
|
||||
|
||||
int max_level_checked = 0;
|
||||
const Options* db_options_;
|
||||
};
|
||||
|
||||
TEST_F(DBTest2, CompressionOptions) {
|
||||
if (!Zlib_Supported() || !Snappy_Supported()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Options options = CurrentOptions();
|
||||
options.level0_file_num_compaction_trigger = 2;
|
||||
options.max_bytes_for_level_base = 100;
|
||||
options.max_bytes_for_level_multiplier = 2;
|
||||
options.num_levels = 7;
|
||||
options.max_background_compactions = 1;
|
||||
options.base_background_compactions = 1;
|
||||
|
||||
CompactionCompressionListener* listener =
|
||||
new CompactionCompressionListener(&options);
|
||||
options.listeners.emplace_back(listener);
|
||||
|
||||
const int kKeySize = 5;
|
||||
const int kValSize = 20;
|
||||
Random rnd(301);
|
||||
|
||||
for (int iter = 0; iter <= 2; iter++) {
|
||||
listener->max_level_checked = 0;
|
||||
|
||||
if (iter == 0) {
|
||||
// Use different compression algorithms for different levels but
|
||||
// always use Zlib for bottommost level
|
||||
options.compression_per_level = {kNoCompression, kNoCompression,
|
||||
kNoCompression, kSnappyCompression,
|
||||
kSnappyCompression, kSnappyCompression,
|
||||
kZlibCompression};
|
||||
options.compression = kNoCompression;
|
||||
options.bottommost_compression = kZlibCompression;
|
||||
} else if (iter == 1) {
|
||||
// Use Snappy except for bottommost level use ZLib
|
||||
options.compression_per_level = {};
|
||||
options.compression = kSnappyCompression;
|
||||
options.bottommost_compression = kZlibCompression;
|
||||
} else if (iter == 2) {
|
||||
// Use Snappy everywhere
|
||||
options.compression_per_level = {};
|
||||
options.compression = kSnappyCompression;
|
||||
options.bottommost_compression = kDisableCompressionOption;
|
||||
}
|
||||
|
||||
DestroyAndReopen(options);
|
||||
// Write 10 random files
|
||||
for (int i = 0; i < 10; i++) {
|
||||
for (int j = 0; j < 5; j++) {
|
||||
ASSERT_OK(
|
||||
Put(RandomString(&rnd, kKeySize), RandomString(&rnd, kValSize)));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
}
|
||||
|
||||
// Make sure that we wrote enough to check all 7 levels
|
||||
ASSERT_EQ(listener->max_level_checked, 6);
|
||||
}
|
||||
}
|
||||
|
||||
class CompactionStallTestListener : public EventListener {
|
||||
public:
|
||||
CompactionStallTestListener() : compacted_files_cnt_(0) {}
|
||||
|
||||
void OnCompactionCompleted(DB* db, const CompactionJobInfo& ci) override {
|
||||
ASSERT_EQ(ci.cf_name, "default");
|
||||
ASSERT_EQ(ci.base_input_level, 0);
|
||||
ASSERT_EQ(ci.compaction_reason, CompactionReason::kLevelL0FilesNum);
|
||||
compacted_files_cnt_ += ci.input_files.size();
|
||||
}
|
||||
std::atomic<size_t> compacted_files_cnt_;
|
||||
};
|
||||
|
||||
TEST_F(DBTest2, CompactionStall) {
|
||||
rocksdb::SyncPoint::GetInstance()->LoadDependency(
|
||||
{{"DBImpl::BGWorkCompaction", "DBTest2::CompactionStall:0"},
|
||||
{"DBImpl::BGWorkCompaction", "DBTest2::CompactionStall:1"},
|
||||
{"DBTest2::CompactionStall:2",
|
||||
"DBImpl::NotifyOnCompactionCompleted::UnlockMutex"}});
|
||||
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
Options options = CurrentOptions();
|
||||
options.level0_file_num_compaction_trigger = 4;
|
||||
options.max_background_compactions = 40;
|
||||
CompactionStallTestListener* listener = new CompactionStallTestListener();
|
||||
options.listeners.emplace_back(listener);
|
||||
DestroyAndReopen(options);
|
||||
|
||||
Random rnd(301);
|
||||
|
||||
// 4 Files in L0
|
||||
for (int i = 0; i < 4; i++) {
|
||||
for (int j = 0; j < 10; j++) {
|
||||
ASSERT_OK(Put(RandomString(&rnd, 10), RandomString(&rnd, 10)));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
|
||||
// Wait for compaction to be triggered
|
||||
TEST_SYNC_POINT("DBTest2::CompactionStall:0");
|
||||
|
||||
// Clear "DBImpl::BGWorkCompaction" SYNC_POINT since we want to hold it again
|
||||
// at DBTest2::CompactionStall::1
|
||||
rocksdb::SyncPoint::GetInstance()->ClearTrace();
|
||||
|
||||
// Another 6 L0 files to trigger compaction again
|
||||
for (int i = 0; i < 6; i++) {
|
||||
for (int j = 0; j < 10; j++) {
|
||||
ASSERT_OK(Put(RandomString(&rnd, 10), RandomString(&rnd, 10)));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
|
||||
// Wait for another compaction to be triggered
|
||||
TEST_SYNC_POINT("DBTest2::CompactionStall:1");
|
||||
|
||||
// Hold NotifyOnCompactionCompleted in the unlock mutex section
|
||||
TEST_SYNC_POINT("DBTest2::CompactionStall:2");
|
||||
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
ASSERT_LT(NumTableFilesAtLevel(0),
|
||||
options.level0_file_num_compaction_trigger);
|
||||
ASSERT_GT(listener->compacted_files_cnt_.load(),
|
||||
10 - options.level0_file_num_compaction_trigger);
|
||||
|
||||
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
TEST_F(DBTest2, FirstSnapshotTest) {
|
||||
@@ -929,7 +1095,131 @@ TEST_P(PinL0IndexAndFilterBlocksTest,
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(PinL0IndexAndFilterBlocksTest,
|
||||
PinL0IndexAndFilterBlocksTest, ::testing::Bool());
|
||||
#ifndef ROCKSDB_LITE
|
||||
static void UniqueIdCallback(void* arg) {
|
||||
int* result = reinterpret_cast<int*>(arg);
|
||||
if (*result == -1) {
|
||||
*result = 0;
|
||||
}
|
||||
|
||||
rocksdb::SyncPoint::GetInstance()->ClearTrace();
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"GetUniqueIdFromFile:FS_IOC_GETVERSION", UniqueIdCallback);
|
||||
}
|
||||
|
||||
class MockPersistentCache : public PersistentCache {
|
||||
public:
|
||||
explicit MockPersistentCache(const bool is_compressed, const size_t max_size)
|
||||
: is_compressed_(is_compressed), max_size_(max_size) {
|
||||
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"GetUniqueIdFromFile:FS_IOC_GETVERSION", UniqueIdCallback);
|
||||
}
|
||||
|
||||
virtual ~MockPersistentCache() {}
|
||||
|
||||
Status Insert(const Slice& page_key, const char* data,
|
||||
const size_t size) override {
|
||||
MutexLock _(&lock_);
|
||||
|
||||
if (size_ > max_size_) {
|
||||
size_ -= data_.begin()->second.size();
|
||||
data_.erase(data_.begin());
|
||||
}
|
||||
|
||||
data_.insert(std::make_pair(page_key.ToString(), std::string(data, size)));
|
||||
size_ += size;
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status Lookup(const Slice& page_key, std::unique_ptr<char[]>* data,
|
||||
size_t* size) override {
|
||||
MutexLock _(&lock_);
|
||||
auto it = data_.find(page_key.ToString());
|
||||
if (it == data_.end()) {
|
||||
return Status::NotFound();
|
||||
}
|
||||
|
||||
assert(page_key.ToString() == it->first);
|
||||
data->reset(new char[it->second.size()]);
|
||||
memcpy(data->get(), it->second.c_str(), it->second.size());
|
||||
*size = it->second.size();
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
bool IsCompressed() override { return is_compressed_; }
|
||||
|
||||
port::Mutex lock_;
|
||||
std::map<std::string, std::string> data_;
|
||||
const bool is_compressed_ = true;
|
||||
size_t size_ = 0;
|
||||
const size_t max_size_ = 10 * 1024; // 10KiB
|
||||
};
|
||||
|
||||
TEST_F(DBTest2, PersistentCache) {
|
||||
int num_iter = 80;
|
||||
|
||||
Options options;
|
||||
options.write_buffer_size = 64 * 1024; // small write buffer
|
||||
options.statistics = rocksdb::CreateDBStatistics();
|
||||
options = CurrentOptions(options);
|
||||
|
||||
auto bsizes = {/*no block cache*/ 0, /*1M*/ 1 * 1024 * 1024};
|
||||
auto types = {/*compressed*/ 1, /*uncompressed*/ 0};
|
||||
for (auto bsize : bsizes) {
|
||||
for (auto type : types) {
|
||||
BlockBasedTableOptions table_options;
|
||||
table_options.persistent_cache.reset(
|
||||
new MockPersistentCache(type, 10 * 1024));
|
||||
table_options.no_block_cache = true;
|
||||
table_options.block_cache = bsize ? NewLRUCache(bsize) : nullptr;
|
||||
table_options.block_cache_compressed = nullptr;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
|
||||
DestroyAndReopen(options);
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
// default column family doesn't have block cache
|
||||
Options no_block_cache_opts;
|
||||
no_block_cache_opts.statistics = options.statistics;
|
||||
no_block_cache_opts = CurrentOptions(no_block_cache_opts);
|
||||
BlockBasedTableOptions table_options_no_bc;
|
||||
table_options_no_bc.no_block_cache = true;
|
||||
no_block_cache_opts.table_factory.reset(
|
||||
NewBlockBasedTableFactory(table_options_no_bc));
|
||||
ReopenWithColumnFamilies(
|
||||
{"default", "pikachu"},
|
||||
std::vector<Options>({no_block_cache_opts, options}));
|
||||
|
||||
Random rnd(301);
|
||||
|
||||
// Write 8MB (80 values, each 100K)
|
||||
ASSERT_EQ(NumTableFilesAtLevel(0, 1), 0);
|
||||
std::vector<std::string> values;
|
||||
std::string str;
|
||||
for (int i = 0; i < num_iter; i++) {
|
||||
if (i % 4 == 0) { // high compression ratio
|
||||
str = RandomString(&rnd, 1000);
|
||||
}
|
||||
values.push_back(str);
|
||||
ASSERT_OK(Put(1, Key(i), values[i]));
|
||||
}
|
||||
|
||||
// flush all data from memtable so that reads are from block cache
|
||||
ASSERT_OK(Flush(1));
|
||||
|
||||
for (int i = 0; i < num_iter; i++) {
|
||||
ASSERT_EQ(Get(1, Key(i)), values[i]);
|
||||
}
|
||||
|
||||
auto hit = options.statistics->getTickerCount(PERSISTENT_CACHE_HIT);
|
||||
auto miss = options.statistics->getTickerCount(PERSISTENT_CACHE_MISS);
|
||||
|
||||
ASSERT_GT(hit, 0);
|
||||
ASSERT_GT(miss, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
} // namespace rocksdb
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
+6
-1
@@ -39,6 +39,11 @@ enum ValueType : unsigned char {
|
||||
kTypeColumnFamilyMerge = 0x6, // WAL only.
|
||||
kTypeSingleDeletion = 0x7,
|
||||
kTypeColumnFamilySingleDeletion = 0x8, // WAL only.
|
||||
kTypeBeginPrepareXID = 0x9, // WAL only.
|
||||
kTypeEndPrepareXID = 0xA, // WAL only.
|
||||
kTypeCommitXID = 0xB, // WAL only.
|
||||
kTypeRollbackXID = 0xC, // WAL only.
|
||||
kTypeNoop = 0xD, // WAL only.
|
||||
kMaxValue = 0x7F // Not used for storing records.
|
||||
};
|
||||
|
||||
@@ -478,5 +483,5 @@ extern bool ReadKeyFromWriteBatchEntry(Slice* input, Slice* key,
|
||||
// input will be advanced to after the record.
|
||||
extern Status ReadRecordFromWriteBatch(Slice* input, char* tag,
|
||||
uint32_t* column_family, Slice* key,
|
||||
Slice* value, Slice* blob);
|
||||
Slice* value, Slice* blob, Slice* xid);
|
||||
} // namespace rocksdb
|
||||
+2
-398
@@ -11,8 +11,6 @@
|
||||
// the last "sync". It then checks for data loss errors by purposely dropping
|
||||
// file data (or entire files) not protected by a "sync".
|
||||
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include "db/db_impl.h"
|
||||
#include "db/filename.h"
|
||||
#include "db/log_format.h"
|
||||
@@ -22,6 +20,7 @@
|
||||
#include "rocksdb/env.h"
|
||||
#include "rocksdb/table.h"
|
||||
#include "rocksdb/write_batch.h"
|
||||
#include "util/fault_injection_test_env.h"
|
||||
#include "util/logging.h"
|
||||
#include "util/mock_env.h"
|
||||
#include "util/mutexlock.h"
|
||||
@@ -35,401 +34,6 @@ static const int kValueSize = 1000;
|
||||
static const int kMaxNumValues = 2000;
|
||||
static const size_t kNumIterations = 3;
|
||||
|
||||
class TestWritableFile;
|
||||
class FaultInjectionTestEnv;
|
||||
|
||||
namespace {
|
||||
|
||||
// Assume a filename, and not a directory name like "/foo/bar/"
|
||||
static std::string GetDirName(const std::string filename) {
|
||||
size_t found = filename.find_last_of("/\\");
|
||||
if (found == std::string::npos) {
|
||||
return "";
|
||||
} else {
|
||||
return filename.substr(0, found);
|
||||
}
|
||||
}
|
||||
|
||||
// Trim the tailing "/" in the end of `str`
|
||||
static std::string TrimDirname(const std::string& str) {
|
||||
size_t found = str.find_last_not_of("/");
|
||||
if (found == std::string::npos) {
|
||||
return str;
|
||||
}
|
||||
return str.substr(0, found + 1);
|
||||
}
|
||||
|
||||
// Return pair <parent directory name, file name> of a full path.
|
||||
static std::pair<std::string, std::string> GetDirAndName(
|
||||
const std::string& name) {
|
||||
std::string dirname = GetDirName(name);
|
||||
std::string fname = name.substr(dirname.size() + 1);
|
||||
return std::make_pair(dirname, fname);
|
||||
}
|
||||
|
||||
// A basic file truncation function suitable for this test.
|
||||
Status Truncate(Env* env, const std::string& filename, uint64_t length) {
|
||||
unique_ptr<SequentialFile> orig_file;
|
||||
const EnvOptions options;
|
||||
Status s = env->NewSequentialFile(filename, &orig_file, options);
|
||||
if (!s.ok()) {
|
||||
fprintf(stderr, "Cannot truncate file %s: %s\n", filename.c_str(),
|
||||
s.ToString().c_str());
|
||||
return s;
|
||||
}
|
||||
|
||||
std::unique_ptr<char[]> scratch(new char[length]);
|
||||
rocksdb::Slice result;
|
||||
s = orig_file->Read(length, &result, scratch.get());
|
||||
#ifdef OS_WIN
|
||||
orig_file.reset();
|
||||
#endif
|
||||
if (s.ok()) {
|
||||
std::string tmp_name = GetDirName(filename) + "/truncate.tmp";
|
||||
unique_ptr<WritableFile> tmp_file;
|
||||
s = env->NewWritableFile(tmp_name, &tmp_file, options);
|
||||
if (s.ok()) {
|
||||
s = tmp_file->Append(result);
|
||||
if (s.ok()) {
|
||||
s = env->RenameFile(tmp_name, filename);
|
||||
} else {
|
||||
fprintf(stderr, "Cannot rename file %s to %s: %s\n", tmp_name.c_str(),
|
||||
filename.c_str(), s.ToString().c_str());
|
||||
env->DeleteFile(tmp_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!s.ok()) {
|
||||
fprintf(stderr, "Cannot truncate file %s: %s\n", filename.c_str(),
|
||||
s.ToString().c_str());
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
struct FileState {
|
||||
std::string filename_;
|
||||
ssize_t pos_;
|
||||
ssize_t pos_at_last_sync_;
|
||||
ssize_t pos_at_last_flush_;
|
||||
|
||||
explicit FileState(const std::string& filename)
|
||||
: filename_(filename),
|
||||
pos_(-1),
|
||||
pos_at_last_sync_(-1),
|
||||
pos_at_last_flush_(-1) { }
|
||||
|
||||
FileState() : pos_(-1), pos_at_last_sync_(-1), pos_at_last_flush_(-1) {}
|
||||
|
||||
bool IsFullySynced() const { return pos_ <= 0 || pos_ == pos_at_last_sync_; }
|
||||
|
||||
Status DropUnsyncedData(Env* env) const;
|
||||
|
||||
Status DropRandomUnsyncedData(Env* env, Random* rand) const;
|
||||
};
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
// A wrapper around WritableFileWriter* file
|
||||
// is written to or sync'ed.
|
||||
class TestWritableFile : public WritableFile {
|
||||
public:
|
||||
explicit TestWritableFile(const std::string& fname,
|
||||
unique_ptr<WritableFile>&& f,
|
||||
FaultInjectionTestEnv* env);
|
||||
virtual ~TestWritableFile();
|
||||
virtual Status Append(const Slice& data) override;
|
||||
virtual Status Truncate(uint64_t size) override { return target_->Truncate(size); }
|
||||
virtual Status Close() override;
|
||||
virtual Status Flush() override;
|
||||
virtual Status Sync() override;
|
||||
virtual bool IsSyncThreadSafe() const override { return true; }
|
||||
|
||||
private:
|
||||
FileState state_;
|
||||
unique_ptr<WritableFile> target_;
|
||||
bool writable_file_opened_;
|
||||
FaultInjectionTestEnv* env_;
|
||||
};
|
||||
|
||||
class TestDirectory : public Directory {
|
||||
public:
|
||||
explicit TestDirectory(FaultInjectionTestEnv* env, std::string dirname,
|
||||
Directory* dir)
|
||||
: env_(env), dirname_(dirname), dir_(dir) {}
|
||||
~TestDirectory() {}
|
||||
|
||||
virtual Status Fsync() override;
|
||||
|
||||
private:
|
||||
FaultInjectionTestEnv* env_;
|
||||
std::string dirname_;
|
||||
unique_ptr<Directory> dir_;
|
||||
};
|
||||
|
||||
class FaultInjectionTestEnv : public EnvWrapper {
|
||||
public:
|
||||
explicit FaultInjectionTestEnv(Env* base)
|
||||
: EnvWrapper(base),
|
||||
filesystem_active_(true) {}
|
||||
virtual ~FaultInjectionTestEnv() { }
|
||||
|
||||
Status NewDirectory(const std::string& name,
|
||||
unique_ptr<Directory>* result) override {
|
||||
unique_ptr<Directory> r;
|
||||
Status s = target()->NewDirectory(name, &r);
|
||||
EXPECT_OK(s);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
result->reset(new TestDirectory(this, TrimDirname(name), r.release()));
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status NewWritableFile(const std::string& fname,
|
||||
unique_ptr<WritableFile>* result,
|
||||
const EnvOptions& soptions) override {
|
||||
if (!IsFilesystemActive()) {
|
||||
return Status::Corruption("Not Active");
|
||||
}
|
||||
// Not allow overwriting files
|
||||
Status s = target()->FileExists(fname);
|
||||
if (s.ok()) {
|
||||
return Status::Corruption("File already exists.");
|
||||
} else if (!s.IsNotFound()) {
|
||||
assert(s.IsIOError());
|
||||
return s;
|
||||
}
|
||||
s = target()->NewWritableFile(fname, result, soptions);
|
||||
if (s.ok()) {
|
||||
result->reset(new TestWritableFile(fname, std::move(*result), this));
|
||||
// WritableFileWriter* file is opened
|
||||
// again then it will be truncated - so forget our saved state.
|
||||
UntrackFile(fname);
|
||||
MutexLock l(&mutex_);
|
||||
open_files_.insert(fname);
|
||||
auto dir_and_name = GetDirAndName(fname);
|
||||
auto& list = dir_to_new_files_since_last_sync_[dir_and_name.first];
|
||||
list.insert(dir_and_name.second);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
virtual Status DeleteFile(const std::string& f) override {
|
||||
if (!IsFilesystemActive()) {
|
||||
return Status::Corruption("Not Active");
|
||||
}
|
||||
Status s = EnvWrapper::DeleteFile(f);
|
||||
if (!s.ok()) {
|
||||
fprintf(stderr, "Cannot delete file %s: %s\n", f.c_str(),
|
||||
s.ToString().c_str());
|
||||
}
|
||||
EXPECT_OK(s);
|
||||
if (s.ok()) {
|
||||
UntrackFile(f);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
virtual Status RenameFile(const std::string& s,
|
||||
const std::string& t) override {
|
||||
if (!IsFilesystemActive()) {
|
||||
return Status::Corruption("Not Active");
|
||||
}
|
||||
Status ret = EnvWrapper::RenameFile(s, t);
|
||||
|
||||
if (ret.ok()) {
|
||||
MutexLock l(&mutex_);
|
||||
if (db_file_state_.find(s) != db_file_state_.end()) {
|
||||
db_file_state_[t] = db_file_state_[s];
|
||||
db_file_state_.erase(s);
|
||||
}
|
||||
|
||||
auto sdn = GetDirAndName(s);
|
||||
auto tdn = GetDirAndName(t);
|
||||
if (dir_to_new_files_since_last_sync_[sdn.first].erase(sdn.second) != 0) {
|
||||
auto& tlist = dir_to_new_files_since_last_sync_[tdn.first];
|
||||
assert(tlist.find(tdn.second) == tlist.end());
|
||||
tlist.insert(tdn.second);
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void WritableFileClosed(const FileState& state) {
|
||||
MutexLock l(&mutex_);
|
||||
if (open_files_.find(state.filename_) != open_files_.end()) {
|
||||
db_file_state_[state.filename_] = state;
|
||||
open_files_.erase(state.filename_);
|
||||
}
|
||||
}
|
||||
|
||||
// For every file that is not fully synced, make a call to `func` with
|
||||
// FileState of the file as the parameter.
|
||||
Status DropFileData(std::function<Status(Env*, FileState)> func) {
|
||||
Status s;
|
||||
MutexLock l(&mutex_);
|
||||
for (std::map<std::string, FileState>::const_iterator it =
|
||||
db_file_state_.begin();
|
||||
s.ok() && it != db_file_state_.end(); ++it) {
|
||||
const FileState& state = it->second;
|
||||
if (!state.IsFullySynced()) {
|
||||
s = func(target(), state);
|
||||
}
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
Status DropUnsyncedFileData() {
|
||||
return DropFileData([&](Env* env, const FileState& state) {
|
||||
return state.DropUnsyncedData(env);
|
||||
});
|
||||
}
|
||||
|
||||
Status DropRandomUnsyncedFileData(Random* rnd) {
|
||||
return DropFileData([&](Env* env, const FileState& state) {
|
||||
return state.DropRandomUnsyncedData(env, rnd);
|
||||
});
|
||||
}
|
||||
|
||||
Status DeleteFilesCreatedAfterLastDirSync() {
|
||||
// Because DeleteFile access this container make a copy to avoid deadlock
|
||||
std::map<std::string, std::set<std::string>> map_copy;
|
||||
{
|
||||
MutexLock l(&mutex_);
|
||||
map_copy.insert(dir_to_new_files_since_last_sync_.begin(),
|
||||
dir_to_new_files_since_last_sync_.end());
|
||||
}
|
||||
|
||||
for (auto& pair : map_copy) {
|
||||
for (std::string name : pair.second) {
|
||||
Status s = DeleteFile(pair.first + "/" + name);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
}
|
||||
}
|
||||
return Status::OK();
|
||||
}
|
||||
void ResetState() {
|
||||
MutexLock l(&mutex_);
|
||||
db_file_state_.clear();
|
||||
dir_to_new_files_since_last_sync_.clear();
|
||||
SetFilesystemActiveNoLock(true);
|
||||
}
|
||||
|
||||
void UntrackFile(const std::string& f) {
|
||||
MutexLock l(&mutex_);
|
||||
auto dir_and_name = GetDirAndName(f);
|
||||
dir_to_new_files_since_last_sync_[dir_and_name.first].erase(
|
||||
dir_and_name.second);
|
||||
db_file_state_.erase(f);
|
||||
open_files_.erase(f);
|
||||
}
|
||||
|
||||
void SyncDir(const std::string& dirname) {
|
||||
MutexLock l(&mutex_);
|
||||
dir_to_new_files_since_last_sync_.erase(dirname);
|
||||
}
|
||||
|
||||
// Setting the filesystem to inactive is the test equivalent to simulating a
|
||||
// system reset. Setting to inactive will freeze our saved filesystem state so
|
||||
// that it will stop being recorded. It can then be reset back to the state at
|
||||
// the time of the reset.
|
||||
bool IsFilesystemActive() {
|
||||
MutexLock l(&mutex_);
|
||||
return filesystem_active_;
|
||||
}
|
||||
void SetFilesystemActiveNoLock(bool active) { filesystem_active_ = active; }
|
||||
void SetFilesystemActive(bool active) {
|
||||
MutexLock l(&mutex_);
|
||||
SetFilesystemActiveNoLock(active);
|
||||
}
|
||||
void AssertNoOpenFile() { ASSERT_TRUE(open_files_.empty()); }
|
||||
|
||||
private:
|
||||
port::Mutex mutex_;
|
||||
std::map<std::string, FileState> db_file_state_;
|
||||
std::set<std::string> open_files_;
|
||||
std::unordered_map<std::string, std::set<std::string>>
|
||||
dir_to_new_files_since_last_sync_;
|
||||
bool filesystem_active_; // Record flushes, syncs, writes
|
||||
};
|
||||
|
||||
Status FileState::DropUnsyncedData(Env* env) const {
|
||||
ssize_t sync_pos = pos_at_last_sync_ == -1 ? 0 : pos_at_last_sync_;
|
||||
return Truncate(env, filename_, sync_pos);
|
||||
}
|
||||
|
||||
Status FileState::DropRandomUnsyncedData(Env* env, Random* rand) const {
|
||||
ssize_t sync_pos = pos_at_last_sync_ == -1 ? 0 : pos_at_last_sync_;
|
||||
assert(pos_ >= sync_pos);
|
||||
int range = static_cast<int>(pos_ - sync_pos);
|
||||
uint64_t truncated_size =
|
||||
static_cast<uint64_t>(sync_pos) + rand->Uniform(range);
|
||||
return Truncate(env, filename_, truncated_size);
|
||||
}
|
||||
|
||||
Status TestDirectory::Fsync() {
|
||||
env_->SyncDir(dirname_);
|
||||
return dir_->Fsync();
|
||||
}
|
||||
|
||||
TestWritableFile::TestWritableFile(const std::string& fname,
|
||||
unique_ptr<WritableFile>&& f,
|
||||
FaultInjectionTestEnv* env)
|
||||
: state_(fname),
|
||||
target_(std::move(f)),
|
||||
writable_file_opened_(true),
|
||||
env_(env) {
|
||||
assert(target_ != nullptr);
|
||||
state_.pos_ = 0;
|
||||
}
|
||||
|
||||
TestWritableFile::~TestWritableFile() {
|
||||
if (writable_file_opened_) {
|
||||
Close();
|
||||
}
|
||||
}
|
||||
|
||||
Status TestWritableFile::Append(const Slice& data) {
|
||||
if (!env_->IsFilesystemActive()) {
|
||||
return Status::Corruption("Not Active");
|
||||
}
|
||||
Status s = target_->Append(data);
|
||||
if (s.ok()) {
|
||||
state_.pos_ += data.size();
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
Status TestWritableFile::Close() {
|
||||
writable_file_opened_ = false;
|
||||
Status s = target_->Close();
|
||||
if (s.ok()) {
|
||||
env_->WritableFileClosed(state_);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
Status TestWritableFile::Flush() {
|
||||
Status s = target_->Flush();
|
||||
if (s.ok() && env_->IsFilesystemActive()) {
|
||||
state_.pos_at_last_flush_ = state_.pos_;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
Status TestWritableFile::Sync() {
|
||||
if (!env_->IsFilesystemActive()) {
|
||||
return Status::OK();
|
||||
}
|
||||
// No need to actual sync.
|
||||
state_.pos_at_last_sync_ = state_.pos_;
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
class FaultInjectionTest : public testing::Test,
|
||||
public testing::WithParamInterface<bool> {
|
||||
protected:
|
||||
@@ -732,7 +336,7 @@ class FaultInjectionTest : public testing::Test,
|
||||
}
|
||||
|
||||
void WaitCompactionFinish() {
|
||||
static_cast<DBImpl*>(db_)->TEST_WaitForCompact();
|
||||
static_cast<DBImpl*>(db_->GetRootDB())->TEST_WaitForCompact();
|
||||
ASSERT_OK(db_->Put(WriteOptions(), "", ""));
|
||||
}
|
||||
};
|
||||
|
||||
+5
-4
@@ -197,6 +197,7 @@ Status FlushJob::Run(FileMetaData* file_meta) {
|
||||
stream << vstorage->NumLevelFiles(level);
|
||||
}
|
||||
stream.EndArray();
|
||||
stream << "immutable_memtables" << cfd_->imm()->NumNotFlushed();
|
||||
|
||||
if (measure_io_stats_) {
|
||||
if (prev_perf_level != PerfLevel::kEnableTime) {
|
||||
@@ -264,10 +265,10 @@ Status FlushJob::WriteLevel0Table(const autovector<MemTable*>& mems,
|
||||
TEST_SYNC_POINT_CALLBACK("FlushJob::WriteLevel0Table:output_compression",
|
||||
&output_compression_);
|
||||
s = BuildTable(
|
||||
dbname_, db_options_.env, *cfd_->ioptions(), env_options_,
|
||||
cfd_->table_cache(), iter.get(), meta, cfd_->internal_comparator(),
|
||||
cfd_->int_tbl_prop_collector_factories(), cfd_->GetID(),
|
||||
cfd_->GetName(), existing_snapshots_,
|
||||
dbname_, db_options_.env, *cfd_->ioptions(), mutable_cf_options_,
|
||||
env_options_, cfd_->table_cache(), iter.get(), meta,
|
||||
cfd_->internal_comparator(), cfd_->int_tbl_prop_collector_factories(),
|
||||
cfd_->GetID(), cfd_->GetName(), existing_snapshots_,
|
||||
earliest_write_conflict_snapshot_, output_compression_,
|
||||
cfd_->ioptions()->compression_opts,
|
||||
mutable_cf_options_.paranoid_file_checks, cfd_->internal_stats(),
|
||||
|
||||
+33
-1
@@ -723,7 +723,39 @@ TEST_F(EventListenerTest, TableFileCreationListenersTest) {
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
listener->CheckAndResetCounters(1, 1, 0, 1, 1, 1);
|
||||
}
|
||||
} // namespace rocksdb
|
||||
|
||||
class MemTableSealedListener : public EventListener {
|
||||
private:
|
||||
SequenceNumber latest_seq_number_;
|
||||
public:
|
||||
MemTableSealedListener() {}
|
||||
void OnMemTableSealed(const MemTableInfo& info) override {
|
||||
latest_seq_number_ = info.first_seqno;
|
||||
}
|
||||
|
||||
void OnFlushCompleted(DB* /*db*/,
|
||||
const FlushJobInfo& flush_job_info) override {
|
||||
ASSERT_LE(flush_job_info.smallest_seqno, latest_seq_number_);
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(EventListenerTest, MemTableSealedListenerTest) {
|
||||
auto listener = std::make_shared<MemTableSealedListener>();
|
||||
Options options;
|
||||
options.create_if_missing = true;
|
||||
options.listeners.push_back(listener);
|
||||
DestroyAndReopen(options);
|
||||
|
||||
for (unsigned int i = 0; i < 10; i++) {
|
||||
std::string tag = std::to_string(i);
|
||||
ASSERT_OK(Put("foo"+tag, "aaa"));
|
||||
ASSERT_OK(Put("bar"+tag, "bbb"));
|
||||
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
}
|
||||
} // namespace rocksdb
|
||||
|
||||
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
|
||||
+28
-8
@@ -22,9 +22,8 @@ Reader::Reporter::~Reporter() {
|
||||
}
|
||||
|
||||
Reader::Reader(std::shared_ptr<Logger> info_log,
|
||||
unique_ptr<SequentialFileReader>&& _file,
|
||||
Reporter* reporter, bool checksum, uint64_t initial_offset,
|
||||
uint64_t log_num)
|
||||
unique_ptr<SequentialFileReader>&& _file, Reporter* reporter,
|
||||
bool checksum, uint64_t initial_offset, uint64_t log_num)
|
||||
: info_log_(info_log),
|
||||
file_(std::move(_file)),
|
||||
reporter_(reporter),
|
||||
@@ -37,7 +36,8 @@ Reader::Reader(std::shared_ptr<Logger> info_log,
|
||||
last_record_offset_(0),
|
||||
end_of_buffer_offset_(0),
|
||||
initial_offset_(initial_offset),
|
||||
log_number_(log_num) {}
|
||||
log_number_(log_num),
|
||||
recycled_(false) {}
|
||||
|
||||
Reader::~Reader() {
|
||||
delete[] backing_store_;
|
||||
@@ -191,6 +191,25 @@ bool Reader::ReadRecord(Slice* record, std::string* scratch,
|
||||
}
|
||||
break;
|
||||
|
||||
case kBadRecordLen:
|
||||
case kBadRecordChecksum:
|
||||
if (recycled_ &&
|
||||
wal_recovery_mode ==
|
||||
WALRecoveryMode::kTolerateCorruptedTailRecords) {
|
||||
scratch->clear();
|
||||
return false;
|
||||
}
|
||||
if (record_type == kBadRecordLen)
|
||||
ReportCorruption(drop_size, "bad record length");
|
||||
else
|
||||
ReportCorruption(drop_size, "checksum mismatch");
|
||||
if (in_fragmented_record) {
|
||||
ReportCorruption(scratch->size(), "error in middle of record");
|
||||
in_fragmented_record = false;
|
||||
scratch->clear();
|
||||
}
|
||||
break;
|
||||
|
||||
default: {
|
||||
char buf[40];
|
||||
snprintf(buf, sizeof(buf), "unknown record type %u", record_type);
|
||||
@@ -337,6 +356,9 @@ unsigned int Reader::ReadPhysicalRecord(Slice* result, size_t* drop_size) {
|
||||
const uint32_t length = a | (b << 8);
|
||||
int header_size = kHeaderSize;
|
||||
if (type >= kRecyclableFullType && type <= kRecyclableLastType) {
|
||||
if (end_of_buffer_offset_ - buffer_.size() == 0) {
|
||||
recycled_ = true;
|
||||
}
|
||||
header_size = kRecyclableHeaderSize;
|
||||
// We need enough for the larger header
|
||||
if (buffer_.size() < (size_t)kRecyclableHeaderSize) {
|
||||
@@ -355,8 +377,7 @@ unsigned int Reader::ReadPhysicalRecord(Slice* result, size_t* drop_size) {
|
||||
*drop_size = buffer_.size();
|
||||
buffer_.clear();
|
||||
if (!eof_) {
|
||||
ReportCorruption(*drop_size, "bad record length");
|
||||
return kBadRecord;
|
||||
return kBadRecordLen;
|
||||
}
|
||||
// If the end of the file has been reached without reading |length| bytes
|
||||
// of payload, assume the writer died in the middle of writing the record.
|
||||
@@ -388,8 +409,7 @@ unsigned int Reader::ReadPhysicalRecord(Slice* result, size_t* drop_size) {
|
||||
// like a valid log record.
|
||||
*drop_size = buffer_.size();
|
||||
buffer_.clear();
|
||||
ReportCorruption(*drop_size, "checksum mismatch");
|
||||
return kBadRecord;
|
||||
return kBadRecordChecksum;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -113,6 +113,9 @@ class Reader {
|
||||
// which log number this is
|
||||
uint64_t const log_number_;
|
||||
|
||||
// Whether this is a recycled log file
|
||||
bool recycled_;
|
||||
|
||||
// Extend record types with the following special values
|
||||
enum {
|
||||
kEof = kMaxRecordType + 1,
|
||||
@@ -126,6 +129,10 @@ class Reader {
|
||||
kBadHeader = kMaxRecordType + 3,
|
||||
// Returned when we read an old record from a previous user of the log.
|
||||
kOldRecord = kMaxRecordType + 4,
|
||||
// Returned when we get a bad record length
|
||||
kBadRecordLen = kMaxRecordType + 5,
|
||||
// Returned when we get a bad record checksum
|
||||
kBadRecordChecksum = kMaxRecordType + 6,
|
||||
};
|
||||
|
||||
// Skips all blocks that are completely before "initial_offset_".
|
||||
|
||||
+48
-10
@@ -174,6 +174,8 @@ class LogTest : public ::testing::TestWithParam<int> {
|
||||
3 * header_size;
|
||||
}
|
||||
|
||||
Slice* get_reader_contents() { return &reader_contents_; }
|
||||
|
||||
void Write(const std::string& msg) {
|
||||
writer_.AddRecord(Slice(msg));
|
||||
}
|
||||
@@ -445,9 +447,13 @@ TEST_P(LogTest, BadLength) {
|
||||
Write("foo");
|
||||
// Least significant size byte is stored in header[4].
|
||||
IncrementByte(4, 1);
|
||||
ASSERT_EQ("foo", Read());
|
||||
ASSERT_EQ(kBlockSize, DroppedBytes());
|
||||
ASSERT_EQ("OK", MatchError("bad record length"));
|
||||
if (!GetParam()) {
|
||||
ASSERT_EQ("foo", Read());
|
||||
ASSERT_EQ(kBlockSize, DroppedBytes());
|
||||
ASSERT_EQ("OK", MatchError("bad record length"));
|
||||
} else {
|
||||
ASSERT_EQ("EOF", Read());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(LogTest, BadLengthAtEndIsIgnored) {
|
||||
@@ -470,8 +476,13 @@ TEST_P(LogTest, ChecksumMismatch) {
|
||||
Write("foooooo");
|
||||
IncrementByte(0, 14);
|
||||
ASSERT_EQ("EOF", Read());
|
||||
ASSERT_EQ(14U + 4 * !!GetParam(), DroppedBytes());
|
||||
ASSERT_EQ("OK", MatchError("checksum mismatch"));
|
||||
if (!GetParam()) {
|
||||
ASSERT_EQ(14U, DroppedBytes());
|
||||
ASSERT_EQ("OK", MatchError("checksum mismatch"));
|
||||
} else {
|
||||
ASSERT_EQ(0U, DroppedBytes());
|
||||
ASSERT_EQ("", ReportMessage());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(LogTest, UnexpectedMiddleType) {
|
||||
@@ -568,11 +579,15 @@ TEST_P(LogTest, ErrorJoinsRecords) {
|
||||
SetByte(offset, 'x');
|
||||
}
|
||||
|
||||
ASSERT_EQ("correct", Read());
|
||||
ASSERT_EQ("EOF", Read());
|
||||
size_t dropped = DroppedBytes();
|
||||
ASSERT_LE(dropped, 2 * kBlockSize + 100);
|
||||
ASSERT_GE(dropped, 2 * kBlockSize);
|
||||
if (!GetParam()) {
|
||||
ASSERT_EQ("correct", Read());
|
||||
ASSERT_EQ("EOF", Read());
|
||||
size_t dropped = DroppedBytes();
|
||||
ASSERT_LE(dropped, 2 * kBlockSize + 100);
|
||||
ASSERT_GE(dropped, 2 * kBlockSize);
|
||||
} else {
|
||||
ASSERT_EQ("EOF", Read());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(LogTest, ReadStart) { CheckInitialOffsetRecord(0, 0); }
|
||||
@@ -690,6 +705,29 @@ TEST_P(LogTest, ClearEofError2) {
|
||||
ASSERT_EQ("OK", MatchError("read error"));
|
||||
}
|
||||
|
||||
TEST_P(LogTest, Recycle) {
|
||||
if (!GetParam()) {
|
||||
return; // test is only valid for recycled logs
|
||||
}
|
||||
Write("foo");
|
||||
Write("bar");
|
||||
Write("baz");
|
||||
Write("bif");
|
||||
Write("blitz");
|
||||
while (get_reader_contents()->size() < log::kBlockSize * 2) {
|
||||
Write("xxxxxxxxxxxxxxxx");
|
||||
}
|
||||
unique_ptr<WritableFileWriter> dest_holder(test::GetWritableFileWriter(
|
||||
new test::OverwritingStringSink(get_reader_contents())));
|
||||
Writer recycle_writer(std::move(dest_holder), 123, true);
|
||||
recycle_writer.AddRecord(Slice("foooo"));
|
||||
recycle_writer.AddRecord(Slice("bar"));
|
||||
ASSERT_GE(get_reader_contents()->size(), log::kBlockSize * 2);
|
||||
ASSERT_EQ("foooo", Read());
|
||||
ASSERT_EQ("bar", Read());
|
||||
ASSERT_EQ("EOF", Read());
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(bool, LogTest, ::testing::Values(0, 2));
|
||||
|
||||
} // namespace log
|
||||
|
||||
@@ -75,6 +75,7 @@ MemTable::MemTable(const InternalKeyComparator& cmp,
|
||||
first_seqno_(0),
|
||||
earliest_seqno_(earliest_seq),
|
||||
mem_next_logfile_number_(0),
|
||||
min_prep_log_referenced_(0),
|
||||
locks_(moptions_.inplace_update_support
|
||||
? moptions_.inplace_update_num_locks
|
||||
: 0),
|
||||
@@ -800,4 +801,17 @@ void MemTableRep::Get(const LookupKey& k, void* callback_args,
|
||||
}
|
||||
}
|
||||
|
||||
void MemTable::RefLogContainingPrepSection(uint64_t log) {
|
||||
assert(log > 0);
|
||||
auto cur = min_prep_log_referenced_.load();
|
||||
while ((log < cur || cur == 0) &&
|
||||
!min_prep_log_referenced_.compare_exchange_strong(cur, log)) {
|
||||
cur = min_prep_log_referenced_.load();
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t MemTable::GetMinLogContainingPrepSection() {
|
||||
return min_prep_log_referenced_.load();
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
@@ -271,6 +271,13 @@ class MemTable {
|
||||
// operations on the same MemTable.
|
||||
void SetNextLogNumber(uint64_t num) { mem_next_logfile_number_ = num; }
|
||||
|
||||
// if this memtable contains data from a committed
|
||||
// two phase transaction we must take note of the
|
||||
// log which contains that data so we can know
|
||||
// when to relese that log
|
||||
void RefLogContainingPrepSection(uint64_t log);
|
||||
uint64_t GetMinLogContainingPrepSection();
|
||||
|
||||
// Notify the underlying storage that no more items will be added.
|
||||
// REQUIRES: external synchronization to prevent simultaneous
|
||||
// operations on the same MemTable.
|
||||
@@ -342,6 +349,10 @@ class MemTable {
|
||||
// The log files earlier than this number can be deleted.
|
||||
uint64_t mem_next_logfile_number_;
|
||||
|
||||
// the earliest log containing a prepared section
|
||||
// which has been inserted into this memtable.
|
||||
std::atomic<uint64_t> min_prep_log_referenced_;
|
||||
|
||||
// rw locks for inplace updates
|
||||
std::vector<port::RWMutex> locks_;
|
||||
|
||||
|
||||
@@ -392,4 +392,24 @@ void MemTableList::InstallNewVersion() {
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t MemTableList::GetMinLogContainingPrepSection() {
|
||||
uint64_t min_log = 0;
|
||||
|
||||
for (auto& m : current_->memlist_) {
|
||||
// this mem has been flushed it no longer
|
||||
// needs to hold on the its prep section
|
||||
if (m->flush_completed_) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto log = m->GetMinLogContainingPrepSection();
|
||||
|
||||
if (log > 0 && (min_log == 0 || log < min_log)) {
|
||||
min_log = log;
|
||||
}
|
||||
}
|
||||
|
||||
return min_log;
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
@@ -215,6 +215,8 @@ class MemTableList {
|
||||
|
||||
size_t* current_memory_usage() { return ¤t_memory_usage_; }
|
||||
|
||||
uint64_t GetMinLogContainingPrepSection();
|
||||
|
||||
private:
|
||||
// DB mutex held
|
||||
void InstallNewVersion();
|
||||
|
||||
+4
-2
@@ -291,9 +291,11 @@ class Repairer {
|
||||
ro.total_order_seek = true;
|
||||
Arena arena;
|
||||
ScopedArenaIterator iter(mem->NewIterator(ro, &arena));
|
||||
MutableCFOptions mutable_cf_options(options_, ioptions_);
|
||||
status = BuildTable(
|
||||
dbname_, env_, ioptions_, env_options_, table_cache_, iter.get(),
|
||||
&meta, icmp_, &int_tbl_prop_collector_factories_,
|
||||
dbname_, env_, ioptions_, mutable_cf_options, env_options_,
|
||||
table_cache_, iter.get(), &meta, icmp_,
|
||||
&int_tbl_prop_collector_factories_,
|
||||
TablePropertiesCollectorFactory::Context::kUnknownColumnFamily,
|
||||
std::string() /* column_family_name */, {}, kMaxSequenceNumber,
|
||||
kNoCompression, CompressionOptions(), false,
|
||||
|
||||
@@ -23,6 +23,8 @@ Status InternalKeyPropertiesCollector::InternalAdd(const Slice& key,
|
||||
if (ikey.type == ValueType::kTypeDeletion ||
|
||||
ikey.type == ValueType::kTypeSingleDeletion) {
|
||||
++deleted_keys_;
|
||||
} else if (ikey.type == ValueType::kTypeMerge) {
|
||||
++merge_operands_;
|
||||
}
|
||||
|
||||
return Status::OK();
|
||||
@@ -33,19 +35,26 @@ Status InternalKeyPropertiesCollector::Finish(
|
||||
assert(properties);
|
||||
assert(properties->find(
|
||||
InternalKeyTablePropertiesNames::kDeletedKeys) == properties->end());
|
||||
std::string val;
|
||||
assert(properties->find(InternalKeyTablePropertiesNames::kMergeOperands) ==
|
||||
properties->end());
|
||||
|
||||
PutVarint64(&val, deleted_keys_);
|
||||
properties->insert({ InternalKeyTablePropertiesNames::kDeletedKeys, val });
|
||||
std::string val_deleted_keys;
|
||||
PutVarint64(&val_deleted_keys, deleted_keys_);
|
||||
properties->insert(
|
||||
{InternalKeyTablePropertiesNames::kDeletedKeys, val_deleted_keys});
|
||||
|
||||
std::string val_merge_operands;
|
||||
PutVarint64(&val_merge_operands, merge_operands_);
|
||||
properties->insert(
|
||||
{InternalKeyTablePropertiesNames::kMergeOperands, val_merge_operands});
|
||||
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
UserCollectedProperties
|
||||
InternalKeyPropertiesCollector::GetReadableProperties() const {
|
||||
return {
|
||||
{ "kDeletedKeys", ToString(deleted_keys_) }
|
||||
};
|
||||
return {{"kDeletedKeys", ToString(deleted_keys_)},
|
||||
{"kMergeOperands", ToString(merge_operands_)}};
|
||||
}
|
||||
|
||||
namespace {
|
||||
@@ -65,6 +74,20 @@ EntryType GetEntryType(ValueType value_type) {
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t GetUint64Property(const UserCollectedProperties& props,
|
||||
const std::string property_name,
|
||||
bool* property_present) {
|
||||
auto pos = props.find(property_name);
|
||||
if (pos == props.end()) {
|
||||
*property_present = false;
|
||||
return 0;
|
||||
}
|
||||
Slice raw = pos->second;
|
||||
uint64_t val = 0;
|
||||
*property_present = true;
|
||||
return GetVarint64(&raw, &val) ? val : 0;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Status UserKeyTablePropertiesCollector::InternalAdd(const Slice& key,
|
||||
@@ -92,16 +115,20 @@ UserKeyTablePropertiesCollector::GetReadableProperties() const {
|
||||
|
||||
const std::string InternalKeyTablePropertiesNames::kDeletedKeys
|
||||
= "rocksdb.deleted.keys";
|
||||
const std::string InternalKeyTablePropertiesNames::kMergeOperands =
|
||||
"rocksdb.merge.operands";
|
||||
|
||||
uint64_t GetDeletedKeys(
|
||||
const UserCollectedProperties& props) {
|
||||
auto pos = props.find(InternalKeyTablePropertiesNames::kDeletedKeys);
|
||||
if (pos == props.end()) {
|
||||
return 0;
|
||||
}
|
||||
Slice raw = pos->second;
|
||||
uint64_t val = 0;
|
||||
return GetVarint64(&raw, &val) ? val : 0;
|
||||
bool property_present_ignored;
|
||||
return GetUint64Property(props, InternalKeyTablePropertiesNames::kDeletedKeys,
|
||||
&property_present_ignored);
|
||||
}
|
||||
|
||||
uint64_t GetMergeOperands(const UserCollectedProperties& props,
|
||||
bool* property_present) {
|
||||
return GetUint64Property(
|
||||
props, InternalKeyTablePropertiesNames::kMergeOperands, property_present);
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
@@ -16,6 +16,7 @@ namespace rocksdb {
|
||||
|
||||
struct InternalKeyTablePropertiesNames {
|
||||
static const std::string kDeletedKeys;
|
||||
static const std::string kMergeOperands;
|
||||
};
|
||||
|
||||
// Base class for internal table properties collector.
|
||||
@@ -65,6 +66,7 @@ class InternalKeyPropertiesCollector : public IntTblPropCollector {
|
||||
|
||||
private:
|
||||
uint64_t deleted_keys_ = 0;
|
||||
uint64_t merge_operands_ = 0;
|
||||
};
|
||||
|
||||
class InternalKeyPropertiesCollectorFactory
|
||||
|
||||
@@ -368,6 +368,8 @@ void TestInternalKeyPropertiesCollector(
|
||||
InternalKey("Y ", 5, ValueType::kTypeDeletion),
|
||||
InternalKey("Z ", 6, ValueType::kTypeDeletion),
|
||||
InternalKey("a ", 7, ValueType::kTypeSingleDeletion),
|
||||
InternalKey("b ", 8, ValueType::kTypeMerge),
|
||||
InternalKey("c ", 9, ValueType::kTypeMerge),
|
||||
};
|
||||
|
||||
std::unique_ptr<TableBuilder> builder;
|
||||
@@ -423,6 +425,11 @@ void TestInternalKeyPropertiesCollector(
|
||||
uint64_t deleted = GetDeletedKeys(user_collected);
|
||||
ASSERT_EQ(5u, deleted); // deletes + single-deletes
|
||||
|
||||
bool property_present;
|
||||
uint64_t merges = GetMergeOperands(user_collected, &property_present);
|
||||
ASSERT_TRUE(property_present);
|
||||
ASSERT_EQ(2u, merges);
|
||||
|
||||
if (sanitized) {
|
||||
uint32_t starts_with_A = 0;
|
||||
ASSERT_NE(user_collected.find("Count"), user_collected.end());
|
||||
|
||||
@@ -8,8 +8,8 @@
|
||||
#define __STDC_FORMAT_MACROS
|
||||
#endif
|
||||
|
||||
#include <inttypes.h>
|
||||
#include "db/transaction_log_impl.h"
|
||||
#include <inttypes.h>
|
||||
#include "db/write_batch_internal.h"
|
||||
#include "util/file_reader_writer.h"
|
||||
|
||||
@@ -250,7 +250,7 @@ void TransactionLogIteratorImpl::UpdateCurrentWriteBatch(const Slice& record) {
|
||||
// currentBatchSeq_ can only change here
|
||||
assert(currentLastSeq_ <= versions_->LastSequence());
|
||||
|
||||
currentBatch_ = move(batch);
|
||||
currentBatch_ = std::move(batch);
|
||||
isValid_ = true;
|
||||
currentStatus_ = Status::OK();
|
||||
}
|
||||
@@ -262,10 +262,9 @@ Status TransactionLogIteratorImpl::OpenLogReader(const LogFile* logFile) {
|
||||
return s;
|
||||
}
|
||||
assert(file);
|
||||
currentLogReader_.reset(new log::Reader(options_->info_log,
|
||||
std::move(file), &reporter_,
|
||||
read_options_.verify_checksums_, 0,
|
||||
logFile->LogNumber()));
|
||||
currentLogReader_.reset(new log::Reader(
|
||||
options_->info_log, std::move(file), &reporter_,
|
||||
read_options_.verify_checksums_, 0, logFile->LogNumber()));
|
||||
return Status::OK();
|
||||
}
|
||||
} // namespace rocksdb
|
||||
|
||||
+47
-21
@@ -1195,10 +1195,23 @@ void VersionStorageInfo::EstimateCompactionBytesNeeded(
|
||||
}
|
||||
|
||||
// Level 1 and up.
|
||||
uint64_t bytes_next_level = 0;
|
||||
for (int level = base_level(); level <= MaxInputLevel(); level++) {
|
||||
uint64_t level_size = 0;
|
||||
for (auto* f : files_[level]) {
|
||||
level_size += f->fd.GetFileSize();
|
||||
if (bytes_next_level > 0) {
|
||||
#ifndef NDEBUG
|
||||
uint64_t level_size2 = 0;
|
||||
for (auto* f : files_[level]) {
|
||||
level_size2 += f->fd.GetFileSize();
|
||||
}
|
||||
assert(level_size2 == bytes_next_level);
|
||||
#endif
|
||||
level_size = bytes_next_level;
|
||||
bytes_next_level = 0;
|
||||
} else {
|
||||
for (auto* f : files_[level]) {
|
||||
level_size += f->fd.GetFileSize();
|
||||
}
|
||||
}
|
||||
if (level == base_level() && level0_compact_triggered) {
|
||||
// Add base level size to compaction if level0 compaction triggered.
|
||||
@@ -1210,18 +1223,29 @@ void VersionStorageInfo::EstimateCompactionBytesNeeded(
|
||||
uint64_t level_target = MaxBytesForLevel(level);
|
||||
if (level_size > level_target) {
|
||||
bytes_compact_to_next_level = level_size - level_target;
|
||||
// Simplify to assume the actual compaction fan-out ratio is always
|
||||
// mutable_cf_options.max_bytes_for_level_multiplier.
|
||||
estimated_compaction_needed_bytes_ +=
|
||||
bytes_compact_to_next_level *
|
||||
(1 + mutable_cf_options.max_bytes_for_level_multiplier);
|
||||
// Estimate the actual compaction fan-out ratio as size ratio between
|
||||
// the two levels.
|
||||
|
||||
assert(bytes_next_level == 0);
|
||||
if (level + 1 < num_levels_) {
|
||||
for (auto* f : files_[level + 1]) {
|
||||
bytes_next_level += f->fd.GetFileSize();
|
||||
}
|
||||
}
|
||||
if (bytes_next_level > 0) {
|
||||
assert(level_size > 0);
|
||||
estimated_compaction_needed_bytes_ += static_cast<uint64_t>(
|
||||
static_cast<double>(bytes_compact_to_next_level) *
|
||||
(static_cast<double>(bytes_next_level) /
|
||||
static_cast<double>(level_size) +
|
||||
1));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void VersionStorageInfo::ComputeCompactionScore(
|
||||
const MutableCFOptions& mutable_cf_options,
|
||||
const CompactionOptionsFIFO& compaction_options_fifo) {
|
||||
const MutableCFOptions& mutable_cf_options) {
|
||||
for (int level = 0; level <= MaxInputLevel(); level++) {
|
||||
double score;
|
||||
if (level == 0) {
|
||||
@@ -1257,7 +1281,7 @@ void VersionStorageInfo::ComputeCompactionScore(
|
||||
|
||||
if (compaction_style_ == kCompactionStyleFIFO) {
|
||||
score = static_cast<double>(total_size) /
|
||||
compaction_options_fifo.max_table_files_size;
|
||||
mutable_cf_options.compaction_options_fifo.max_table_files_size;
|
||||
} else {
|
||||
score = static_cast<double>(num_sorted_runs) /
|
||||
mutable_cf_options.level0_file_num_compaction_trigger;
|
||||
@@ -1527,11 +1551,11 @@ void VersionStorageInfo::GenerateLevel0NonOverlapping() {
|
||||
std::vector<FdWithKeyRange> level0_sorted_file(
|
||||
level_files_brief_[0].files,
|
||||
level_files_brief_[0].files + level_files_brief_[0].num_files);
|
||||
sort(level0_sorted_file.begin(), level0_sorted_file.end(),
|
||||
[this](const FdWithKeyRange & f1, const FdWithKeyRange & f2)->bool {
|
||||
return (internal_comparator_->Compare(f1.smallest_key, f2.smallest_key) <
|
||||
0);
|
||||
});
|
||||
std::sort(level0_sorted_file.begin(), level0_sorted_file.end(),
|
||||
[this](const FdWithKeyRange& f1, const FdWithKeyRange& f2) -> bool {
|
||||
return (internal_comparator_->Compare(f1.smallest_key,
|
||||
f2.smallest_key) < 0);
|
||||
});
|
||||
|
||||
for (size_t i = 1; i < level0_sorted_file.size(); ++i) {
|
||||
FdWithKeyRange& f = level0_sorted_file[i];
|
||||
@@ -2113,8 +2137,7 @@ void VersionSet::AppendVersion(ColumnFamilyData* column_family_data,
|
||||
Version* v) {
|
||||
// compute new compaction score
|
||||
v->storage_info()->ComputeCompactionScore(
|
||||
*column_family_data->GetLatestMutableCFOptions(),
|
||||
column_family_data->ioptions()->compaction_options_fifo);
|
||||
*column_family_data->GetLatestMutableCFOptions());
|
||||
|
||||
// Mark v finalized
|
||||
v->storage_info_.SetFinalized();
|
||||
@@ -2297,10 +2320,6 @@ Status VersionSet::LogAndApply(ColumnFamilyData* column_family_data,
|
||||
if (s.ok() && new_descriptor_log) {
|
||||
s = SetCurrentFile(env_, dbname_, pending_manifest_file_number_,
|
||||
db_options_->disableDataSync ? nullptr : db_directory);
|
||||
// Leave the old file behind since PurgeObsoleteFiles will take care of it
|
||||
// later. It's unsafe to delete now since file deletion may be disabled.
|
||||
obsolete_manifests_.emplace_back(
|
||||
DescriptorFileName("", manifest_file_number_));
|
||||
}
|
||||
|
||||
if (s.ok()) {
|
||||
@@ -2319,6 +2338,13 @@ Status VersionSet::LogAndApply(ColumnFamilyData* column_family_data,
|
||||
mu->Lock();
|
||||
}
|
||||
|
||||
// Append the old mainfest file to the obsolete_manifests_ list to be deleted
|
||||
// by PurgeObsoleteFiles later.
|
||||
if (s.ok() && new_descriptor_log) {
|
||||
obsolete_manifests_.emplace_back(
|
||||
DescriptorFileName("", manifest_file_number_));
|
||||
}
|
||||
|
||||
// Install the new version
|
||||
if (s.ok()) {
|
||||
if (edit->is_column_family_add_) {
|
||||
|
||||
+1
-3
@@ -121,9 +121,7 @@ class VersionStorageInfo {
|
||||
// We use compaction scores to figure out which compaction to do next
|
||||
// REQUIRES: db_mutex held!!
|
||||
// TODO find a better way to pass compaction_options_fifo.
|
||||
void ComputeCompactionScore(
|
||||
const MutableCFOptions& mutable_cf_options,
|
||||
const CompactionOptionsFIFO& compaction_options_fifo);
|
||||
void ComputeCompactionScore(const MutableCFOptions& mutable_cf_options);
|
||||
|
||||
// Estimate est_comp_needed_bytes_
|
||||
void EstimateCompactionBytesNeeded(
|
||||
|
||||
+318
-31
@@ -20,6 +20,11 @@
|
||||
// kTypeColumnFamilyDeletion varint32 varstring varstring
|
||||
// kTypeColumnFamilySingleDeletion varint32 varstring varstring
|
||||
// kTypeColumnFamilyMerge varint32 varstring varstring
|
||||
// kTypeBeginPrepareXID varstring
|
||||
// kTypeEndPrepareXID
|
||||
// kTypeCommitXID varstring
|
||||
// kTypeRollbackXID varstring
|
||||
// kTypeNoop
|
||||
// varstring :=
|
||||
// len: varint32
|
||||
// data: uint8[len]
|
||||
@@ -48,11 +53,15 @@ namespace rocksdb {
|
||||
namespace {
|
||||
|
||||
enum ContentFlags : uint32_t {
|
||||
DEFERRED = 1,
|
||||
HAS_PUT = 2,
|
||||
HAS_DELETE = 4,
|
||||
HAS_SINGLE_DELETE = 8,
|
||||
HAS_MERGE = 16,
|
||||
DEFERRED = 1 << 0,
|
||||
HAS_PUT = 1 << 1,
|
||||
HAS_DELETE = 1 << 2,
|
||||
HAS_SINGLE_DELETE = 1 << 3,
|
||||
HAS_MERGE = 1 << 4,
|
||||
HAS_BEGIN_PREPARE = 1 << 5,
|
||||
HAS_END_PREPARE = 1 << 6,
|
||||
HAS_COMMIT = 1 << 7,
|
||||
HAS_ROLLBACK = 1 << 8,
|
||||
};
|
||||
|
||||
struct BatchContentClassifier : public WriteBatch::Handler {
|
||||
@@ -77,6 +86,26 @@ struct BatchContentClassifier : public WriteBatch::Handler {
|
||||
content_flags |= ContentFlags::HAS_MERGE;
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status MarkBeginPrepare() override {
|
||||
content_flags |= ContentFlags::HAS_BEGIN_PREPARE;
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status MarkEndPrepare(const Slice&) override {
|
||||
content_flags |= ContentFlags::HAS_END_PREPARE;
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status MarkCommit(const Slice&) override {
|
||||
content_flags |= ContentFlags::HAS_COMMIT;
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status MarkRollback(const Slice&) override {
|
||||
content_flags |= ContentFlags::HAS_ROLLBACK;
|
||||
return Status::OK();
|
||||
}
|
||||
};
|
||||
|
||||
} // anon namespace
|
||||
@@ -209,9 +238,25 @@ bool ReadKeyFromWriteBatchEntry(Slice* input, Slice* key, bool cf_record) {
|
||||
return GetLengthPrefixedSlice(input, key);
|
||||
}
|
||||
|
||||
bool WriteBatch::HasBeginPrepare() const {
|
||||
return (ComputeContentFlags() & ContentFlags::HAS_BEGIN_PREPARE) != 0;
|
||||
}
|
||||
|
||||
bool WriteBatch::HasEndPrepare() const {
|
||||
return (ComputeContentFlags() & ContentFlags::HAS_END_PREPARE) != 0;
|
||||
}
|
||||
|
||||
bool WriteBatch::HasCommit() const {
|
||||
return (ComputeContentFlags() & ContentFlags::HAS_COMMIT) != 0;
|
||||
}
|
||||
|
||||
bool WriteBatch::HasRollback() const {
|
||||
return (ComputeContentFlags() & ContentFlags::HAS_ROLLBACK) != 0;
|
||||
}
|
||||
|
||||
Status ReadRecordFromWriteBatch(Slice* input, char* tag,
|
||||
uint32_t* column_family, Slice* key,
|
||||
Slice* value, Slice* blob) {
|
||||
Slice* value, Slice* blob, Slice* xid) {
|
||||
assert(key != nullptr && value != nullptr);
|
||||
*tag = (*input)[0];
|
||||
input->remove_prefix(1);
|
||||
@@ -257,6 +302,24 @@ Status ReadRecordFromWriteBatch(Slice* input, char* tag,
|
||||
return Status::Corruption("bad WriteBatch Blob");
|
||||
}
|
||||
break;
|
||||
case kTypeNoop:
|
||||
case kTypeBeginPrepareXID:
|
||||
break;
|
||||
case kTypeEndPrepareXID:
|
||||
if (!GetLengthPrefixedSlice(input, xid)) {
|
||||
return Status::Corruption("bad EndPrepare XID");
|
||||
}
|
||||
break;
|
||||
case kTypeCommitXID:
|
||||
if (!GetLengthPrefixedSlice(input, xid)) {
|
||||
return Status::Corruption("bad Commit XID");
|
||||
}
|
||||
break;
|
||||
case kTypeRollbackXID:
|
||||
if (!GetLengthPrefixedSlice(input, xid)) {
|
||||
return Status::Corruption("bad Rollback XID");
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return Status::Corruption("unknown WriteBatch tag");
|
||||
}
|
||||
@@ -270,7 +333,7 @@ Status WriteBatch::Iterate(Handler* handler) const {
|
||||
}
|
||||
|
||||
input.remove_prefix(WriteBatchInternal::kHeader);
|
||||
Slice key, value, blob;
|
||||
Slice key, value, blob, xid;
|
||||
int found = 0;
|
||||
Status s;
|
||||
while (s.ok() && !input.empty() && handler->Continue()) {
|
||||
@@ -278,7 +341,7 @@ Status WriteBatch::Iterate(Handler* handler) const {
|
||||
uint32_t column_family = 0; // default
|
||||
|
||||
s = ReadRecordFromWriteBatch(&input, &tag, &column_family, &key, &value,
|
||||
&blob);
|
||||
&blob, &xid);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
@@ -315,6 +378,28 @@ Status WriteBatch::Iterate(Handler* handler) const {
|
||||
case kTypeLogData:
|
||||
handler->LogData(blob);
|
||||
break;
|
||||
case kTypeBeginPrepareXID:
|
||||
assert(content_flags_.load(std::memory_order_relaxed) &
|
||||
(ContentFlags::DEFERRED | ContentFlags::HAS_BEGIN_PREPARE));
|
||||
handler->MarkBeginPrepare();
|
||||
break;
|
||||
case kTypeEndPrepareXID:
|
||||
assert(content_flags_.load(std::memory_order_relaxed) &
|
||||
(ContentFlags::DEFERRED | ContentFlags::HAS_END_PREPARE));
|
||||
handler->MarkEndPrepare(xid);
|
||||
break;
|
||||
case kTypeCommitXID:
|
||||
assert(content_flags_.load(std::memory_order_relaxed) &
|
||||
(ContentFlags::DEFERRED | ContentFlags::HAS_COMMIT));
|
||||
handler->MarkCommit(xid);
|
||||
break;
|
||||
case kTypeRollbackXID:
|
||||
assert(content_flags_.load(std::memory_order_relaxed) &
|
||||
(ContentFlags::DEFERRED | ContentFlags::HAS_ROLLBACK));
|
||||
handler->MarkRollback(xid);
|
||||
break;
|
||||
case kTypeNoop:
|
||||
break;
|
||||
default:
|
||||
return Status::Corruption("unknown WriteBatch tag");
|
||||
}
|
||||
@@ -391,6 +476,47 @@ void WriteBatch::Put(ColumnFamilyHandle* column_family, const SliceParts& key,
|
||||
WriteBatchInternal::Put(this, GetColumnFamilyID(column_family), key, value);
|
||||
}
|
||||
|
||||
void WriteBatchInternal::InsertNoop(WriteBatch* b) {
|
||||
b->rep_.push_back(static_cast<char>(kTypeNoop));
|
||||
}
|
||||
|
||||
void WriteBatchInternal::MarkEndPrepare(WriteBatch* b, const Slice& xid) {
|
||||
// a manually constructed batch can only contain one prepare section
|
||||
assert(b->rep_[12] == static_cast<char>(kTypeNoop));
|
||||
|
||||
// all savepoints up to this point are cleared
|
||||
if (b->save_points_ != nullptr) {
|
||||
while (!b->save_points_->stack.empty()) {
|
||||
b->save_points_->stack.pop();
|
||||
}
|
||||
}
|
||||
|
||||
// rewrite noop as begin marker
|
||||
b->rep_[12] = static_cast<char>(kTypeBeginPrepareXID);
|
||||
b->rep_.push_back(static_cast<char>(kTypeEndPrepareXID));
|
||||
PutLengthPrefixedSlice(&b->rep_, xid);
|
||||
b->content_flags_.store(b->content_flags_.load(std::memory_order_relaxed) |
|
||||
ContentFlags::HAS_END_PREPARE |
|
||||
ContentFlags::HAS_BEGIN_PREPARE,
|
||||
std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
void WriteBatchInternal::MarkCommit(WriteBatch* b, const Slice& xid) {
|
||||
b->rep_.push_back(static_cast<char>(kTypeCommitXID));
|
||||
PutLengthPrefixedSlice(&b->rep_, xid);
|
||||
b->content_flags_.store(b->content_flags_.load(std::memory_order_relaxed) |
|
||||
ContentFlags::HAS_COMMIT,
|
||||
std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
void WriteBatchInternal::MarkRollback(WriteBatch* b, const Slice& xid) {
|
||||
b->rep_.push_back(static_cast<char>(kTypeRollbackXID));
|
||||
PutLengthPrefixedSlice(&b->rep_, xid);
|
||||
b->content_flags_.store(b->content_flags_.load(std::memory_order_relaxed) |
|
||||
ContentFlags::HAS_ROLLBACK,
|
||||
std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
void WriteBatchInternal::Delete(WriteBatch* b, uint32_t column_family_id,
|
||||
const Slice& key) {
|
||||
WriteBatchInternal::SetCount(b, WriteBatchInternal::Count(b) + 1);
|
||||
@@ -555,38 +681,48 @@ Status WriteBatch::RollbackToSavePoint() {
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
namespace {
|
||||
class MemTableInserter : public WriteBatch::Handler {
|
||||
public:
|
||||
SequenceNumber sequence_;
|
||||
ColumnFamilyMemTables* const cf_mems_;
|
||||
FlushScheduler* const flush_scheduler_;
|
||||
const bool ignore_missing_column_families_;
|
||||
const uint64_t log_number_;
|
||||
const uint64_t recovering_log_number_;
|
||||
// log number that all Memtables inserted into should reference
|
||||
uint64_t log_number_ref_;
|
||||
DBImpl* db_;
|
||||
const bool dont_filter_deletes_;
|
||||
const bool concurrent_memtable_writes_;
|
||||
// current recovered transaction we are rebuilding (recovery)
|
||||
WriteBatch* rebuilding_trx_;
|
||||
|
||||
// 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,
|
||||
bool ignore_missing_column_families,
|
||||
uint64_t recovering_log_number, 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),
|
||||
recovering_log_number_(recovering_log_number),
|
||||
log_number_ref_(0),
|
||||
db_(reinterpret_cast<DBImpl*>(db)),
|
||||
dont_filter_deletes_(dont_filter_deletes),
|
||||
concurrent_memtable_writes_(concurrent_memtable_writes) {
|
||||
concurrent_memtable_writes_(concurrent_memtable_writes),
|
||||
rebuilding_trx_(nullptr) {
|
||||
assert(cf_mems_);
|
||||
if (!dont_filter_deletes_) {
|
||||
assert(db_);
|
||||
}
|
||||
}
|
||||
|
||||
void set_log_number_ref(uint64_t log) { log_number_ref_ = log; }
|
||||
|
||||
SequenceNumber get_final_sequence() { return sequence_; }
|
||||
|
||||
bool SeekToColumnFamily(uint32_t column_family_id, Status* s) {
|
||||
// If we are in a concurrent mode, it is the caller's responsibility
|
||||
// to clone the original ColumnFamilyMemTables so that each thread
|
||||
@@ -602,26 +738,40 @@ class MemTableInserter : public WriteBatch::Handler {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (log_number_ != 0 && log_number_ < cf_mems_->GetLogNumber()) {
|
||||
// This is true only in recovery environment (log_number_ is always 0 in
|
||||
if (recovering_log_number_ != 0 &&
|
||||
recovering_log_number_ < cf_mems_->GetLogNumber()) {
|
||||
// This is true only in recovery environment (recovering_log_number_ is
|
||||
// always 0 in
|
||||
// non-recovery, regular write code-path)
|
||||
// * If log_number_ < cf_mems_->GetLogNumber(), this means that column
|
||||
// * If recovering_log_number_ < cf_mems_->GetLogNumber(), this means that
|
||||
// column
|
||||
// family already contains updates from this log. We can't apply updates
|
||||
// twice because of update-in-place or merge workloads -- ignore the
|
||||
// update
|
||||
*s = Status::OK();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (log_number_ref_ > 0) {
|
||||
cf_mems_->GetMemTable()->RefLogContainingPrepSection(log_number_ref_);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual Status PutCF(uint32_t column_family_id, const Slice& key,
|
||||
const Slice& value) override {
|
||||
if (rebuilding_trx_ != nullptr) {
|
||||
WriteBatchInternal::Put(rebuilding_trx_, column_family_id, key, value);
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status seek_status;
|
||||
if (!SeekToColumnFamily(column_family_id, &seek_status)) {
|
||||
++sequence_;
|
||||
return seek_status;
|
||||
}
|
||||
|
||||
MemTable* mem = cf_mems_->GetMemTable();
|
||||
auto* moptions = mem->GetMemTableOptions();
|
||||
if (!moptions->inplace_update_support) {
|
||||
@@ -675,11 +825,6 @@ class MemTableInserter : public WriteBatch::Handler {
|
||||
|
||||
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) {
|
||||
@@ -706,22 +851,50 @@ class MemTableInserter : public WriteBatch::Handler {
|
||||
|
||||
virtual Status DeleteCF(uint32_t column_family_id,
|
||||
const Slice& key) override {
|
||||
if (rebuilding_trx_ != nullptr) {
|
||||
WriteBatchInternal::Delete(rebuilding_trx_, column_family_id, key);
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status seek_status;
|
||||
if (!SeekToColumnFamily(column_family_id, &seek_status)) {
|
||||
++sequence_;
|
||||
return seek_status;
|
||||
}
|
||||
|
||||
return DeleteImpl(column_family_id, key, kTypeDeletion);
|
||||
}
|
||||
|
||||
virtual Status SingleDeleteCF(uint32_t column_family_id,
|
||||
const Slice& key) override {
|
||||
if (rebuilding_trx_ != nullptr) {
|
||||
WriteBatchInternal::SingleDelete(rebuilding_trx_, column_family_id, key);
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status seek_status;
|
||||
if (!SeekToColumnFamily(column_family_id, &seek_status)) {
|
||||
++sequence_;
|
||||
return seek_status;
|
||||
}
|
||||
|
||||
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_);
|
||||
if (rebuilding_trx_ != nullptr) {
|
||||
WriteBatchInternal::Merge(rebuilding_trx_, column_family_id, key, value);
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status seek_status;
|
||||
if (!SeekToColumnFamily(column_family_id, &seek_status)) {
|
||||
++sequence_;
|
||||
return seek_status;
|
||||
}
|
||||
|
||||
MemTable* mem = cf_mems_->GetMemTable();
|
||||
auto* moptions = mem->GetMemTableOptions();
|
||||
bool perform_merge = false;
|
||||
@@ -807,8 +980,102 @@ class MemTableInserter : public WriteBatch::Handler {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Status MarkBeginPrepare() override {
|
||||
assert(rebuilding_trx_ == nullptr);
|
||||
assert(db_);
|
||||
|
||||
if (recovering_log_number_ != 0) {
|
||||
// during recovery we rebuild a hollow transaction
|
||||
// from all encountered prepare sections of the wal
|
||||
if (db_->allow_2pc() == false) {
|
||||
return Status::NotSupported(
|
||||
"WAL contains prepared transactions. Open with "
|
||||
"TransactionDB::Open().");
|
||||
}
|
||||
|
||||
// we are now iterating through a prepared section
|
||||
rebuilding_trx_ = new WriteBatch();
|
||||
} else {
|
||||
// in non-recovery we ignore prepare markers
|
||||
// and insert the values directly. making sure we have a
|
||||
// log for each insertion to reference.
|
||||
assert(log_number_ref_ > 0);
|
||||
}
|
||||
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status MarkEndPrepare(const Slice& name) override {
|
||||
assert(db_);
|
||||
assert((rebuilding_trx_ != nullptr) == (recovering_log_number_ != 0));
|
||||
|
||||
if (recovering_log_number_ != 0) {
|
||||
assert(db_->allow_2pc());
|
||||
db_->InsertRecoveredTransaction(recovering_log_number_, name.ToString(),
|
||||
rebuilding_trx_);
|
||||
rebuilding_trx_ = nullptr;
|
||||
} else {
|
||||
assert(rebuilding_trx_ == nullptr);
|
||||
assert(log_number_ref_ > 0);
|
||||
}
|
||||
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status MarkCommit(const Slice& name) override {
|
||||
assert(db_);
|
||||
|
||||
Status s;
|
||||
|
||||
if (recovering_log_number_ != 0) {
|
||||
// in recovery when we encounter a commit marker
|
||||
// we lookup this transaction in our set of rebuilt transactions
|
||||
// and commit.
|
||||
auto trx = db_->GetRecoveredTransaction(name.ToString());
|
||||
|
||||
// the log contaiting the prepared section may have
|
||||
// been released in the last incarnation because the
|
||||
// data was flushed to L0
|
||||
if (trx != nullptr) {
|
||||
// at this point individual CF lognumbers will prevent
|
||||
// duplicate re-insertion of values.
|
||||
assert(log_number_ref_ == 0);
|
||||
// all insertes must refernce this trx log number
|
||||
log_number_ref_ = trx->log_number_;
|
||||
s = trx->batch_->Iterate(this);
|
||||
log_number_ref_ = 0;
|
||||
|
||||
if (s.ok()) {
|
||||
db_->DeleteRecoveredTransaction(name.ToString());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// in non recovery we simply ignore this tag
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
Status MarkRollback(const Slice& name) override {
|
||||
assert(db_);
|
||||
|
||||
if (recovering_log_number_ != 0) {
|
||||
auto trx = db_->GetRecoveredTransaction(name.ToString());
|
||||
|
||||
// the log containing the transactions prep section
|
||||
// may have been released in the previous incarnation
|
||||
// because we knew it had been rolled back
|
||||
if (trx != nullptr) {
|
||||
db_->DeleteRecoveredTransaction(name.ToString());
|
||||
}
|
||||
} else {
|
||||
// in non recovery we simply ignore this tag
|
||||
}
|
||||
|
||||
return Status::OK();
|
||||
}
|
||||
};
|
||||
} // namespace
|
||||
|
||||
// This function can only be called in these conditions:
|
||||
// 1) During Recovery()
|
||||
@@ -823,30 +1090,50 @@ Status WriteBatchInternal::InsertInto(
|
||||
MemTableInserter inserter(sequence, memtables, flush_scheduler,
|
||||
ignore_missing_column_families, log_number, db,
|
||||
dont_filter_deletes, concurrent_memtable_writes);
|
||||
|
||||
for (size_t i = 0; i < writers.size(); i++) {
|
||||
if (!writers[i]->CallbackFailed()) {
|
||||
writers[i]->status = writers[i]->batch->Iterate(&inserter);
|
||||
if (!writers[i]->status.ok()) {
|
||||
return writers[i]->status;
|
||||
}
|
||||
auto w = writers[i];
|
||||
if (!w->ShouldWriteToMemtable()) {
|
||||
continue;
|
||||
}
|
||||
inserter.set_log_number_ref(w->log_ref);
|
||||
w->status = w->batch->Iterate(&inserter);
|
||||
if (!w->status.ok()) {
|
||||
return w->status;
|
||||
}
|
||||
}
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status WriteBatchInternal::InsertInto(const WriteBatch* batch,
|
||||
Status WriteBatchInternal::InsertInto(WriteThread::Writer* writer,
|
||||
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(WriteBatchInternal::Sequence(writer->batch),
|
||||
memtables, flush_scheduler,
|
||||
ignore_missing_column_families, log_number, db,
|
||||
dont_filter_deletes, concurrent_memtable_writes);
|
||||
assert(writer->ShouldWriteToMemtable());
|
||||
inserter.set_log_number_ref(writer->log_ref);
|
||||
return writer->batch->Iterate(&inserter);
|
||||
}
|
||||
|
||||
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,
|
||||
bool concurrent_memtable_writes, SequenceNumber* last_seq_used) {
|
||||
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);
|
||||
Status s = batch->Iterate(&inserter);
|
||||
if (last_seq_used != nullptr) {
|
||||
*last_seq_used = inserter.get_final_sequence();
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
void WriteBatchInternal::SetContents(WriteBatch* b, const Slice& contents) {
|
||||
|
||||
@@ -92,6 +92,14 @@ class WriteBatchInternal {
|
||||
static void Merge(WriteBatch* batch, uint32_t column_family_id,
|
||||
const SliceParts& key, const SliceParts& value);
|
||||
|
||||
static void MarkEndPrepare(WriteBatch* batch, const Slice& xid);
|
||||
|
||||
static void MarkRollback(WriteBatch* batch, const Slice& xid);
|
||||
|
||||
static void MarkCommit(WriteBatch* batch, const Slice& xid);
|
||||
|
||||
static void InsertNoop(WriteBatch* batch);
|
||||
|
||||
// Return the number of entries in the batch.
|
||||
static int Count(const WriteBatch* batch);
|
||||
|
||||
@@ -149,7 +157,17 @@ class WriteBatchInternal {
|
||||
bool concurrent_memtable_writes = false);
|
||||
|
||||
// Convenience form of InsertInto when you have only one batch
|
||||
// last_seq_used returns the last sequnce number used in a MemTable insert
|
||||
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,
|
||||
SequenceNumber* last_seq_used = nullptr);
|
||||
|
||||
static Status InsertInto(WriteThread::Writer* writer,
|
||||
ColumnFamilyMemTables* memtables,
|
||||
FlushScheduler* flush_scheduler,
|
||||
bool ignore_missing_column_families = false,
|
||||
|
||||
@@ -231,6 +231,22 @@ namespace {
|
||||
virtual void LogData(const Slice& blob) override {
|
||||
seen += "LogData(" + blob.ToString() + ")";
|
||||
}
|
||||
virtual Status MarkBeginPrepare() override {
|
||||
seen += "MarkBeginPrepare()";
|
||||
return Status::OK();
|
||||
}
|
||||
virtual Status MarkEndPrepare(const Slice& xid) override {
|
||||
seen += "MarkEndPrepare(" + xid.ToString() + ")";
|
||||
return Status::OK();
|
||||
}
|
||||
virtual Status MarkCommit(const Slice& xid) override {
|
||||
seen += "MarkCommit(" + xid.ToString() + ")";
|
||||
return Status::OK();
|
||||
}
|
||||
virtual Status MarkRollback(const Slice& xid) override {
|
||||
seen += "MarkRollback(" + xid.ToString() + ")";
|
||||
return Status::OK();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -308,6 +324,31 @@ TEST_F(WriteBatchTest, Blob) {
|
||||
handler.seen);
|
||||
}
|
||||
|
||||
TEST_F(WriteBatchTest, PrepareCommit) {
|
||||
WriteBatch batch;
|
||||
WriteBatchInternal::InsertNoop(&batch);
|
||||
batch.Put(Slice("k1"), Slice("v1"));
|
||||
batch.Put(Slice("k2"), Slice("v2"));
|
||||
batch.SetSavePoint();
|
||||
WriteBatchInternal::MarkEndPrepare(&batch, Slice("xid1"));
|
||||
Status s = batch.RollbackToSavePoint();
|
||||
ASSERT_EQ(s, Status::NotFound());
|
||||
WriteBatchInternal::MarkCommit(&batch, Slice("xid1"));
|
||||
WriteBatchInternal::MarkRollback(&batch, Slice("xid1"));
|
||||
ASSERT_EQ(2, batch.Count());
|
||||
|
||||
TestHandler handler;
|
||||
batch.Iterate(&handler);
|
||||
ASSERT_EQ(
|
||||
"MarkBeginPrepare()"
|
||||
"Put(k1, v1)"
|
||||
"Put(k2, v2)"
|
||||
"MarkEndPrepare(xid1)"
|
||||
"MarkCommit(xid1)"
|
||||
"MarkRollback(xid1)",
|
||||
handler.seen);
|
||||
}
|
||||
|
||||
// It requires more than 30GB of memory to run the test. With single memory
|
||||
// allocation of more than 30GB.
|
||||
// Not all platform can run it. Also it runs a long time. So disable it.
|
||||
|
||||
+15
-2
@@ -11,11 +11,12 @@
|
||||
#include <chrono>
|
||||
#include <condition_variable>
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
|
||||
#include "db/write_callback.h"
|
||||
#include "rocksdb/types.h"
|
||||
#include "rocksdb/status.h"
|
||||
#include "rocksdb/types.h"
|
||||
#include "rocksdb/write_batch.h"
|
||||
#include "util/autovector.h"
|
||||
#include "util/instrumented_mutex.h"
|
||||
@@ -79,6 +80,9 @@ class WriteThread {
|
||||
WriteBatch* batch;
|
||||
bool sync;
|
||||
bool disableWAL;
|
||||
bool disable_memtable;
|
||||
uint64_t log_used; // log number that this batch was inserted into
|
||||
uint64_t log_ref; // log number that memtable insert should reference
|
||||
bool in_batch_group;
|
||||
WriteCallback* callback;
|
||||
bool made_waitable; // records lazy construction of mutex and cv
|
||||
@@ -96,6 +100,9 @@ class WriteThread {
|
||||
: batch(nullptr),
|
||||
sync(false),
|
||||
disableWAL(false),
|
||||
disable_memtable(false),
|
||||
log_used(0),
|
||||
log_ref(0),
|
||||
in_batch_group(false),
|
||||
callback(nullptr),
|
||||
made_waitable(false),
|
||||
@@ -153,6 +160,12 @@ class WriteThread {
|
||||
return (callback != nullptr) && !callback_status.ok();
|
||||
}
|
||||
|
||||
bool ShouldWriteToMemtable() {
|
||||
return !CallbackFailed() && !disable_memtable;
|
||||
}
|
||||
|
||||
bool ShouldWriteToWAL() { return !CallbackFailed() && !disableWAL; }
|
||||
|
||||
// No other mutexes may be acquired while holding StateMutex(), it is
|
||||
// always last in the order
|
||||
std::mutex& StateMutex() {
|
||||
|
||||
@@ -691,6 +691,8 @@ extern ROCKSDB_LIBRARY_API void rocksdb_options_set_inplace_update_support(
|
||||
rocksdb_options_t*, unsigned char);
|
||||
extern ROCKSDB_LIBRARY_API void rocksdb_options_set_inplace_update_num_locks(
|
||||
rocksdb_options_t*, size_t);
|
||||
extern ROCKSDB_LIBRARY_API void rocksdb_options_set_report_bg_io_stats(
|
||||
rocksdb_options_t*, int);
|
||||
|
||||
enum {
|
||||
rocksdb_no_compression = 0,
|
||||
@@ -817,6 +819,8 @@ extern ROCKSDB_LIBRARY_API void rocksdb_readoptions_set_read_tier(
|
||||
rocksdb_readoptions_t*, int);
|
||||
extern ROCKSDB_LIBRARY_API void rocksdb_readoptions_set_tailing(
|
||||
rocksdb_readoptions_t*, unsigned char);
|
||||
extern ROCKSDB_LIBRARY_API void rocksdb_readoptions_set_readahead_size(
|
||||
rocksdb_readoptions_t*, size_t);
|
||||
|
||||
/* Write options */
|
||||
|
||||
@@ -843,6 +847,8 @@ extern ROCKSDB_LIBRARY_API void rocksdb_flushoptions_set_wait(
|
||||
extern ROCKSDB_LIBRARY_API rocksdb_cache_t* rocksdb_cache_create_lru(
|
||||
size_t capacity);
|
||||
extern ROCKSDB_LIBRARY_API void rocksdb_cache_destroy(rocksdb_cache_t* cache);
|
||||
extern ROCKSDB_LIBRARY_API void rocksdb_cache_set_capacity(
|
||||
rocksdb_cache_t* cache, size_t capacity);
|
||||
|
||||
/* Env */
|
||||
|
||||
@@ -932,6 +938,15 @@ extern ROCKSDB_LIBRARY_API void rocksdb_get_options_from_string(
|
||||
const rocksdb_options_t* base_options, const char* opts_str,
|
||||
rocksdb_options_t* new_options, char** errptr);
|
||||
|
||||
extern ROCKSDB_LIBRARY_API void rocksdb_delete_file_in_range(
|
||||
rocksdb_t* db, const char* start_key, size_t start_key_len,
|
||||
const char* limit_key, size_t limit_key_len, char** errptr);
|
||||
|
||||
extern ROCKSDB_LIBRARY_API void rocksdb_delete_file_in_range_cf(
|
||||
rocksdb_t* db, rocksdb_column_family_handle_t* column_family,
|
||||
const char* start_key, size_t start_key_len, const char* limit_key,
|
||||
size_t limit_key_len, char** errptr);
|
||||
|
||||
// referring to convention (3), this should be used by client
|
||||
// to free memory that was malloc()ed
|
||||
extern ROCKSDB_LIBRARY_API void rocksdb_free(void* ptr);
|
||||
|
||||
+12
-15
@@ -22,15 +22,13 @@
|
||||
#ifndef STORAGE_ROCKSDB_INCLUDE_CACHE_H_
|
||||
#define STORAGE_ROCKSDB_INCLUDE_CACHE_H_
|
||||
|
||||
#include <memory>
|
||||
#include <stdint.h>
|
||||
#include <memory>
|
||||
#include "rocksdb/slice.h"
|
||||
#include "rocksdb/status.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
using std::shared_ptr;
|
||||
|
||||
class Cache;
|
||||
|
||||
// Create a new cache with a fixed size capacity. The cache is sharded
|
||||
@@ -39,23 +37,23 @@ class Cache;
|
||||
//
|
||||
// The parameter num_shard_bits defaults to 4, and strict_capacity_limit
|
||||
// defaults to false.
|
||||
extern shared_ptr<Cache> NewLRUCache(size_t capacity);
|
||||
extern shared_ptr<Cache> NewLRUCache(size_t capacity, int num_shard_bits);
|
||||
extern shared_ptr<Cache> NewLRUCache(size_t capacity, int num_shard_bits,
|
||||
bool strict_capacity_limit);
|
||||
extern std::shared_ptr<Cache> NewLRUCache(size_t capacity);
|
||||
extern std::shared_ptr<Cache> NewLRUCache(size_t capacity, int num_shard_bits);
|
||||
extern std::shared_ptr<Cache> NewLRUCache(size_t capacity, int num_shard_bits,
|
||||
bool strict_capacity_limit);
|
||||
|
||||
class Cache {
|
||||
public:
|
||||
Cache() { }
|
||||
Cache() {}
|
||||
|
||||
// Destroys all existing entries by calling the "deleter"
|
||||
// function that was passed via the Insert() function.
|
||||
//
|
||||
// @See Insert
|
||||
virtual ~Cache();
|
||||
virtual ~Cache() {}
|
||||
|
||||
// Opaque handle to an entry stored in the cache.
|
||||
struct Handle { };
|
||||
struct Handle {};
|
||||
|
||||
// Insert a mapping from key->value into the cache and assign it
|
||||
// the specified charge against the total cache capacity.
|
||||
@@ -98,9 +96,8 @@ class Cache {
|
||||
// underlying entry will be kept around until all existing handles
|
||||
// to it have been released.
|
||||
virtual void Erase(const Slice& key) = 0;
|
||||
|
||||
// Return a new numeric id. May be used by multiple clients who are
|
||||
// sharing the same cache to partition the key space. Typically the
|
||||
// sharding the same cache to partition the key space. Typically the
|
||||
// client will allocate a new id at startup and prepend the id to
|
||||
// its cache keys.
|
||||
virtual uint64_t NewId() = 0;
|
||||
@@ -136,8 +133,8 @@ class Cache {
|
||||
// memory - call this only if you're shutting down the process.
|
||||
// Any attempts of using cache after this call will fail terribly.
|
||||
// Always delete the DB object before calling this method!
|
||||
virtual void DisownData() {
|
||||
// default implementation is noop
|
||||
virtual void DisownData(){
|
||||
// default implementation is noop
|
||||
};
|
||||
|
||||
// Apply callback to all entries in the cache
|
||||
@@ -157,7 +154,7 @@ class Cache {
|
||||
|
||||
// No copying allowed
|
||||
Cache(const Cache&);
|
||||
void operator=(const Cache&);
|
||||
Cache& operator=(const Cache&);
|
||||
};
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
@@ -5,8 +5,10 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <unordered_map>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "rocksdb/db.h"
|
||||
#include "rocksdb/options.h"
|
||||
#include "rocksdb/table.h"
|
||||
@@ -76,6 +78,9 @@ Status GetStringFromColumnFamilyOptions(std::string* opts_str,
|
||||
const ColumnFamilyOptions& db_options,
|
||||
const std::string& delimiter = "; ");
|
||||
|
||||
Status GetStringFromCompressionType(std::string* compression_str,
|
||||
CompressionType compression_type);
|
||||
|
||||
Status GetBlockBasedTableOptionsFromString(
|
||||
const BlockBasedTableOptions& table_options,
|
||||
const std::string& opts_str,
|
||||
|
||||
@@ -68,6 +68,12 @@ struct EnvOptions {
|
||||
// If true, then use mmap to write data
|
||||
bool use_mmap_writes = true;
|
||||
|
||||
// If true, then use O_DIRECT for reading data
|
||||
bool use_direct_reads = false;
|
||||
|
||||
// If true, then use O_DIRECT for writing data
|
||||
bool use_direct_writes = false;
|
||||
|
||||
// If false, fallocate() calls are bypassed
|
||||
bool allow_fallocate = true;
|
||||
|
||||
|
||||
@@ -75,16 +75,14 @@ struct ImmutableCFOptions {
|
||||
|
||||
bool purge_redundant_kvs_while_flush;
|
||||
|
||||
uint32_t min_partial_merge_operands;
|
||||
|
||||
bool disable_data_sync;
|
||||
|
||||
bool use_fsync;
|
||||
|
||||
CompressionType compression;
|
||||
|
||||
std::vector<CompressionType> compression_per_level;
|
||||
|
||||
CompressionType bottommost_compression;
|
||||
|
||||
CompressionOptions compression_opts;
|
||||
|
||||
bool level_compaction_dynamic_level_bytes;
|
||||
|
||||
@@ -20,6 +20,7 @@ typedef std::unordered_map<std::string, std::shared_ptr<const TableProperties>>
|
||||
class DB;
|
||||
class Status;
|
||||
struct CompactionJobStats;
|
||||
enum CompressionType : char;
|
||||
|
||||
enum class TableFileCreationReason {
|
||||
kFlush,
|
||||
@@ -142,11 +143,32 @@ struct CompactionJobInfo {
|
||||
// Reason to run the compaction
|
||||
CompactionReason compaction_reason;
|
||||
|
||||
// Compression algorithm used for output files
|
||||
CompressionType compression;
|
||||
|
||||
// If non-null, this variable stores detailed information
|
||||
// about this compaction.
|
||||
CompactionJobStats stats;
|
||||
};
|
||||
|
||||
struct MemTableInfo {
|
||||
// the name of the column family to which memtable belongs
|
||||
std::string cf_name;
|
||||
// Sequence number of the first element that was inserted
|
||||
// into the memtable.
|
||||
SequenceNumber first_seqno;
|
||||
// Sequence number that is guaranteed to be smaller than or equal
|
||||
// to the sequence number of any key that could be inserted into this
|
||||
// memtable. It can then be assumed that any write with a larger(or equal)
|
||||
// sequence number will be present in this memtable or a later memtable.
|
||||
SequenceNumber earliest_seqno;
|
||||
// Total number of entries in memtable
|
||||
uint64_t num_entries;
|
||||
// Total number of deletes in memtable
|
||||
uint64_t num_deletes;
|
||||
|
||||
};
|
||||
|
||||
// EventListener class contains a set of call-back functions that will
|
||||
// be called when specific RocksDB event happens such as flush. It can
|
||||
// be used as a building block for developing custom features such as
|
||||
@@ -243,6 +265,19 @@ class EventListener {
|
||||
// returned value.
|
||||
virtual void OnTableFileCreationStarted(
|
||||
const TableFileCreationBriefInfo& /*info*/) {}
|
||||
|
||||
// A call-back function for RocksDB which will be called before
|
||||
// a memtable is made immutable.
|
||||
//
|
||||
// Note that the this function must be implemented in a way such that
|
||||
// it should not run for an extended period of time before the function
|
||||
// returns. Otherwise, RocksDB may be blocked.
|
||||
//
|
||||
// Note that if applications would like to use the passed reference
|
||||
// outside this function call, they should make copies from these
|
||||
// returned value.
|
||||
virtual void OnMemTableSealed(
|
||||
const MemTableInfo& /*info*/) {}
|
||||
|
||||
virtual ~EventListener() {}
|
||||
};
|
||||
|
||||
+15
-11
@@ -64,6 +64,9 @@ enum CompressionType : char {
|
||||
kXpressCompression = 0x6,
|
||||
// zstd format is not finalized yet so it's subject to changes.
|
||||
kZSTDNotFinalCompression = 0x40,
|
||||
|
||||
// kDisableCompressionOption is used to disable some compression options.
|
||||
kDisableCompressionOption = -1,
|
||||
};
|
||||
|
||||
enum CompactionStyle : char {
|
||||
@@ -369,6 +372,13 @@ struct ColumnFamilyOptions {
|
||||
// change when data grows.
|
||||
std::vector<CompressionType> compression_per_level;
|
||||
|
||||
// Compression algorithm that will be used for the bottommost level that
|
||||
// contain files. If level-compaction is used, this option will only affect
|
||||
// levels after base level.
|
||||
//
|
||||
// Default: kDisableCompressionOption (Disabled)
|
||||
CompressionType bottommost_compression;
|
||||
|
||||
// different options for compression algorithms
|
||||
CompressionOptions compression_opts;
|
||||
|
||||
@@ -1303,6 +1313,10 @@ struct DBOptions {
|
||||
// Default: kPointInTimeRecovery
|
||||
WALRecoveryMode wal_recovery_mode;
|
||||
|
||||
// if set to false then recovery will fail when a prepared
|
||||
// transaction is encountered in the WAL
|
||||
bool allow_2pc = false;
|
||||
|
||||
// A global cache for table-level rows.
|
||||
// Default: nullptr (disabled)
|
||||
// Not supported in ROCKSDB_LITE mode!
|
||||
@@ -1538,17 +1552,6 @@ struct FlushOptions {
|
||||
FlushOptions() : wait(true) {}
|
||||
};
|
||||
|
||||
// Get options based on some guidelines. Now only tune parameter based on
|
||||
// flush/compaction and fill default parameters for other parameters.
|
||||
// total_write_buffer_limit: budget for memory spent for mem tables
|
||||
// read_amplification_threshold: comfortable value of read amplification
|
||||
// write_amplification_threshold: comfortable value of write amplification.
|
||||
// target_db_size: estimated total DB size.
|
||||
extern Options GetOptions(size_t total_write_buffer_limit,
|
||||
int read_amplification_threshold = 8,
|
||||
int write_amplification_threshold = 32,
|
||||
uint64_t target_db_size = 68719476736 /* 64GB */);
|
||||
|
||||
// Create a Logger from provided DBOptions
|
||||
extern Status CreateLoggerFromOptions(const std::string& dbname,
|
||||
const DBOptions& options,
|
||||
@@ -1599,6 +1602,7 @@ struct CompactRangeOptions {
|
||||
BottommostLevelCompaction bottommost_level_compaction =
|
||||
BottommostLevelCompaction::kIfHaveCompactionFilter;
|
||||
};
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
#endif // STORAGE_ROCKSDB_INCLUDE_OPTIONS_H_
|
||||
@@ -13,12 +13,14 @@ namespace rocksdb {
|
||||
|
||||
// How much perf stats to collect. Affects perf_context and iostats_context.
|
||||
|
||||
enum PerfLevel {
|
||||
enum PerfLevel : char {
|
||||
kUninitialized = -1, // unknown setting
|
||||
kDisable = 0, // disable perf stats
|
||||
kEnableCount = 1, // enable only count stats
|
||||
kEnableTimeExceptForMutex = 2, // Other than count stats, also enable time
|
||||
// stats except for mutexes
|
||||
kEnableTime = 3 // enable count and time stats
|
||||
kEnableTime = 3, // enable count and time stats
|
||||
kOutOfBounds = 4 // N.B. Must always be the last value!
|
||||
};
|
||||
|
||||
// set the perf stats level for current thread
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
// 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.
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <memory>
|
||||
|
||||
#include "rocksdb/slice.h"
|
||||
#include "rocksdb/statistics.h"
|
||||
#include "rocksdb/status.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
// PersistentCache
|
||||
//
|
||||
// Persistent cache interface for caching IO pages on a persistent medium. The
|
||||
// cache interface is specifically designed for persistent read cache.
|
||||
class PersistentCache {
|
||||
public:
|
||||
virtual ~PersistentCache() {}
|
||||
|
||||
// Insert to page cache
|
||||
//
|
||||
// page_key Identifier to identify a page uniquely across restarts
|
||||
// data Page data
|
||||
// size Size of the page
|
||||
virtual Status Insert(const Slice& key, const char* data,
|
||||
const size_t size) = 0;
|
||||
|
||||
// Lookup page cache by page identifier
|
||||
//
|
||||
// page_key Page identifier
|
||||
// buf Buffer where the data should be copied
|
||||
// size Size of the page
|
||||
virtual Status Lookup(const Slice& key, std::unique_ptr<char[]>* data,
|
||||
size_t* size) = 0;
|
||||
|
||||
// Is cache storing uncompressed data ?
|
||||
//
|
||||
// True if the cache is configured to store uncompressed data else false
|
||||
virtual bool IsCompressed() = 0;
|
||||
};
|
||||
|
||||
} // namespace rocksdb
|
||||
@@ -49,8 +49,7 @@ struct ExternalSstFileInfo {
|
||||
// All keys in files generated by SstFileWriter will have sequence number = 0
|
||||
class SstFileWriter {
|
||||
public:
|
||||
SstFileWriter(const EnvOptions& env_options,
|
||||
const ImmutableCFOptions& ioptions,
|
||||
SstFileWriter(const EnvOptions& env_options, const Options& options,
|
||||
const Comparator* user_comparator);
|
||||
|
||||
~SstFileWriter();
|
||||
|
||||
@@ -39,10 +39,18 @@ enum Tickers : uint32_t {
|
||||
BLOCK_CACHE_INDEX_MISS,
|
||||
// # of times cache hit when accessing index block from block cache.
|
||||
BLOCK_CACHE_INDEX_HIT,
|
||||
// # of bytes of index blocks inserted into cache
|
||||
BLOCK_CACHE_INDEX_BYTES_INSERT,
|
||||
// # of bytes of index block erased from cache
|
||||
BLOCK_CACHE_INDEX_BYTES_EVICT,
|
||||
// # of times cache miss when accessing filter block from block cache.
|
||||
BLOCK_CACHE_FILTER_MISS,
|
||||
// # of times cache hit when accessing filter block from block cache.
|
||||
BLOCK_CACHE_FILTER_HIT,
|
||||
// # of bytes of bloom filter blocks inserted into cache
|
||||
BLOCK_CACHE_FILTER_BYTES_INSERT,
|
||||
// # of bytes of bloom filter block erased from cache
|
||||
BLOCK_CACHE_FILTER_BYTES_EVICT,
|
||||
// # of times cache miss when accessing data block from block cache.
|
||||
BLOCK_CACHE_DATA_MISS,
|
||||
// # of times cache hit when accessing data block from block cache.
|
||||
@@ -51,9 +59,15 @@ enum Tickers : uint32_t {
|
||||
BLOCK_CACHE_BYTES_READ,
|
||||
// # of bytes written into cache.
|
||||
BLOCK_CACHE_BYTES_WRITE,
|
||||
|
||||
// # of times bloom filter has avoided file reads.
|
||||
BLOOM_FILTER_USEFUL,
|
||||
|
||||
// # persistent cache hit
|
||||
PERSISTENT_CACHE_HIT,
|
||||
// # persistent cache miss
|
||||
PERSISTENT_CACHE_MISS,
|
||||
|
||||
// # of memtable hits.
|
||||
MEMTABLE_HIT,
|
||||
// # of memtable misses.
|
||||
@@ -185,8 +199,13 @@ const std::vector<std::pair<Tickers, std::string>> TickersNameMap = {
|
||||
{BLOCK_CACHE_ADD_FAILURES, "rocksdb.block.cache.add.failures"},
|
||||
{BLOCK_CACHE_INDEX_MISS, "rocksdb.block.cache.index.miss"},
|
||||
{BLOCK_CACHE_INDEX_HIT, "rocksdb.block.cache.index.hit"},
|
||||
{BLOCK_CACHE_INDEX_BYTES_INSERT, "rocksdb.block.cache.index.bytes.insert"},
|
||||
{BLOCK_CACHE_INDEX_BYTES_EVICT, "rocksdb.block.cache.index.bytes.evict"},
|
||||
{BLOCK_CACHE_FILTER_MISS, "rocksdb.block.cache.filter.miss"},
|
||||
{BLOCK_CACHE_FILTER_HIT, "rocksdb.block.cache.filter.hit"},
|
||||
{BLOCK_CACHE_FILTER_BYTES_INSERT,
|
||||
"rocksdb.block.cache.filter.bytes.insert"},
|
||||
{BLOCK_CACHE_FILTER_BYTES_EVICT, "rocksdb.block.cache.filter.bytes.evict"},
|
||||
{BLOCK_CACHE_DATA_MISS, "rocksdb.block.cache.data.miss"},
|
||||
{BLOCK_CACHE_DATA_HIT, "rocksdb.block.cache.data.hit"},
|
||||
{BLOCK_CACHE_BYTES_READ, "rocksdb.block.cache.bytes.read"},
|
||||
|
||||
@@ -21,15 +21,16 @@
|
||||
#include <unordered_map>
|
||||
|
||||
#include "rocksdb/env.h"
|
||||
#include "rocksdb/immutable_options.h"
|
||||
#include "rocksdb/iterator.h"
|
||||
#include "rocksdb/options.h"
|
||||
#include "rocksdb/immutable_options.h"
|
||||
#include "rocksdb/status.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
// -- Block-based Table
|
||||
class FlushBlockPolicyFactory;
|
||||
class PersistentCache;
|
||||
class RandomAccessFile;
|
||||
struct TableReaderOptions;
|
||||
struct TableBuilderOptions;
|
||||
@@ -83,10 +84,8 @@ struct BlockBasedTableOptions {
|
||||
|
||||
IndexType index_type = kBinarySearch;
|
||||
|
||||
// Influence the behavior when kHashSearch is used.
|
||||
// if false, stores a precise prefix to block range mapping
|
||||
// if true, does not store prefix and allows prefix hash collision
|
||||
// (less memory consumption)
|
||||
// This option is now deprecated. No matter what value it is set to,
|
||||
// it will behave as if hash_index_allow_collision=true.
|
||||
bool hash_index_allow_collision = true;
|
||||
|
||||
// Use the specified checksum type. Newly created table files will be
|
||||
@@ -103,6 +102,10 @@ struct BlockBasedTableOptions {
|
||||
// If NULL, rocksdb will automatically create and use an 8MB internal cache.
|
||||
std::shared_ptr<Cache> block_cache = nullptr;
|
||||
|
||||
// If non-NULL use the specified cache for pages read from device
|
||||
// IF NULL, no page cache is used
|
||||
std::shared_ptr<PersistentCache> persistent_cache = nullptr;
|
||||
|
||||
// If non-NULL use the specified cache for compressed blocks.
|
||||
// If NULL, rocksdb will not use a compressed block cache.
|
||||
std::shared_ptr<Cache> block_cache_compressed = nullptr;
|
||||
|
||||
@@ -44,6 +44,7 @@ struct TablePropertiesNames {
|
||||
static const std::string kComparator;
|
||||
static const std::string kMergeOperator;
|
||||
static const std::string kPropertyCollectors;
|
||||
static const std::string kCompression;
|
||||
};
|
||||
|
||||
extern const std::string kPropertiesBlock;
|
||||
@@ -170,6 +171,9 @@ struct TableProperties {
|
||||
// {collector_name[1]},{collector_name[2]},{collector_name[3]} ..
|
||||
std::string property_collectors_names;
|
||||
|
||||
// The compression algo used to compress the SST files.
|
||||
std::string compression_name;
|
||||
|
||||
// user collected properties
|
||||
UserCollectedProperties user_collected_properties;
|
||||
UserCollectedProperties readable_properties;
|
||||
@@ -189,5 +193,7 @@ struct TableProperties {
|
||||
// itself. Especially some properties regarding to the internal keys (which
|
||||
// is unknown to `table`).
|
||||
extern uint64_t GetDeletedKeys(const UserCollectedProperties& props);
|
||||
extern uint64_t GetMergeOperands(const UserCollectedProperties& props,
|
||||
bool* property_present);
|
||||
|
||||
} // namespace rocksdb
|
||||
@@ -75,11 +75,21 @@ struct BackupableDBOptions {
|
||||
// Default: 0
|
||||
uint64_t backup_rate_limit;
|
||||
|
||||
// Backup rate limiter. Used to control transfer speed for backup. If this is
|
||||
// not null, backup_rate_limit is ignored.
|
||||
// Default: nullptr
|
||||
std::shared_ptr<RateLimiter> backup_rate_limiter{nullptr};
|
||||
|
||||
// Max bytes that can be transferred in a second during restore.
|
||||
// If 0, go as fast as you can
|
||||
// Default: 0
|
||||
uint64_t restore_rate_limit;
|
||||
|
||||
// Restore rate limiter. Used to control transfer speed during restore. If
|
||||
// this is not null, restore_rate_limit is ignored.
|
||||
// Default: nullptr
|
||||
std::shared_ptr<RateLimiter> restore_rate_limiter{nullptr};
|
||||
|
||||
// Only used if share_table_files is set to true. If true, will consider that
|
||||
// backups can come from different databases, hence a sst is not uniquely
|
||||
// identifed by its name, but by the triple (file name, crc32, file length)
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
// Copyright (c) 2016-present, 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.
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "rocksdb/env.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
// Returns a new Env when called with a URI string. Populates the unique_ptr
|
||||
// argument if granting ownership to caller.
|
||||
typedef std::function<Env*(const std::string&, std::unique_ptr<Env>*)>
|
||||
EnvFactoryFunc;
|
||||
|
||||
// Creates a new Env using the registered factory function corresponding to a
|
||||
// prefix of uri.
|
||||
//
|
||||
// If no prefixes match, returns nullptr. If multiple prefixes match, the
|
||||
// factory function used is unspecified.
|
||||
//
|
||||
// Populates env_guard with result pointer if caller is granted ownership.
|
||||
Env* NewEnvFromUri(const std::string& uri, std::unique_ptr<Env>* env_guard);
|
||||
|
||||
// To register an Env factory function, initialize an EnvRegistrar object with
|
||||
// static storage duration. For example:
|
||||
//
|
||||
// static EnvRegistrar hdfs_reg("hdfs://", &CreateHdfsEnv);
|
||||
//
|
||||
// Then, calling NewEnvFromUri("hdfs://some_path", ...) will use CreateHdfsEnv
|
||||
// to make a new Env.
|
||||
class EnvRegistrar {
|
||||
public:
|
||||
explicit EnvRegistrar(std::string uri_prefix, EnvFactoryFunc env_factory);
|
||||
};
|
||||
|
||||
} // namespace rocksdb
|
||||
#endif // ROCKSDB_LITE
|
||||
@@ -0,0 +1,251 @@
|
||||
// Copyright (c) 2011-present, 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.
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "rocksdb/env.h"
|
||||
#include "rocksdb/iterator.h"
|
||||
#include "rocksdb/ldb_tool.h"
|
||||
#include "rocksdb/options.h"
|
||||
#include "rocksdb/slice.h"
|
||||
#include "rocksdb/utilities/db_ttl.h"
|
||||
#include "rocksdb/utilities/ldb_cmd_execute_result.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
class LDBCommand {
|
||||
public:
|
||||
// Command-line arguments
|
||||
static const std::string ARG_DB;
|
||||
static const std::string ARG_PATH;
|
||||
static const std::string ARG_HEX;
|
||||
static const std::string ARG_KEY_HEX;
|
||||
static const std::string ARG_VALUE_HEX;
|
||||
static const std::string ARG_CF_NAME;
|
||||
static const std::string ARG_TTL;
|
||||
static const std::string ARG_TTL_START;
|
||||
static const std::string ARG_TTL_END;
|
||||
static const std::string ARG_TIMESTAMP;
|
||||
static const std::string ARG_FROM;
|
||||
static const std::string ARG_TO;
|
||||
static const std::string ARG_MAX_KEYS;
|
||||
static const std::string ARG_BLOOM_BITS;
|
||||
static const std::string ARG_FIX_PREFIX_LEN;
|
||||
static const std::string ARG_COMPRESSION_TYPE;
|
||||
static const std::string ARG_COMPRESSION_MAX_DICT_BYTES;
|
||||
static const std::string ARG_BLOCK_SIZE;
|
||||
static const std::string ARG_AUTO_COMPACTION;
|
||||
static const std::string ARG_DB_WRITE_BUFFER_SIZE;
|
||||
static const std::string ARG_WRITE_BUFFER_SIZE;
|
||||
static const std::string ARG_FILE_SIZE;
|
||||
static const std::string ARG_CREATE_IF_MISSING;
|
||||
static const std::string ARG_NO_VALUE;
|
||||
|
||||
struct ParsedParams {
|
||||
std::string cmd;
|
||||
std::vector<std::string> cmd_params;
|
||||
std::map<std::string, std::string> option_map;
|
||||
std::vector<std::string> flags;
|
||||
};
|
||||
|
||||
static LDBCommand* SelectCommand(const ParsedParams& parsed_parms);
|
||||
|
||||
static LDBCommand* InitFromCmdLineArgs(
|
||||
const std::vector<std::string>& args, const Options& options,
|
||||
const LDBOptions& ldb_options,
|
||||
const std::vector<ColumnFamilyDescriptor>* column_families,
|
||||
const std::function<LDBCommand*(const ParsedParams&)>& selector =
|
||||
SelectCommand);
|
||||
|
||||
static LDBCommand* InitFromCmdLineArgs(
|
||||
int argc, char** argv, const Options& options,
|
||||
const LDBOptions& ldb_options,
|
||||
const std::vector<ColumnFamilyDescriptor>* column_families);
|
||||
|
||||
bool ValidateCmdLineOptions();
|
||||
|
||||
virtual Options PrepareOptionsForOpenDB();
|
||||
|
||||
virtual void SetDBOptions(Options options) { options_ = options; }
|
||||
|
||||
virtual void SetColumnFamilies(
|
||||
const std::vector<ColumnFamilyDescriptor>* column_families) {
|
||||
if (column_families != nullptr) {
|
||||
column_families_ = *column_families;
|
||||
} else {
|
||||
column_families_.clear();
|
||||
}
|
||||
}
|
||||
|
||||
void SetLDBOptions(const LDBOptions& ldb_options) {
|
||||
ldb_options_ = ldb_options;
|
||||
}
|
||||
|
||||
virtual bool NoDBOpen() { return false; }
|
||||
|
||||
virtual ~LDBCommand() { CloseDB(); }
|
||||
|
||||
/* Run the command, and return the execute result. */
|
||||
void Run();
|
||||
|
||||
virtual void DoCommand() = 0;
|
||||
|
||||
LDBCommandExecuteResult GetExecuteState() { return exec_state_; }
|
||||
|
||||
void ClearPreviousRunState() { exec_state_.Reset(); }
|
||||
|
||||
// Consider using Slice::DecodeHex directly instead if you don't need the
|
||||
// 0x prefix
|
||||
static std::string HexToString(const std::string& str);
|
||||
|
||||
// Consider using Slice::ToString(true) directly instead if
|
||||
// you don't need the 0x prefix
|
||||
static std::string StringToHex(const std::string& str);
|
||||
|
||||
static const char* DELIM;
|
||||
|
||||
protected:
|
||||
LDBCommandExecuteResult exec_state_;
|
||||
std::string db_path_;
|
||||
std::string column_family_name_;
|
||||
DB* db_;
|
||||
DBWithTTL* db_ttl_;
|
||||
std::map<std::string, ColumnFamilyHandle*> cf_handles_;
|
||||
|
||||
/**
|
||||
* true implies that this command can work if the db is opened in read-only
|
||||
* mode.
|
||||
*/
|
||||
bool is_read_only_;
|
||||
|
||||
/** If true, the key is input/output as hex in get/put/scan/delete etc. */
|
||||
bool is_key_hex_;
|
||||
|
||||
/** If true, the value is input/output as hex in get/put/scan/delete etc. */
|
||||
bool is_value_hex_;
|
||||
|
||||
/** If true, the value is treated as timestamp suffixed */
|
||||
bool is_db_ttl_;
|
||||
|
||||
// If true, the kvs are output with their insert/modify timestamp in a ttl db
|
||||
bool timestamp_;
|
||||
|
||||
/**
|
||||
* Map of options passed on the command-line.
|
||||
*/
|
||||
const std::map<std::string, std::string> option_map_;
|
||||
|
||||
/**
|
||||
* Flags passed on the command-line.
|
||||
*/
|
||||
const std::vector<std::string> flags_;
|
||||
|
||||
/** List of command-line options valid for this command */
|
||||
const std::vector<std::string> valid_cmd_line_options_;
|
||||
|
||||
bool ParseKeyValue(const std::string& line, std::string* key,
|
||||
std::string* value, bool is_key_hex, bool is_value_hex);
|
||||
|
||||
LDBCommand(const std::map<std::string, std::string>& options,
|
||||
const std::vector<std::string>& flags, bool is_read_only,
|
||||
const std::vector<std::string>& valid_cmd_line_options);
|
||||
|
||||
void OpenDB();
|
||||
|
||||
void CloseDB();
|
||||
|
||||
ColumnFamilyHandle* GetCfHandle();
|
||||
|
||||
static std::string PrintKeyValue(const std::string& key,
|
||||
const std::string& value, bool is_key_hex,
|
||||
bool is_value_hex);
|
||||
|
||||
static std::string PrintKeyValue(const std::string& key,
|
||||
const std::string& value, bool is_hex);
|
||||
|
||||
/**
|
||||
* Return true if the specified flag is present in the specified flags vector
|
||||
*/
|
||||
static bool IsFlagPresent(const std::vector<std::string>& flags,
|
||||
const std::string& flag) {
|
||||
return (std::find(flags.begin(), flags.end(), flag) != flags.end());
|
||||
}
|
||||
|
||||
static std::string HelpRangeCmdArgs();
|
||||
|
||||
/**
|
||||
* A helper function that returns a list of command line options
|
||||
* used by this command. It includes the common options and the ones
|
||||
* passed in.
|
||||
*/
|
||||
static std::vector<std::string> BuildCmdLineOptions(
|
||||
std::vector<std::string> options);
|
||||
|
||||
bool ParseIntOption(const std::map<std::string, std::string>& options,
|
||||
const std::string& option, int& value,
|
||||
LDBCommandExecuteResult& exec_state);
|
||||
|
||||
bool ParseStringOption(const std::map<std::string, std::string>& options,
|
||||
const std::string& option, std::string* value);
|
||||
|
||||
Options options_;
|
||||
std::vector<ColumnFamilyDescriptor> column_families_;
|
||||
LDBOptions ldb_options_;
|
||||
|
||||
private:
|
||||
/**
|
||||
* Interpret command line options and flags to determine if the key
|
||||
* should be input/output in hex.
|
||||
*/
|
||||
bool IsKeyHex(const std::map<std::string, std::string>& options,
|
||||
const std::vector<std::string>& flags);
|
||||
|
||||
/**
|
||||
* Interpret command line options and flags to determine if the value
|
||||
* should be input/output in hex.
|
||||
*/
|
||||
bool IsValueHex(const std::map<std::string, std::string>& options,
|
||||
const std::vector<std::string>& flags);
|
||||
|
||||
/**
|
||||
* Returns the value of the specified option as a boolean.
|
||||
* default_val is used if the option is not found in options.
|
||||
* Throws an exception if the value of the option is not
|
||||
* "true" or "false" (case insensitive).
|
||||
*/
|
||||
bool ParseBooleanOption(const std::map<std::string, std::string>& options,
|
||||
const std::string& option, bool default_val);
|
||||
|
||||
/**
|
||||
* Converts val to a boolean.
|
||||
* val must be either true or false (case insensitive).
|
||||
* Otherwise an exception is thrown.
|
||||
*/
|
||||
bool StringToBool(std::string val);
|
||||
};
|
||||
|
||||
class LDBCommandRunner {
|
||||
public:
|
||||
static void PrintHelp(const char* exec_name);
|
||||
|
||||
static void RunCommand(
|
||||
int argc, char** argv, Options options, const LDBOptions& ldb_options,
|
||||
const std::vector<ColumnFamilyDescriptor>* column_families);
|
||||
};
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
#endif // ROCKSDB_LITE
|
||||
File renamed without changes.
@@ -0,0 +1,67 @@
|
||||
// Copyright (c) 2011-present, 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.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include "rocksdb/cache.h"
|
||||
#include "rocksdb/slice.h"
|
||||
#include "rocksdb/status.h"
|
||||
#include "util/lru_cache_handle.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
class SimCache;
|
||||
|
||||
// For instrumentation purpose, use NewSimCache instead of NewLRUCache API
|
||||
// NewSimCache is a wrapper function returning a SimCache instance that can
|
||||
// have additional interface provided in Simcache class besides Cache interface
|
||||
// to predict block cache hit rate without actually allocating the memory. It
|
||||
// can help users tune their current block cache size, and determine how
|
||||
// efficient they are using the memory.
|
||||
extern std::shared_ptr<SimCache> NewSimCache(std::shared_ptr<Cache> cache,
|
||||
size_t sim_capacity,
|
||||
int num_shard_bits);
|
||||
|
||||
class SimCache : public Cache {
|
||||
public:
|
||||
SimCache() {}
|
||||
|
||||
virtual ~SimCache() {}
|
||||
|
||||
// returns the maximum configured capacity of the simcache for simulation
|
||||
virtual size_t GetSimCapacity() const = 0;
|
||||
|
||||
// simcache doesn't provide internal handler reference to user, so always
|
||||
// PinnedUsage = 0 and the behavior will be not exactly consistent the
|
||||
// with real cache.
|
||||
// returns the memory size for the entries residing in the simcache.
|
||||
virtual size_t GetSimUsage() const = 0;
|
||||
|
||||
// sets the maximum configured capacity of the simcache. When the new
|
||||
// capacity is less than the old capacity and the existing usage is
|
||||
// greater than new capacity, the implementation will purge old entries
|
||||
// to fit new capapicty.
|
||||
virtual void SetSimCapacity(size_t capacity) = 0;
|
||||
|
||||
// returns the lookup times of simcache
|
||||
virtual uint64_t get_lookup_counter() const = 0;
|
||||
// returns the hit times of simcache
|
||||
virtual uint64_t get_hit_counter() const = 0;
|
||||
// returns the hit rate of simcache
|
||||
virtual double get_hit_rate() const = 0;
|
||||
// reset the lookup and hit counters
|
||||
virtual void reset_counter() = 0;
|
||||
// String representation of the statistics of the simcache
|
||||
virtual std::string ToString() const = 0;
|
||||
|
||||
private:
|
||||
SimCache(const SimCache&);
|
||||
SimCache& operator=(const SimCache&);
|
||||
};
|
||||
|
||||
} // namespace rocksdb
|
||||
@@ -20,6 +20,8 @@ class Iterator;
|
||||
class TransactionDB;
|
||||
class WriteBatchWithIndex;
|
||||
|
||||
typedef std::string TransactionName;
|
||||
|
||||
// Provides notification to the caller of SetSnapshotOnNextOperation when
|
||||
// the actual snapshot gets created
|
||||
class TransactionNotifier {
|
||||
@@ -114,6 +116,9 @@ class Transaction {
|
||||
// longer be valid and should be discarded after a call to ClearSnapshot().
|
||||
virtual void ClearSnapshot() = 0;
|
||||
|
||||
// Prepare the current transation for 2PC
|
||||
virtual Status Prepare() = 0;
|
||||
|
||||
// Write all batched keys to the db atomically.
|
||||
//
|
||||
// Returns OK on success.
|
||||
@@ -132,7 +137,7 @@ class Transaction {
|
||||
virtual Status Commit() = 0;
|
||||
|
||||
// Discard all batched writes in this transaction.
|
||||
virtual void Rollback() = 0;
|
||||
virtual Status Rollback() = 0;
|
||||
|
||||
// Records the state of the transaction for future calls to
|
||||
// RollbackToSavePoint(). May be called multiple times to set multiple save
|
||||
@@ -378,10 +383,41 @@ class Transaction {
|
||||
const Slice& key) = 0;
|
||||
virtual void UndoGetForUpdate(const Slice& key) = 0;
|
||||
|
||||
virtual Status RebuildFromWriteBatch(WriteBatch* src_batch) = 0;
|
||||
|
||||
virtual WriteBatch* GetCommitTimeWriteBatch() = 0;
|
||||
|
||||
virtual void SetLogNumber(uint64_t log) { log_number_ = log; }
|
||||
|
||||
virtual uint64_t GetLogNumber() { return log_number_; }
|
||||
|
||||
virtual Status SetName(const TransactionName& name) = 0;
|
||||
|
||||
virtual TransactionName GetName() { return name_; }
|
||||
|
||||
enum ExecutionStatus {
|
||||
STARTED = 0,
|
||||
AWAITING_PREPARE = 1,
|
||||
PREPARED = 2,
|
||||
AWAITING_COMMIT = 3,
|
||||
COMMITED = 4,
|
||||
AWAITING_ROLLBACK = 5,
|
||||
ROLLEDBACK = 6,
|
||||
LOCKS_STOLEN = 7,
|
||||
};
|
||||
|
||||
// Execution status of the transaction.
|
||||
std::atomic<ExecutionStatus> exec_status_;
|
||||
|
||||
protected:
|
||||
explicit Transaction(const TransactionDB* db) {}
|
||||
Transaction() {}
|
||||
|
||||
// the log in which the prepared section for this txn resides
|
||||
// (for two phase commit)
|
||||
uint64_t log_number_;
|
||||
TransactionName name_;
|
||||
|
||||
private:
|
||||
// No copying allowed
|
||||
Transaction(const Transaction&);
|
||||
|
||||
@@ -124,6 +124,9 @@ class TransactionDB : public StackableDB {
|
||||
const TransactionOptions& txn_options = TransactionOptions(),
|
||||
Transaction* old_txn = nullptr) = 0;
|
||||
|
||||
virtual Transaction* GetTransactionByName(const TransactionName& name) = 0;
|
||||
virtual void GetAllPreparedTransactions(std::vector<Transaction*>* trans) = 0;
|
||||
|
||||
protected:
|
||||
// To Create an TransactionDB, call Open()
|
||||
explicit TransactionDB(DB* db) : StackableDB(db) {}
|
||||
|
||||
@@ -34,7 +34,8 @@ enum WriteType {
|
||||
kMergeRecord,
|
||||
kDeleteRecord,
|
||||
kSingleDeleteRecord,
|
||||
kLogDataRecord
|
||||
kLogDataRecord,
|
||||
kXIDRecord,
|
||||
};
|
||||
|
||||
// an entry for Put, Merge, Delete, or SingleDelete entry for write batches.
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
#pragma once
|
||||
|
||||
#define ROCKSDB_MAJOR 4
|
||||
#define ROCKSDB_MINOR 8
|
||||
#define ROCKSDB_MINOR 9
|
||||
#define ROCKSDB_PATCH 0
|
||||
|
||||
// Do not use these. We made the mistake of declaring macros starting with
|
||||
|
||||
@@ -186,6 +186,23 @@ class WriteBatch : public WriteBatchBase {
|
||||
// The default implementation of LogData does nothing.
|
||||
virtual void LogData(const Slice& blob);
|
||||
|
||||
virtual Status MarkBeginPrepare() {
|
||||
return Status::InvalidArgument("MarkBeginPrepare() handler not defined.");
|
||||
}
|
||||
|
||||
virtual Status MarkEndPrepare(const Slice& xid) {
|
||||
return Status::InvalidArgument("MarkEndPrepare() handler not defined.");
|
||||
}
|
||||
|
||||
virtual Status MarkRollback(const Slice& xid) {
|
||||
return Status::InvalidArgument(
|
||||
"MarkRollbackPrepare() handler not defined.");
|
||||
}
|
||||
|
||||
virtual Status MarkCommit(const Slice& xid) {
|
||||
return Status::InvalidArgument("MarkCommit() handler not defined.");
|
||||
}
|
||||
|
||||
// Continue is called by WriteBatch::Iterate. If it returns false,
|
||||
// iteration is halted. Otherwise, it continues iterating. The default
|
||||
// implementation always returns true.
|
||||
@@ -214,6 +231,18 @@ class WriteBatch : public WriteBatchBase {
|
||||
// Returns trie if MergeCF will be called during Iterate
|
||||
bool HasMerge() const;
|
||||
|
||||
// Returns true if MarkBeginPrepare will be called during Iterate
|
||||
bool HasBeginPrepare() const;
|
||||
|
||||
// Returns true if MarkEndPrepare will be called during Iterate
|
||||
bool HasEndPrepare() const;
|
||||
|
||||
// Returns trie if MarkCommit will be called during Iterate
|
||||
bool HasCommit() const;
|
||||
|
||||
// Returns trie if MarkRollback will be called during Iterate
|
||||
bool HasRollback() const;
|
||||
|
||||
using WriteBatchBase::GetWriteBatch;
|
||||
WriteBatch* GetWriteBatch() override { return this; }
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
#include <jni.h>
|
||||
|
||||
#include "include/org_rocksdb_AbstractCompactionFilter.h"
|
||||
#include "rocksdb/compaction_filter.h"
|
||||
|
||||
// <editor-fold desc="org.rocksdb.AbstractCompactionFilter">
|
||||
|
||||
@@ -54,12 +54,18 @@ void LoggerJniCallback::Logv(const InfoLogLevel log_level,
|
||||
case rocksdb::InfoLogLevel::INFO_LEVEL:
|
||||
jlog_level = InfoLogLevelJni::INFO_LEVEL(env);
|
||||
break;
|
||||
case rocksdb::InfoLogLevel::WARN_LEVEL:
|
||||
jlog_level = InfoLogLevelJni::WARN_LEVEL(env);
|
||||
break;
|
||||
case rocksdb::InfoLogLevel::ERROR_LEVEL:
|
||||
jlog_level = InfoLogLevelJni::ERROR_LEVEL(env);
|
||||
break;
|
||||
case rocksdb::InfoLogLevel::FATAL_LEVEL:
|
||||
jlog_level = InfoLogLevelJni::FATAL_LEVEL(env);
|
||||
break;
|
||||
case rocksdb::InfoLogLevel::HEADER_LEVEL:
|
||||
jlog_level = InfoLogLevelJni::HEADER_LEVEL(env);
|
||||
break;
|
||||
default:
|
||||
jlog_level = InfoLogLevelJni::FATAL_LEVEL(env);
|
||||
break;
|
||||
|
||||
@@ -2066,7 +2066,7 @@ void Java_org_rocksdb_ColumnFamilyOptions_setMergeOperator(
|
||||
* Method: setCompactionFilterHandle
|
||||
* Signature: (JJ)V
|
||||
*/
|
||||
void Java_org_rocksdb_ColumnFamilyOptions_setCompactionFilterHandle__JJ(
|
||||
void Java_org_rocksdb_ColumnFamilyOptions_setCompactionFilterHandle(
|
||||
JNIEnv* env, jobject jobj, jlong jopt_handle,
|
||||
jlong jcompactionfilter_handle) {
|
||||
reinterpret_cast<rocksdb::ColumnFamilyOptions*>(jopt_handle)->
|
||||
|
||||
@@ -676,15 +676,20 @@ class InfoLogLevelJni {
|
||||
return getEnum(env, "FATAL_LEVEL");
|
||||
}
|
||||
|
||||
// Get the HEADER_LEVEL enum field of org.rocksdb.InfoLogLevel
|
||||
static jobject HEADER_LEVEL(JNIEnv* env) {
|
||||
return getEnum(env, "HEADER_LEVEL");
|
||||
}
|
||||
|
||||
private:
|
||||
// Get the java class id of org.rocksdb.WBWIRocksIterator.WriteType.
|
||||
// Get the java class id of org.rocksdb.InfoLogLevel
|
||||
static jclass getJClass(JNIEnv* env) {
|
||||
jclass jclazz = env->FindClass("org/rocksdb/InfoLogLevel");
|
||||
assert(jclazz != nullptr);
|
||||
return jclazz;
|
||||
}
|
||||
|
||||
// Get an enum field of org.rocksdb.WBWIRocksIterator.WriteType
|
||||
// Get an enum field of org.rocksdb.InfoLogLevel
|
||||
static jobject getEnum(JNIEnv* env, const char name[]) {
|
||||
jclass jclazz = getJClass(env);
|
||||
jfieldID jfid =
|
||||
|
||||
@@ -1484,6 +1484,42 @@ void Java_org_rocksdb_RocksDB_compactRange__J_3BI_3BIZIIJ(
|
||||
jend, jend_len, jreduce_level, jtarget_level, jtarget_path_id);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
// rocksdb::DB::PauseBackgroundWork
|
||||
|
||||
/*
|
||||
* Class: org_rocksdb_RocksDB
|
||||
* Method: pauseBackgroundWork
|
||||
* Signature: (J)V
|
||||
*/
|
||||
void Java_org_rocksdb_RocksDB_pauseBackgroundWork(
|
||||
JNIEnv* env, jobject jobj, jlong jdb_handle) {
|
||||
auto* db = reinterpret_cast<rocksdb::DB*>(jdb_handle);
|
||||
auto s = db->PauseBackgroundWork();
|
||||
if (s.ok()) {
|
||||
return;
|
||||
}
|
||||
rocksdb::RocksDBExceptionJni::ThrowNew(env, s);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
// rocksdb::DB::ContinueBackgroundWork
|
||||
|
||||
/*
|
||||
* Class: org_rocksdb_RocksDB
|
||||
* Method: continueBackgroundWork
|
||||
* Signature: (J)V
|
||||
*/
|
||||
void Java_org_rocksdb_RocksDB_continueBackgroundWork(
|
||||
JNIEnv* env, jobject jobj, jlong jdb_handle) {
|
||||
auto* db = reinterpret_cast<rocksdb::DB*>(jdb_handle);
|
||||
auto s = db->ContinueBackgroundWork();
|
||||
if (s.ok()) {
|
||||
return;
|
||||
}
|
||||
rocksdb::RocksDBExceptionJni::ThrowNew(env, s);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
// rocksdb::DB::GetLatestSequenceNumber
|
||||
|
||||
|
||||
@@ -29,10 +29,10 @@ jlong Java_org_rocksdb_Statistics_getTickerCount0(
|
||||
|
||||
/*
|
||||
* Class: org_rocksdb_Statistics
|
||||
* Method: geHistogramData0
|
||||
* Method: getHistogramData0
|
||||
* Signature: (IJ)Lorg/rocksdb/HistogramData;
|
||||
*/
|
||||
jobject Java_org_rocksdb_Statistics_geHistogramData0(
|
||||
jobject Java_org_rocksdb_Statistics_getHistogramData0(
|
||||
JNIEnv* env, jobject jobj, jint histogramType, jlong handle) {
|
||||
auto st = reinterpret_cast<rocksdb::Statistics*>(handle);
|
||||
assert(st != nullptr);
|
||||
|
||||
@@ -9,7 +9,8 @@ public enum InfoLogLevel {
|
||||
WARN_LEVEL((byte)2),
|
||||
ERROR_LEVEL((byte)3),
|
||||
FATAL_LEVEL((byte)4),
|
||||
NUM_INFO_LOG_LEVELS((byte)5);
|
||||
HEADER_LEVEL((byte)5),
|
||||
NUM_INFO_LOG_LEVELS((byte)6);
|
||||
|
||||
private final byte value_;
|
||||
|
||||
|
||||
@@ -1631,6 +1631,23 @@ public class RocksDB extends RocksObject {
|
||||
columnFamilyHandle.nativeHandle_);
|
||||
}
|
||||
|
||||
/**
|
||||
* This function will wait until all currently running background processes
|
||||
* finish. After it returns, no background process will be run until
|
||||
* {@link #continueBackgroundWork()} is called
|
||||
*/
|
||||
public void pauseBackgroundWork() throws RocksDBException {
|
||||
pauseBackgroundWork(nativeHandle_);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resumes backround work which was suspended by
|
||||
* previously calling {@link #pauseBackgroundWork()}
|
||||
*/
|
||||
public void continueBackgroundWork() throws RocksDBException {
|
||||
continueBackgroundWork(nativeHandle_);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>The sequence number of the most recent transaction.</p>
|
||||
*
|
||||
@@ -1876,6 +1893,8 @@ public class RocksDB extends RocksObject {
|
||||
private native void compactRange(long handle, byte[] begin, int beginLen,
|
||||
byte[] end, int endLen, boolean reduce_level, int target_level,
|
||||
int target_path_id, long cfHandle) throws RocksDBException;
|
||||
private native void pauseBackgroundWork(long handle) throws RocksDBException;
|
||||
private native void continueBackgroundWork(long handle) throws RocksDBException;
|
||||
private native long getLatestSequenceNumber(long handle);
|
||||
private native void disableFileDeletions(long handle) throws RocksDBException;
|
||||
private native void enableFileDeletions(long handle,
|
||||
|
||||
@@ -22,9 +22,9 @@ public class Statistics {
|
||||
return getTickerCount0(tickerType.getValue(), statsHandle_);
|
||||
}
|
||||
|
||||
public HistogramData geHistogramData(final HistogramType histogramType) {
|
||||
public HistogramData getHistogramData(final HistogramType histogramType) {
|
||||
assert(isInitialized());
|
||||
return geHistogramData0(
|
||||
return getHistogramData0(
|
||||
histogramType.getValue(), statsHandle_);
|
||||
}
|
||||
|
||||
@@ -33,5 +33,5 @@ public class Statistics {
|
||||
}
|
||||
|
||||
private native long getTickerCount0(int tickerType, long handle);
|
||||
private native HistogramData geHistogramData0(int histogramType, long handle);
|
||||
private native HistogramData getHistogramData0(int histogramType, long handle);
|
||||
}
|
||||
@@ -86,7 +86,7 @@ public class StatisticsCollector {
|
||||
// Collect histogram data
|
||||
for(HistogramType histogramType : HistogramType.values()) {
|
||||
HistogramData histogramData =
|
||||
statistics.geHistogramData(histogramType);
|
||||
statistics.getHistogramData(histogramType);
|
||||
statsCallback.histogramCallback(histogramType, histogramData);
|
||||
}
|
||||
|
||||
|
||||
@@ -48,6 +48,36 @@ public class LoggerTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void warnLogger() throws RocksDBException {
|
||||
final AtomicInteger logMessageCounter = new AtomicInteger();
|
||||
try (final Options options = new Options().
|
||||
setInfoLogLevel(InfoLogLevel.WARN_LEVEL).
|
||||
setCreateIfMissing(true);
|
||||
|
||||
final Logger logger = new Logger(options) {
|
||||
// Create new logger with max log level passed by options
|
||||
@Override
|
||||
protected void log(InfoLogLevel infoLogLevel, String logMsg) {
|
||||
assertThat(logMsg).isNotNull();
|
||||
assertThat(logMsg.length()).isGreaterThan(0);
|
||||
logMessageCounter.incrementAndGet();
|
||||
}
|
||||
}
|
||||
) {
|
||||
|
||||
// Set custom logger to options
|
||||
options.setLogger(logger);
|
||||
|
||||
try (final RocksDB db = RocksDB.open(options,
|
||||
dbFolder.getRoot().getAbsolutePath())) {
|
||||
// there should be zero messages
|
||||
// using warn level as log level.
|
||||
assertThat(logMessageCounter.get()).isEqualTo(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void fatalLogger() throws RocksDBException {
|
||||
@@ -119,6 +149,30 @@ public class LoggerTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setWarnLogLevel() {
|
||||
final AtomicInteger logMessageCounter = new AtomicInteger();
|
||||
try (final Options options = new Options().
|
||||
setInfoLogLevel(InfoLogLevel.FATAL_LEVEL).
|
||||
setCreateIfMissing(true);
|
||||
final Logger logger = new Logger(options) {
|
||||
// Create new logger with max log level passed by options
|
||||
@Override
|
||||
protected void log(InfoLogLevel infoLogLevel, String logMsg) {
|
||||
assertThat(logMsg).isNotNull();
|
||||
assertThat(logMsg.length()).isGreaterThan(0);
|
||||
logMessageCounter.incrementAndGet();
|
||||
}
|
||||
}
|
||||
) {
|
||||
assertThat(logger.infoLogLevel()).
|
||||
isEqualTo(InfoLogLevel.FATAL_LEVEL);
|
||||
logger.setInfoLogLevel(InfoLogLevel.WARN_LEVEL);
|
||||
assertThat(logger.infoLogLevel()).
|
||||
isEqualTo(InfoLogLevel.WARN_LEVEL);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setInfoLogLevel() {
|
||||
final AtomicInteger logMessageCounter = new AtomicInteger();
|
||||
|
||||
@@ -637,6 +637,19 @@ public class RocksDBTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void pauseContinueBackgroundWork() throws RocksDBException {
|
||||
try (final Options options = new Options().setCreateIfMissing(true);
|
||||
final RocksDB db = RocksDB.open(options,
|
||||
dbFolder.getRoot().getAbsolutePath())
|
||||
) {
|
||||
db.pauseBackgroundWork();
|
||||
db.continueBackgroundWork();
|
||||
db.pauseBackgroundWork();
|
||||
db.continueBackgroundWork();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void enableDisableFileDeletions() throws RocksDBException {
|
||||
try (final Options options = new Options().setCreateIfMissing(true);
|
||||
|
||||
@@ -80,6 +80,7 @@ namespace port {
|
||||
// For use at db/file_indexer.h kLevelMaxIndex
|
||||
const int kMaxInt32 = std::numeric_limits<int32_t>::max();
|
||||
const uint64_t kMaxUint64 = std::numeric_limits<uint64_t>::max();
|
||||
const int64_t kMaxInt64 = std::numeric_limits<int64_t>::max();
|
||||
const size_t kMaxSizet = std::numeric_limits<size_t>::max();
|
||||
|
||||
static const bool kLittleEndian = PLATFORM_IS_LITTLE_ENDIAN;
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@
|
||||
#include "port/stack_trace.h"
|
||||
|
||||
#if defined(ROCKSDB_LITE) || !(defined(ROCKSDB_BACKTRACE) || defined(OS_MACOSX)) || \
|
||||
defined(CYGWIN)
|
||||
defined(CYGWIN) || defined(OS_FREEBSD)
|
||||
|
||||
// noop
|
||||
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
// Copyright (c) 2011-present, 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 <mutex>
|
||||
|
||||
#include <rocksdb/env.h>
|
||||
#include "port/win/env_win.h"
|
||||
|
||||
namespace rocksdb {
|
||||
namespace port {
|
||||
|
||||
// We choose to create this on the heap and using std::once for the following
|
||||
// reasons
|
||||
// 1) Currently available MS compiler does not implement atomic C++11
|
||||
// initialization of
|
||||
// function local statics
|
||||
// 2) We choose not to destroy the env because joining the threads from the
|
||||
// system loader
|
||||
// which destroys the statics (same as from DLLMain) creates a system loader
|
||||
// dead-lock.
|
||||
// in this manner any remaining threads are terminated OK.
|
||||
namespace {
|
||||
std::once_flag winenv_once_flag;
|
||||
Env* envptr;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
Env* Env::Default() {
|
||||
using namespace port;
|
||||
std::call_once(winenv_once_flag, []() { envptr = new WinEnv(); });
|
||||
return envptr;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+903
-2108
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,276 @@
|
||||
// Copyright (c) 2011-present, 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.
|
||||
//
|
||||
// An Env is an interface used by the rocksdb implementation to access
|
||||
// operating system functionality like the filesystem etc. Callers
|
||||
// may wish to provide a custom Env object when opening a database to
|
||||
// get fine gain control; e.g., to rate limit file system operations.
|
||||
//
|
||||
// All Env implementations are safe for concurrent access from
|
||||
// multiple threads without any external synchronization.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <rocksdb/env.h>
|
||||
#include "util/threadpool.h"
|
||||
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
|
||||
namespace rocksdb {
|
||||
namespace port {
|
||||
|
||||
// Currently not designed for inheritance but rather a replacement
|
||||
class WinEnvThreads {
|
||||
public:
|
||||
|
||||
explicit WinEnvThreads(Env* hosted_env);
|
||||
|
||||
~WinEnvThreads();
|
||||
|
||||
WinEnvThreads(const WinEnvThreads&) = delete;
|
||||
WinEnvThreads& operator=(const WinEnvThreads&) = delete;
|
||||
|
||||
void Schedule(void(*function)(void*), void* arg, Env::Priority pri,
|
||||
void* tag,
|
||||
void(*unschedFunction)(void* arg));
|
||||
|
||||
int UnSchedule(void* arg, Env::Priority pri);
|
||||
|
||||
void StartThread(void(*function)(void* arg), void* arg);
|
||||
|
||||
void WaitForJoin();
|
||||
|
||||
unsigned int GetThreadPoolQueueLen(Env::Priority pri) const;
|
||||
|
||||
static uint64_t gettid();
|
||||
|
||||
uint64_t GetThreadID() const;
|
||||
|
||||
void SleepForMicroseconds(int micros);
|
||||
|
||||
// Allow increasing the number of worker threads.
|
||||
void SetBackgroundThreads(int num, Env::Priority pri);
|
||||
|
||||
void IncBackgroundThreadsIfNeeded(int num, Env::Priority pri);
|
||||
|
||||
private:
|
||||
|
||||
Env* hosted_env_;
|
||||
mutable std::mutex mu_;
|
||||
std::vector<ThreadPool> thread_pools_;
|
||||
std::vector<std::thread> threads_to_join_;
|
||||
|
||||
};
|
||||
|
||||
// Designed for inheritance so can be re-used
|
||||
// but certain parts replaced
|
||||
class WinEnvIO {
|
||||
public:
|
||||
explicit WinEnvIO(Env* hosted_env);
|
||||
|
||||
virtual ~WinEnvIO();
|
||||
|
||||
virtual Status DeleteFile(const std::string& fname);
|
||||
|
||||
virtual Status GetCurrentTime(int64_t* unix_time);
|
||||
|
||||
virtual Status NewSequentialFile(const std::string& fname,
|
||||
std::unique_ptr<SequentialFile>* result,
|
||||
const EnvOptions& options);
|
||||
|
||||
virtual Status NewRandomAccessFile(const std::string& fname,
|
||||
std::unique_ptr<RandomAccessFile>* result,
|
||||
const EnvOptions& options);
|
||||
|
||||
virtual Status NewWritableFile(const std::string& fname,
|
||||
std::unique_ptr<WritableFile>* result,
|
||||
const EnvOptions& options);
|
||||
|
||||
virtual Status NewDirectory(const std::string& name,
|
||||
std::unique_ptr<Directory>* result);
|
||||
|
||||
virtual Status FileExists(const std::string& fname);
|
||||
|
||||
virtual Status GetChildren(const std::string& dir,
|
||||
std::vector<std::string>* result);
|
||||
|
||||
virtual Status CreateDir(const std::string& name);
|
||||
|
||||
virtual Status CreateDirIfMissing(const std::string& name);
|
||||
|
||||
virtual Status DeleteDir(const std::string& name);
|
||||
|
||||
virtual Status GetFileSize(const std::string& fname,
|
||||
uint64_t* size);
|
||||
|
||||
static uint64_t FileTimeToUnixTime(const FILETIME& ftTime);
|
||||
|
||||
virtual Status GetFileModificationTime(const std::string& fname,
|
||||
uint64_t* file_mtime);
|
||||
|
||||
virtual Status RenameFile(const std::string& src,
|
||||
const std::string& target);
|
||||
|
||||
virtual Status LinkFile(const std::string& src,
|
||||
const std::string& target);
|
||||
|
||||
virtual Status LockFile(const std::string& lockFname,
|
||||
FileLock** lock);
|
||||
|
||||
virtual Status UnlockFile(FileLock* lock);
|
||||
|
||||
virtual Status GetTestDirectory(std::string* result);
|
||||
|
||||
virtual Status NewLogger(const std::string& fname,
|
||||
std::shared_ptr<Logger>* result);
|
||||
|
||||
virtual uint64_t NowMicros();
|
||||
|
||||
virtual uint64_t NowNanos();
|
||||
|
||||
virtual Status GetHostName(char* name, uint64_t len);
|
||||
|
||||
virtual Status GetAbsolutePath(const std::string& db_path,
|
||||
std::string* output_path);
|
||||
|
||||
virtual std::string TimeToString(uint64_t secondsSince1970);
|
||||
|
||||
virtual EnvOptions OptimizeForLogWrite(const EnvOptions& env_options,
|
||||
const DBOptions& db_options) const;
|
||||
|
||||
virtual EnvOptions OptimizeForManifestWrite(
|
||||
const EnvOptions& env_options) const;
|
||||
|
||||
size_t GetPageSize() const { return page_size_; }
|
||||
|
||||
size_t GetAllocationGranularity() const { return allocation_granularity_; }
|
||||
|
||||
uint64_t GetPerfCounterFrequency() const { return perf_counter_frequency_; }
|
||||
|
||||
private:
|
||||
// Returns true iff the named directory exists and is a directory.
|
||||
virtual bool DirExists(const std::string& dname);
|
||||
|
||||
typedef VOID(WINAPI * FnGetSystemTimePreciseAsFileTime)(LPFILETIME);
|
||||
|
||||
Env* hosted_env_;
|
||||
size_t page_size_;
|
||||
size_t allocation_granularity_;
|
||||
uint64_t perf_counter_frequency_;
|
||||
FnGetSystemTimePreciseAsFileTime GetSystemTimePreciseAsFileTime_;
|
||||
};
|
||||
|
||||
class WinEnv : public Env {
|
||||
public:
|
||||
WinEnv();
|
||||
|
||||
~WinEnv();
|
||||
|
||||
Status DeleteFile(const std::string& fname) override;
|
||||
|
||||
Status GetCurrentTime(int64_t* unix_time) override;
|
||||
|
||||
Status NewSequentialFile(const std::string& fname,
|
||||
std::unique_ptr<SequentialFile>* result,
|
||||
const EnvOptions& options) override;
|
||||
|
||||
Status NewRandomAccessFile(const std::string& fname,
|
||||
std::unique_ptr<RandomAccessFile>* result,
|
||||
const EnvOptions& options) override;
|
||||
|
||||
Status NewWritableFile(const std::string& fname,
|
||||
std::unique_ptr<WritableFile>* result,
|
||||
const EnvOptions& options) override;
|
||||
|
||||
Status NewDirectory(const std::string& name,
|
||||
std::unique_ptr<Directory>* result) override;
|
||||
|
||||
Status FileExists(const std::string& fname) override;
|
||||
|
||||
Status GetChildren(const std::string& dir,
|
||||
std::vector<std::string>* result) override;
|
||||
|
||||
Status CreateDir(const std::string& name) override;
|
||||
|
||||
Status CreateDirIfMissing(const std::string& name) override;
|
||||
|
||||
Status DeleteDir(const std::string& name) override;
|
||||
|
||||
Status GetFileSize(const std::string& fname,
|
||||
uint64_t* size) override;
|
||||
|
||||
Status GetFileModificationTime(const std::string& fname,
|
||||
uint64_t* file_mtime) override;
|
||||
|
||||
Status RenameFile(const std::string& src,
|
||||
const std::string& target) override;
|
||||
|
||||
Status LinkFile(const std::string& src,
|
||||
const std::string& target) override;
|
||||
|
||||
Status LockFile(const std::string& lockFname,
|
||||
FileLock** lock) override;
|
||||
|
||||
Status UnlockFile(FileLock* lock) override;
|
||||
|
||||
Status GetTestDirectory(std::string* result) override;
|
||||
|
||||
Status NewLogger(const std::string& fname,
|
||||
std::shared_ptr<Logger>* result) override;
|
||||
|
||||
uint64_t NowMicros() override;
|
||||
|
||||
uint64_t NowNanos() override;
|
||||
|
||||
Status GetHostName(char* name, uint64_t len) override;
|
||||
|
||||
Status GetAbsolutePath(const std::string& db_path,
|
||||
std::string* output_path) override;
|
||||
|
||||
std::string TimeToString(uint64_t secondsSince1970) override;
|
||||
|
||||
Status GetThreadList(
|
||||
std::vector<ThreadStatus>* thread_list) override;
|
||||
|
||||
void Schedule(void(*function)(void*), void* arg, Env::Priority pri,
|
||||
void* tag,
|
||||
void(*unschedFunction)(void* arg)) override;
|
||||
|
||||
int UnSchedule(void* arg, Env::Priority pri) override;
|
||||
|
||||
void StartThread(void(*function)(void* arg), void* arg) override;
|
||||
|
||||
void WaitForJoin();
|
||||
|
||||
unsigned int GetThreadPoolQueueLen(Env::Priority pri) const override;
|
||||
|
||||
uint64_t GetThreadID() const override;
|
||||
|
||||
void SleepForMicroseconds(int micros) override;
|
||||
|
||||
// Allow increasing the number of worker threads.
|
||||
void SetBackgroundThreads(int num, Env::Priority pri) override;
|
||||
|
||||
void IncBackgroundThreadsIfNeeded(int num, Env::Priority pri) override;
|
||||
|
||||
EnvOptions OptimizeForLogWrite(const EnvOptions& env_options,
|
||||
const DBOptions& db_options) const override;
|
||||
|
||||
EnvOptions OptimizeForManifestWrite(
|
||||
const EnvOptions& env_options) const override;
|
||||
|
||||
private:
|
||||
|
||||
WinEnvIO winenv_io_;
|
||||
WinEnvThreads winenv_threads_;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,963 @@
|
||||
// Copyright (c) 2011-present, 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 "port/win/io_win.h"
|
||||
|
||||
#include "util/sync_point.h"
|
||||
#include "util/coding.h"
|
||||
#include "util/iostats_context_imp.h"
|
||||
#include "util/sync_point.h"
|
||||
#include "util/aligned_buffer.h"
|
||||
|
||||
|
||||
namespace rocksdb {
|
||||
namespace port {
|
||||
|
||||
std::string GetWindowsErrSz(DWORD err) {
|
||||
LPSTR lpMsgBuf;
|
||||
FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
|
||||
FORMAT_MESSAGE_IGNORE_INSERTS,
|
||||
NULL, err,
|
||||
0, // Default language
|
||||
reinterpret_cast<LPSTR>(&lpMsgBuf), 0, NULL);
|
||||
|
||||
std::string Err = lpMsgBuf;
|
||||
LocalFree(lpMsgBuf);
|
||||
return Err;
|
||||
}
|
||||
|
||||
// We preserve the original name of this interface to denote the original idea
|
||||
// behind it.
|
||||
// All reads happen by a specified offset and pwrite interface does not change
|
||||
// the position of the file pointer. Judging from the man page and errno it does
|
||||
// execute
|
||||
// lseek atomically to return the position of the file back where it was.
|
||||
// WriteFile() does not
|
||||
// have this capability. Therefore, for both pread and pwrite the pointer is
|
||||
// advanced to the next position
|
||||
// which is fine for writes because they are (should be) sequential.
|
||||
// Because all the reads/writes happen by the specified offset, the caller in
|
||||
// theory should not
|
||||
// rely on the current file offset.
|
||||
SSIZE_T pwrite(HANDLE hFile, const char* src, size_t numBytes,
|
||||
uint64_t offset) {
|
||||
assert(numBytes <= std::numeric_limits<DWORD>::max());
|
||||
OVERLAPPED overlapped = { 0 };
|
||||
ULARGE_INTEGER offsetUnion;
|
||||
offsetUnion.QuadPart = offset;
|
||||
|
||||
overlapped.Offset = offsetUnion.LowPart;
|
||||
overlapped.OffsetHigh = offsetUnion.HighPart;
|
||||
|
||||
SSIZE_T result = 0;
|
||||
|
||||
unsigned long bytesWritten = 0;
|
||||
|
||||
if (FALSE == WriteFile(hFile, src, static_cast<DWORD>(numBytes), &bytesWritten,
|
||||
&overlapped)) {
|
||||
result = -1;
|
||||
} else {
|
||||
result = bytesWritten;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// See comments for pwrite above
|
||||
SSIZE_T pread(HANDLE hFile, char* src, size_t numBytes, uint64_t offset) {
|
||||
assert(numBytes <= std::numeric_limits<DWORD>::max());
|
||||
OVERLAPPED overlapped = { 0 };
|
||||
ULARGE_INTEGER offsetUnion;
|
||||
offsetUnion.QuadPart = offset;
|
||||
|
||||
overlapped.Offset = offsetUnion.LowPart;
|
||||
overlapped.OffsetHigh = offsetUnion.HighPart;
|
||||
|
||||
SSIZE_T result = 0;
|
||||
|
||||
unsigned long bytesRead = 0;
|
||||
|
||||
if (FALSE == ReadFile(hFile, src, static_cast<DWORD>(numBytes), &bytesRead,
|
||||
&overlapped)) {
|
||||
return -1;
|
||||
} else {
|
||||
result = bytesRead;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// SetFileInformationByHandle() is capable of fast pre-allocates.
|
||||
// However, this does not change the file end position unless the file is
|
||||
// truncated and the pre-allocated space is not considered filled with zeros.
|
||||
Status fallocate(const std::string& filename, HANDLE hFile,
|
||||
uint64_t to_size) {
|
||||
Status status;
|
||||
|
||||
FILE_ALLOCATION_INFO alloc_info;
|
||||
alloc_info.AllocationSize.QuadPart = to_size;
|
||||
|
||||
if (!SetFileInformationByHandle(hFile, FileAllocationInfo, &alloc_info,
|
||||
sizeof(FILE_ALLOCATION_INFO))) {
|
||||
auto lastError = GetLastError();
|
||||
status = IOErrorFromWindowsError(
|
||||
"Failed to pre-allocate space: " + filename, lastError);
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
Status ftruncate(const std::string& filename, HANDLE hFile,
|
||||
uint64_t toSize) {
|
||||
Status status;
|
||||
|
||||
FILE_END_OF_FILE_INFO end_of_file;
|
||||
end_of_file.EndOfFile.QuadPart = toSize;
|
||||
|
||||
if (!SetFileInformationByHandle(hFile, FileEndOfFileInfo, &end_of_file,
|
||||
sizeof(FILE_END_OF_FILE_INFO))) {
|
||||
auto lastError = GetLastError();
|
||||
status = IOErrorFromWindowsError("Failed to Set end of file: " + filename,
|
||||
lastError);
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
size_t GetUniqueIdFromFile(HANDLE hFile, char* id, size_t max_size) {
|
||||
|
||||
if (max_size < kMaxVarint64Length * 3) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
BY_HANDLE_FILE_INFORMATION FileInfo;
|
||||
|
||||
BOOL result = GetFileInformationByHandle(hFile, &FileInfo);
|
||||
|
||||
TEST_SYNC_POINT_CALLBACK("GetUniqueIdFromFile:FS_IOC_GETVERSION", &result);
|
||||
|
||||
if (!result) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
char* rid = id;
|
||||
rid = EncodeVarint64(rid, uint64_t(FileInfo.dwVolumeSerialNumber));
|
||||
rid = EncodeVarint64(rid, uint64_t(FileInfo.nFileIndexHigh));
|
||||
rid = EncodeVarint64(rid, uint64_t(FileInfo.nFileIndexLow));
|
||||
|
||||
assert(rid >= id);
|
||||
return static_cast<size_t>(rid - id);
|
||||
}
|
||||
|
||||
WinMmapReadableFile::WinMmapReadableFile(const std::string& fileName, HANDLE hFile, HANDLE hMap,
|
||||
const void* mapped_region, size_t length)
|
||||
: fileName_(fileName),
|
||||
hFile_(hFile),
|
||||
hMap_(hMap),
|
||||
mapped_region_(mapped_region),
|
||||
length_(length) {}
|
||||
|
||||
WinMmapReadableFile::~WinMmapReadableFile() {
|
||||
BOOL ret = ::UnmapViewOfFile(mapped_region_);
|
||||
assert(ret);
|
||||
|
||||
ret = ::CloseHandle(hMap_);
|
||||
assert(ret);
|
||||
|
||||
ret = ::CloseHandle(hFile_);
|
||||
assert(ret);
|
||||
}
|
||||
|
||||
Status WinMmapReadableFile::Read(uint64_t offset, size_t n, Slice* result,
|
||||
char* scratch) const {
|
||||
Status s;
|
||||
|
||||
if (offset > length_) {
|
||||
*result = Slice();
|
||||
return IOError(fileName_, EINVAL);
|
||||
} else if (offset + n > length_) {
|
||||
n = length_ - offset;
|
||||
}
|
||||
*result =
|
||||
Slice(reinterpret_cast<const char*>(mapped_region_)+offset, n);
|
||||
return s;
|
||||
}
|
||||
|
||||
Status WinMmapReadableFile::InvalidateCache(size_t offset, size_t length) {
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
size_t WinMmapReadableFile::GetUniqueId(char* id, size_t max_size) const {
|
||||
return GetUniqueIdFromFile(hFile_, id, max_size);
|
||||
}
|
||||
|
||||
// Can only truncate or reserve to a sector size aligned if
|
||||
// used on files that are opened with Unbuffered I/O
|
||||
Status WinMmapFile::TruncateFile(uint64_t toSize) {
|
||||
return ftruncate(filename_, hFile_, toSize);
|
||||
}
|
||||
|
||||
Status WinMmapFile::UnmapCurrentRegion() {
|
||||
Status status;
|
||||
|
||||
if (mapped_begin_ != nullptr) {
|
||||
if (!::UnmapViewOfFile(mapped_begin_)) {
|
||||
status = IOErrorFromWindowsError(
|
||||
"Failed to unmap file view: " + filename_, GetLastError());
|
||||
}
|
||||
|
||||
// Move on to the next portion of the file
|
||||
file_offset_ += view_size_;
|
||||
|
||||
// UnmapView automatically sends data to disk but not the metadata
|
||||
// which is good and provides some equivalent of fdatasync() on Linux
|
||||
// therefore, we donot need separate flag for metadata
|
||||
mapped_begin_ = nullptr;
|
||||
mapped_end_ = nullptr;
|
||||
dst_ = nullptr;
|
||||
|
||||
last_sync_ = nullptr;
|
||||
pending_sync_ = false;
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
Status WinMmapFile::MapNewRegion() {
|
||||
|
||||
Status status;
|
||||
|
||||
assert(mapped_begin_ == nullptr);
|
||||
|
||||
size_t minDiskSize = file_offset_ + view_size_;
|
||||
|
||||
if (minDiskSize > reserved_size_) {
|
||||
status = Allocate(file_offset_, view_size_);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
}
|
||||
|
||||
// Need to remap
|
||||
if (hMap_ == NULL || reserved_size_ > mapping_size_) {
|
||||
|
||||
if (hMap_ != NULL) {
|
||||
// Unmap the previous one
|
||||
BOOL ret = ::CloseHandle(hMap_);
|
||||
assert(ret);
|
||||
hMap_ = NULL;
|
||||
}
|
||||
|
||||
ULARGE_INTEGER mappingSize;
|
||||
mappingSize.QuadPart = reserved_size_;
|
||||
|
||||
hMap_ = CreateFileMappingA(
|
||||
hFile_,
|
||||
NULL, // Security attributes
|
||||
PAGE_READWRITE, // There is not a write only mode for mapping
|
||||
mappingSize.HighPart, // Enable mapping the whole file but the actual
|
||||
// amount mapped is determined by MapViewOfFile
|
||||
mappingSize.LowPart,
|
||||
NULL); // Mapping name
|
||||
|
||||
if (NULL == hMap_) {
|
||||
return IOErrorFromWindowsError(
|
||||
"WindowsMmapFile failed to create file mapping for: " + filename_,
|
||||
GetLastError());
|
||||
}
|
||||
|
||||
mapping_size_ = reserved_size_;
|
||||
}
|
||||
|
||||
ULARGE_INTEGER offset;
|
||||
offset.QuadPart = file_offset_;
|
||||
|
||||
// View must begin at the granularity aligned offset
|
||||
mapped_begin_ = reinterpret_cast<char*>(
|
||||
MapViewOfFileEx(hMap_, FILE_MAP_WRITE, offset.HighPart, offset.LowPart,
|
||||
view_size_, NULL));
|
||||
|
||||
if (!mapped_begin_) {
|
||||
status = IOErrorFromWindowsError(
|
||||
"WindowsMmapFile failed to map file view: " + filename_,
|
||||
GetLastError());
|
||||
} else {
|
||||
mapped_end_ = mapped_begin_ + view_size_;
|
||||
dst_ = mapped_begin_;
|
||||
last_sync_ = mapped_begin_;
|
||||
pending_sync_ = false;
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
Status WinMmapFile::PreallocateInternal(uint64_t spaceToReserve) {
|
||||
return fallocate(filename_, hFile_, spaceToReserve);
|
||||
}
|
||||
|
||||
WinMmapFile::WinMmapFile(const std::string& fname, HANDLE hFile, size_t page_size,
|
||||
size_t allocation_granularity, const EnvOptions& options)
|
||||
: filename_(fname),
|
||||
hFile_(hFile),
|
||||
hMap_(NULL),
|
||||
page_size_(page_size),
|
||||
allocation_granularity_(allocation_granularity),
|
||||
reserved_size_(0),
|
||||
mapping_size_(0),
|
||||
view_size_(0),
|
||||
mapped_begin_(nullptr),
|
||||
mapped_end_(nullptr),
|
||||
dst_(nullptr),
|
||||
last_sync_(nullptr),
|
||||
file_offset_(0),
|
||||
pending_sync_(false) {
|
||||
// Allocation granularity must be obtained from GetSystemInfo() and must be
|
||||
// a power of two.
|
||||
assert(allocation_granularity > 0);
|
||||
assert((allocation_granularity & (allocation_granularity - 1)) == 0);
|
||||
|
||||
assert(page_size > 0);
|
||||
assert((page_size & (page_size - 1)) == 0);
|
||||
|
||||
// Only for memory mapped writes
|
||||
assert(options.use_mmap_writes);
|
||||
|
||||
// View size must be both the multiple of allocation_granularity AND the
|
||||
// page size and the granularity is usually a multiple of a page size.
|
||||
const size_t viewSize = 32 * 1024; // 32Kb similar to the Windows File Cache in buffered mode
|
||||
view_size_ = Roundup(viewSize, allocation_granularity_);
|
||||
}
|
||||
|
||||
WinMmapFile::~WinMmapFile() {
|
||||
if (hFile_) {
|
||||
this->Close();
|
||||
}
|
||||
}
|
||||
|
||||
Status WinMmapFile::Append(const Slice& data) {
|
||||
const char* src = data.data();
|
||||
size_t left = data.size();
|
||||
|
||||
while (left > 0) {
|
||||
assert(mapped_begin_ <= dst_);
|
||||
size_t avail = mapped_end_ - dst_;
|
||||
|
||||
if (avail == 0) {
|
||||
Status s = UnmapCurrentRegion();
|
||||
if (s.ok()) {
|
||||
s = MapNewRegion();
|
||||
}
|
||||
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
} else {
|
||||
size_t n = std::min(left, avail);
|
||||
memcpy(dst_, src, n);
|
||||
dst_ += n;
|
||||
src += n;
|
||||
left -= n;
|
||||
pending_sync_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Now make sure that the last partial page is padded with zeros if needed
|
||||
size_t bytesToPad = Roundup(size_t(dst_), page_size_) - size_t(dst_);
|
||||
if (bytesToPad > 0) {
|
||||
memset(dst_, 0, bytesToPad);
|
||||
}
|
||||
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
// Means Close() will properly take care of truncate
|
||||
// and it does not need any additional information
|
||||
Status WinMmapFile::Truncate(uint64_t size) {
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status WinMmapFile::Close() {
|
||||
Status s;
|
||||
|
||||
assert(NULL != hFile_);
|
||||
|
||||
// We truncate to the precise size so no
|
||||
// uninitialized data at the end. SetEndOfFile
|
||||
// which we use does not write zeros and it is good.
|
||||
uint64_t targetSize = GetFileSize();
|
||||
|
||||
if (mapped_begin_ != nullptr) {
|
||||
// Sync before unmapping to make sure everything
|
||||
// is on disk and there is not a lazy writing
|
||||
// so we are deterministic with the tests
|
||||
Sync();
|
||||
s = UnmapCurrentRegion();
|
||||
}
|
||||
|
||||
if (NULL != hMap_) {
|
||||
BOOL ret = ::CloseHandle(hMap_);
|
||||
if (!ret && s.ok()) {
|
||||
auto lastError = GetLastError();
|
||||
s = IOErrorFromWindowsError(
|
||||
"Failed to Close mapping for file: " + filename_, lastError);
|
||||
}
|
||||
|
||||
hMap_ = NULL;
|
||||
}
|
||||
|
||||
if (hFile_ != NULL) {
|
||||
|
||||
TruncateFile(targetSize);
|
||||
|
||||
BOOL ret = ::CloseHandle(hFile_);
|
||||
hFile_ = NULL;
|
||||
|
||||
if (!ret && s.ok()) {
|
||||
auto lastError = GetLastError();
|
||||
s = IOErrorFromWindowsError(
|
||||
"Failed to close file map handle: " + filename_, lastError);
|
||||
}
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
Status WinMmapFile::Flush() { return Status::OK(); }
|
||||
|
||||
// Flush only data
|
||||
Status WinMmapFile::Sync() {
|
||||
Status s;
|
||||
|
||||
// Some writes occurred since last sync
|
||||
if (dst_ > last_sync_) {
|
||||
assert(mapped_begin_);
|
||||
assert(dst_);
|
||||
assert(dst_ > mapped_begin_);
|
||||
assert(dst_ < mapped_end_);
|
||||
|
||||
size_t page_begin =
|
||||
TruncateToPageBoundary(page_size_, last_sync_ - mapped_begin_);
|
||||
size_t page_end =
|
||||
TruncateToPageBoundary(page_size_, dst_ - mapped_begin_ - 1);
|
||||
|
||||
// Flush only the amount of that is a multiple of pages
|
||||
if (!::FlushViewOfFile(mapped_begin_ + page_begin,
|
||||
(page_end - page_begin) + page_size_)) {
|
||||
s = IOErrorFromWindowsError("Failed to FlushViewOfFile: " + filename_,
|
||||
GetLastError());
|
||||
} else {
|
||||
last_sync_ = dst_;
|
||||
}
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
/**
|
||||
* Flush data as well as metadata to stable storage.
|
||||
*/
|
||||
Status WinMmapFile::Fsync() {
|
||||
Status s = Sync();
|
||||
|
||||
// Flush metadata
|
||||
if (s.ok() && pending_sync_) {
|
||||
if (!::FlushFileBuffers(hFile_)) {
|
||||
s = IOErrorFromWindowsError("Failed to FlushFileBuffers: " + filename_,
|
||||
GetLastError());
|
||||
}
|
||||
pending_sync_ = false;
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the size of valid data in the file. This will not match the
|
||||
* size that is returned from the filesystem because we use mmap
|
||||
* to extend file by map_size every time.
|
||||
*/
|
||||
uint64_t WinMmapFile::GetFileSize() {
|
||||
size_t used = dst_ - mapped_begin_;
|
||||
return file_offset_ + used;
|
||||
}
|
||||
|
||||
Status WinMmapFile::InvalidateCache(size_t offset, size_t length) {
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status WinMmapFile::Allocate(uint64_t offset, uint64_t len) {
|
||||
Status status;
|
||||
TEST_KILL_RANDOM("WinMmapFile::Allocate", rocksdb_kill_odds);
|
||||
|
||||
// Make sure that we reserve an aligned amount of space
|
||||
// since the reservation block size is driven outside so we want
|
||||
// to check if we are ok with reservation here
|
||||
size_t spaceToReserve = Roundup(offset + len, view_size_);
|
||||
// Nothing to do
|
||||
if (spaceToReserve <= reserved_size_) {
|
||||
return status;
|
||||
}
|
||||
|
||||
IOSTATS_TIMER_GUARD(allocate_nanos);
|
||||
status = PreallocateInternal(spaceToReserve);
|
||||
if (status.ok()) {
|
||||
reserved_size_ = spaceToReserve;
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
size_t WinMmapFile::GetUniqueId(char* id, size_t max_size) const {
|
||||
return GetUniqueIdFromFile(hFile_, id, max_size);
|
||||
}
|
||||
|
||||
WinSequentialFile::WinSequentialFile(const std::string& fname, HANDLE f,
|
||||
const EnvOptions& options)
|
||||
: filename_(fname),
|
||||
file_(f),
|
||||
use_os_buffer_(options.use_os_buffer)
|
||||
{}
|
||||
|
||||
WinSequentialFile::~WinSequentialFile() {
|
||||
assert(file_ != INVALID_HANDLE_VALUE);
|
||||
CloseHandle(file_);
|
||||
}
|
||||
|
||||
Status WinSequentialFile::Read(size_t n, Slice* result, char* scratch) {
|
||||
Status s;
|
||||
size_t r = 0;
|
||||
|
||||
// Windows ReadFile API accepts a DWORD.
|
||||
// While it is possible to read in a loop if n is > UINT_MAX
|
||||
// it is a highly unlikely case.
|
||||
if (n > UINT_MAX) {
|
||||
return IOErrorFromWindowsError(filename_, ERROR_INVALID_PARAMETER);
|
||||
}
|
||||
|
||||
DWORD bytesToRead = static_cast<DWORD>(n); //cast is safe due to the check above
|
||||
DWORD bytesRead = 0;
|
||||
BOOL ret = ReadFile(file_, scratch, bytesToRead, &bytesRead, NULL);
|
||||
if (ret == TRUE) {
|
||||
r = bytesRead;
|
||||
} else {
|
||||
return IOErrorFromWindowsError(filename_, GetLastError());
|
||||
}
|
||||
|
||||
*result = Slice(scratch, r);
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
Status WinSequentialFile::Skip(uint64_t n) {
|
||||
// Can't handle more than signed max as SetFilePointerEx accepts a signed 64-bit
|
||||
// integer. As such it is a highly unlikley case to have n so large.
|
||||
if (n > _I64_MAX) {
|
||||
return IOErrorFromWindowsError(filename_, ERROR_INVALID_PARAMETER);
|
||||
}
|
||||
|
||||
LARGE_INTEGER li;
|
||||
li.QuadPart = static_cast<int64_t>(n); //cast is safe due to the check above
|
||||
BOOL ret = SetFilePointerEx(file_, li, NULL, FILE_CURRENT);
|
||||
if (ret == FALSE) {
|
||||
return IOErrorFromWindowsError(filename_, GetLastError());
|
||||
}
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status WinSequentialFile::InvalidateCache(size_t offset, size_t length) {
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
SSIZE_T WinRandomAccessFile::ReadIntoBuffer(uint64_t user_offset, uint64_t first_page_start,
|
||||
size_t bytes_to_read, size_t& left,
|
||||
AlignedBuffer& buffer, char* dest) const {
|
||||
assert(buffer.CurrentSize() == 0);
|
||||
assert(buffer.Capacity() >= bytes_to_read);
|
||||
|
||||
SSIZE_T read =
|
||||
PositionedReadInternal(buffer.Destination(), bytes_to_read, first_page_start);
|
||||
|
||||
if (read > 0) {
|
||||
buffer.Size(read);
|
||||
|
||||
// Let's figure out how much we read from the users standpoint
|
||||
if ((first_page_start + buffer.CurrentSize()) > user_offset) {
|
||||
assert(first_page_start <= user_offset);
|
||||
size_t buffer_offset = user_offset - first_page_start;
|
||||
read = buffer.Read(dest, buffer_offset, left);
|
||||
} else {
|
||||
read = 0;
|
||||
}
|
||||
left -= read;
|
||||
}
|
||||
return read;
|
||||
}
|
||||
|
||||
SSIZE_T WinRandomAccessFile::ReadIntoOneShotBuffer(uint64_t user_offset, uint64_t first_page_start,
|
||||
size_t bytes_to_read, size_t& left,
|
||||
char* dest) const {
|
||||
AlignedBuffer bigBuffer;
|
||||
bigBuffer.Alignment(buffer_.Alignment());
|
||||
bigBuffer.AllocateNewBuffer(bytes_to_read);
|
||||
|
||||
return ReadIntoBuffer(user_offset, first_page_start, bytes_to_read, left,
|
||||
bigBuffer, dest);
|
||||
}
|
||||
|
||||
SSIZE_T WinRandomAccessFile::ReadIntoInstanceBuffer(uint64_t user_offset,
|
||||
uint64_t first_page_start,
|
||||
size_t bytes_to_read, size_t& left,
|
||||
char* dest) const {
|
||||
SSIZE_T read = ReadIntoBuffer(user_offset, first_page_start, bytes_to_read,
|
||||
left, buffer_, dest);
|
||||
|
||||
if (read > 0) {
|
||||
buffered_start_ = first_page_start;
|
||||
}
|
||||
|
||||
return read;
|
||||
}
|
||||
|
||||
void WinRandomAccessFile::CalculateReadParameters(uint64_t offset, size_t bytes_requested,
|
||||
size_t& actual_bytes_toread,
|
||||
uint64_t& first_page_start) const {
|
||||
|
||||
const size_t alignment = buffer_.Alignment();
|
||||
|
||||
first_page_start = TruncateToPageBoundary(alignment, offset);
|
||||
const uint64_t last_page_start =
|
||||
TruncateToPageBoundary(alignment, offset + bytes_requested - 1);
|
||||
actual_bytes_toread = (last_page_start - first_page_start) + alignment;
|
||||
}
|
||||
|
||||
SSIZE_T WinRandomAccessFile::PositionedReadInternal(char* src, size_t numBytes,
|
||||
uint64_t offset) const {
|
||||
return pread(hFile_, src, numBytes, offset);
|
||||
}
|
||||
|
||||
WinRandomAccessFile::WinRandomAccessFile(const std::string& fname, HANDLE hFile, size_t alignment,
|
||||
const EnvOptions& options)
|
||||
: filename_(fname),
|
||||
hFile_(hFile),
|
||||
use_os_buffer_(options.use_os_buffer),
|
||||
read_ahead_(false),
|
||||
compaction_readahead_size_(options.compaction_readahead_size),
|
||||
random_access_max_buffer_size_(options.random_access_max_buffer_size),
|
||||
buffer_(),
|
||||
buffered_start_(0) {
|
||||
assert(!options.use_mmap_reads);
|
||||
|
||||
// Unbuffered access, use internal buffer for reads
|
||||
if (!use_os_buffer_) {
|
||||
// Do not allocate the buffer either until the first request or
|
||||
// until there is a call to allocate a read-ahead buffer
|
||||
buffer_.Alignment(alignment);
|
||||
}
|
||||
}
|
||||
|
||||
WinRandomAccessFile::~WinRandomAccessFile() {
|
||||
if (hFile_ != NULL && hFile_ != INVALID_HANDLE_VALUE) {
|
||||
::CloseHandle(hFile_);
|
||||
}
|
||||
}
|
||||
|
||||
void WinRandomAccessFile::EnableReadAhead() { this->Hint(SEQUENTIAL); }
|
||||
|
||||
Status WinRandomAccessFile::Read(uint64_t offset, size_t n, Slice* result,
|
||||
char* scratch) const {
|
||||
|
||||
Status s;
|
||||
SSIZE_T r = -1;
|
||||
size_t left = n;
|
||||
char* dest = scratch;
|
||||
|
||||
if (n == 0) {
|
||||
*result = Slice(scratch, 0);
|
||||
return s;
|
||||
}
|
||||
|
||||
// When in unbuffered mode we need to do the following changes:
|
||||
// - use our own aligned buffer
|
||||
// - always read at the offset of that is a multiple of alignment
|
||||
if (!use_os_buffer_) {
|
||||
|
||||
uint64_t first_page_start = 0;
|
||||
size_t actual_bytes_toread = 0;
|
||||
size_t bytes_requested = left;
|
||||
|
||||
if (!read_ahead_ && random_access_max_buffer_size_ == 0) {
|
||||
CalculateReadParameters(offset, bytes_requested, actual_bytes_toread,
|
||||
first_page_start);
|
||||
|
||||
assert(actual_bytes_toread > 0);
|
||||
|
||||
r = ReadIntoOneShotBuffer(offset, first_page_start,
|
||||
actual_bytes_toread, left, dest);
|
||||
} else {
|
||||
|
||||
std::unique_lock<std::mutex> lock(buffer_mut_);
|
||||
|
||||
// Let's see if at least some of the requested data is already
|
||||
// in the buffer
|
||||
if (offset >= buffered_start_ &&
|
||||
offset < (buffered_start_ + buffer_.CurrentSize())) {
|
||||
size_t buffer_offset = offset - buffered_start_;
|
||||
r = buffer_.Read(dest, buffer_offset, left);
|
||||
assert(r >= 0);
|
||||
|
||||
left -= size_t(r);
|
||||
offset += r;
|
||||
dest += r;
|
||||
}
|
||||
|
||||
// Still some left or none was buffered
|
||||
if (left > 0) {
|
||||
// Figure out the start/end offset for reading and amount to read
|
||||
bytes_requested = left;
|
||||
|
||||
if (read_ahead_ && bytes_requested < compaction_readahead_size_) {
|
||||
bytes_requested = compaction_readahead_size_;
|
||||
}
|
||||
|
||||
CalculateReadParameters(offset, bytes_requested, actual_bytes_toread,
|
||||
first_page_start);
|
||||
|
||||
assert(actual_bytes_toread > 0);
|
||||
|
||||
if (buffer_.Capacity() < actual_bytes_toread) {
|
||||
// If we are in read-ahead mode or the requested size
|
||||
// exceeds max buffer size then use one-shot
|
||||
// big buffer otherwise reallocate main buffer
|
||||
if (read_ahead_ ||
|
||||
(actual_bytes_toread > random_access_max_buffer_size_)) {
|
||||
// Unlock the mutex since we are not using instance buffer
|
||||
lock.unlock();
|
||||
r = ReadIntoOneShotBuffer(offset, first_page_start,
|
||||
actual_bytes_toread, left, dest);
|
||||
} else {
|
||||
buffer_.AllocateNewBuffer(actual_bytes_toread);
|
||||
r = ReadIntoInstanceBuffer(offset, first_page_start,
|
||||
actual_bytes_toread, left, dest);
|
||||
}
|
||||
} else {
|
||||
buffer_.Clear();
|
||||
r = ReadIntoInstanceBuffer(offset, first_page_start,
|
||||
actual_bytes_toread, left, dest);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
r = PositionedReadInternal(scratch, left, offset);
|
||||
if (r > 0) {
|
||||
left -= r;
|
||||
}
|
||||
}
|
||||
|
||||
*result = Slice(scratch, (r < 0) ? 0 : n - left);
|
||||
|
||||
if (r < 0) {
|
||||
s = IOErrorFromLastWindowsError(filename_);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
bool WinRandomAccessFile::ShouldForwardRawRequest() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
void WinRandomAccessFile::Hint(AccessPattern pattern) {
|
||||
if (pattern == SEQUENTIAL && !use_os_buffer_ &&
|
||||
compaction_readahead_size_ > 0) {
|
||||
std::lock_guard<std::mutex> lg(buffer_mut_);
|
||||
if (!read_ahead_) {
|
||||
read_ahead_ = true;
|
||||
// This would allocate read-ahead size + 2 alignments
|
||||
// - one for memory alignment which added implicitly by AlignedBuffer
|
||||
// - We add one more alignment because we will read one alignment more
|
||||
// from disk
|
||||
buffer_.AllocateNewBuffer(compaction_readahead_size_ +
|
||||
buffer_.Alignment());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Status WinRandomAccessFile::InvalidateCache(size_t offset, size_t length) {
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
size_t WinRandomAccessFile::GetUniqueId(char* id, size_t max_size) const {
|
||||
return GetUniqueIdFromFile(hFile_, id, max_size);
|
||||
}
|
||||
|
||||
Status WinWritableFile::PreallocateInternal(uint64_t spaceToReserve) {
|
||||
return fallocate(filename_, hFile_, spaceToReserve);
|
||||
}
|
||||
|
||||
WinWritableFile::WinWritableFile(const std::string& fname, HANDLE hFile, size_t alignment,
|
||||
size_t capacity, const EnvOptions& options)
|
||||
: filename_(fname),
|
||||
hFile_(hFile),
|
||||
use_os_buffer_(options.use_os_buffer),
|
||||
alignment_(alignment),
|
||||
filesize_(0),
|
||||
reservedsize_(0) {
|
||||
assert(!options.use_mmap_writes);
|
||||
}
|
||||
|
||||
WinWritableFile::~WinWritableFile() {
|
||||
if (NULL != hFile_ && INVALID_HANDLE_VALUE != hFile_) {
|
||||
WinWritableFile::Close();
|
||||
}
|
||||
}
|
||||
|
||||
// Indicates if the class makes use of unbuffered I/O
|
||||
bool WinWritableFile::UseOSBuffer() const {
|
||||
return use_os_buffer_;
|
||||
}
|
||||
|
||||
size_t WinWritableFile::GetRequiredBufferAlignment() const {
|
||||
return alignment_;
|
||||
}
|
||||
|
||||
Status WinWritableFile::Append(const Slice& data) {
|
||||
|
||||
// Used for buffered access ONLY
|
||||
assert(use_os_buffer_);
|
||||
assert(data.size() < std::numeric_limits<DWORD>::max());
|
||||
|
||||
Status s;
|
||||
|
||||
DWORD bytesWritten = 0;
|
||||
if (!WriteFile(hFile_, data.data(),
|
||||
static_cast<DWORD>(data.size()), &bytesWritten, NULL)) {
|
||||
auto lastError = GetLastError();
|
||||
s = IOErrorFromWindowsError(
|
||||
"Failed to WriteFile: " + filename_,
|
||||
lastError);
|
||||
} else {
|
||||
assert(size_t(bytesWritten) == data.size());
|
||||
filesize_ += data.size();
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
Status WinWritableFile::PositionedAppend(const Slice& data, uint64_t offset) {
|
||||
Status s;
|
||||
|
||||
SSIZE_T ret = pwrite(hFile_, data.data(), data.size(), offset);
|
||||
|
||||
// Error break
|
||||
if (ret < 0) {
|
||||
auto lastError = GetLastError();
|
||||
s = IOErrorFromWindowsError(
|
||||
"Failed to pwrite for: " + filename_, lastError);
|
||||
} else {
|
||||
// With positional write it is not clear at all
|
||||
// if this actually extends the filesize
|
||||
assert(size_t(ret) == data.size());
|
||||
filesize_ += data.size();
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
// Need to implement this so the file is truncated correctly
|
||||
// when buffered and unbuffered mode
|
||||
Status WinWritableFile::Truncate(uint64_t size) {
|
||||
Status s = ftruncate(filename_, hFile_, size);
|
||||
if (s.ok()) {
|
||||
filesize_ = size;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
Status WinWritableFile::Close() {
|
||||
|
||||
Status s;
|
||||
|
||||
assert(INVALID_HANDLE_VALUE != hFile_);
|
||||
|
||||
if (fsync(hFile_) < 0) {
|
||||
auto lastError = GetLastError();
|
||||
s = IOErrorFromWindowsError("fsync failed at Close() for: " + filename_,
|
||||
lastError);
|
||||
}
|
||||
|
||||
if (FALSE == ::CloseHandle(hFile_)) {
|
||||
auto lastError = GetLastError();
|
||||
s = IOErrorFromWindowsError("CloseHandle failed for: " + filename_,
|
||||
lastError);
|
||||
}
|
||||
|
||||
hFile_ = INVALID_HANDLE_VALUE;
|
||||
return s;
|
||||
}
|
||||
|
||||
// write out the cached data to the OS cache
|
||||
// This is now taken care of the WritableFileWriter
|
||||
Status WinWritableFile::Flush() {
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status WinWritableFile::Sync() {
|
||||
Status s;
|
||||
// Calls flush buffers
|
||||
if (fsync(hFile_) < 0) {
|
||||
auto lastError = GetLastError();
|
||||
s = IOErrorFromWindowsError("fsync failed at Sync() for: " + filename_,
|
||||
lastError);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
Status WinWritableFile::Fsync() { return Sync(); }
|
||||
|
||||
uint64_t WinWritableFile::GetFileSize() {
|
||||
// Double accounting now here with WritableFileWriter
|
||||
// and this size will be wrong when unbuffered access is used
|
||||
// but tests implement their own writable files and do not use WritableFileWrapper
|
||||
// so we need to squeeze a square peg through
|
||||
// a round hole here.
|
||||
return filesize_;
|
||||
}
|
||||
|
||||
Status WinWritableFile::Allocate(uint64_t offset, uint64_t len) {
|
||||
Status status;
|
||||
TEST_KILL_RANDOM("WinWritableFile::Allocate", rocksdb_kill_odds);
|
||||
|
||||
// Make sure that we reserve an aligned amount of space
|
||||
// since the reservation block size is driven outside so we want
|
||||
// to check if we are ok with reservation here
|
||||
size_t spaceToReserve = Roundup(offset + len, alignment_);
|
||||
// Nothing to do
|
||||
if (spaceToReserve <= reservedsize_) {
|
||||
return status;
|
||||
}
|
||||
|
||||
IOSTATS_TIMER_GUARD(allocate_nanos);
|
||||
status = PreallocateInternal(spaceToReserve);
|
||||
if (status.ok()) {
|
||||
reservedsize_ = spaceToReserve;
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
size_t WinWritableFile::GetUniqueId(char* id, size_t max_size) const {
|
||||
return GetUniqueIdFromFile(hFile_, id, max_size);
|
||||
}
|
||||
|
||||
Status WinDirectory::Fsync() { return Status::OK(); }
|
||||
|
||||
WinFileLock::~WinFileLock() {
|
||||
BOOL ret = ::CloseHandle(hFile_);
|
||||
assert(ret);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,359 @@
|
||||
// Copyright (c) 2011-present, 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.
|
||||
#pragma once
|
||||
|
||||
#include <rocksdb/Status.h>
|
||||
#include <rocksdb/env.h>
|
||||
|
||||
#include "util/aligned_buffer.h"
|
||||
|
||||
#include <string>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <Windows.h>
|
||||
|
||||
#include <mutex>
|
||||
|
||||
namespace rocksdb {
|
||||
namespace port {
|
||||
|
||||
std::string GetWindowsErrSz(DWORD err);
|
||||
|
||||
inline Status IOErrorFromWindowsError(const std::string& context, DWORD err) {
|
||||
return Status::IOError(context, GetWindowsErrSz(err));
|
||||
}
|
||||
|
||||
inline Status IOErrorFromLastWindowsError(const std::string& context) {
|
||||
return IOErrorFromWindowsError(context, GetLastError());
|
||||
}
|
||||
|
||||
inline Status IOError(const std::string& context, int err_number) {
|
||||
return Status::IOError(context, strerror(err_number));
|
||||
}
|
||||
|
||||
// Note the below two do not set errno because they are used only here in this
|
||||
// file
|
||||
// on a Windows handle and, therefore, not necessary. Translating GetLastError()
|
||||
// to errno
|
||||
// is a sad business
|
||||
inline int fsync(HANDLE hFile) {
|
||||
if (!FlushFileBuffers(hFile)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
SSIZE_T pwrite(HANDLE hFile, const char* src, size_t numBytes,
|
||||
uint64_t offset);
|
||||
|
||||
SSIZE_T pread(HANDLE hFile, char* src, size_t numBytes, uint64_t offset);
|
||||
|
||||
Status fallocate(const std::string& filename, HANDLE hFile,
|
||||
uint64_t to_size);
|
||||
|
||||
Status ftruncate(const std::string& filename, HANDLE hFile,
|
||||
uint64_t toSize);
|
||||
|
||||
|
||||
size_t GetUniqueIdFromFile(HANDLE hFile, char* id, size_t max_size);
|
||||
|
||||
// mmap() based random-access
|
||||
class WinMmapReadableFile : public RandomAccessFile {
|
||||
const std::string fileName_;
|
||||
HANDLE hFile_;
|
||||
HANDLE hMap_;
|
||||
|
||||
const void* mapped_region_;
|
||||
const size_t length_;
|
||||
|
||||
public:
|
||||
// mapped_region_[0,length-1] contains the mmapped contents of the file.
|
||||
WinMmapReadableFile(const std::string& fileName, HANDLE hFile, HANDLE hMap,
|
||||
const void* mapped_region, size_t length);
|
||||
|
||||
~WinMmapReadableFile();
|
||||
|
||||
virtual Status Read(uint64_t offset, size_t n, Slice* result,
|
||||
char* scratch) const override;
|
||||
|
||||
virtual Status InvalidateCache(size_t offset, size_t length) override;
|
||||
|
||||
virtual size_t GetUniqueId(char* id, size_t max_size) const override;
|
||||
};
|
||||
|
||||
// We preallocate and use memcpy to append new
|
||||
// data to the file. This is safe since we either properly close the
|
||||
// file before reading from it, or for log files, the reading code
|
||||
// knows enough to skip zero suffixes.
|
||||
class WinMmapFile : public WritableFile {
|
||||
private:
|
||||
const std::string filename_;
|
||||
HANDLE hFile_;
|
||||
HANDLE hMap_;
|
||||
|
||||
const size_t page_size_; // We flush the mapping view in page_size
|
||||
// increments. We may decide if this is a memory
|
||||
// page size or SSD page size
|
||||
const size_t
|
||||
allocation_granularity_; // View must start at such a granularity
|
||||
|
||||
size_t reserved_size_; // Preallocated size
|
||||
|
||||
size_t mapping_size_; // The max size of the mapping object
|
||||
// we want to guess the final file size to minimize the remapping
|
||||
size_t view_size_; // How much memory to map into a view at a time
|
||||
|
||||
char* mapped_begin_; // Must begin at the file offset that is aligned with
|
||||
// allocation_granularity_
|
||||
char* mapped_end_;
|
||||
char* dst_; // Where to write next (in range [mapped_begin_,mapped_end_])
|
||||
char* last_sync_; // Where have we synced up to
|
||||
|
||||
uint64_t file_offset_; // Offset of mapped_begin_ in file
|
||||
|
||||
// Do we have unsynced writes?
|
||||
bool pending_sync_;
|
||||
|
||||
// Can only truncate or reserve to a sector size aligned if
|
||||
// used on files that are opened with Unbuffered I/O
|
||||
Status TruncateFile(uint64_t toSize);
|
||||
|
||||
Status UnmapCurrentRegion();
|
||||
|
||||
Status MapNewRegion();
|
||||
|
||||
virtual Status PreallocateInternal(uint64_t spaceToReserve);
|
||||
|
||||
public:
|
||||
|
||||
WinMmapFile(const std::string& fname, HANDLE hFile, size_t page_size,
|
||||
size_t allocation_granularity, const EnvOptions& options);
|
||||
|
||||
~WinMmapFile();
|
||||
|
||||
virtual Status Append(const Slice& data) override;
|
||||
|
||||
// Means Close() will properly take care of truncate
|
||||
// and it does not need any additional information
|
||||
virtual Status Truncate(uint64_t size) override;
|
||||
|
||||
virtual Status Close() override;
|
||||
|
||||
virtual Status Flush() override;
|
||||
|
||||
// Flush only data
|
||||
virtual Status Sync() override;
|
||||
|
||||
/**
|
||||
* Flush data as well as metadata to stable storage.
|
||||
*/
|
||||
virtual Status Fsync() override;
|
||||
|
||||
/**
|
||||
* Get the size of valid data in the file. This will not match the
|
||||
* size that is returned from the filesystem because we use mmap
|
||||
* to extend file by map_size every time.
|
||||
*/
|
||||
virtual uint64_t GetFileSize() override;
|
||||
|
||||
virtual Status InvalidateCache(size_t offset, size_t length) override;
|
||||
|
||||
virtual Status Allocate(uint64_t offset, uint64_t len) override;
|
||||
|
||||
virtual size_t GetUniqueId(char* id, size_t max_size) const override;
|
||||
};
|
||||
|
||||
class WinSequentialFile : public SequentialFile {
|
||||
private:
|
||||
const std::string filename_;
|
||||
HANDLE file_;
|
||||
|
||||
// There is no equivalent of advising away buffered pages as in posix.
|
||||
// To implement this flag we would need to do unbuffered reads which
|
||||
// will need to be aligned (not sure there is a guarantee that the buffer
|
||||
// passed in is aligned).
|
||||
// Hence we currently ignore this flag. It is used only in a few cases
|
||||
// which should not be perf critical.
|
||||
// If perf evaluation finds this to be a problem, we can look into
|
||||
// implementing this.
|
||||
bool use_os_buffer_;
|
||||
|
||||
public:
|
||||
WinSequentialFile(const std::string& fname, HANDLE f,
|
||||
const EnvOptions& options);
|
||||
|
||||
~WinSequentialFile();
|
||||
|
||||
virtual Status Read(size_t n, Slice* result, char* scratch) override;
|
||||
|
||||
virtual Status Skip(uint64_t n) override;
|
||||
|
||||
virtual Status InvalidateCache(size_t offset, size_t length) override;
|
||||
};
|
||||
|
||||
// pread() based random-access
|
||||
class WinRandomAccessFile : public RandomAccessFile {
|
||||
const std::string filename_;
|
||||
HANDLE hFile_;
|
||||
const bool use_os_buffer_;
|
||||
bool read_ahead_;
|
||||
const size_t compaction_readahead_size_;
|
||||
const size_t random_access_max_buffer_size_;
|
||||
mutable std::mutex buffer_mut_;
|
||||
mutable AlignedBuffer buffer_;
|
||||
mutable uint64_t
|
||||
buffered_start_; // file offset set that is currently buffered
|
||||
|
||||
/*
|
||||
* The function reads a requested amount of bytes into the specified aligned
|
||||
* buffer Upon success the function sets the length of the buffer to the
|
||||
* amount of bytes actually read even though it might be less than actually
|
||||
* requested. It then copies the amount of bytes requested by the user (left)
|
||||
* to the user supplied buffer (dest) and reduces left by the amount of bytes
|
||||
* copied to the user buffer
|
||||
*
|
||||
* @user_offset [in] - offset on disk where the read was requested by the user
|
||||
* @first_page_start [in] - actual page aligned disk offset that we want to
|
||||
* read from
|
||||
* @bytes_to_read [in] - total amount of bytes that will be read from disk
|
||||
* which is generally greater or equal to the amount
|
||||
* that the user has requested due to the
|
||||
* either alignment requirements or read_ahead in
|
||||
* effect.
|
||||
* @left [in/out] total amount of bytes that needs to be copied to the user
|
||||
* buffer. It is reduced by the amount of bytes that actually
|
||||
* copied
|
||||
* @buffer - buffer to use
|
||||
* @dest - user supplied buffer
|
||||
*/
|
||||
SSIZE_T ReadIntoBuffer(uint64_t user_offset, uint64_t first_page_start,
|
||||
size_t bytes_to_read, size_t& left,
|
||||
AlignedBuffer& buffer, char* dest) const;
|
||||
|
||||
SSIZE_T ReadIntoOneShotBuffer(uint64_t user_offset, uint64_t first_page_start,
|
||||
size_t bytes_to_read, size_t& left,
|
||||
char* dest) const;
|
||||
|
||||
SSIZE_T ReadIntoInstanceBuffer(uint64_t user_offset,
|
||||
uint64_t first_page_start,
|
||||
size_t bytes_to_read, size_t& left,
|
||||
char* dest) const;
|
||||
|
||||
void CalculateReadParameters(uint64_t offset, size_t bytes_requested,
|
||||
size_t& actual_bytes_toread,
|
||||
uint64_t& first_page_start) const;
|
||||
|
||||
// Override for behavior change
|
||||
virtual SSIZE_T PositionedReadInternal(char* src, size_t numBytes,
|
||||
uint64_t offset) const;
|
||||
|
||||
public:
|
||||
WinRandomAccessFile(const std::string& fname, HANDLE hFile, size_t alignment,
|
||||
const EnvOptions& options);
|
||||
|
||||
~WinRandomAccessFile();
|
||||
|
||||
virtual void EnableReadAhead() override;
|
||||
|
||||
virtual Status Read(uint64_t offset, size_t n, Slice* result,
|
||||
char* scratch) const override;
|
||||
|
||||
virtual bool ShouldForwardRawRequest() const override;
|
||||
|
||||
virtual void Hint(AccessPattern pattern) override;
|
||||
|
||||
virtual Status InvalidateCache(size_t offset, size_t length) override;
|
||||
|
||||
virtual size_t GetUniqueId(char* id, size_t max_size) const override;
|
||||
};
|
||||
|
||||
|
||||
// This is a sequential write class. It has been mimicked (as others) after
|
||||
// the original Posix class. We add support for unbuffered I/O on windows as
|
||||
// well
|
||||
// we utilize the original buffer as an alignment buffer to write directly to
|
||||
// file with no buffering.
|
||||
// No buffering requires that the provided buffer is aligned to the physical
|
||||
// sector size (SSD page size) and
|
||||
// that all SetFilePointer() operations to occur with such an alignment.
|
||||
// We thus always write in sector/page size increments to the drive and leave
|
||||
// the tail for the next write OR for Close() at which point we pad with zeros.
|
||||
// No padding is required for
|
||||
// buffered access.
|
||||
class WinWritableFile : public WritableFile {
|
||||
private:
|
||||
const std::string filename_;
|
||||
HANDLE hFile_;
|
||||
const bool use_os_buffer_; // Used to indicate unbuffered access, the file
|
||||
const uint64_t alignment_;
|
||||
// must be opened as unbuffered if false
|
||||
uint64_t filesize_; // How much data is actually written disk
|
||||
uint64_t reservedsize_; // how far we have reserved space
|
||||
|
||||
virtual Status PreallocateInternal(uint64_t spaceToReserve);
|
||||
|
||||
public:
|
||||
WinWritableFile(const std::string& fname, HANDLE hFile, size_t alignment,
|
||||
size_t capacity, const EnvOptions& options);
|
||||
|
||||
~WinWritableFile();
|
||||
|
||||
// Indicates if the class makes use of unbuffered I/O
|
||||
virtual bool UseOSBuffer() const override;
|
||||
|
||||
virtual size_t GetRequiredBufferAlignment() const override;
|
||||
|
||||
virtual Status Append(const Slice& data) override;
|
||||
|
||||
virtual Status PositionedAppend(const Slice& data, uint64_t offset) override;
|
||||
|
||||
// Need to implement this so the file is truncated correctly
|
||||
// when buffered and unbuffered mode
|
||||
virtual Status Truncate(uint64_t size) override;
|
||||
|
||||
virtual Status Close() override;
|
||||
|
||||
// write out the cached data to the OS cache
|
||||
// This is now taken care of the WritableFileWriter
|
||||
virtual Status Flush() override;
|
||||
|
||||
virtual Status Sync() override;
|
||||
|
||||
virtual Status Fsync() override;
|
||||
|
||||
virtual uint64_t GetFileSize() override;
|
||||
|
||||
virtual Status Allocate(uint64_t offset, uint64_t len) override;
|
||||
|
||||
virtual size_t GetUniqueId(char* id, size_t max_size) const override;
|
||||
};
|
||||
|
||||
class WinDirectory : public Directory {
|
||||
public:
|
||||
WinDirectory() {}
|
||||
|
||||
virtual Status Fsync() override;
|
||||
};
|
||||
|
||||
class WinFileLock : public FileLock {
|
||||
public:
|
||||
explicit WinFileLock(HANDLE hFile) : hFile_(hFile) {
|
||||
assert(hFile != NULL);
|
||||
assert(hFile != INVALID_HANDLE_VALUE);
|
||||
}
|
||||
|
||||
~WinFileLock();
|
||||
|
||||
private:
|
||||
HANDLE hFile_;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
+2
-1
@@ -69,7 +69,6 @@ typedef SSIZE_T ssize_t;
|
||||
namespace rocksdb {
|
||||
|
||||
#define PREFETCH(addr, rw, locality)
|
||||
std::string GetWindowsErrSz(DWORD err);
|
||||
|
||||
namespace port {
|
||||
|
||||
@@ -81,6 +80,7 @@ namespace port {
|
||||
// For use at db/file_indexer.h kLevelMaxIndex
|
||||
const int kMaxInt32 = std::numeric_limits<int>::max();
|
||||
const uint64_t kMaxUint64 = std::numeric_limits<uint64_t>::max();
|
||||
const int64_t kMaxInt64 = std::numeric_limits<int64_t>::max();
|
||||
|
||||
const size_t kMaxSizet = std::numeric_limits<size_t>::max();
|
||||
|
||||
@@ -95,6 +95,7 @@ const size_t kMaxSizet = std::numeric_limits<size_t>::max();
|
||||
|
||||
// For use at db/file_indexer.h kLevelMaxIndex
|
||||
const int kMaxInt32 = INT32_MAX;
|
||||
const int64_t kMaxInt64 = INT64_MAX;
|
||||
const uint64_t kMaxUint64 = UINT64_MAX;
|
||||
|
||||
#ifdef _WIN64
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
// where enough posix functionality is available.
|
||||
|
||||
#include "port/win/win_logger.h"
|
||||
#include "port/win/io_win.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <stdio.h>
|
||||
@@ -25,6 +26,8 @@
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
namespace port {
|
||||
|
||||
WinLogger::WinLogger(uint64_t (*gettid)(), Env* env, HANDLE file,
|
||||
const InfoLogLevel log_level)
|
||||
: Logger(log_level),
|
||||
@@ -152,4 +155,6 @@ void WinLogger::Logv(const char* format, va_list ap) {
|
||||
|
||||
size_t WinLogger::GetLogFileSize() const { return log_size_; }
|
||||
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
@@ -23,6 +23,8 @@ namespace rocksdb {
|
||||
|
||||
class Env;
|
||||
|
||||
namespace port {
|
||||
|
||||
class WinLogger : public rocksdb::Logger {
|
||||
public:
|
||||
WinLogger(uint64_t (*gettid)(), Env* env, HANDLE file,
|
||||
@@ -55,4 +57,6 @@ class WinLogger : public rocksdb::Logger {
|
||||
const static uint64_t flush_every_seconds_ = 5;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
Loaded 100 of 207 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user