mirror of
https://github.com/facebook/rocksdb.git
synced 2026-11-06 02:52:35 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d903241757 | ||
|
|
01475b49ee | ||
|
|
de3fdee3ce | ||
|
|
6afcb0d933 | ||
|
|
d6f3ff3629 | ||
|
|
4e0ea33c3c | ||
|
|
58d8de115f | ||
|
|
95ef90988b | ||
|
|
1b3810391e | ||
|
|
4f1f992b67 | ||
|
|
89b20d3cce | ||
|
|
a31b8cb7a7 | ||
|
|
2701f5c267 | ||
|
|
fdc0675f5c | ||
|
|
605cd9bfcb | ||
|
|
c7eb50b132 | ||
|
|
2462cebfe8 | ||
|
|
becc230aa9 | ||
|
|
515b11ffab | ||
|
|
876cb8bfb4 | ||
|
|
21c55bdb6e | ||
|
|
4b95253587 | ||
|
|
c6a8665b37 | ||
|
|
880ee363eb | ||
|
|
6797e6ffac | ||
|
|
dda6c72ac8 | ||
|
|
56222f57df | ||
|
|
15b7a4ab88 | ||
|
|
6ea41f8527 | ||
|
|
efd013d6d8 | ||
|
|
e6f68faf99 | ||
|
|
816ae098ea | ||
|
|
e295da1266 | ||
|
|
8e6b38d895 | ||
|
|
296545a2c7 | ||
|
|
61dbfbb6ce | ||
|
|
907f24d0e1 | ||
|
|
0f691c4b5b | ||
|
|
7c190070b4 | ||
|
|
e1b3ee8a79 | ||
|
|
cd4178a015 | ||
|
|
b954847fca | ||
|
|
a00bf1b3cf | ||
|
|
01f77cb192 | ||
|
|
eb53c05a35 | ||
|
|
32df9733d1 | ||
|
|
5aaef91d4a | ||
|
|
a45ee83181 | ||
|
|
38fae9e65d | ||
|
|
1a11c934d2 | ||
|
|
9b5adea97f | ||
|
|
95d96eeeb1 | ||
|
|
ff45d1b547 | ||
|
|
cb2476a0ca | ||
|
|
6b7167651f | ||
|
|
9eb0b53954 | ||
|
|
1fe3bf8298 | ||
|
|
43692793e3 | ||
|
|
95c192475d | ||
|
|
197b832afa | ||
|
|
bdb1d19a69 | ||
|
|
b726bf5961 | ||
|
|
faa7eb3b99 | ||
|
|
c4cef07f1b | ||
|
|
892e9d3047 | ||
|
|
4f2b0946d1 | ||
|
|
3b7ed677de | ||
|
|
56ac686292 | ||
|
|
3fc713ed92 | ||
|
|
343507afb1 | ||
|
|
aa432be4b9 | ||
|
|
8cd9f04feb | ||
|
|
8a4ee7e90b | ||
|
|
af6248d8b8 | ||
|
|
0d7b261236 | ||
|
|
6576fa05af | ||
|
|
c4e19b77e8 | ||
|
|
fa813f7478 | ||
|
|
d6b79e2fd0 | ||
|
|
a52e4d7d02 | ||
|
|
f9bd66779f | ||
|
|
88a2776db5 | ||
|
|
e87d5df1ae | ||
|
|
7b79238b65 | ||
|
|
4939fc3892 | ||
|
|
3a2bccc845 | ||
|
|
cf8adc971e | ||
|
|
30a24f2d3d | ||
|
|
d26a848074 | ||
|
|
249e796dfc | ||
|
|
162c9170dd | ||
|
|
8366e10ffc | ||
|
|
0babce57f7 | ||
|
|
812dbfb483 | ||
|
|
550bf895e9 | ||
|
|
886af59105 | ||
|
|
e3b1e3dfa6 | ||
|
|
f5177c761f | ||
|
|
97fd2a6383 | ||
|
|
7c919deccc | ||
|
|
9a33a723ba | ||
|
|
81f6b33d93 | ||
|
|
bc8af90e8c | ||
|
|
8100ec2cd1 | ||
|
|
7360db39e6 | ||
|
|
2a79af1c54 | ||
|
|
6faddd7c55 | ||
|
|
5009b5326b | ||
|
|
2d05eaeb28 | ||
|
|
bde7d1055f | ||
|
|
ca3db54788 | ||
|
|
20699df843 | ||
|
|
e9c1face6c | ||
|
|
fcc47fa5f1 | ||
|
|
56887f6cb8 |
No files matched your search
@@ -64,3 +64,5 @@ java/javadoc
|
||||
scan_build_report/
|
||||
t
|
||||
LOG
|
||||
|
||||
db_logs/
|
||||
+7
-2
@@ -113,6 +113,7 @@ set(SOURCES
|
||||
db/db_impl.cc
|
||||
db/db_impl_debug.cc
|
||||
db/db_impl_experimental.cc
|
||||
db/db_impl_add_file.cc
|
||||
db/db_impl_readonly.cc
|
||||
db/db_info_dumper.cc
|
||||
db/db_iter.cc
|
||||
@@ -133,7 +134,6 @@ set(SOURCES
|
||||
db/merge_helper.cc
|
||||
db/merge_operator.cc
|
||||
db/repair.cc
|
||||
db/slice.cc
|
||||
db/snapshot_impl.cc
|
||||
db/table_cache.cc
|
||||
db/table_properties_collector.cc
|
||||
@@ -193,7 +193,6 @@ set(SOURCES
|
||||
util/arena.cc
|
||||
util/bloom.cc
|
||||
util/build_version.cc
|
||||
util/cache.cc
|
||||
util/coding.cc
|
||||
util/compaction_job_stats_impl.cc
|
||||
util/comparator.cc
|
||||
@@ -214,6 +213,7 @@ set(SOURCES
|
||||
util/histogram_windowing.cc
|
||||
util/instrumented_mutex.cc
|
||||
util/iostats_context.cc
|
||||
util/lru_cache.cc
|
||||
tools/ldb_cmd.cc
|
||||
tools/ldb_tool.cc
|
||||
util/logging.cc
|
||||
@@ -229,6 +229,7 @@ set(SOURCES
|
||||
util/perf_level.cc
|
||||
util/random.cc
|
||||
util/rate_limiter.cc
|
||||
util/sharded_cache.cc
|
||||
util/slice.cc
|
||||
util/statistics.cc
|
||||
util/status.cc
|
||||
@@ -267,6 +268,7 @@ set(SOURCES
|
||||
utilities/persistent_cache/persistent_cache_tier.cc
|
||||
utilities/persistent_cache/volatile_tier_impl.cc
|
||||
utilities/redis/redis_lists.cc
|
||||
utilities/simulator_cache/sim_cache.cc
|
||||
utilities/spatialdb/spatial_db.cc
|
||||
utilities/table_properties_collectors/compact_on_deletion_collector.cc
|
||||
utilities/transactions/optimistic_transaction_impl.cc
|
||||
@@ -346,6 +348,7 @@ set(TESTS
|
||||
db/db_inplace_update_test.cc
|
||||
db/db_iter_test.cc
|
||||
db/db_log_iter_test.cc
|
||||
db/db_options_test.cc
|
||||
db/db_properties_test.cc
|
||||
db/db_table_properties_test.cc
|
||||
db/db_tailing_iter_test.cc
|
||||
@@ -353,6 +356,7 @@ set(TESTS
|
||||
db/db_test2.cc
|
||||
db/db_block_cache_test.cc
|
||||
db/db_bloom_filter_test.cc
|
||||
db/db_flush_test.cc
|
||||
db/db_iterator_test.cc
|
||||
db/db_sst_test.cc
|
||||
db/db_universal_compaction_test.cc
|
||||
@@ -417,6 +421,7 @@ set(TESTS
|
||||
util/options_test.cc
|
||||
util/rate_limiter_test.cc
|
||||
util/slice_transform_test.cc
|
||||
util/statistics_test.cc
|
||||
util/thread_list_test.cc
|
||||
util/thread_local_test.cc
|
||||
utilities/backupable/backupable_db_test.cc
|
||||
|
||||
+27
-10
@@ -1,5 +1,22 @@
|
||||
# Rocksdb Change Log
|
||||
## Unreleased
|
||||
## 4.10.2
|
||||
### Bug Fixes
|
||||
* backport the bug fix of the regression data corruption after DB recovery when stale WAL file deletion is reordered.
|
||||
|
||||
## 4.10.1
|
||||
### Bug Fixes
|
||||
* Fix the regression deadlock bug of DB recovery if options.max_successive_merges hits.
|
||||
|
||||
## 4.10.0
|
||||
### Public API Change
|
||||
* options.memtable_prefix_bloom_bits changes to options.memtable_prefix_bloom_bits_ratio and deprecate options.memtable_prefix_bloom_probes
|
||||
* enum type CompressionType and PerfLevel changes from char to unsigned char. Value of all PerfLevel shift by one.
|
||||
* Deprecate options.filter_deletes.
|
||||
|
||||
### New Features
|
||||
* Add avoid_flush_during_recovery option.
|
||||
* Add a read option background_purge_on_iterator_cleanup to avoid deleting files in foreground when destroying iterators. Instead, a job is scheduled in high priority queue and would be executed in a separate background thread.
|
||||
* RepairDB support for column families. RepairDB now associates data with non-default column families using information embedded in the SST/WAL files (4.7 or later). For data written by 4.6 or earlier, RepairDB associates it with the default column family.
|
||||
* Add options.write_buffer_manager which allows users to control total memtable sizes across multiple DB instances.
|
||||
|
||||
## 4.9.0 (6/9/2016)
|
||||
### Public API changes
|
||||
@@ -29,7 +46,7 @@
|
||||
## 4.6.0 (3/10/2016)
|
||||
### 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.
|
||||
* 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. Signature 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.
|
||||
* Add monotonically increasing counter (DB property "rocksdb.current-super-version-number") that increments upon any change to the LSM tree.
|
||||
|
||||
@@ -145,8 +162,8 @@
|
||||
* options.hard_rate_limit is deprecated.
|
||||
* When options.soft_rate_limit or options.level0_slowdown_writes_trigger is triggered, the way to slow down writes is changed to: write rate to DB is limited to to options.delayed_write_rate.
|
||||
* DB::GetApproximateSizes() adds a parameter to allow the estimation to include data in mem table, with default to be not to include. It is now only supported in skip list mem table.
|
||||
* DB::CompactRange() now accept CompactRangeOptions instead of multiple paramters. CompactRangeOptions is defined in include/rocksdb/options.h.
|
||||
* CompactRange() will now skip bottommost level compaction for level based compaction if there is no compaction filter, bottommost_level_compaction is introduced in CompactRangeOptions to control when it's possbile to skip bottommost level compaction. This mean that if you want the compaction to produce a single file you need to set bottommost_level_compaction to BottommostLevelCompaction::kForce.
|
||||
* DB::CompactRange() now accept CompactRangeOptions instead of multiple parameters. CompactRangeOptions is defined in include/rocksdb/options.h.
|
||||
* CompactRange() will now skip bottommost level compaction for level based compaction if there is no compaction filter, bottommost_level_compaction is introduced in CompactRangeOptions to control when it's possible to skip bottommost level compaction. This mean that if you want the compaction to produce a single file you need to set bottommost_level_compaction to BottommostLevelCompaction::kForce.
|
||||
* Add Cache.GetPinnedUsage() to get the size of memory occupied by entries that are in use by the system.
|
||||
* DB:Open() will fail if the compression specified in Options is not linked with the binary. If you see this failure, recompile RocksDB with compression libraries present on your system. Also, previously our default compression was snappy. This behavior is now changed. Now, the default compression is snappy only if it's available on the system. If it isn't we change the default to kNoCompression.
|
||||
* We changed how we account for memory used in block cache. Previously, we only counted the sum of block sizes currently present in block cache. Now, we count the actual memory usage of the blocks. For example, a block of size 4.5KB will use 8KB memory with jemalloc. This might decrease your memory usage and possibly decrease performance. Increase block cache size if you see this happening after an upgrade.
|
||||
@@ -178,7 +195,7 @@
|
||||
Lower numbered levels will be placed earlier in the db_paths and higher
|
||||
numbered levels will be placed later in the db_paths vector.
|
||||
* Potentially big performance improvements if you're using RocksDB with lots of column families (100-1000)
|
||||
* Added BlockBasedTableOptions.format_version option, which allows user to specify which version of block based table he wants. As a general guidline, newer versions have more features, but might not be readable by older versions of RocksDB.
|
||||
* Added BlockBasedTableOptions.format_version option, which allows user to specify which version of block based table he wants. As a general guideline, newer versions have more features, but might not be readable by older versions of RocksDB.
|
||||
* Added new block based table format (version 2), which you can enable by setting BlockBasedTableOptions.format_version = 2. This format changes how we encode size information in compressed blocks and should help with memory allocations if you're using Zlib or BZip2 compressions.
|
||||
* MemEnv (env that stores data in memory) is now available in default library build. You can create it by calling NewMemEnv().
|
||||
* Add SliceTransform.SameResultWhenAppended() to help users determine it is safe to apply prefix bloom/hash.
|
||||
@@ -254,7 +271,7 @@
|
||||
|
||||
## 3.5.0 (9/3/2014)
|
||||
### New Features
|
||||
* Add include/utilities/write_batch_with_index.h, providing a utilitiy class to query data out of WriteBatch when building it.
|
||||
* Add include/utilities/write_batch_with_index.h, providing a utility class to query data out of WriteBatch when building it.
|
||||
* Move BlockBasedTable related options to BlockBasedTableOptions from Options. Change corresponding JNI interface. Options affected include:
|
||||
no_block_cache, block_cache, block_cache_compressed, block_size, block_size_deviation, block_restart_interval, filter_policy, whole_key_filtering. filter_policy is changed to shared_ptr from a raw pointer.
|
||||
* Remove deprecated options: disable_seek_compaction and db_stats_log_interval
|
||||
@@ -283,7 +300,7 @@
|
||||
### New Features
|
||||
* Added JSON API prototype.
|
||||
* HashLinklist reduces performance outlier caused by skewed bucket by switching data in the bucket from linked list to skip list. Add parameter threshold_use_skiplist in NewHashLinkListRepFactory().
|
||||
* RocksDB is now able to reclaim storage space more effectively during the compaction process. This is done by compensating the size of each deletion entry by the 2X average value size, which makes compaction to be triggerred by deletion entries more easily.
|
||||
* RocksDB is now able to reclaim storage space more effectively during the compaction process. This is done by compensating the size of each deletion entry by the 2X average value size, which makes compaction to be triggered by deletion entries more easily.
|
||||
* Add TimeOut API to write. Now WriteOptions have a variable called timeout_hint_us. With timeout_hint_us set to non-zero, any write associated with this timeout_hint_us may be aborted when it runs longer than the specified timeout_hint_us, and it is guaranteed that any write completes earlier than the specified time-out will not be aborted due to the time-out condition.
|
||||
* Add a rate_limiter option, which controls total throughput of flush and compaction. The throughput is specified in bytes/sec. Flush always has precedence over compaction when available bandwidth is constrained.
|
||||
|
||||
@@ -298,11 +315,11 @@
|
||||
2) It added some complexity to the important code-paths,
|
||||
3) None of our internal customers were really using it.
|
||||
Because of that, Options::disable_seek_compaction is now obsolete. It is still a parameter in Options, so it does not break the build, but it does not have any effect. We plan to completely remove it at some point, so we ask users to please remove this option from your code base.
|
||||
* Add two paramters to NewHashLinkListRepFactory() for logging on too many entries in a hash bucket when flushing.
|
||||
* Add two parameters to NewHashLinkListRepFactory() for logging on too many entries in a hash bucket when flushing.
|
||||
* Added new option BlockBasedTableOptions::hash_index_allow_collision. When enabled, prefix hash index for block-based table will not store prefix and allow hash collision, reducing memory consumption.
|
||||
|
||||
### New Features
|
||||
* PlainTable now supports a new key encoding: for keys of the same prefix, the prefix is only written once. It can be enabled through encoding_type paramter of NewPlainTableFactory()
|
||||
* PlainTable now supports a new key encoding: for keys of the same prefix, the prefix is only written once. It can be enabled through encoding_type parameter of NewPlainTableFactory()
|
||||
* Add AdaptiveTableFactory, which is used to convert from a DB of PlainTable to BlockBasedTabe, or vise versa. It can be created using NewAdaptiveTableFactory()
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
@@ -9,3 +9,4 @@ This is the list of all known third-party language bindings for RocksDB. If some
|
||||
* Haskell - https://hackage.haskell.org/package/rocksdb-haskell
|
||||
* PHP - https://github.com/Photonios/rocksdb-php
|
||||
* C# - https://github.com/warrenfalk/rocksdb-sharp
|
||||
* Rust - https://github.com/spacejam/rust-rocksdb
|
||||
@@ -138,6 +138,15 @@ dummy := $(shell (export ROCKSDB_ROOT="$(CURDIR)"; "$(CURDIR)/build_tools/build_
|
||||
include make_config.mk
|
||||
CLEAN_FILES += make_config.mk
|
||||
|
||||
missing_make_config_paths := $(shell \
|
||||
grep "\/\S*" -o $(CURDIR)/make_config.mk | \
|
||||
while read path; \
|
||||
do [ -e $$path ] || echo $$path; \
|
||||
done | sort | uniq)
|
||||
|
||||
$(foreach path, $(missing_make_config_paths), \
|
||||
$(warning Warning: $(path) dont exist))
|
||||
|
||||
ifneq ($(PLATFORM), IOS)
|
||||
CFLAGS += -g
|
||||
CXXFLAGS += -g
|
||||
@@ -203,10 +212,6 @@ default: all
|
||||
WARNING_FLAGS = -W -Wextra -Wall -Wsign-compare -Wshadow \
|
||||
-Wno-unused-parameter
|
||||
|
||||
ifndef DISABLE_WARNING_AS_ERROR
|
||||
WARNING_FLAGS += -Werror
|
||||
endif
|
||||
|
||||
CFLAGS += $(WARNING_FLAGS) -I. -I./include $(PLATFORM_CCFLAGS) $(OPT)
|
||||
CXXFLAGS += $(WARNING_FLAGS) -I. -I./include $(PLATFORM_CXXFLAGS) $(OPT) -Woverloaded-virtual -Wnon-virtual-dtor -Wno-missing-field-initializers
|
||||
|
||||
@@ -242,8 +247,8 @@ util/build_version.cc: FORCE
|
||||
else mv -f $@-t $@; fi
|
||||
|
||||
LIBOBJECTS = $(LIB_SOURCES:.cc=.o)
|
||||
LIBOBJECTS += $(TOOL_SOURCES:.cc=.o)
|
||||
MOCKOBJECTS = $(MOCK_SOURCES:.cc=.o)
|
||||
LIBOBJECTS += $(TOOL_LIB_SOURCES:.cc=.o)
|
||||
MOCKOBJECTS = $(MOCK_LIB_SOURCES:.cc=.o)
|
||||
|
||||
GTEST = $(GTEST_DIR)/gtest/gtest-all.o
|
||||
TESTUTIL = ./util/testutil.o
|
||||
@@ -253,7 +258,7 @@ VALGRIND_VER := $(join $(VALGRIND_VER),valgrind)
|
||||
|
||||
VALGRIND_OPTS = --error-exitcode=$(VALGRIND_ERROR) --leak-check=full
|
||||
|
||||
BENCHTOOLOBJECTS = $(BENCH_SOURCES:.cc=.o) $(LIBOBJECTS) $(TESTUTIL)
|
||||
BENCHTOOLOBJECTS = $(BENCH_LIB_SOURCES:.cc=.o) $(LIBOBJECTS) $(TESTUTIL)
|
||||
|
||||
TESTS = \
|
||||
db_test \
|
||||
@@ -265,8 +270,10 @@ TESTS = \
|
||||
db_compaction_filter_test \
|
||||
db_compaction_test \
|
||||
db_dynamic_level_test \
|
||||
db_flush_test \
|
||||
db_inplace_update_test \
|
||||
db_iterator_test \
|
||||
db_options_test \
|
||||
db_sst_test \
|
||||
db_tailing_iter_test \
|
||||
db_universal_compaction_test \
|
||||
@@ -363,6 +370,7 @@ TESTS = \
|
||||
ldb_cmd_test \
|
||||
iostats_context_test \
|
||||
persistent_cache_test \
|
||||
statistics_test \
|
||||
|
||||
PARALLEL_TEST = \
|
||||
backupable_db_test \
|
||||
@@ -455,7 +463,7 @@ $(SHARED3): $(SHARED4)
|
||||
endif
|
||||
|
||||
$(SHARED4):
|
||||
$(CXX) $(PLATFORM_SHARED_LDFLAGS)$(SHARED3) $(CXXFLAGS) $(PLATFORM_SHARED_CFLAGS) $(LIB_SOURCES) $(TOOL_SOURCES) \
|
||||
$(CXX) $(PLATFORM_SHARED_LDFLAGS)$(SHARED3) $(CXXFLAGS) $(PLATFORM_SHARED_CFLAGS) $(LIB_SOURCES) $(TOOL_LIB_SOURCES) \
|
||||
$(LDFLAGS) -o $@
|
||||
|
||||
endif # PLATFORM_SHARED_EXT
|
||||
@@ -807,7 +815,7 @@ $(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)
|
||||
$(TOOLS_LIBRARY): $(BENCH_LIB_SOURCES:.cc=.o) $(TOOL_LIB_SOURCES:.cc=.o) $(LIB_SOURCES:.cc=.o) $(TESTUTIL)
|
||||
$(AM_V_AR)rm -f $@
|
||||
$(AM_V_at)$(AR) $(ARFLAGS) $@ $^
|
||||
|
||||
@@ -911,12 +919,18 @@ db_compaction_test: db/db_compaction_test.o db/db_test_util.o $(LIBOBJECTS) $(TE
|
||||
db_dynamic_level_test: db/db_dynamic_level_test.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
db_flush_test: db/db_flush_test.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
db_inplace_update_test: db/db_inplace_update_test.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
db_iterator_test: db/db_iterator_test.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
db_options_test: db/db_options_test.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
db_sst_test: db/db_sst_test.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
@@ -1187,6 +1201,9 @@ iostats_context_test: util/iostats_context_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
persistent_cache_test: utilities/persistent_cache/persistent_cache_test.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
statistics_test: util/statistics_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
#-------------------------------------------------
|
||||
# make install related stuff
|
||||
INSTALL_PATH ?= /usr/local
|
||||
@@ -1397,7 +1414,7 @@ endif
|
||||
# Source files dependencies detection
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
all_sources = $(LIB_SOURCES) $(TEST_BENCH_SOURCES) $(MOCK_SOURCES)
|
||||
all_sources = $(LIB_SOURCES) $(MAIN_SOURCES) $(MOCK_LIB_SOURCES) $(TOOL_LIB_SOURCES) $(BENCH_LIB_SOURCES) $(TEST_LIB_SOURCES)
|
||||
DEPFILES = $(all_sources:.cc=.d)
|
||||
|
||||
# Add proper dependency support so changing a .h file forces a .cc file to
|
||||
|
||||
@@ -1,10 +1,16 @@
|
||||
This document lists users of RocksDB and their use cases. If you are using RocksDB, please open a pull request and add yourself to the list.
|
||||
|
||||
## Facebook
|
||||
At Facebook, we use RocksDB as a backend for many different stateful services. We're also experimenting with running RocksDB as a storage engine for two databases:
|
||||
At Facebook, we use RocksDB as storage engines in multiple data management services and a backend for many different stateful services, including:
|
||||
|
||||
1. MyRocks -- https://github.com/MySQLOnRocksDB/mysql-5.6
|
||||
2. MongoRocks -- https://github.com/mongodb-partners/mongo-rocks
|
||||
3. ZippyDB -- Facebook's distributed key-value store with Paxos-style replication, built on top of RocksDB.[*] https://www.youtube.com/watch?v=DfiN7pG0D0khtt
|
||||
4. Laser -- Laser is a high query throughput, low (millisecond) latency, key-value storage service built on top of RocksDB.[*]
|
||||
4. Dragan -- a distributed graph query engine. https://code.facebook.com/posts/1737605303120405/dragon-a-distributed-graph-query-engine/
|
||||
5. Stylus -- a low-level stream processing framework writtenin C++.[*]
|
||||
|
||||
[*] https://research.facebook.com/publications/realtime-data-processing-at-facebook/
|
||||
|
||||
## LinkedIn
|
||||
Two different use cases at Linkedin are using RocksDB as a storage engine:
|
||||
@@ -59,3 +65,9 @@ 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.
|
||||
|
||||
## TiKV
|
||||
[TiKV](https://github.com/pingcap/tikv) is a GEO-replicated, high-performance, distributed, transactional key-value database. TiKV is powered by Rust and Raft. TiKV uses RocksDB as its persistence layer.
|
||||
|
||||
## Apache Flink
|
||||
[Apache Flink](https://flink.apache.org/news/2016/03/08/release-1.0.0.html) uses RocksDB to store state locally on a machine.
|
||||
@@ -6,33 +6,66 @@
|
||||
* @phutil-library-version 2
|
||||
*/
|
||||
|
||||
phutil_register_library_map(array(
|
||||
'__library_version__' => 2,
|
||||
'class' =>
|
||||
array(
|
||||
'ArcanistCpplintLinter' => 'cpp_linter/ArcanistCpplintLinter.php',
|
||||
'BaseDirectoryScopedFormatLinter' => 'cpp_linter/BaseDirectoryScopedFormatLinter.php',
|
||||
'FacebookArcanistConfiguration' => 'config/FacebookArcanistConfiguration.php',
|
||||
'FacebookFbcodeLintEngine' => 'lint_engine/FacebookFbcodeLintEngine.php',
|
||||
'FacebookFbcodeUnitTestEngine' => 'unit_engine/FacebookFbcodeUnitTestEngine.php',
|
||||
'FacebookHowtoevenLintEngine' => 'lint_engine/FacebookHowtoevenLintEngine.php',
|
||||
'FacebookHowtoevenLinter' => 'cpp_linter/FacebookHowtoevenLinter.php',
|
||||
'FbcodeClangFormatLinter' => 'cpp_linter/FbcodeClangFormatLinter.php',
|
||||
'FbcodeCppLinter' => 'cpp_linter/FbcodeCppLinter.php',
|
||||
),
|
||||
'function' =>
|
||||
array(
|
||||
),
|
||||
'xmap' =>
|
||||
array(
|
||||
'ArcanistCpplintLinter' => 'ArcanistLinter',
|
||||
'BaseDirectoryScopedFormatLinter' => 'ArcanistLinter',
|
||||
'FacebookArcanistConfiguration' => 'ArcanistConfiguration',
|
||||
'FacebookFbcodeLintEngine' => 'ArcanistLintEngine',
|
||||
'FacebookFbcodeUnitTestEngine' => 'ArcanistBaseUnitTestEngine',
|
||||
'FacebookHowtoevenLintEngine' => 'ArcanistLintEngine',
|
||||
'FacebookHowtoevenLinter' => 'ArcanistLinter',
|
||||
'FbcodeClangFormatLinter' => 'BaseDirectoryScopedFormatLinter',
|
||||
'FbcodeCppLinter' => 'ArcanistLinter',
|
||||
),
|
||||
));
|
||||
if (class_exists('ArcanistWorkflow')) {
|
||||
phutil_register_library_map(array(
|
||||
'__library_version__' => 2,
|
||||
'class' =>
|
||||
array(
|
||||
'ArcanistCpplintLinter' => 'cpp_linter/ArcanistCpplintLinter.php',
|
||||
'BaseDirectoryScopedFormatLinter' => 'cpp_linter/BaseDirectoryScopedFormatLinter.php',
|
||||
'FacebookArcanistConfiguration' => 'config/FacebookArcanistConfiguration.php',
|
||||
'FacebookFbcodeLintEngine' => 'lint_engine/FacebookFbcodeLintEngine.php',
|
||||
'FacebookFbcodeUnitTestEngine' => 'unit_engine/FacebookFbcodeUnitTestEngine.php',
|
||||
'FacebookHowtoevenLintEngine' => 'lint_engine/FacebookHowtoevenLintEngine.php',
|
||||
'FacebookHowtoevenLinter' => 'cpp_linter/FacebookHowtoevenLinter.php',
|
||||
'FbcodeClangFormatLinter' => 'cpp_linter/FbcodeClangFormatLinter.php',
|
||||
'FbcodeCppLinter' => 'cpp_linter/FbcodeCppLinter.php',
|
||||
),
|
||||
'function' =>
|
||||
array(
|
||||
),
|
||||
'xmap' =>
|
||||
array(
|
||||
'ArcanistCpplintLinter' => 'ArcanistLinter',
|
||||
'BaseDirectoryScopedFormatLinter' => 'ArcanistLinter',
|
||||
'FacebookArcanistConfiguration' => 'ArcanistConfiguration',
|
||||
'FacebookFbcodeLintEngine' => 'ArcanistLintEngine',
|
||||
'FacebookFbcodeUnitTestEngine' => 'ArcanistBaseUnitTestEngine',
|
||||
'FacebookHowtoevenLintEngine' => 'ArcanistLintEngine',
|
||||
'FacebookHowtoevenLinter' => 'ArcanistLinter',
|
||||
'FbcodeClangFormatLinter' => 'BaseDirectoryScopedFormatLinter',
|
||||
'FbcodeCppLinter' => 'ArcanistLinter',
|
||||
),
|
||||
));
|
||||
} else {
|
||||
phutil_register_library_map(array(
|
||||
'__library_version__' => 2,
|
||||
'class' =>
|
||||
array(
|
||||
'ArcanistCpplintLinter' => 'cpp_linter/ArcanistCpplintLinter.php',
|
||||
'BaseDirectoryScopedFormatLinter' => 'cpp_linter/BaseDirectoryScopedFormatLinter.php',
|
||||
'FacebookArcanistConfiguration' => 'config/FacebookOldArcanistConfiguration.php',
|
||||
'FacebookFbcodeLintEngine' => 'lint_engine/FacebookFbcodeLintEngine.php',
|
||||
'FacebookFbcodeUnitTestEngine' => 'unit_engine/FacebookFbcodeUnitTestEngine.php',
|
||||
'FacebookHowtoevenLintEngine' => 'lint_engine/FacebookHowtoevenLintEngine.php',
|
||||
'FacebookHowtoevenLinter' => 'cpp_linter/FacebookHowtoevenLinter.php',
|
||||
'FbcodeClangFormatLinter' => 'cpp_linter/FbcodeClangFormatLinter.php',
|
||||
'FbcodeCppLinter' => 'cpp_linter/FbcodeCppLinter.php',
|
||||
),
|
||||
'function' =>
|
||||
array(
|
||||
),
|
||||
'xmap' =>
|
||||
array(
|
||||
'ArcanistCpplintLinter' => 'ArcanistLinter',
|
||||
'BaseDirectoryScopedFormatLinter' => 'ArcanistLinter',
|
||||
'FacebookArcanistConfiguration' => 'ArcanistConfiguration',
|
||||
'FacebookFbcodeLintEngine' => 'ArcanistLintEngine',
|
||||
'FacebookFbcodeUnitTestEngine' => 'ArcanistBaseUnitTestEngine',
|
||||
'FacebookHowtoevenLintEngine' => 'ArcanistLintEngine',
|
||||
'FacebookHowtoevenLinter' => 'ArcanistLinter',
|
||||
'FbcodeClangFormatLinter' => 'BaseDirectoryScopedFormatLinter',
|
||||
'FbcodeCppLinter' => 'ArcanistLinter',
|
||||
),
|
||||
));
|
||||
}
|
||||
@@ -4,203 +4,33 @@
|
||||
// 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.
|
||||
|
||||
require('RocksDBCommonHelper.php');
|
||||
|
||||
define("DIFF_COMMAND", "diff");
|
||||
|
||||
class FacebookArcanistConfiguration extends ArcanistConfiguration {
|
||||
|
||||
public function didRunWorkflow($command,
|
||||
ArcanistBaseWorkflow $workflow,
|
||||
ArcanistWorkflow $workflow,
|
||||
$error_code) {
|
||||
if ($command == 'diff' && !$workflow->isRawDiffSource()) {
|
||||
$this->startTestsInJenkins($workflow);
|
||||
$this->startTestsInSandcastle($workflow);
|
||||
// Default options don't terminate on failure, but that's what we want. In
|
||||
// the current case we use assertions intentionally as "terminate on failure
|
||||
// invariants".
|
||||
assert_options(ASSERT_BAIL, true);
|
||||
|
||||
assert($workflow);
|
||||
assert(strlen($command) > 0);
|
||||
|
||||
if ($command == DIFF_COMMAND && !$workflow->isRawDiffSource()) {
|
||||
$diffID = $workflow->getDiffId();
|
||||
|
||||
// When submitting a diff this code path gets executed multiple times in
|
||||
// a row. We only care about the case when ID for the diff is provided
|
||||
// because that's what we need to apply the diff and trigger the tests.
|
||||
if (strlen($diffID) > 0) {
|
||||
assert(is_numeric($diffID));
|
||||
startTestsInSandcastle(true /* $applyDiff */, $workflow, $diffID);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
/* Run tests in sandcastle */
|
||||
function postURL($diffID, $url) {
|
||||
$cmd = 'echo \'{"diff_id": "' . $diffID . '", '
|
||||
. '"name":"click here for sandcastle tests for D' . $diffID . '", '
|
||||
. '"link":"' . $url . '"}\' | '
|
||||
. 'http_proxy=fwdproxy.any.facebook.com:8080 '
|
||||
. 'https_proxy=fwdproxy.any.facebook.com:8080 arc call-conduit '
|
||||
. 'differential.updateunitresults';
|
||||
shell_exec($cmd);
|
||||
}
|
||||
|
||||
function updateTestCommand($diffID, $test, $status) {
|
||||
$cmd = 'echo \'{"diff_id": "' . $diffID . '", '
|
||||
. '"name":"' . $test . '", '
|
||||
. '"result":"' . $status . '"}\' | '
|
||||
. 'http_proxy=fwdproxy.any.facebook.com:8080 '
|
||||
. 'https_proxy=fwdproxy.any.facebook.com:8080 arc call-conduit '
|
||||
. 'differential.updateunitresults';
|
||||
return $cmd;
|
||||
}
|
||||
|
||||
function updateTest($diffID, $test) {
|
||||
shell_exec($this->updateTestCommand($diffID, $test, "waiting"));
|
||||
}
|
||||
|
||||
function getSteps($diffID, $username, $test) {
|
||||
$arcrc_content = exec("cat ~/.arcrc | gzip -f | base64 -w0");
|
||||
|
||||
// Sandcastle machines don't have arc setup. We copy the user certificate
|
||||
// and authenticate using that in sandcastle
|
||||
$setup = array(
|
||||
"name" => "Setup arcrc",
|
||||
"shell" => "echo " . $arcrc_content . " | base64 --decode"
|
||||
. " | gzip -d > ~/.arcrc",
|
||||
"user" => "root"
|
||||
);
|
||||
|
||||
// arc demands certain permission on its config
|
||||
$fix_permission = array(
|
||||
"name" => "Fix environment",
|
||||
"shell" => "chmod 600 ~/.arcrc",
|
||||
"user" => "root"
|
||||
);
|
||||
|
||||
// fbcode is a sub-repo. We cannot patch until we add it to ignore otherwise
|
||||
// git thinks it is uncommited change
|
||||
$fix_git_ignore = array(
|
||||
"name" => "Fix git ignore",
|
||||
"shell" => "echo fbcode >> .git/info/exclude",
|
||||
"user" => "root"
|
||||
);
|
||||
|
||||
// Patch the code (keep your fingures crossed)
|
||||
$patch = array(
|
||||
"name" => "Patch " . $diffID,
|
||||
"shell" => "HTTPS_PROXY=fwdproxy:8080 arc --arcrc-file ~/.arcrc "
|
||||
. "patch --diff " . $diffID,
|
||||
"user" => "root"
|
||||
);
|
||||
|
||||
// Clean up the user arc config we are using
|
||||
$cleanup = array(
|
||||
"name" => "Arc cleanup",
|
||||
"shell" => "rm -f ~/.arcrc",
|
||||
"user" => "root"
|
||||
);
|
||||
|
||||
// Construct the steps in the order of execution
|
||||
$steps[] = $setup;
|
||||
$steps[] = $fix_permission;
|
||||
$steps[] = $fix_git_ignore;
|
||||
$steps[] = $patch;
|
||||
|
||||
// Run the actual command
|
||||
$this->updateTest($diffID, $test);
|
||||
$cmd = $this->updateTestCommand($diffID, $test, "running") . ";"
|
||||
. "./build_tools/precommit_checker.py " . $test
|
||||
. "; exit_code=$?; ([[ \$exit_code -eq 0 ]] &&"
|
||||
. $this->updateTestCommand($diffID, $test, "pass") . ")"
|
||||
. "||" . $this->updateTestCommand($diffID, $test, "fail")
|
||||
. "; cat /tmp/precommit-check.log"
|
||||
. "; for f in `ls t/log-*`; do echo \$f; cat \$f; done;"
|
||||
. "[[ \$exit_code -eq 0 ]]";
|
||||
|
||||
$run_test = array(
|
||||
"name" => "Run " . $test,
|
||||
"shell" => $cmd,
|
||||
"user" => "root",
|
||||
);
|
||||
|
||||
$steps[] = $run_test;
|
||||
$steps[] = $cleanup;
|
||||
|
||||
return $steps;
|
||||
}
|
||||
|
||||
function startTestsInSandcastle($workflow) {
|
||||
// extract information we need from workflow or CLI
|
||||
$diffID = $workflow->getDiffId();
|
||||
$username = exec("whoami");
|
||||
|
||||
if ($diffID == null || $username == null) {
|
||||
// there is no diff and we can't extract username
|
||||
// we cannot schedule sandcasstle job
|
||||
return;
|
||||
}
|
||||
|
||||
if (strcmp(getenv("ROCKSDB_CHECK_ALL"), 1) == 0) {
|
||||
// extract all tests from the CI definition
|
||||
$output = file_get_contents("build_tools/rocksdb-lego-determinator");
|
||||
preg_match_all('/[ ]{2}([a-zA-Z0-9_]+)[\)]{1}/', $output, $matches);
|
||||
$tests = $matches[1];
|
||||
} else {
|
||||
// manually list of tests we want to run in sandcastle
|
||||
$tests = array(
|
||||
"unit", "unit_481", "clang_unit", "tsan", "asan", "lite_test", "valgrind"
|
||||
);
|
||||
}
|
||||
|
||||
// construct a job definition for each test and add it to the master plan
|
||||
foreach ($tests as $test) {
|
||||
$arg[] = array(
|
||||
"name" => "RocksDB diff " . $diffID . " test " . $test,
|
||||
"steps" => $this->getSteps($diffID, $username, $test)
|
||||
);
|
||||
}
|
||||
|
||||
// we cannot submit the parallel execution master plan to sandcastle
|
||||
// we need supply the job plan as a determinator
|
||||
// so we construct a small job that will spit out the master job plan
|
||||
// which sandcastle will parse and execute
|
||||
// Why compress ? Otherwise we run over the max string size.
|
||||
$cmd = "echo " . base64_encode(json_encode($arg))
|
||||
. " | gzip -f | base64 -w0";
|
||||
$arg_encoded = shell_exec($cmd);
|
||||
|
||||
$command = array(
|
||||
"name" => "Run diff " . $diffID . "for user " . $username,
|
||||
"steps" => array()
|
||||
);
|
||||
|
||||
$command["steps"][] = array(
|
||||
"name" => "Generate determinator",
|
||||
"shell" => "echo " . $arg_encoded . " | base64 --decode | gzip -d"
|
||||
. " | base64 --decode",
|
||||
"determinator" => true,
|
||||
"user" => "root"
|
||||
);
|
||||
|
||||
// submit to sandcastle
|
||||
$url = 'https://interngraph.intern.facebook.com/sandcastle/generate?'
|
||||
.'command=SandcastleUniversalCommand'
|
||||
.'&vcs=rocksdb-git&revision=origin%2Fmaster&type=lego'
|
||||
.'&user=krad&alias=rocksdb-precommit'
|
||||
.'&command-args=' . urlencode(json_encode($command));
|
||||
|
||||
$cmd = 'https_proxy= HTTPS_PROXY= curl -s -k -F app=659387027470559 '
|
||||
. '-F token=AeO_3f2Ya3TujjnxGD4 "' . $url . '"';
|
||||
|
||||
$output = shell_exec($cmd);
|
||||
|
||||
// extract sandcastle URL from the response
|
||||
preg_match('/url": "(.+)"/', $output, $sandcastle_url);
|
||||
|
||||
echo "\nSandcastle URL: " . $sandcastle_url[1] . "\n";
|
||||
|
||||
// Ask phabricator to display it on the diff UI
|
||||
$this->postURL($diffID, $sandcastle_url[1]);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
/* Send off builds to jenkins */
|
||||
function startTestsInJenkins($workflow) {
|
||||
$diffID = $workflow->getDiffID();
|
||||
if ($diffID === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$results = $workflow->getTestResults();
|
||||
if (!$results) {
|
||||
return;
|
||||
}
|
||||
|
||||
$url = "https://ci-builds.fb.com/view/rocksdb/job/rocksdb_diff_check/"
|
||||
."buildWithParameters?token=AUTH&DIFF_ID=$diffID";
|
||||
system("curl --noproxy '*' \"$url\" > /dev/null 2>&1");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
// Copyright 2004-present Facebook. 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.
|
||||
|
||||
require('RocksDBCommonHelper.php');
|
||||
|
||||
define("DIFF_COMMAND", "diff");
|
||||
|
||||
class FacebookArcanistConfiguration extends ArcanistConfiguration {
|
||||
|
||||
public function didRunWorkflow($command,
|
||||
ArcanistBaseWorkflow $workflow,
|
||||
$error_code) {
|
||||
// Default options don't terminate on failure, but that's what we want. In
|
||||
// the current case we use assertions intentionally as "terminate on failure
|
||||
// invariants".
|
||||
assert_options(ASSERT_BAIL, true);
|
||||
|
||||
assert($workflow);
|
||||
assert(strlen($command) > 0);
|
||||
|
||||
if ($command == DIFF_COMMAND && !$workflow->isRawDiffSource()) {
|
||||
$diffID = $workflow->getDiffId();
|
||||
|
||||
// When submitting a diff this code path gets executed multiple times in
|
||||
// a row. We only care about the case when ID for the diff is provided
|
||||
// because that's what we need to apply the diff and trigger the tests.
|
||||
if (strlen($diffID) > 0) {
|
||||
assert(is_numeric($diffID));
|
||||
startTestsInSandcastle(true /* $applyDiff */, $workflow, $diffID);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,326 @@
|
||||
<?php
|
||||
// Copyright 2004-present Facebook. 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.
|
||||
|
||||
// Name of the environment variables which need to be set by the entity which
|
||||
// triggers continuous runs so that code at the end of the file gets executed
|
||||
// and Sandcastle run starts.
|
||||
define("ENV_POST_RECEIVE_HOOK", "POST_RECEIVE_HOOK");
|
||||
define("ENV_HTTPS_APP_VALUE", "HTTPS_APP_VALUE");
|
||||
define("ENV_HTTPS_TOKEN_VALUE", "HTTPS_TOKEN_VALUE");
|
||||
|
||||
define("PRIMARY_TOKEN_FILE", '/home/krad/.sandcastle');
|
||||
define("SECONDARY_TOKEN_FILE", '$HOME/.sandcastle');
|
||||
define("CONT_RUN_ALIAS", "leveldb");
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
/* Run tests in sandcastle */
|
||||
function postURL($diffID, $url) {
|
||||
assert(strlen($diffID) > 0);
|
||||
assert(is_numeric($diffID));
|
||||
assert(strlen($url) > 0);
|
||||
|
||||
$cmd = 'echo \'{"diff_id": "' . $diffID . '", '
|
||||
. '"name":"click here for sandcastle tests for D' . $diffID . '", '
|
||||
. '"link":"' . $url . '"}\' | '
|
||||
. 'http_proxy=fwdproxy.any.facebook.com:8080 '
|
||||
. 'https_proxy=fwdproxy.any.facebook.com:8080 arc call-conduit '
|
||||
. 'differential.updateunitresults';
|
||||
shell_exec($cmd);
|
||||
}
|
||||
|
||||
function buildUpdateTestStatusCmd($diffID, $test, $status) {
|
||||
assert(strlen($diffID) > 0);
|
||||
assert(is_numeric($diffID));
|
||||
assert(strlen($test) > 0);
|
||||
assert(strlen($status) > 0);
|
||||
|
||||
$cmd = 'echo \'{"diff_id": "' . $diffID . '", '
|
||||
. '"name":"' . $test . '", '
|
||||
. '"result":"' . $status . '"}\' | '
|
||||
. 'http_proxy=fwdproxy.any.facebook.com:8080 '
|
||||
. 'https_proxy=fwdproxy.any.facebook.com:8080 arc call-conduit '
|
||||
. 'differential.updateunitresults';
|
||||
return $cmd;
|
||||
}
|
||||
|
||||
function updateTestStatus($diffID, $test) {
|
||||
assert(strlen($diffID) > 0);
|
||||
assert(is_numeric($diffID));
|
||||
assert(strlen($test) > 0);
|
||||
|
||||
shell_exec(buildUpdateTestStatusCmd($diffID, $test, "waiting"));
|
||||
}
|
||||
|
||||
function getSteps($applyDiff, $diffID, $username, $test) {
|
||||
assert(strlen($username) > 0);
|
||||
assert(strlen($test) > 0);
|
||||
|
||||
if ($applyDiff) {
|
||||
assert(strlen($diffID) > 0);
|
||||
assert(is_numeric($diffID));
|
||||
|
||||
$arcrc_content = exec("cat ~/.arcrc | gzip -f | base64 -w0");
|
||||
assert(strlen($arcrc_content) > 0);
|
||||
|
||||
// Sandcastle machines don't have arc setup. We copy the user certificate
|
||||
// and authenticate using that in Sandcastle.
|
||||
$setup = array(
|
||||
"name" => "Setup arcrc",
|
||||
"shell" => "echo " . $arcrc_content . " | base64 --decode"
|
||||
. " | gzip -d > ~/.arcrc",
|
||||
"user" => "root"
|
||||
);
|
||||
|
||||
// arc demands certain permission on its config.
|
||||
$fix_permission = array(
|
||||
"name" => "Fix environment",
|
||||
"shell" => "chmod 600 ~/.arcrc",
|
||||
"user" => "root"
|
||||
);
|
||||
|
||||
// Construct the steps in the order of execution.
|
||||
$steps[] = $setup;
|
||||
$steps[] = $fix_permission;
|
||||
}
|
||||
|
||||
// fbcode is a sub-repo. We cannot patch until we add it to ignore otherwise
|
||||
// Git thinks it is an uncommited change.
|
||||
$fix_git_ignore = array(
|
||||
"name" => "Fix git ignore",
|
||||
"shell" => "echo fbcode >> .git/info/exclude",
|
||||
"user" => "root"
|
||||
);
|
||||
|
||||
$steps[] = $fix_git_ignore;
|
||||
|
||||
// This will be the command used to execute particular type of tests.
|
||||
$cmd = "";
|
||||
|
||||
if ($applyDiff) {
|
||||
// Patch the code (keep your fingures crossed).
|
||||
$patch = array(
|
||||
"name" => "Patch " . $diffID,
|
||||
"shell" => "HTTPS_PROXY=fwdproxy:8080 arc --arcrc-file ~/.arcrc "
|
||||
. "patch --nocommit --diff " . $diffID,
|
||||
"user" => "root"
|
||||
);
|
||||
|
||||
$steps[] = $patch;
|
||||
|
||||
updateTestStatus($diffID, $test);
|
||||
$cmd = buildUpdateTestStatusCmd($diffID, $test, "running") . "; ";
|
||||
}
|
||||
|
||||
// Run the actual command.
|
||||
$cmd = $cmd . "./build_tools/precommit_checker.py " . $test
|
||||
. "; exit_code=$?; ";
|
||||
|
||||
if ($applyDiff) {
|
||||
$cmd = $cmd . "([[ \$exit_code -eq 0 ]] &&"
|
||||
. buildUpdateTestStatusCmd($diffID, $test, "pass") . ")"
|
||||
. "||" . buildUpdateTestStatusCmd($diffID, $test, "fail")
|
||||
. "; ";
|
||||
}
|
||||
|
||||
$cmd = $cmd . " cat /tmp/precommit-check.log"
|
||||
. "; for f in `ls t/log-*`; do echo \$f; cat \$f; done;"
|
||||
. "[[ \$exit_code -eq 0 ]]";
|
||||
assert(strlen($cmd) > 0);
|
||||
|
||||
$run_test = array(
|
||||
"name" => "Run " . $test,
|
||||
"shell" => $cmd,
|
||||
"user" => "root",
|
||||
);
|
||||
|
||||
$steps[] = $run_test;
|
||||
|
||||
if ($applyDiff) {
|
||||
// Clean up the user arc config we are using.
|
||||
$cleanup = array(
|
||||
"name" => "Arc cleanup",
|
||||
"shell" => "rm -f ~/.arcrc",
|
||||
"user" => "root"
|
||||
);
|
||||
|
||||
$steps[] = $cleanup;
|
||||
}
|
||||
|
||||
assert(count($steps) > 0);
|
||||
return $steps;
|
||||
}
|
||||
|
||||
function getSandcastleConfig() {
|
||||
$sandcastle_config = array();
|
||||
|
||||
// This is a case when we're executed from a continuous run. Fetch the values
|
||||
// from the environment.
|
||||
if (getenv(ENV_POST_RECEIVE_HOOK)) {
|
||||
$sandcastle_config[0] = getenv(ENV_HTTPS_APP_VALUE);
|
||||
$sandcastle_config[1] = getenv(ENV_HTTPS_TOKEN_VALUE);
|
||||
} else {
|
||||
// This is a typical `[p]arc diff` case. Fetch the values from the specific
|
||||
// configuration files.
|
||||
assert(file_exists(PRIMARY_TOKEN_FILE) ||
|
||||
file_exists(SECONDARY_TOKEN_FILE));
|
||||
|
||||
// Try the primary location first, followed by a secondary.
|
||||
if (file_exists(PRIMARY_TOKEN_FILE)) {
|
||||
$cmd = 'cat ' . PRIMARY_TOKEN_FILE;
|
||||
} else {
|
||||
$cmd = 'cat ' . SECONDARY_TOKEN_FILE;
|
||||
}
|
||||
|
||||
assert(strlen($cmd) > 0);
|
||||
$sandcastle_config = explode(':', rtrim(shell_exec($cmd)));
|
||||
}
|
||||
|
||||
// In this case be very explicit about the implications.
|
||||
if (count($sandcastle_config) != 2) {
|
||||
echo "Sandcastle configuration files don't contain valid information " .
|
||||
"or the necessary environment variables aren't defined. Unable " .
|
||||
"to validate the code changes.";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
assert(strlen($sandcastle_config[0]) > 0);
|
||||
assert(strlen($sandcastle_config[1]) > 0);
|
||||
assert(count($sandcastle_config) > 0);
|
||||
|
||||
return $sandcastle_config;
|
||||
}
|
||||
|
||||
// This function can be called either from `[p]arc diff` command or during
|
||||
// the Git post-receive hook.
|
||||
function startTestsInSandcastle($applyDiff, $workflow, $diffID) {
|
||||
// Default options don't terminate on failure, but that's what we want. In
|
||||
// the current case we use assertions intentionally as "terminate on failure
|
||||
// invariants".
|
||||
assert_options(ASSERT_BAIL, true);
|
||||
|
||||
// In case of a diff we'll send notificatios to the author. Else it'll go to
|
||||
// the entire team because failures indicate that build quality has regressed.
|
||||
$username = $applyDiff ? exec("whoami") : CONT_RUN_ALIAS;
|
||||
assert(strlen($username) > 0);
|
||||
|
||||
if ($applyDiff) {
|
||||
assert($workflow);
|
||||
assert(strlen($diffID) > 0);
|
||||
assert(is_numeric($diffID));
|
||||
}
|
||||
|
||||
if (strcmp(getenv("ROCKSDB_CHECK_ALL"), 1) == 0) {
|
||||
// Extract all tests from the CI definition.
|
||||
$output = file_get_contents("build_tools/rocksdb-lego-determinator");
|
||||
assert(strlen($output) > 0);
|
||||
|
||||
preg_match_all('/[ ]{2}([a-zA-Z0-9_]+)[\)]{1}/', $output, $matches);
|
||||
$tests = $matches[1];
|
||||
assert(count($tests) > 0);
|
||||
} else {
|
||||
// Manually list of tests we want to run in Sandcastle.
|
||||
$tests = array(
|
||||
"unit", "unit_481", "clang_unit", "tsan", "asan", "lite_test",
|
||||
"valgrind", "release"
|
||||
);
|
||||
}
|
||||
|
||||
$send_email_template = array(
|
||||
'type' => 'email',
|
||||
'triggers' => array('fail'),
|
||||
'emails' => array($username . '@fb.com'),
|
||||
);
|
||||
|
||||
// Construct a job definition for each test and add it to the master plan.
|
||||
foreach ($tests as $test) {
|
||||
$stepName = "RocksDB diff " . $diffID . " test " . $test;
|
||||
|
||||
if (!$applyDiff) {
|
||||
$stepName = "RocksDB continuous integration test " . $test;
|
||||
}
|
||||
|
||||
$arg[] = array(
|
||||
"name" => $stepName,
|
||||
"report" => array($send_email_template),
|
||||
"steps" => getSteps($applyDiff, $diffID, $username, $test)
|
||||
);
|
||||
}
|
||||
|
||||
// We cannot submit the parallel execution master plan to Sandcastle and
|
||||
// need supply the job plan as a determinator. So we construct a small job
|
||||
// that will spit out the master job plan which Sandcastle will parse and
|
||||
// execute. Why compress the job definitions? Otherwise we run over the max
|
||||
// string size.
|
||||
$cmd = "echo " . base64_encode(json_encode($arg))
|
||||
. " | gzip -f | base64 -w0";
|
||||
assert(strlen($cmd) > 0);
|
||||
|
||||
$arg_encoded = shell_exec($cmd);
|
||||
assert(strlen($arg_encoded) > 0);
|
||||
|
||||
$runName = "Run diff " . $diffID . "for user " . $username;
|
||||
|
||||
if (!$applyDiff) {
|
||||
$runName = "RocksDB continuous integration build and test run";
|
||||
}
|
||||
|
||||
$command = array(
|
||||
"name" => $runName,
|
||||
"steps" => array()
|
||||
);
|
||||
|
||||
$command["steps"][] = array(
|
||||
"name" => "Generate determinator",
|
||||
"shell" => "echo " . $arg_encoded . " | base64 --decode | gzip -d"
|
||||
. " | base64 --decode",
|
||||
"determinator" => true,
|
||||
"user" => "root"
|
||||
);
|
||||
|
||||
// Submit to Sandcastle.
|
||||
$url = 'https://interngraph.intern.facebook.com/sandcastle/generate?'
|
||||
.'command=SandcastleUniversalCommand'
|
||||
.'&vcs=rocksdb-git&revision=origin%2Fmaster&type=lego'
|
||||
.'&user=' . $username . '&alias=rocksdb-precommit'
|
||||
.'&command-args=' . urlencode(json_encode($command));
|
||||
|
||||
// Fetch the configuration necessary to submit a successful HTTPS request.
|
||||
$sandcastle_config = getSandcastleConfig();
|
||||
|
||||
$app = $sandcastle_config[0];
|
||||
$token = $sandcastle_config[1];
|
||||
|
||||
$cmd = 'https_proxy= HTTPS_PROXY= curl -s -k -F app=' . $app . ' '
|
||||
. '-F token=' . $token . ' "' . $url . '"';
|
||||
|
||||
$output = shell_exec($cmd);
|
||||
assert(strlen($output) > 0);
|
||||
|
||||
// Extract Sandcastle URL from the response.
|
||||
preg_match('/url": "(.+)"/', $output, $sandcastle_url);
|
||||
|
||||
assert(count($sandcastle_url) > 0, "Unable to submit Sandcastle request.");
|
||||
assert(strlen($sandcastle_url[1]) > 0, "Unable to extract Sandcastle URL.");
|
||||
|
||||
if ($applyDiff) {
|
||||
echo "\nSandcastle URL: " . $sandcastle_url[1] . "\n";
|
||||
// Ask Phabricator to display it on the diff UI.
|
||||
postURL($diffID, $sandcastle_url[1]);
|
||||
} else {
|
||||
echo "Continuous integration started Sandcastle tests. You can look at ";
|
||||
echo "the progress at:\n" . $sandcastle_url[1] . "\n";
|
||||
}
|
||||
}
|
||||
|
||||
// Continuous run cript will set the environment variable and based on that
|
||||
// we'll trigger the execution of tests in Sandcastle. In that case we don't
|
||||
// need to apply any diffs and there's no associated workflow either.
|
||||
if (getenv(ENV_POST_RECEIVE_HOOK)) {
|
||||
startTestsInSandcastle(
|
||||
false /* $applyDiff */,
|
||||
NULL /* $workflow */,
|
||||
NULL /* $diffID */);
|
||||
}
|
||||
@@ -44,7 +44,7 @@ abstract class BaseDirectoryScopedFormatLinter extends ArcanistLinter {
|
||||
$futures[$path] = $this->getFormatFuture($path, $changed);
|
||||
}
|
||||
|
||||
foreach (Futures($futures)->limit(8) as $p => $f) {
|
||||
foreach (id(new FutureIterator($futures))->limit(8) as $p => $f) {
|
||||
$this->rawLintOutput[$p] = $f->resolvex();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,14 +42,12 @@ class FacebookFbcodeLintEngine extends ArcanistLintEngine {
|
||||
$python_linter = new ArcanistPEP8Linter();
|
||||
$linters[] = $python_linter;
|
||||
|
||||
if (!$this->getCommitHookMode()) {
|
||||
$cpp_linters = array();
|
||||
$cpp_linters[] = $linters[] = new ArcanistCpplintLinter();
|
||||
$cpp_linters[] = $linters[] = new FbcodeCppLinter();
|
||||
$cpp_linters = array();
|
||||
$cpp_linters[] = $linters[] = new ArcanistCpplintLinter();
|
||||
$cpp_linters[] = $linters[] = new FbcodeCppLinter();
|
||||
|
||||
$clang_format_linter = new FbcodeClangFormatLinter();
|
||||
$linters[] = $clang_format_linter;
|
||||
}
|
||||
$clang_format_linter = new FbcodeClangFormatLinter();
|
||||
$linters[] = $clang_format_linter;
|
||||
|
||||
$spelling_linter = new ArcanistSpellingLinter();
|
||||
$linters[] = $spelling_linter;
|
||||
|
||||
@@ -7,15 +7,11 @@
|
||||
class FacebookFbcodeUnitTestEngine extends ArcanistBaseUnitTestEngine {
|
||||
|
||||
public function run() {
|
||||
// Here we create a new unit test "jenkins_async_test" and promise we'll
|
||||
// update the results later.
|
||||
// Jenkins updates the results using `arc call-conduit
|
||||
// differential.updateunitresults` call. If you change the name here, also
|
||||
// make sure to change the name in Jenkins script that updates the test
|
||||
// result -- they have to be the same.
|
||||
$result = new ArcanistUnitTestResult();
|
||||
$result->setName("jenkins_async_test");
|
||||
$result->setResult(ArcanistUnitTestResult::RESULT_POSTPONED);
|
||||
return array($result);
|
||||
// For a call to `arc call-conduit differential.updateunitresults` to
|
||||
// succeed we need at least one entry here.
|
||||
$result = new ArcanistUnitTestResult();
|
||||
$result->setName("dummy_placeholder_entry");
|
||||
$result->setResult(ArcanistUnitTestResult::RESULT_PASS);
|
||||
return array($result);
|
||||
}
|
||||
}
|
||||
@@ -52,12 +52,7 @@ if [ -z "$ROCKSDB_NO_FBCODE" -a -d /mnt/gvfs/third-party ]; then
|
||||
FBCODE_BUILD="true"
|
||||
# If we're compiling with TSAN we need pic build
|
||||
PIC_BUILD=$COMPILE_WITH_TSAN
|
||||
if [ -z "$ROCKSDB_FBCODE_BUILD_WITH_481" ]; then
|
||||
source "$PWD/build_tools/fbcode_config.sh"
|
||||
else
|
||||
# we need this to build with MySQL. Don't use for other purposes.
|
||||
source "$PWD/build_tools/fbcode_config4.8.1.sh"
|
||||
fi
|
||||
source "$PWD/build_tools/fbcode_config.sh"
|
||||
fi
|
||||
|
||||
# Delete existing output, if it exists
|
||||
|
||||
Executable
+135
@@ -0,0 +1,135 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Copyright (c) 2016, Facebook. All rights reserved.
|
||||
#
|
||||
# Overall wrapper script for RocksDB continuous builds. The implementation is a
|
||||
# trivial pulling scheme. We loop infinitely, check if any new changes have been
|
||||
# committed, if yes then trigger a Sandcastle run, and finally go to sleep again
|
||||
# for a certain interval.
|
||||
#
|
||||
|
||||
SRC_GIT_REPO=/data/git/rocksdb
|
||||
error=0
|
||||
|
||||
function log {
|
||||
DATE=`date +%Y-%m-%d:%H:%M:%S`
|
||||
echo $DATE $@
|
||||
}
|
||||
|
||||
function log_err {
|
||||
log "ERROR: $@ Error code: $error."
|
||||
}
|
||||
|
||||
function update_repo_status {
|
||||
# Update the parent first.
|
||||
pushd $SRC_GIT_REPO
|
||||
|
||||
# This is a fatal error. Something in the environment isn't right and we will
|
||||
# terminate the execution.
|
||||
error=$?
|
||||
if [ ! $error -eq 0 ]; then
|
||||
log_err "Where is $SRC_GIT_REPO?"
|
||||
exit $error
|
||||
fi
|
||||
|
||||
HTTPS_PROXY=fwdproxy:8080 git fetch -f
|
||||
|
||||
error=$?
|
||||
if [ ! $error -eq 0 ]; then
|
||||
log_err "git fetch -f failed."
|
||||
popd
|
||||
return $error
|
||||
fi
|
||||
|
||||
git update-ref refs/heads/master refs/remotes/origin/master
|
||||
|
||||
error=$?
|
||||
if [ ! $error -eq 0 ]; then
|
||||
log_err "git update-ref failed."
|
||||
popd
|
||||
return $error
|
||||
fi
|
||||
|
||||
popd
|
||||
|
||||
# We're back in an instance-specific directory. Get the latest changes.
|
||||
git pull --rebase
|
||||
|
||||
error=$?
|
||||
if [ ! $error -eq 0 ]; then
|
||||
log_err "git pull --rebase failed."
|
||||
return $error
|
||||
fi
|
||||
}
|
||||
|
||||
#
|
||||
# Execution starts here.
|
||||
#
|
||||
|
||||
# Path to the determinator from the root of the RocksDB repo.
|
||||
CONTRUN_DETERMINATOR=./arcanist_util/config/RocksDBCommonHelper.php
|
||||
|
||||
# Value of the previous commit.
|
||||
PREV_COMMIT=
|
||||
|
||||
log "Starting to monitor for new RocksDB changes ..."
|
||||
log "Running under `pwd` as `whoami`."
|
||||
|
||||
# Paranoia. Make sure that we're using the right branch.
|
||||
git checkout master
|
||||
|
||||
error=$?
|
||||
if [ ! $error -eq 0 ]; then
|
||||
log_err "This is not good. Can't checkout master. Bye-bye!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# We'll run forever and let the execution environment terminate us if we'll
|
||||
# exceed whatever timeout is set for the job.
|
||||
while true;
|
||||
do
|
||||
# Get the latest changes committed.
|
||||
update_repo_status
|
||||
|
||||
error=$?
|
||||
if [ $error -eq 0 ]; then
|
||||
LAST_COMMIT=`git log -1 | head -1 | grep commit | awk '{ print $2; }'`
|
||||
|
||||
log "Last commit is '$LAST_COMMIT', previous commit is '$PREV_COMMIT'."
|
||||
|
||||
if [ "$PREV_COMMIT" == "$LAST_COMMIT" ]; then
|
||||
log "There were no changes since the last time I checked. Going to sleep."
|
||||
else
|
||||
if [ ! -z "$LAST_COMMIT" ]; then
|
||||
log "New code has been committed or previous commit not known. " \
|
||||
"Will trigger the tests."
|
||||
|
||||
PREV_COMMIT=$LAST_COMMIT
|
||||
log "Updated previous commit to '$PREV_COMMIT'."
|
||||
|
||||
#
|
||||
# This is where we'll trigger the Sandcastle run. The values for
|
||||
# HTTPS_APP_VALUE and HTTPS_APP_VALUE will be set in the container we're
|
||||
# running in.
|
||||
#
|
||||
POST_RECEIVE_HOOK=1 php $CONTRUN_DETERMINATOR
|
||||
|
||||
error=$?
|
||||
if [ $error -eq 0 ]; then
|
||||
log "Sandcastle run successfully triggered."
|
||||
else
|
||||
log_err "Failed to trigger Sandcastle run."
|
||||
fi
|
||||
else
|
||||
log_err "Previous commit not updated. Don't know what the last one is."
|
||||
fi
|
||||
fi
|
||||
else
|
||||
log_err "Getting latest changes failed. Will skip running tests for now."
|
||||
fi
|
||||
|
||||
# Always sleep, even if errors happens while trying to determine the latest
|
||||
# commit. This will prevent us terminating in case of transient errors.
|
||||
log "Will go to sleep for 5 minutes."
|
||||
sleep 5m
|
||||
done
|
||||
+19
-16
@@ -1,16 +1,19 @@
|
||||
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
|
||||
# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
|
||||
GCC_BASE=/mnt/gvfs/third-party2/gcc/7331085db891a2ef4a88a48a751d834e8d68f4cb/7.x/centos7-native/b2ef2b6
|
||||
CLANG_BASE=/mnt/gvfs/third-party2/llvm-fb/963d9aeda70cc4779885b1277484fe7544a04e3e/9.0.0/platform007/9e92d53/
|
||||
LIBGCC_BASE=/mnt/gvfs/third-party2/libgcc/6ace84e956873d53638c738b6f65f3f469cca74c/7.x/platform007/5620abc
|
||||
GLIBC_BASE=/mnt/gvfs/third-party2/glibc/192b0f42d63dcf6210d6ceae387b49af049e6e0c/2.26/platform007/f259413
|
||||
SNAPPY_BASE=/mnt/gvfs/third-party2/snappy/7f9bdaada18f59bc27ec2b0871eb8a6144343aef/1.1.3/platform007/ca4da3d
|
||||
ZLIB_BASE=/mnt/gvfs/third-party2/zlib/2d9f0b9a4274cc21f61272a9e89bdb859bce8f1f/1.2.8/platform007/ca4da3d
|
||||
BZIP2_BASE=/mnt/gvfs/third-party2/bzip2/dc49a21c5fceec6456a7a28a94dcd16690af1337/1.0.6/platform007/ca4da3d
|
||||
LZ4_BASE=/mnt/gvfs/third-party2/lz4/0f607f8fc442ea7d6b876931b1898bb573d5e5da/1.9.1/platform007/ca4da3d
|
||||
ZSTD_BASE=/mnt/gvfs/third-party2/zstd/ca22bc441a4eb709e9e0b1f9fec9750fed7b31c5/1.4.x/platform007/15a3614
|
||||
GFLAGS_BASE=/mnt/gvfs/third-party2/gflags/0b9929d2588991c65a57168bf88aff2db87c5d48/2.2.0/platform007/ca4da3d
|
||||
JEMALLOC_BASE=/mnt/gvfs/third-party2/jemalloc/c26f08f47ac35fc31da2633b7da92d6b863246eb/master/platform007/c26c002
|
||||
NUMA_BASE=/mnt/gvfs/third-party2/numa/3f3fb57a5ccc5fd21c66416c0b83e0aa76a05376/2.0.11/platform007/ca4da3d
|
||||
LIBUNWIND_BASE=/mnt/gvfs/third-party2/libunwind/40c73d874898b386a71847f1b99115d93822d11f/1.4/platform007/6f3e0a9
|
||||
TBB_BASE=/mnt/gvfs/third-party2/tbb/4ce8e8dba77cdbd81b75d6f0c32fd7a1b76a11ec/2018_U5/platform007/ca4da3d
|
||||
KERNEL_HEADERS_BASE=/mnt/gvfs/third-party2/kernel-headers/fb251ecd2f5ae16f8671f7014c246e52a748fe0b/fb/platform007/da39a3e
|
||||
BINUTILS_BASE=/mnt/gvfs/third-party2/binutils/ab9f09bba370e7066cafd4eb59752db93f2e8312/2.29.1/platform007/15a3614
|
||||
VALGRIND_BASE=/mnt/gvfs/third-party2/valgrind/d42d152a15636529b0861ec493927200ebebca8e/3.15.0/platform007/ca4da3d
|
||||
LUA_BASE=/mnt/gvfs/third-party2/lua/f0cd714433206d5139df61659eb7b28b1dea6683/5.3.4/platform007/5007832
|
||||
@@ -117,6 +117,7 @@ class TsanErrorParser(MatchErrorParser):
|
||||
_TEST_NAME_TO_PARSERS = {
|
||||
'punit': [CompilerErrorParser, GTestErrorParser],
|
||||
'unit': [CompilerErrorParser, GTestErrorParser],
|
||||
'release': [CompilerErrorParser, GTestErrorParser],
|
||||
'unit_481': [CompilerErrorParser, GTestErrorParser],
|
||||
'clang_unit': [CompilerErrorParser, GTestErrorParser],
|
||||
'clang_analyze': [CompilerErrorParser, ScanBuildErrorParser],
|
||||
|
||||
@@ -13,8 +13,8 @@ source "$BASEDIR/dependencies.sh"
|
||||
CFLAGS=""
|
||||
|
||||
# libgcc
|
||||
LIBGCC_INCLUDE="$LIBGCC_BASE/include"
|
||||
LIBGCC_LIBS=" -L $LIBGCC_BASE/libs"
|
||||
LIBGCC_INCLUDE="$LIBGCC_BASE/include/c++/7.3.0"
|
||||
LIBGCC_LIBS=" -L $LIBGCC_BASE/lib"
|
||||
|
||||
# glibc
|
||||
GLIBC_INCLUDE="$GLIBC_BASE/include"
|
||||
@@ -43,12 +43,16 @@ if test -z $PIC_BUILD; then
|
||||
LZ4_INCLUDE=" -I $LZ4_BASE/include/"
|
||||
LZ4_LIBS=" $LZ4_BASE/lib/liblz4.a"
|
||||
CFLAGS+=" -DLZ4"
|
||||
|
||||
ZSTD_INCLUDE=" -I $ZSTD_BASE/include/"
|
||||
ZSTD_LIBS=" $ZSTD_BASE/lib/libzstd.a"
|
||||
CFLAGS+=" -DZSTD"
|
||||
fi
|
||||
|
||||
ZSTD_INCLUDE=" -I $ZSTD_BASE/include/"
|
||||
if test -z $PIC_BUILD; then
|
||||
ZSTD_LIBS=" $ZSTD_BASE/lib/libzstd.a"
|
||||
else
|
||||
ZSTD_LIBS=" $ZSTD_BASE/lib/libzstd_pic.a"
|
||||
fi
|
||||
CFLAGS+=" -DZSTD"
|
||||
|
||||
# location of gflags headers and libraries
|
||||
GFLAGS_INCLUDE=" -I $GFLAGS_BASE/include/"
|
||||
if test -z $PIC_BUILD; then
|
||||
@@ -56,7 +60,7 @@ if test -z $PIC_BUILD; then
|
||||
else
|
||||
GFLAGS_LIBS=" $GFLAGS_BASE/lib/libgflags_pic.a"
|
||||
fi
|
||||
CFLAGS+=" -DGFLAGS=google"
|
||||
CFLAGS+=" -DGFLAGS=gflags"
|
||||
|
||||
# location of jemalloc
|
||||
JEMALLOC_INCLUDE=" -I $JEMALLOC_BASE/include/"
|
||||
@@ -72,13 +76,22 @@ if test -z $PIC_BUILD; then
|
||||
LIBUNWIND="$LIBUNWIND_BASE/lib/libunwind.a"
|
||||
fi
|
||||
|
||||
# location of TBB
|
||||
TBB_INCLUDE=" -isystem $TBB_BASE/include/"
|
||||
if test -z $PIC_BUILD; then
|
||||
TBB_LIBS="$TBB_BASE/lib/libtbb.a"
|
||||
else
|
||||
TBB_LIBS="$TBB_BASE/lib/libtbb_pic.a"
|
||||
fi
|
||||
CFLAGS+=" -DTBB"
|
||||
|
||||
# use Intel SSE support for checksum calculations
|
||||
export USE_SSE=1
|
||||
|
||||
BINUTILS="$BINUTILS_BASE/bin"
|
||||
AR="$BINUTILS/ar"
|
||||
|
||||
DEPS_INCLUDE="$SNAPPY_INCLUDE $ZLIB_INCLUDE $BZIP_INCLUDE $LZ4_INCLUDE $ZSTD_INCLUDE $GFLAGS_INCLUDE $NUMA_INCLUDE"
|
||||
DEPS_INCLUDE="$SNAPPY_INCLUDE $ZLIB_INCLUDE $BZIP_INCLUDE $LZ4_INCLUDE $ZSTD_INCLUDE $GFLAGS_INCLUDE $NUMA_INCLUDE $TBB_INCLUDE"
|
||||
|
||||
STDLIBS="-L $GCC_BASE/lib64"
|
||||
|
||||
@@ -87,7 +100,7 @@ CLANG_LIB="$CLANG_BASE/lib"
|
||||
CLANG_SRC="$CLANG_BASE/../../src"
|
||||
|
||||
CLANG_ANALYZER="$CLANG_BIN/clang++"
|
||||
CLANG_SCAN_BUILD="$CLANG_SRC/clang/tools/scan-build/scan-build"
|
||||
CLANG_SCAN_BUILD="$CLANG_SRC/llvm/tools/clang/tools/scan-build/bin/scan-build"
|
||||
|
||||
if [ -z "$USE_CLANG" ]; then
|
||||
# gcc
|
||||
@@ -95,8 +108,8 @@ if [ -z "$USE_CLANG" ]; then
|
||||
CXX="$GCC_BASE/bin/g++"
|
||||
|
||||
CFLAGS+=" -B$BINUTILS/gold"
|
||||
CFLAGS+=" -isystem $GLIBC_INCLUDE"
|
||||
CFLAGS+=" -isystem $LIBGCC_INCLUDE"
|
||||
CFLAGS+=" -isystem $GLIBC_INCLUDE"
|
||||
JEMALLOC=1
|
||||
else
|
||||
# clang
|
||||
@@ -107,8 +120,8 @@ else
|
||||
KERNEL_HEADERS_INCLUDE="$KERNEL_HEADERS_BASE/include"
|
||||
|
||||
CFLAGS+=" -B$BINUTILS/gold -nostdinc -nostdlib"
|
||||
CFLAGS+=" -isystem $LIBGCC_BASE/include/c++/4.9.x "
|
||||
CFLAGS+=" -isystem $LIBGCC_BASE/include/c++/4.9.x/x86_64-facebook-linux "
|
||||
CFLAGS+=" -isystem $LIBGCC_BASE/include/c++/7.x "
|
||||
CFLAGS+=" -isystem $LIBGCC_BASE/include/c++/7.x/x86_64-facebook-linux "
|
||||
CFLAGS+=" -isystem $GLIBC_INCLUDE"
|
||||
CFLAGS+=" -isystem $LIBGCC_INCLUDE"
|
||||
CFLAGS+=" -isystem $CLANG_INCLUDE"
|
||||
@@ -119,18 +132,21 @@ else
|
||||
fi
|
||||
|
||||
CFLAGS+=" $DEPS_INCLUDE"
|
||||
CFLAGS+=" -DROCKSDB_PLATFORM_POSIX -DROCKSDB_LIB_IO_POSIX -DROCKSDB_FALLOCATE_PRESENT -DROCKSDB_MALLOC_USABLE_SIZE"
|
||||
CFLAGS+=" -DROCKSDB_PLATFORM_POSIX -DROCKSDB_LIB_IO_POSIX -DROCKSDB_FALLOCATE_PRESENT -DROCKSDB_MALLOC_USABLE_SIZE -DROCKSDB_RANGESYNC_PRESENT -DROCKSDB_SCHED_GETCPU_PRESENT -DROCKSDB_SUPPORT_THREAD_LOCAL -DHAVE_SSE42"
|
||||
CXXFLAGS+=" $CFLAGS"
|
||||
|
||||
EXEC_LDFLAGS=" $SNAPPY_LIBS $ZLIB_LIBS $BZIP_LIBS $LZ4_LIBS $ZSTD_LIBS $GFLAGS_LIBS $NUMA_LIB"
|
||||
EXEC_LDFLAGS+=" -Wl,--dynamic-linker,/usr/local/fbcode/gcc-4.9-glibc-2.20/lib/ld.so"
|
||||
EXEC_LDFLAGS=" $SNAPPY_LIBS $ZLIB_LIBS $BZIP_LIBS $LZ4_LIBS $ZSTD_LIBS $GFLAGS_LIBS $NUMA_LIB $TBB_LIBS"
|
||||
EXEC_LDFLAGS+=" -B$BINUTILS/gold"
|
||||
EXEC_LDFLAGS+=" -Wl,--dynamic-linker,/usr/local/fbcode/platform007/lib/ld.so"
|
||||
EXEC_LDFLAGS+=" $LIBUNWIND"
|
||||
EXEC_LDFLAGS+=" -Wl,-rpath=/usr/local/fbcode/gcc-4.9-glibc-2.20/lib"
|
||||
EXEC_LDFLAGS+=" -Wl,-rpath=/usr/local/fbcode/platform007/lib"
|
||||
# required by libtbb
|
||||
EXEC_LDFLAGS+=" -ldl"
|
||||
|
||||
PLATFORM_LDFLAGS="$LIBGCC_LIBS $GLIBC_LIBS $STDLIBS -lgcc -lstdc++"
|
||||
|
||||
EXEC_LDFLAGS_SHARED="$SNAPPY_LIBS $ZLIB_LIBS $BZIP_LIBS $LZ4_LIBS $ZSTD_LIBS $GFLAGS_LIBS"
|
||||
EXEC_LDFLAGS_SHARED="$SNAPPY_LIBS $ZLIB_LIBS $BZIP_LIBS $LZ4_LIBS $ZSTD_LIBS $GFLAGS_LIBS $TBB_LIBS"
|
||||
|
||||
VALGRIND_VER="$VALGRIND_BASE/bin/"
|
||||
|
||||
export CC CXX AR CFLAGS CXXFLAGS EXEC_LDFLAGS EXEC_LDFLAGS_SHARED VALGRIND_VER JEMALLOC_LIB JEMALLOC_INCLUDE CLANG_ANALYZER CLANG_SCAN_BUILD
|
||||
export CC CXX AR CFLAGS CXXFLAGS EXEC_LDFLAGS EXEC_LDFLAGS_SHARED VALGRIND_VER JEMALLOC_LIB JEMALLOC_INCLUDE CLANG_ANALYZER CLANG_SCAN_BUILD
|
||||
@@ -9,7 +9,7 @@ source "$BASEDIR/dependencies_4.8.1.sh"
|
||||
|
||||
# location of libgcc
|
||||
LIBGCC_INCLUDE="$LIBGCC_BASE/include"
|
||||
LIBGCC_LIBS=" -L $LIBGCC_BASE/libs"
|
||||
LIBGCC_LIBS=" -L $LIBGCC_BASE/lib"
|
||||
|
||||
# location of glibc
|
||||
GLIBC_INCLUDE="$GLIBC_BASE/include"
|
||||
|
||||
@@ -12,17 +12,15 @@ import re
|
||||
import os
|
||||
import time
|
||||
|
||||
|
||||
#
|
||||
# Simple logger
|
||||
#
|
||||
|
||||
|
||||
class Log:
|
||||
|
||||
LOG_FILE = "/tmp/precommit-check.log"
|
||||
|
||||
def __init__(self):
|
||||
self.filename = Log.LOG_FILE
|
||||
def __init__(self, filename):
|
||||
self.filename = filename
|
||||
self.f = open(self.filename, 'w+', 0)
|
||||
|
||||
def caption(self, str):
|
||||
@@ -52,9 +50,9 @@ class Log:
|
||||
|
||||
class Env(object):
|
||||
|
||||
def __init__(self, tests):
|
||||
def __init__(self, logfile, tests):
|
||||
self.tests = tests
|
||||
self.log = Log()
|
||||
self.log = Log(logfile)
|
||||
|
||||
def shell(self, cmd, path=os.getcwd()):
|
||||
if path:
|
||||
@@ -87,8 +85,9 @@ class Env(object):
|
||||
|
||||
class PreCommitChecker(Env):
|
||||
|
||||
def __init__(self, tests):
|
||||
Env.__init__(self, tests)
|
||||
def __init__(self, args):
|
||||
Env.__init__(self, args.logfile, args.tests)
|
||||
self.ignore_failure = args.ignore_failure
|
||||
|
||||
#
|
||||
# Get commands for a given job from the determinator file
|
||||
@@ -144,6 +143,7 @@ class PreCommitChecker(Env):
|
||||
self.print_row("TEST", "RESULT")
|
||||
self.print_separator()
|
||||
|
||||
result = True
|
||||
for test in self.tests:
|
||||
start_time = time.time()
|
||||
self.print_test(test)
|
||||
@@ -152,11 +152,14 @@ class PreCommitChecker(Env):
|
||||
if not result:
|
||||
self.log.error("Error running test %s" % test)
|
||||
self.print_result("FAIL (%dm)" % elapsed_min)
|
||||
return False
|
||||
self.print_result("PASS (%dm)" % elapsed_min)
|
||||
if not self.ignore_failure:
|
||||
return False
|
||||
result = False
|
||||
else:
|
||||
self.print_result("PASS (%dm)" % elapsed_min)
|
||||
|
||||
self.print_separator()
|
||||
return True
|
||||
return result
|
||||
|
||||
#
|
||||
# Print a line
|
||||
@@ -182,17 +185,24 @@ class PreCommitChecker(Env):
|
||||
#
|
||||
parser = argparse.ArgumentParser(description='RocksDB pre-commit checker.')
|
||||
|
||||
# --log <logfile>
|
||||
parser.add_argument('--logfile', default='/tmp/precommit-check.log',
|
||||
help='Log file. Default is /tmp/precommit-check.log')
|
||||
# --ignore_failure
|
||||
parser.add_argument('--ignore_failure', action='store_true', default=False,
|
||||
help='Stop when an error occurs')
|
||||
# <test ....>
|
||||
parser.add_argument('test', nargs='+',
|
||||
parser.add_argument('tests', nargs='+',
|
||||
help='CI test(s) to run. e.g: unit punit asan tsan ubsan')
|
||||
|
||||
print("Please follow log %s" % Log.LOG_FILE)
|
||||
|
||||
args = parser.parse_args()
|
||||
checker = PreCommitChecker(args.test)
|
||||
checker = PreCommitChecker(args)
|
||||
|
||||
print("Please follow log %s" % checker.log.filename)
|
||||
|
||||
if not checker.run_tests():
|
||||
print("Error running tests. Please check log file %s" % Log.LOG_FILE)
|
||||
print("Error running tests. Please check log file %s"
|
||||
% checker.log.filename)
|
||||
sys.exit(1)
|
||||
|
||||
sys.exit(0)
|
||||
@@ -146,6 +146,26 @@ UNIT_TEST_COMMANDS="[
|
||||
}
|
||||
]"
|
||||
|
||||
#
|
||||
# RocksDB release build and unit tests
|
||||
#
|
||||
RELEASE_UNIT_TEST_COMMANDS="[
|
||||
{
|
||||
'name':'Rocksdb Release Unit Test',
|
||||
'oncall':'$ONCALL',
|
||||
'steps': [
|
||||
$CLEANUP_ENV,
|
||||
{
|
||||
'name':'Build and test RocksDB debug version',
|
||||
'shell':'$SHM $DEBUG make J=1 release',
|
||||
'user':'root',
|
||||
$PARSER
|
||||
},
|
||||
],
|
||||
$REPORT
|
||||
}
|
||||
]"
|
||||
|
||||
#
|
||||
# RocksDB unit test on gcc-4.8.1
|
||||
#
|
||||
@@ -665,6 +685,9 @@ case $1 in
|
||||
unit)
|
||||
echo $UNIT_TEST_COMMANDS
|
||||
;;
|
||||
release)
|
||||
echo $RELEASE_UNIT_TEST_COMMANDS
|
||||
;;
|
||||
unit_481)
|
||||
echo $UNIT_TEST_COMMANDS_481
|
||||
;;
|
||||
|
||||
+18
-29
@@ -273,35 +273,24 @@ TEST_F(AutoRollLoggerTest, LogFlushWhileRolling) {
|
||||
ASSERT_TRUE(auto_roll_logger);
|
||||
std::thread flush_thread;
|
||||
|
||||
rocksdb::SyncPoint::GetInstance()->LoadDependency({
|
||||
// Need to pin the old logger before beginning the roll, as rolling grabs
|
||||
// the mutex, which would prevent us from accessing the old logger.
|
||||
{"AutoRollLogger::Flush:PinnedLogger",
|
||||
"AutoRollLoggerTest::LogFlushWhileRolling:PreRollAndPostThreadInit"},
|
||||
// Need to finish the flush thread init before this callback because the
|
||||
// callback accesses flush_thread.get_id() in order to apply certain sync
|
||||
// points only to the flush thread.
|
||||
{"AutoRollLoggerTest::LogFlushWhileRolling:PreRollAndPostThreadInit",
|
||||
"AutoRollLoggerTest::LogFlushWhileRolling:FlushCallbackBegin"},
|
||||
// Need to reset logger at this point in Flush() to exercise a race
|
||||
// condition case, which is executing the flush with the pinned (old)
|
||||
// logger after the roll has cut over to a new logger.
|
||||
{"AutoRollLoggerTest::LogFlushWhileRolling:FlushCallback1",
|
||||
"AutoRollLogger::ResetLogger:BeforeNewLogger"},
|
||||
{"AutoRollLogger::ResetLogger:AfterNewLogger",
|
||||
"AutoRollLoggerTest::LogFlushWhileRolling:FlushCallback2"},
|
||||
});
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"PosixLogger::Flush:BeginCallback", [&](void* arg) {
|
||||
TEST_SYNC_POINT(
|
||||
"AutoRollLoggerTest::LogFlushWhileRolling:FlushCallbackBegin");
|
||||
if (std::this_thread::get_id() == flush_thread.get_id()) {
|
||||
TEST_SYNC_POINT(
|
||||
"AutoRollLoggerTest::LogFlushWhileRolling:FlushCallback1");
|
||||
TEST_SYNC_POINT(
|
||||
"AutoRollLoggerTest::LogFlushWhileRolling:FlushCallback2");
|
||||
}
|
||||
});
|
||||
// Notes:
|
||||
// (1) Need to pin the old logger before beginning the roll, as rolling grabs
|
||||
// the mutex, which would prevent us from accessing the old logger. This
|
||||
// also marks flush_thread with AutoRollLogger::Flush:PinnedLogger.
|
||||
// (2) Need to reset logger during PosixLogger::Flush() to exercise a race
|
||||
// condition case, which is executing the flush with the pinned (old)
|
||||
// logger after auto-roll logger has cut over to a new logger.
|
||||
// (3) PosixLogger::Flush() happens in both threads but its SyncPoints only
|
||||
// are enabled in flush_thread (the one pinning the old logger).
|
||||
rocksdb::SyncPoint::GetInstance()->LoadDependencyAndMarkers(
|
||||
{{"AutoRollLogger::Flush:PinnedLogger",
|
||||
"AutoRollLoggerTest::LogFlushWhileRolling:PreRollAndPostThreadInit"},
|
||||
{"PosixLogger::Flush:Begin1",
|
||||
"AutoRollLogger::ResetLogger:BeforeNewLogger"},
|
||||
{"AutoRollLogger::ResetLogger:AfterNewLogger",
|
||||
"PosixLogger::Flush:Begin2"}},
|
||||
{{"AutoRollLogger::Flush:PinnedLogger", "PosixLogger::Flush:Begin1"},
|
||||
{"AutoRollLogger::Flush:PinnedLogger", "PosixLogger::Flush:Begin2"}});
|
||||
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
flush_thread = std::thread([&]() { auto_roll_logger->Flush(); });
|
||||
|
||||
@@ -1692,11 +1692,6 @@ void rocksdb_options_set_verify_checksums_in_compaction(
|
||||
opt->rep.verify_checksums_in_compaction = v;
|
||||
}
|
||||
|
||||
void rocksdb_options_set_filter_deletes(
|
||||
rocksdb_options_t* opt, unsigned char v) {
|
||||
opt->rep.filter_deletes = v;
|
||||
}
|
||||
|
||||
void rocksdb_options_set_max_sequential_skip_in_iterations(
|
||||
rocksdb_options_t* opt, uint64_t v) {
|
||||
opt->rep.max_sequential_skip_in_iterations = v;
|
||||
@@ -1799,14 +1794,9 @@ void rocksdb_options_set_memtable_vector_rep(rocksdb_options_t *opt) {
|
||||
opt->rep.memtable_factory.reset(factory);
|
||||
}
|
||||
|
||||
void rocksdb_options_set_memtable_prefix_bloom_bits(
|
||||
rocksdb_options_t* opt, uint32_t v) {
|
||||
opt->rep.memtable_prefix_bloom_bits = v;
|
||||
}
|
||||
|
||||
void rocksdb_options_set_memtable_prefix_bloom_probes(
|
||||
rocksdb_options_t* opt, uint32_t v) {
|
||||
opt->rep.memtable_prefix_bloom_probes = v;
|
||||
void rocksdb_options_set_memtable_prefix_bloom_size_ratio(
|
||||
rocksdb_options_t* opt, double v) {
|
||||
opt->rep.memtable_prefix_bloom_size_ratio = v;
|
||||
}
|
||||
|
||||
void rocksdb_options_set_memtable_prefix_bloom_huge_page_tlb_size(
|
||||
|
||||
+14
-15
@@ -26,7 +26,6 @@
|
||||
#include "db/table_properties_collector.h"
|
||||
#include "db/version_set.h"
|
||||
#include "db/write_controller.h"
|
||||
#include "db/writebuffer.h"
|
||||
#include "memtable/hash_skiplist_rep.h"
|
||||
#include "util/autovector.h"
|
||||
#include "util/compression.h"
|
||||
@@ -130,11 +129,6 @@ Status CheckConcurrentWritesSupported(const ColumnFamilyOptions& cf_options) {
|
||||
"In-place memtable updates (inplace_update_support) is not compatible "
|
||||
"with concurrent writes (allow_concurrent_memtable_write)");
|
||||
}
|
||||
if (cf_options.filter_deletes) {
|
||||
return Status::InvalidArgument(
|
||||
"Delete filtering (filter_deletes) is not compatible with concurrent "
|
||||
"memtable writes (allow_concurrent_memtable_writes)");
|
||||
}
|
||||
if (!cf_options.memtable_factory->IsInsertConcurrentlySupported()) {
|
||||
return Status::InvalidArgument(
|
||||
"Memtable doesn't concurrent writes (allow_concurrent_memtable_write)");
|
||||
@@ -177,6 +171,12 @@ ColumnFamilyOptions SanitizeOptions(const DBOptions& db_options,
|
||||
if (result.max_write_buffer_number_to_maintain < 0) {
|
||||
result.max_write_buffer_number_to_maintain = result.max_write_buffer_number;
|
||||
}
|
||||
// bloom filter size shouldn't exceed 1/4 of memtable size.
|
||||
if (result.memtable_prefix_bloom_size_ratio > 0.25) {
|
||||
result.memtable_prefix_bloom_size_ratio = 0.25;
|
||||
} else if (result.memtable_prefix_bloom_size_ratio < 0) {
|
||||
result.memtable_prefix_bloom_size_ratio = 0;
|
||||
}
|
||||
XFUNC_TEST("memtablelist_history", "transaction_xftest_SanitizeOptions",
|
||||
xf_transaction_set_memtable_history1,
|
||||
xf_transaction_set_memtable_history,
|
||||
@@ -329,7 +329,7 @@ void SuperVersionUnrefHandle(void* ptr) {
|
||||
|
||||
ColumnFamilyData::ColumnFamilyData(
|
||||
uint32_t id, const std::string& name, Version* _dummy_versions,
|
||||
Cache* _table_cache, WriteBuffer* write_buffer,
|
||||
Cache* _table_cache, WriteBufferManager* write_buffer_manager,
|
||||
const ColumnFamilyOptions& cf_options, const DBOptions* db_options,
|
||||
const EnvOptions& env_options, ColumnFamilySet* column_family_set)
|
||||
: id_(id),
|
||||
@@ -343,7 +343,7 @@ ColumnFamilyData::ColumnFamilyData(
|
||||
SanitizeOptions(*db_options, &internal_comparator_, cf_options)),
|
||||
ioptions_(options_),
|
||||
mutable_cf_options_(options_, ioptions_),
|
||||
write_buffer_(write_buffer),
|
||||
write_buffer_manager_(write_buffer_manager),
|
||||
mem_(nullptr),
|
||||
imm_(options_.min_write_buffer_number_to_merge,
|
||||
options_.max_write_buffer_number_to_maintain),
|
||||
@@ -675,7 +675,7 @@ MemTable* ColumnFamilyData::ConstructNewMemtable(
|
||||
const MutableCFOptions& mutable_cf_options, SequenceNumber earliest_seq) {
|
||||
assert(current_ != nullptr);
|
||||
return new MemTable(internal_comparator_, ioptions_, mutable_cf_options,
|
||||
write_buffer_, earliest_seq);
|
||||
write_buffer_manager_, earliest_seq);
|
||||
}
|
||||
|
||||
void ColumnFamilyData::CreateNewMemtable(
|
||||
@@ -854,7 +854,7 @@ ColumnFamilySet::ColumnFamilySet(const std::string& dbname,
|
||||
const DBOptions* db_options,
|
||||
const EnvOptions& env_options,
|
||||
Cache* table_cache,
|
||||
WriteBuffer* write_buffer,
|
||||
WriteBufferManager* write_buffer_manager,
|
||||
WriteController* write_controller)
|
||||
: max_column_family_(0),
|
||||
dummy_cfd_(new ColumnFamilyData(0, "", nullptr, nullptr, nullptr,
|
||||
@@ -865,7 +865,7 @@ ColumnFamilySet::ColumnFamilySet(const std::string& dbname,
|
||||
db_options_(db_options),
|
||||
env_options_(env_options),
|
||||
table_cache_(table_cache),
|
||||
write_buffer_(write_buffer),
|
||||
write_buffer_manager_(write_buffer_manager),
|
||||
write_controller_(write_controller) {
|
||||
// initialize linked list
|
||||
dummy_cfd_->prev_ = dummy_cfd_;
|
||||
@@ -928,10 +928,9 @@ ColumnFamilyData* ColumnFamilySet::CreateColumnFamily(
|
||||
const std::string& name, uint32_t id, Version* dummy_versions,
|
||||
const ColumnFamilyOptions& options) {
|
||||
assert(column_families_.find(name) == column_families_.end());
|
||||
ColumnFamilyData* new_cfd =
|
||||
new ColumnFamilyData(id, name, dummy_versions, table_cache_,
|
||||
write_buffer_, options, db_options_,
|
||||
env_options_, this);
|
||||
ColumnFamilyData* new_cfd = new ColumnFamilyData(
|
||||
id, name, dummy_versions, table_cache_, write_buffer_manager_, options,
|
||||
db_options_, env_options_, this);
|
||||
column_families_.insert({name, id});
|
||||
column_family_data_.insert({id, new_cfd});
|
||||
max_column_family_ = std::max(max_column_family_, id);
|
||||
|
||||
+5
-4
@@ -323,7 +323,7 @@ class ColumnFamilyData {
|
||||
friend class ColumnFamilySet;
|
||||
ColumnFamilyData(uint32_t id, const std::string& name,
|
||||
Version* dummy_versions, Cache* table_cache,
|
||||
WriteBuffer* write_buffer,
|
||||
WriteBufferManager* write_buffer_manager,
|
||||
const ColumnFamilyOptions& options,
|
||||
const DBOptions* db_options, const EnvOptions& env_options,
|
||||
ColumnFamilySet* column_family_set);
|
||||
@@ -348,7 +348,7 @@ class ColumnFamilyData {
|
||||
|
||||
std::unique_ptr<InternalStats> internal_stats_;
|
||||
|
||||
WriteBuffer* write_buffer_;
|
||||
WriteBufferManager* write_buffer_manager_;
|
||||
|
||||
MemTable* mem_;
|
||||
MemTableList imm_;
|
||||
@@ -438,7 +438,8 @@ class ColumnFamilySet {
|
||||
|
||||
ColumnFamilySet(const std::string& dbname, const DBOptions* db_options,
|
||||
const EnvOptions& env_options, Cache* table_cache,
|
||||
WriteBuffer* write_buffer, WriteController* write_controller);
|
||||
WriteBufferManager* write_buffer_manager,
|
||||
WriteController* write_controller);
|
||||
~ColumnFamilySet();
|
||||
|
||||
ColumnFamilyData* GetDefault() const;
|
||||
@@ -495,7 +496,7 @@ class ColumnFamilySet {
|
||||
const DBOptions* const db_options_;
|
||||
const EnvOptions env_options_;
|
||||
Cache* table_cache_;
|
||||
WriteBuffer* write_buffer_;
|
||||
WriteBufferManager* write_buffer_manager_;
|
||||
WriteController* write_controller_;
|
||||
};
|
||||
|
||||
|
||||
+136
-3
@@ -18,6 +18,7 @@
|
||||
#include "rocksdb/env.h"
|
||||
#include "rocksdb/iterator.h"
|
||||
#include "util/coding.h"
|
||||
#include "util/fault_injection_test_env.h"
|
||||
#include "util/options_parser.h"
|
||||
#include "util/string_util.h"
|
||||
#include "util/sync_point.h"
|
||||
@@ -147,7 +148,7 @@ class ColumnFamilyTest : public testing::Test {
|
||||
void Close() {
|
||||
for (auto h : handles_) {
|
||||
if (h) {
|
||||
delete h;
|
||||
db_->DestroyColumnFamilyHandle(h);
|
||||
}
|
||||
}
|
||||
handles_.clear();
|
||||
@@ -258,7 +259,7 @@ class ColumnFamilyTest : public testing::Test {
|
||||
void DropColumnFamilies(const std::vector<int>& cfs) {
|
||||
for (auto cf : cfs) {
|
||||
ASSERT_OK(db_->DropColumnFamily(handles_[cf]));
|
||||
delete handles_[cf];
|
||||
db_->DestroyColumnFamilyHandle(handles_[cf]);
|
||||
handles_[cf] = nullptr;
|
||||
names_[cf] = "";
|
||||
}
|
||||
@@ -500,6 +501,135 @@ TEST_F(ColumnFamilyTest, DontReuseColumnFamilyID) {
|
||||
}
|
||||
}
|
||||
|
||||
class FlushEmptyCFTestWithParam : public ColumnFamilyTest,
|
||||
public testing::WithParamInterface<bool> {
|
||||
public:
|
||||
FlushEmptyCFTestWithParam() { allow_2pc_ = GetParam(); }
|
||||
|
||||
// Required if inheriting from testing::WithParamInterface<>
|
||||
static void SetUpTestCase() {}
|
||||
static void TearDownTestCase() {}
|
||||
|
||||
bool allow_2pc_;
|
||||
};
|
||||
|
||||
TEST_P(FlushEmptyCFTestWithParam, FlushEmptyCFTest) {
|
||||
std::unique_ptr<FaultInjectionTestEnv> fault_env(
|
||||
new FaultInjectionTestEnv(env_));
|
||||
db_options_.env = fault_env.get();
|
||||
db_options_.allow_2pc = allow_2pc_;
|
||||
Open();
|
||||
CreateColumnFamilies({"one", "two"});
|
||||
// Generate log file A.
|
||||
ASSERT_OK(Put(1, "foo", "v1")); // seqID 1
|
||||
|
||||
Reopen();
|
||||
// Log file A is not dropped after reopening because default column family's
|
||||
// min log number is 0.
|
||||
// It flushes to SST file X
|
||||
ASSERT_OK(Put(1, "foo", "v1")); // seqID 2
|
||||
ASSERT_OK(Put(1, "bar", "v2")); // seqID 3
|
||||
// Current log file is file B now. While flushing, a new log file C is created
|
||||
// and is set to current. Boths' min log number is set to file C in memory, so
|
||||
// after flushing file B is deleted. At the same time, the min log number of
|
||||
// default CF is not written to manifest. Log file A still remains.
|
||||
// Flushed to SST file Y.
|
||||
Flush(1);
|
||||
Flush(0);
|
||||
ASSERT_OK(Put(1, "bar", "v3")); // seqID 4
|
||||
ASSERT_OK(Put(1, "foo", "v4")); // seqID 5
|
||||
|
||||
// Preserve file system state up to here to simulate a crash condition.
|
||||
fault_env->SetFilesystemActive(false);
|
||||
std::vector<std::string> names;
|
||||
for (auto name : names_) {
|
||||
if (name != "") {
|
||||
names.push_back(name);
|
||||
}
|
||||
}
|
||||
|
||||
Close();
|
||||
fault_env->ResetState();
|
||||
|
||||
// Before opening, there are four files:
|
||||
// Log file A contains seqID 1
|
||||
// Log file C contains seqID 4, 5
|
||||
// SST file X contains seqID 1
|
||||
// SST file Y contains seqID 2, 3
|
||||
// Min log number:
|
||||
// default CF: 0
|
||||
// CF one, two: C
|
||||
// When opening the DB, all the seqID should be preserved.
|
||||
Open(names, {});
|
||||
ASSERT_EQ("v4", Get(1, "foo"));
|
||||
ASSERT_EQ("v3", Get(1, "bar"));
|
||||
Close();
|
||||
|
||||
db_options_.env = env_;
|
||||
}
|
||||
|
||||
TEST_P(FlushEmptyCFTestWithParam, FlushEmptyCFTest2) {
|
||||
std::unique_ptr<FaultInjectionTestEnv> fault_env(
|
||||
new FaultInjectionTestEnv(env_));
|
||||
db_options_.env = fault_env.get();
|
||||
db_options_.allow_2pc = allow_2pc_;
|
||||
Open();
|
||||
CreateColumnFamilies({"one", "two"});
|
||||
// Generate log file A.
|
||||
ASSERT_OK(Put(1, "foo", "v1")); // seqID 1
|
||||
|
||||
Reopen();
|
||||
// Log file A is not dropped after reopening because default column family's
|
||||
// min log number is 0.
|
||||
// It flushes to SST file X
|
||||
ASSERT_OK(Put(1, "foo", "v1")); // seqID 2
|
||||
ASSERT_OK(Put(1, "bar", "v2")); // seqID 3
|
||||
// Current log file is file B now. While flushing, a new log file C is created
|
||||
// and is set to current. Both CFs' min log number is set to file C so after
|
||||
// flushing file B is deleted. Log file A still remains.
|
||||
// Flushed to SST file Y.
|
||||
Flush(1);
|
||||
ASSERT_OK(Put(0, "bar", "v2")); // seqID 4
|
||||
ASSERT_OK(Put(2, "bar", "v2")); // seqID 5
|
||||
ASSERT_OK(Put(1, "bar", "v3")); // seqID 6
|
||||
// Flushing all column families. This forces all CFs' min log to current. This
|
||||
// is written to the manifest file. Log file C is cleared.
|
||||
Flush(0);
|
||||
Flush(1);
|
||||
Flush(2);
|
||||
// Write to log file D
|
||||
ASSERT_OK(Put(1, "bar", "v4")); // seqID 7
|
||||
ASSERT_OK(Put(1, "bar", "v5")); // seqID 8
|
||||
// Preserve file system state up to here to simulate a crash condition.
|
||||
fault_env->SetFilesystemActive(false);
|
||||
std::vector<std::string> names;
|
||||
for (auto name : names_) {
|
||||
if (name != "") {
|
||||
names.push_back(name);
|
||||
}
|
||||
}
|
||||
|
||||
Close();
|
||||
fault_env->ResetState();
|
||||
// Before opening, there are two logfiles:
|
||||
// Log file A contains seqID 1
|
||||
// Log file D contains seqID 7, 8
|
||||
// Min log number:
|
||||
// default CF: D
|
||||
// CF one, two: D
|
||||
// When opening the DB, log file D should be replayed using the seqID
|
||||
// specified in the file.
|
||||
Open(names, {});
|
||||
ASSERT_EQ("v1", Get(1, "foo"));
|
||||
ASSERT_EQ("v5", Get(1, "bar"));
|
||||
Close();
|
||||
|
||||
db_options_.env = env_;
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(FlushEmptyCFTestWithParam, FlushEmptyCFTestWithParam,
|
||||
::testing::Bool());
|
||||
|
||||
TEST_F(ColumnFamilyTest, AddDrop) {
|
||||
Open();
|
||||
CreateColumnFamilies({"one", "two", "three"});
|
||||
@@ -1950,6 +2080,9 @@ TEST_F(ColumnFamilyTest, FlushStaleColumnFamilies) {
|
||||
// 3 files for default column families, 1 file for column family [two], zero
|
||||
// files for column family [one], because it's empty
|
||||
AssertCountLiveFiles(4);
|
||||
|
||||
Flush(0);
|
||||
ASSERT_EQ(0, dbfull()->TEST_total_log_size());
|
||||
Close();
|
||||
}
|
||||
|
||||
@@ -2043,7 +2176,7 @@ TEST_F(ColumnFamilyTest, ReadDroppedColumnFamily) {
|
||||
ASSERT_OK(db_->DropColumnFamily(handles_[2]));
|
||||
} else {
|
||||
// delete CF two
|
||||
delete handles_[2];
|
||||
db_->DestroyColumnFamilyHandle(handles_[2]);
|
||||
handles_[2] = nullptr;
|
||||
}
|
||||
// Make sure iterator created can still be used.
|
||||
|
||||
+1
-2
@@ -187,8 +187,7 @@ Compaction::Compaction(VersionStorageInfo* vstorage,
|
||||
}
|
||||
}
|
||||
|
||||
Slice smallest_user_key;
|
||||
GetBoundaryKeys(vstorage, inputs_, &smallest_user_key, &largest_user_key_);
|
||||
GetBoundaryKeys(vstorage, inputs_, &smallest_user_key_, &largest_user_key_);
|
||||
}
|
||||
|
||||
Compaction::~Compaction() {
|
||||
|
||||
@@ -219,6 +219,8 @@ class Compaction {
|
||||
output_table_properties_ = std::move(tp);
|
||||
}
|
||||
|
||||
Slice GetSmallestUserKey() const { return smallest_user_key_; }
|
||||
|
||||
Slice GetLargestUserKey() const { return largest_user_key_; }
|
||||
|
||||
CompactionReason compaction_reason() { return compaction_reason_; }
|
||||
@@ -294,6 +296,9 @@ class Compaction {
|
||||
// table properties of output files
|
||||
TablePropertiesCollection output_table_properties_;
|
||||
|
||||
// smallest user keys in compaction
|
||||
Slice smallest_user_key_;
|
||||
|
||||
// largest user keys in compaction
|
||||
Slice largest_user_key_;
|
||||
|
||||
|
||||
@@ -10,13 +10,13 @@
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
|
||||
#include "db/compaction_job.h"
|
||||
#include "db/column_family.h"
|
||||
#include "db/compaction_job.h"
|
||||
#include "db/version_set.h"
|
||||
#include "db/writebuffer.h"
|
||||
#include "rocksdb/cache.h"
|
||||
#include "rocksdb/db.h"
|
||||
#include "rocksdb/options.h"
|
||||
#include "rocksdb/write_buffer_manager.h"
|
||||
#include "table/mock_table.h"
|
||||
#include "util/file_reader_writer.h"
|
||||
#include "util/string_util.h"
|
||||
@@ -70,9 +70,9 @@ class CompactionJobTest : public testing::Test {
|
||||
dbname_(test::TmpDir() + "/compaction_job_test"),
|
||||
mutable_cf_options_(Options(), ImmutableCFOptions(Options())),
|
||||
table_cache_(NewLRUCache(50000, 16)),
|
||||
write_buffer_(db_options_.db_write_buffer_size),
|
||||
write_buffer_manager_(db_options_.db_write_buffer_size),
|
||||
versions_(new VersionSet(dbname_, &db_options_, env_options_,
|
||||
table_cache_.get(), &write_buffer_,
|
||||
table_cache_.get(), &write_buffer_manager_,
|
||||
&write_controller_)),
|
||||
shutting_down_(false),
|
||||
mock_table_factory_(new mock::MockTableFactory()) {
|
||||
@@ -285,7 +285,7 @@ class CompactionJobTest : public testing::Test {
|
||||
WriteController write_controller_;
|
||||
DBOptions db_options_;
|
||||
ColumnFamilyOptions cf_options_;
|
||||
WriteBuffer write_buffer_;
|
||||
WriteBufferManager write_buffer_manager_;
|
||||
std::unique_ptr<VersionSet> versions_;
|
||||
InstrumentedMutex mutex_;
|
||||
std::atomic<bool> shutting_down_;
|
||||
|
||||
@@ -1708,14 +1708,14 @@ Compaction* UniversalCompactionPicker::PickCompactionUniversalSizeAmp(
|
||||
LogToBuffer(
|
||||
log_buffer,
|
||||
"[%s] Universal: size amp not needed. newer-files-total-size %" PRIu64
|
||||
"earliest-file-size %" PRIu64,
|
||||
" earliest-file-size %" PRIu64,
|
||||
cf_name.c_str(), candidate_size, earliest_file_size);
|
||||
return nullptr;
|
||||
} else {
|
||||
LogToBuffer(
|
||||
log_buffer,
|
||||
"[%s] Universal: size amp needed. newer-files-total-size %" PRIu64
|
||||
"earliest-file-size %" PRIu64,
|
||||
" earliest-file-size %" PRIu64,
|
||||
cf_name.c_str(), candidate_size, earliest_file_size);
|
||||
}
|
||||
assert(start_index < sorted_runs.size() - 1);
|
||||
|
||||
@@ -39,6 +39,7 @@ class DBBlockCacheTest : public DBTestBase {
|
||||
Options GetOptions(const BlockBasedTableOptions& table_options) {
|
||||
Options options = CurrentOptions();
|
||||
options.create_if_missing = true;
|
||||
options.avoid_flush_during_recovery = false;
|
||||
// options.compression = kNoCompression;
|
||||
options.statistics = rocksdb::CreateDBStatistics();
|
||||
options.table_factory.reset(new BlockBasedTableFactory(table_options));
|
||||
|
||||
+26
-49
@@ -20,15 +20,29 @@ class DBBloomFilterTest : public DBTestBase {
|
||||
DBBloomFilterTest() : DBTestBase("/db_bloom_filter_test") {}
|
||||
};
|
||||
|
||||
class DBBloomFilterTestWithParam : public DBTestBase,
|
||||
public testing::WithParamInterface<bool> {
|
||||
protected:
|
||||
bool use_block_based_filter_;
|
||||
|
||||
public:
|
||||
DBBloomFilterTestWithParam() : DBTestBase("/db_bloom_filter_tests") {}
|
||||
|
||||
~DBBloomFilterTestWithParam() {}
|
||||
|
||||
void SetUp() override { use_block_based_filter_ = GetParam(); }
|
||||
};
|
||||
|
||||
// KeyMayExist can lead to a few false positives, but not false negatives.
|
||||
// To make test deterministic, use a much larger number of bits per key-20 than
|
||||
// bits in the key, so that false positives are eliminated
|
||||
TEST_F(DBBloomFilterTest, KeyMayExist) {
|
||||
TEST_P(DBBloomFilterTestWithParam, KeyMayExist) {
|
||||
do {
|
||||
ReadOptions ropts;
|
||||
std::string value;
|
||||
anon::OptionsOverride options_override;
|
||||
options_override.filter_policy.reset(NewBloomFilterPolicy(20));
|
||||
options_override.filter_policy.reset(
|
||||
NewBloomFilterPolicy(20, use_block_based_filter_));
|
||||
Options options = CurrentOptions(options_override);
|
||||
options.statistics = rocksdb::CreateDBStatistics();
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
@@ -87,45 +101,6 @@ TEST_F(DBBloomFilterTest, KeyMayExist) {
|
||||
ChangeOptions(kSkipPlainTable | kSkipHashIndex | kSkipFIFOCompaction));
|
||||
}
|
||||
|
||||
// A delete is skipped for key if KeyMayExist(key) returns False
|
||||
// Tests Writebatch consistency and proper delete behaviour
|
||||
TEST_F(DBBloomFilterTest, FilterDeletes) {
|
||||
do {
|
||||
anon::OptionsOverride options_override;
|
||||
options_override.filter_policy.reset(NewBloomFilterPolicy(20));
|
||||
Options options = CurrentOptions(options_override);
|
||||
options.filter_deletes = true;
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
WriteBatch batch;
|
||||
|
||||
batch.Delete(handles_[1], "a");
|
||||
dbfull()->Write(WriteOptions(), &batch);
|
||||
ASSERT_EQ(AllEntriesFor("a", 1), "[ ]"); // Delete skipped
|
||||
batch.Clear();
|
||||
|
||||
batch.Put(handles_[1], "a", "b");
|
||||
batch.Delete(handles_[1], "a");
|
||||
dbfull()->Write(WriteOptions(), &batch);
|
||||
ASSERT_EQ(Get(1, "a"), "NOT_FOUND");
|
||||
ASSERT_EQ(AllEntriesFor("a", 1), "[ DEL, b ]"); // Delete issued
|
||||
batch.Clear();
|
||||
|
||||
batch.Delete(handles_[1], "c");
|
||||
batch.Put(handles_[1], "c", "d");
|
||||
dbfull()->Write(WriteOptions(), &batch);
|
||||
ASSERT_EQ(Get(1, "c"), "d");
|
||||
ASSERT_EQ(AllEntriesFor("c", 1), "[ d ]"); // Delete skipped
|
||||
batch.Clear();
|
||||
|
||||
ASSERT_OK(Flush(1)); // A stray Flush
|
||||
|
||||
batch.Delete(handles_[1], "c");
|
||||
dbfull()->Write(WriteOptions(), &batch);
|
||||
ASSERT_EQ(AllEntriesFor("c", 1), "[ DEL, d ]"); // Delete issued
|
||||
batch.Clear();
|
||||
} while (ChangeCompactOptions());
|
||||
}
|
||||
|
||||
TEST_F(DBBloomFilterTest, GetFilterByPrefixBloom) {
|
||||
Options options = last_options_;
|
||||
options.prefix_extractor.reset(NewFixedPrefixTransform(8));
|
||||
@@ -315,7 +290,7 @@ TEST_F(DBBloomFilterTest, WholeKeyFilterProp) {
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 12);
|
||||
}
|
||||
|
||||
TEST_F(DBBloomFilterTest, BloomFilter) {
|
||||
TEST_P(DBBloomFilterTestWithParam, BloomFilter) {
|
||||
do {
|
||||
Options options = CurrentOptions();
|
||||
env_->count_random_reads_ = true;
|
||||
@@ -324,7 +299,8 @@ TEST_F(DBBloomFilterTest, BloomFilter) {
|
||||
// trigger reset of table_factory
|
||||
BlockBasedTableOptions table_options;
|
||||
table_options.no_block_cache = true;
|
||||
table_options.filter_policy.reset(NewBloomFilterPolicy(10));
|
||||
table_options.filter_policy.reset(
|
||||
NewBloomFilterPolicy(10, use_block_based_filter_));
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
@@ -367,6 +343,9 @@ TEST_F(DBBloomFilterTest, BloomFilter) {
|
||||
} while (ChangeCompactOptions());
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(DBBloomFilterTestWithParam, DBBloomFilterTestWithParam,
|
||||
::testing::Bool());
|
||||
|
||||
TEST_F(DBBloomFilterTest, BloomFilterRate) {
|
||||
while (ChangeFilterOptions()) {
|
||||
Options options = CurrentOptions();
|
||||
@@ -615,7 +594,8 @@ class BloomStatsTestWithParam
|
||||
|
||||
options_.create_if_missing = true;
|
||||
options_.prefix_extractor.reset(rocksdb::NewFixedPrefixTransform(4));
|
||||
options_.memtable_prefix_bloom_bits = 8 * 1024;
|
||||
options_.memtable_prefix_bloom_size_ratio =
|
||||
8.0 * 1024.0 / static_cast<double>(options_.write_buffer_size);
|
||||
if (use_block_table_) {
|
||||
BlockBasedTableOptions table_options;
|
||||
table_options.hash_index_allow_collision = false;
|
||||
@@ -684,13 +664,10 @@ TEST_P(BloomStatsTestWithParam, BloomStatsTest) {
|
||||
ASSERT_EQ(0, perf_context.bloom_sst_miss_count);
|
||||
|
||||
// check SST bloom stats
|
||||
// NOTE: hits per get differs because of code paths differences
|
||||
// in BlockBasedTable::Get()
|
||||
int hits_per_get = use_block_table_ && !use_block_based_builder_ ? 2 : 1;
|
||||
ASSERT_EQ(value1, Get(key1));
|
||||
ASSERT_EQ(hits_per_get, perf_context.bloom_sst_hit_count);
|
||||
ASSERT_EQ(1, perf_context.bloom_sst_hit_count);
|
||||
ASSERT_EQ(value3, Get(key3));
|
||||
ASSERT_EQ(2 * hits_per_get, perf_context.bloom_sst_hit_count);
|
||||
ASSERT_EQ(2, perf_context.bloom_sst_hit_count);
|
||||
|
||||
ASSERT_EQ("NOT_FOUND", Get(key2));
|
||||
ASSERT_EQ(1, perf_context.bloom_sst_miss_count);
|
||||
|
||||
+15
-14
@@ -212,8 +212,8 @@ TEST_P(DBCompactionTestWithParam, CompactionDeletionTrigger) {
|
||||
}
|
||||
|
||||
TEST_F(DBCompactionTest, SkipStatsUpdateTest) {
|
||||
// This test verify UpdateAccumulatedStats is not on by observing
|
||||
// the compaction behavior when there are many of deletion entries.
|
||||
// This test verify UpdateAccumulatedStats is not on
|
||||
// if options.skip_stats_update_on_db_open = true
|
||||
// The test will need to be updated if the internal behavior changes.
|
||||
|
||||
Options options = DeletionTriggerOptions(CurrentOptions());
|
||||
@@ -230,10 +230,6 @@ TEST_F(DBCompactionTest, SkipStatsUpdateTest) {
|
||||
dbfull()->TEST_WaitForFlushMemTable();
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
|
||||
for (int k = 0; k < kTestSize; ++k) {
|
||||
ASSERT_OK(Delete(Key(k)));
|
||||
}
|
||||
|
||||
// Reopen the DB with stats-update disabled
|
||||
options.skip_stats_update_on_db_open = true;
|
||||
env_->random_file_open_counter_.store(0);
|
||||
@@ -953,19 +949,21 @@ TEST_P(DBCompactionTestWithParam, ManualCompactionPartial) {
|
||||
"DBImpl::BackgroundCompaction:NonTrivial",
|
||||
[&](void* arg) { non_trivial_move++; });
|
||||
bool first = true;
|
||||
bool second = true;
|
||||
// Purpose of dependencies:
|
||||
// 4 -> 1: ensure the order of two non-trivial compactions
|
||||
// 5 -> 2 and 5 -> 3: ensure we do a check before two non-trivial compactions
|
||||
// are installed
|
||||
rocksdb::SyncPoint::GetInstance()->LoadDependency(
|
||||
{{"DBCompaction::ManualPartial:4", "DBCompaction::ManualPartial:1"},
|
||||
{"DBCompaction::ManualPartial:2", "DBCompaction::ManualPartial:3"},
|
||||
{"DBCompaction::ManualPartial:5",
|
||||
"DBImpl::BackgroundCompaction:NonTrivial:AfterRun"}});
|
||||
{"DBCompaction::ManualPartial:5", "DBCompaction::ManualPartial:2"},
|
||||
{"DBCompaction::ManualPartial:5", "DBCompaction::ManualPartial:3"}});
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::BackgroundCompaction:NonTrivial:AfterRun", [&](void* arg) {
|
||||
if (first) {
|
||||
TEST_SYNC_POINT("DBCompaction::ManualPartial:4");
|
||||
first = false;
|
||||
TEST_SYNC_POINT("DBCompaction::ManualPartial:4");
|
||||
TEST_SYNC_POINT("DBCompaction::ManualPartial:3");
|
||||
} else if (second) {
|
||||
} else { // second non-trivial compaction
|
||||
TEST_SYNC_POINT("DBCompaction::ManualPartial:2");
|
||||
}
|
||||
});
|
||||
@@ -1042,6 +1040,7 @@ TEST_P(DBCompactionTestWithParam, ManualCompactionPartial) {
|
||||
std::string end_string = Key(199);
|
||||
Slice begin(begin_string);
|
||||
Slice end(end_string);
|
||||
// First non-trivial compaction is triggered
|
||||
ASSERT_OK(db_->CompactRange(compact_options, &begin, &end));
|
||||
});
|
||||
|
||||
@@ -1065,15 +1064,17 @@ TEST_P(DBCompactionTestWithParam, ManualCompactionPartial) {
|
||||
values[i] = RandomString(&rnd, value_size);
|
||||
ASSERT_OK(Put(Key(i), values[i]));
|
||||
}
|
||||
// Second non-trivial compaction is triggered
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
// 3 files in L0
|
||||
// Before two non-trivial compactions are installed, there are 3 files in L0
|
||||
ASSERT_EQ("3,0,0,0,0,1,2", FilesPerLevel(0));
|
||||
TEST_SYNC_POINT("DBCompaction::ManualPartial:5");
|
||||
|
||||
// 1 file in L6, 1 file in L1
|
||||
dbfull()->TEST_WaitForFlushMemTable();
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
// After two non-trivial compactions are installed, there is 1 file in L6, and
|
||||
// 1 file in L1
|
||||
ASSERT_EQ("0,1,0,0,0,0,1", FilesPerLevel(0));
|
||||
threads.join();
|
||||
|
||||
|
||||
@@ -14,19 +14,20 @@
|
||||
#endif
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <stdint.h>
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
#include <stdint.h>
|
||||
#include "db/db_impl.h"
|
||||
#include "db/filename.h"
|
||||
#include "db/job_context.h"
|
||||
#include "db/version_set.h"
|
||||
#include "port/port.h"
|
||||
#include "rocksdb/db.h"
|
||||
#include "rocksdb/env.h"
|
||||
#include "port/port.h"
|
||||
#include "util/file_util.h"
|
||||
#include "util/mutexlock.h"
|
||||
#include "util/sync_point.h"
|
||||
#include "util/file_util.h"
|
||||
#include "util/testharness.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
@@ -83,7 +84,6 @@ int DBImpl::IsFileDeletionsEnabled() const {
|
||||
Status DBImpl::GetLiveFiles(std::vector<std::string>& ret,
|
||||
uint64_t* manifest_file_size,
|
||||
bool flush_memtable) {
|
||||
|
||||
*manifest_file_size = 0;
|
||||
|
||||
mutex_.Lock();
|
||||
@@ -126,7 +126,7 @@ Status DBImpl::GetLiveFiles(std::vector<std::string>& ret,
|
||||
}
|
||||
|
||||
ret.clear();
|
||||
ret.reserve(live.size() + 2); //*.sst + CURRENT + MANIFEST
|
||||
ret.reserve(live.size() + 3); // *.sst + CURRENT + MANIFEST + OPTIONS
|
||||
|
||||
// create names of the live files. The names are not absolute
|
||||
// paths, instead they are relative to dbname_;
|
||||
@@ -136,6 +136,7 @@ Status DBImpl::GetLiveFiles(std::vector<std::string>& ret,
|
||||
|
||||
ret.push_back(CurrentFileName(""));
|
||||
ret.push_back(DescriptorFileName("", versions_->manifest_file_number()));
|
||||
ret.push_back(OptionsFileName("", versions_->options_file_number()));
|
||||
|
||||
// find length of manifest file while holding the mutex lock
|
||||
*manifest_file_size = versions_->manifest_file_size();
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
// 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"
|
||||
#include "util/sync_point.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
class DBFlushTest : public DBTestBase {
|
||||
public:
|
||||
DBFlushTest() : DBTestBase("/db_flush_test") {}
|
||||
};
|
||||
|
||||
// We had issue when two background threads trying to flush at the same time,
|
||||
// only one of them get committed. The test verifies the issue is fixed.
|
||||
TEST_F(DBFlushTest, FlushWhileWritingManifest) {
|
||||
Options options;
|
||||
options.disable_auto_compactions = true;
|
||||
options.max_background_flushes = 2;
|
||||
Reopen(options);
|
||||
FlushOptions no_wait;
|
||||
no_wait.wait = false;
|
||||
|
||||
SyncPoint::GetInstance()->LoadDependency(
|
||||
{{"VersionSet::LogAndApply:WriteManifest",
|
||||
"DBFlushTest::FlushWhileWritingManifest:1"},
|
||||
{"MemTableList::InstallMemtableFlushResults:InProgress",
|
||||
"VersionSet::LogAndApply:WriteManifestDone"}});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
ASSERT_OK(Put("foo", "v"));
|
||||
ASSERT_OK(dbfull()->Flush(no_wait));
|
||||
TEST_SYNC_POINT("DBFlushTest::FlushWhileWritingManifest:1");
|
||||
ASSERT_OK(Put("bar", "v"));
|
||||
ASSERT_OK(dbfull()->Flush(no_wait));
|
||||
// If the issue is hit we will wait here forever.
|
||||
dbfull()->TEST_WaitForFlushMemTable();
|
||||
#ifndef ROCKSDB_LITE
|
||||
ASSERT_EQ(2, TotalTableFiles());
|
||||
#endif // ROCKSDB_LITE
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
rocksdb::port::InstallStackTraceHandler();
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
+279
-306
@@ -6,7 +6,6 @@
|
||||
// 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_impl.h"
|
||||
|
||||
#ifndef __STDC_FORMAT_MACROS
|
||||
@@ -57,7 +56,6 @@
|
||||
#include "db/version_set.h"
|
||||
#include "db/write_batch_internal.h"
|
||||
#include "db/write_callback.h"
|
||||
#include "db/writebuffer.h"
|
||||
#include "db/xfunc_test_points.h"
|
||||
#include "memtable/hash_linklist_rep.h"
|
||||
#include "memtable/hash_skiplist_rep.h"
|
||||
@@ -68,12 +66,12 @@
|
||||
#include "rocksdb/db.h"
|
||||
#include "rocksdb/env.h"
|
||||
#include "rocksdb/merge_operator.h"
|
||||
#include "rocksdb/sst_file_writer.h"
|
||||
#include "rocksdb/statistics.h"
|
||||
#include "rocksdb/status.h"
|
||||
#include "rocksdb/table.h"
|
||||
#include "rocksdb/version.h"
|
||||
#include "rocksdb/wal_filter.h"
|
||||
#include "rocksdb/write_buffer_manager.h"
|
||||
#include "table/block.h"
|
||||
#include "table/block_based_table_factory.h"
|
||||
#include "table/merger.h"
|
||||
@@ -148,6 +146,10 @@ DBOptions SanitizeOptions(const std::string& dbname, const DBOptions& src) {
|
||||
result.info_log = nullptr;
|
||||
}
|
||||
}
|
||||
if (!result.write_buffer_manager) {
|
||||
result.write_buffer_manager.reset(
|
||||
new WriteBufferManager(result.db_write_buffer_size));
|
||||
}
|
||||
if (result.base_background_compactions == -1) {
|
||||
result.base_background_compactions = result.max_background_compactions;
|
||||
}
|
||||
@@ -196,6 +198,13 @@ DBOptions SanitizeOptions(const std::string& dbname, const DBOptions& src) {
|
||||
result.new_table_reader_for_compaction_inputs = true;
|
||||
}
|
||||
|
||||
// Force flush on DB open if 2PC is enabled, since with 2PC we have no
|
||||
// guarantee that consecutive log files have consecutive sequence id, which
|
||||
// make recovery complicated.
|
||||
if (result.allow_2pc) {
|
||||
result.avoid_flush_during_recovery = false;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -309,7 +318,7 @@ DBImpl::DBImpl(const DBOptions& options, const std::string& dbname)
|
||||
total_log_size_(0),
|
||||
max_total_in_memory_state_(0),
|
||||
is_snapshot_supported_(true),
|
||||
write_buffer_(options.db_write_buffer_size),
|
||||
write_buffer_manager_(db_options_.write_buffer_manager.get()),
|
||||
write_thread_(options.enable_write_thread_adaptive_yield
|
||||
? options.write_thread_max_yield_usec
|
||||
: 0,
|
||||
@@ -322,6 +331,7 @@ DBImpl::DBImpl(const DBOptions& options, const std::string& dbname)
|
||||
num_running_compactions_(0),
|
||||
bg_flush_scheduled_(0),
|
||||
num_running_flushes_(0),
|
||||
bg_purge_scheduled_(0),
|
||||
disable_delete_obsolete_files_(0),
|
||||
delete_obsolete_files_next_run_(
|
||||
options.env->NowMicros() +
|
||||
@@ -348,7 +358,7 @@ DBImpl::DBImpl(const DBOptions& options, const std::string& dbname)
|
||||
NewLRUCache(table_cache_size, db_options_.table_cache_numshardbits);
|
||||
|
||||
versions_.reset(new VersionSet(dbname_, &db_options_, env_options_,
|
||||
table_cache_.get(), &write_buffer_,
|
||||
table_cache_.get(), write_buffer_manager_,
|
||||
&write_controller_));
|
||||
column_family_memtables_.reset(
|
||||
new ColumnFamilyMemTablesImpl(versions_->GetColumnFamilySet()));
|
||||
@@ -401,7 +411,9 @@ DBImpl::~DBImpl() {
|
||||
bg_flush_scheduled_ -= flushes_unscheduled;
|
||||
|
||||
// Wait for background work to finish
|
||||
while (bg_compaction_scheduled_ || bg_flush_scheduled_) {
|
||||
while (bg_compaction_scheduled_ || bg_flush_scheduled_ ||
|
||||
bg_purge_scheduled_) {
|
||||
TEST_SYNC_POINT("DBImpl::~DBImpl:WaitJob");
|
||||
bg_cv_.Wait();
|
||||
}
|
||||
EraseThreadStatusDbInfo();
|
||||
@@ -825,6 +837,7 @@ void DBImpl::FindObsoleteFiles(JobContext* job_context, bool force,
|
||||
|
||||
if (!alive_log_files_.empty()) {
|
||||
uint64_t min_log_number = job_context->log_number;
|
||||
size_t num_alive_log_files = alive_log_files_.size();
|
||||
// find newly obsoleted log files
|
||||
while (alive_log_files_.begin()->number < min_log_number) {
|
||||
auto& earliest = *alive_log_files_.begin();
|
||||
@@ -835,6 +848,11 @@ void DBImpl::FindObsoleteFiles(JobContext* job_context, bool force,
|
||||
} else {
|
||||
job_context->log_delete_files.push_back(earliest.number);
|
||||
}
|
||||
if (job_context->size_log_to_delete == 0) {
|
||||
job_context->prev_total_log_size = total_log_size_;
|
||||
job_context->num_alive_log_files = num_alive_log_files;
|
||||
}
|
||||
job_context->size_log_to_delete += earliest.size;
|
||||
total_log_size_ -= earliest.size;
|
||||
alive_log_files_.pop_front();
|
||||
// Current log should always stay alive since it can't have
|
||||
@@ -874,11 +892,42 @@ bool CompareCandidateFile(const JobContext::CandidateFileInfo& first,
|
||||
}
|
||||
}; // namespace
|
||||
|
||||
// Delete obsolete files and log status and information of file deletion
|
||||
void DBImpl::DeleteObsoleteFileImpl(Status file_deletion_status, int job_id,
|
||||
const std::string& fname, FileType type,
|
||||
uint64_t number, uint32_t path_id) {
|
||||
if (type == kTableFile) {
|
||||
file_deletion_status = DeleteSSTFile(&db_options_, fname, path_id);
|
||||
} else {
|
||||
file_deletion_status = env_->DeleteFile(fname);
|
||||
}
|
||||
if (file_deletion_status.ok()) {
|
||||
Log(InfoLogLevel::DEBUG_LEVEL, db_options_.info_log,
|
||||
"[JOB %d] Delete %s type=%d #%" PRIu64 " -- %s\n", job_id,
|
||||
fname.c_str(), type, number, file_deletion_status.ToString().c_str());
|
||||
} else if (env_->FileExists(fname).IsNotFound()) {
|
||||
Log(InfoLogLevel::INFO_LEVEL, db_options_.info_log,
|
||||
"[JOB %d] Tried to delete a non-existing file %s type=%d #%" PRIu64
|
||||
" -- %s\n",
|
||||
job_id, fname.c_str(), type, number,
|
||||
file_deletion_status.ToString().c_str());
|
||||
} else {
|
||||
Log(InfoLogLevel::ERROR_LEVEL, db_options_.info_log,
|
||||
"[JOB %d] Failed to delete %s type=%d #%" PRIu64 " -- %s\n", job_id,
|
||||
fname.c_str(), type, number, file_deletion_status.ToString().c_str());
|
||||
}
|
||||
if (type == kTableFile) {
|
||||
EventHelpers::LogAndNotifyTableFileDeletion(
|
||||
&event_logger_, job_id, number, fname, file_deletion_status, GetName(),
|
||||
db_options_.listeners);
|
||||
}
|
||||
}
|
||||
|
||||
// Diffs the files listed in filenames and those that do not
|
||||
// belong to live files are posibly removed. Also, removes all the
|
||||
// files in sst_delete_files and log_delete_files.
|
||||
// It is not necessary to hold the mutex when invoking this method.
|
||||
void DBImpl::PurgeObsoleteFiles(const JobContext& state) {
|
||||
void DBImpl::PurgeObsoleteFiles(const JobContext& state, bool schedule_only) {
|
||||
// we'd better have sth to delete
|
||||
assert(state.HaveSomethingToDelete());
|
||||
|
||||
@@ -927,6 +976,15 @@ void DBImpl::PurgeObsoleteFiles(const JobContext& state) {
|
||||
std::unique(candidate_files.begin(), candidate_files.end()),
|
||||
candidate_files.end());
|
||||
|
||||
if (state.prev_total_log_size > 0) {
|
||||
Log(InfoLogLevel::INFO_LEVEL, db_options_.info_log,
|
||||
"[JOB %d] Try to delete WAL files size %" PRIu64
|
||||
", prev total WAL file size %" PRIu64
|
||||
", number of live WAL files %" ROCKSDB_PRIszt ".\n",
|
||||
state.job_id, state.size_log_to_delete, state.prev_total_log_size,
|
||||
state.num_alive_log_files);
|
||||
}
|
||||
|
||||
std::vector<std::string> old_info_log_files;
|
||||
InfoLogPrefix info_log_prefix(!db_options_.db_log_dir.empty(), dbname_);
|
||||
for (const auto& candidate_file : candidate_files) {
|
||||
@@ -1005,34 +1063,14 @@ void DBImpl::PurgeObsoleteFiles(const JobContext& state) {
|
||||
continue;
|
||||
}
|
||||
#endif // !ROCKSDB_LITE
|
||||
|
||||
Status file_deletion_status;
|
||||
if (type == kTableFile) {
|
||||
file_deletion_status = DeleteSSTFile(&db_options_, fname, path_id);
|
||||
if (schedule_only) {
|
||||
InstrumentedMutexLock guard_lock(&mutex_);
|
||||
SchedulePendingPurge(fname, type, number, path_id, state.job_id);
|
||||
} else {
|
||||
file_deletion_status = env_->DeleteFile(fname);
|
||||
}
|
||||
if (file_deletion_status.ok()) {
|
||||
Log(InfoLogLevel::DEBUG_LEVEL, db_options_.info_log,
|
||||
"[JOB %d] Delete %s type=%d #%" PRIu64 " -- %s\n", state.job_id,
|
||||
fname.c_str(), type, number,
|
||||
file_deletion_status.ToString().c_str());
|
||||
} else if (env_->FileExists(fname).IsNotFound()) {
|
||||
Log(InfoLogLevel::INFO_LEVEL, db_options_.info_log,
|
||||
"[JOB %d] Tried to delete a non-existing file %s type=%d #%" PRIu64
|
||||
" -- %s\n",
|
||||
state.job_id, fname.c_str(), type, number,
|
||||
file_deletion_status.ToString().c_str());
|
||||
} else {
|
||||
Log(InfoLogLevel::ERROR_LEVEL, db_options_.info_log,
|
||||
"[JOB %d] Failed to delete %s type=%d #%" PRIu64 " -- %s\n",
|
||||
state.job_id, fname.c_str(), type, number,
|
||||
file_deletion_status.ToString().c_str());
|
||||
}
|
||||
if (type == kTableFile) {
|
||||
EventHelpers::LogAndNotifyTableFileDeletion(
|
||||
&event_logger_, state.job_id, number, fname,
|
||||
file_deletion_status, GetName(),
|
||||
db_options_.listeners);
|
||||
DeleteObsoleteFileImpl(file_deletion_status, state.job_id, fname, type,
|
||||
number, path_id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1342,7 +1380,10 @@ Status DBImpl::RecoverLogFiles(const std::vector<uint64_t>& log_numbers,
|
||||
}
|
||||
#endif
|
||||
|
||||
bool continue_replay_log = true;
|
||||
bool stop_replay_by_wal_filter = false;
|
||||
bool stop_replay_for_corruption = false;
|
||||
bool flushed = false;
|
||||
SequenceNumber recovered_sequence = 0;
|
||||
for (auto log_number : log_numbers) {
|
||||
// The previous incarnation may not have written any MANIFEST
|
||||
// records after allocating this log number. So we manually
|
||||
@@ -1350,6 +1391,23 @@ Status DBImpl::RecoverLogFiles(const std::vector<uint64_t>& log_numbers,
|
||||
versions_->MarkFileNumberUsedDuringRecovery(log_number);
|
||||
// Open the log file
|
||||
std::string fname = LogFileName(db_options_.wal_dir, log_number);
|
||||
|
||||
Log(InfoLogLevel::INFO_LEVEL, db_options_.info_log,
|
||||
"Recovering log #%" PRIu64 " mode %d", log_number,
|
||||
db_options_.wal_recovery_mode);
|
||||
auto logFileDropped = [this, &fname]() {
|
||||
uint64_t bytes;
|
||||
if (env_->GetFileSize(fname, &bytes).ok()) {
|
||||
auto info_log = db_options_.info_log.get();
|
||||
Log(InfoLogLevel::WARN_LEVEL, info_log, "%s: dropping %d bytes",
|
||||
fname.c_str(), static_cast<int>(bytes));
|
||||
}
|
||||
};
|
||||
if (stop_replay_by_wal_filter) {
|
||||
logFileDropped();
|
||||
continue;
|
||||
}
|
||||
|
||||
unique_ptr<SequentialFileReader> file_reader;
|
||||
{
|
||||
unique_ptr<SequentialFile> file;
|
||||
@@ -1385,9 +1443,6 @@ Status DBImpl::RecoverLogFiles(const std::vector<uint64_t>& log_numbers,
|
||||
// large sequence numbers).
|
||||
log::Reader reader(db_options_.info_log, std::move(file_reader), &reporter,
|
||||
true /*checksum*/, 0 /*initial_offset*/, log_number);
|
||||
Log(InfoLogLevel::INFO_LEVEL, db_options_.info_log,
|
||||
"Recovering log #%" PRIu64 " mode %d skip-recovery %d", log_number,
|
||||
db_options_.wal_recovery_mode, !continue_replay_log);
|
||||
|
||||
// Determine if we should tolerate incomplete records at the tail end of the
|
||||
// Read all the records and add to a memtable
|
||||
@@ -1395,17 +1450,8 @@ Status DBImpl::RecoverLogFiles(const std::vector<uint64_t>& log_numbers,
|
||||
Slice record;
|
||||
WriteBatch batch;
|
||||
|
||||
if (!continue_replay_log) {
|
||||
uint64_t bytes;
|
||||
if (env_->GetFileSize(fname, &bytes).ok()) {
|
||||
auto info_log = db_options_.info_log.get();
|
||||
Log(InfoLogLevel::WARN_LEVEL, info_log, "%s: dropping %d bytes",
|
||||
fname.c_str(), static_cast<int>(bytes));
|
||||
}
|
||||
}
|
||||
|
||||
while (
|
||||
continue_replay_log &&
|
||||
!stop_replay_by_wal_filter &&
|
||||
reader.ReadRecord(&record, &scratch, db_options_.wal_recovery_mode) &&
|
||||
status.ok()) {
|
||||
if (record.size() < WriteBatchInternal::kHeader) {
|
||||
@@ -1414,6 +1460,31 @@ Status DBImpl::RecoverLogFiles(const std::vector<uint64_t>& log_numbers,
|
||||
continue;
|
||||
}
|
||||
WriteBatchInternal::SetContents(&batch, record);
|
||||
SequenceNumber sequence = WriteBatchInternal::Sequence(&batch);
|
||||
|
||||
// In point-in-time recovery mode, if sequence id of log files are
|
||||
// consecutive, we continue recovery despite corruption. This could happen
|
||||
// when we open and write to a corrupted DB, where sequence id will start
|
||||
// from the last sequence id we recovered.
|
||||
if (db_options_.wal_recovery_mode ==
|
||||
WALRecoveryMode::kPointInTimeRecovery) {
|
||||
if (sequence == recovered_sequence + 1) {
|
||||
stop_replay_for_corruption = false;
|
||||
}
|
||||
if (stop_replay_for_corruption) {
|
||||
logFileDropped();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
recovered_sequence = sequence;
|
||||
bool no_prev_seq = true;
|
||||
if (*next_sequence == kMaxSequenceNumber) {
|
||||
*next_sequence = sequence;
|
||||
} else {
|
||||
no_prev_seq = false;
|
||||
WriteBatchInternal::SetSequence(&batch, *next_sequence);
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
if (db_options_.wal_filter != nullptr) {
|
||||
@@ -1433,7 +1504,7 @@ Status DBImpl::RecoverLogFiles(const std::vector<uint64_t>& log_numbers,
|
||||
continue;
|
||||
case WalFilter::WalProcessingOption::kStopReplay:
|
||||
// skip current record and stop replay
|
||||
continue_replay_log = false;
|
||||
stop_replay_by_wal_filter = true;
|
||||
continue;
|
||||
case WalFilter::WalProcessingOption::kCorruptedRecord: {
|
||||
status = Status::Corruption("Corruption reported by Wal Filter ",
|
||||
@@ -1489,19 +1560,29 @@ 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
|
||||
//
|
||||
// If we pass DB through and options.max_successive_merges is hit
|
||||
// during recovery, Get() will be issued which will try to acquire
|
||||
// DB mutex and cause deadlock, as DB mutex is already held.
|
||||
// The DB pointer is not needed unless 2PC is used.
|
||||
// TODO(sdong) fix the allow_2pc case too.
|
||||
bool has_valid_writes = false;
|
||||
status = WriteBatchInternal::InsertInto(
|
||||
&batch, column_family_memtables_.get(), &flush_scheduler_, true,
|
||||
log_number, this, true, false, next_sequence);
|
||||
log_number, db_options_.allow_2pc ? this : nullptr, false /* concurrent_memtable_writes */,
|
||||
next_sequence, &has_valid_writes);
|
||||
// If it is the first log file and there is no column family updated
|
||||
// after replaying the file, this file may be a stale file. We ignore
|
||||
// sequence IDs from the file. Otherwise, if a newer stale log file that
|
||||
// has been deleted, the sequenceID may be wrong.
|
||||
if (no_prev_seq && !has_valid_writes) {
|
||||
*next_sequence = kMaxSequenceNumber;
|
||||
}
|
||||
MaybeIgnoreError(&status);
|
||||
if (!status.ok()) {
|
||||
// We are treating this as a failure while reading since we read valid
|
||||
@@ -1510,7 +1591,7 @@ Status DBImpl::RecoverLogFiles(const std::vector<uint64_t>& log_numbers,
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!read_only) {
|
||||
if (has_valid_writes && !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
|
||||
ColumnFamilyData* cfd;
|
||||
@@ -1529,6 +1610,7 @@ Status DBImpl::RecoverLogFiles(const std::vector<uint64_t>& log_numbers,
|
||||
// file-systems cause the DB::Open() to fail.
|
||||
return status;
|
||||
}
|
||||
flushed = true;
|
||||
|
||||
cfd->CreateNewMemtable(*cfd->GetLatestMutableCFOptions(),
|
||||
*next_sequence);
|
||||
@@ -1545,8 +1627,7 @@ Status DBImpl::RecoverLogFiles(const std::vector<uint64_t>& log_numbers,
|
||||
WALRecoveryMode::kPointInTimeRecovery) {
|
||||
// We should ignore the error but not continue replaying
|
||||
status = Status::OK();
|
||||
continue_replay_log = false;
|
||||
|
||||
stop_replay_for_corruption = true;
|
||||
Log(InfoLogLevel::INFO_LEVEL, db_options_.info_log,
|
||||
"Point in time recovered to log #%" PRIu64 " seq #%" PRIu64,
|
||||
log_number, *next_sequence);
|
||||
@@ -1588,14 +1669,20 @@ Status DBImpl::RecoverLogFiles(const std::vector<uint64_t>& log_numbers,
|
||||
|
||||
// flush the final memtable (if non-empty)
|
||||
if (cfd->mem()->GetFirstSequenceNumber() != 0) {
|
||||
status = WriteLevel0TableForRecovery(job_id, cfd, cfd->mem(), edit);
|
||||
if (!status.ok()) {
|
||||
// Recovery failed
|
||||
break;
|
||||
}
|
||||
// If flush happened in the middle of recovery (e.g. due to memtable
|
||||
// being full), we flush at the end. Otherwise we'll need to record
|
||||
// where we were on last flush, which make the logic complicated.
|
||||
if (flushed || !db_options_.avoid_flush_during_recovery) {
|
||||
status = WriteLevel0TableForRecovery(job_id, cfd, cfd->mem(), edit);
|
||||
if (!status.ok()) {
|
||||
// Recovery failed
|
||||
break;
|
||||
}
|
||||
flushed = true;
|
||||
|
||||
cfd->CreateNewMemtable(*cfd->GetLatestMutableCFOptions(),
|
||||
*next_sequence);
|
||||
cfd->CreateNewMemtable(*cfd->GetLatestMutableCFOptions(),
|
||||
*next_sequence);
|
||||
}
|
||||
}
|
||||
|
||||
// write MANIFEST with update
|
||||
@@ -1604,7 +1691,9 @@ Status DBImpl::RecoverLogFiles(const std::vector<uint64_t>& log_numbers,
|
||||
// recovered and should be ignored on next reincarnation.
|
||||
// Since we already recovered max_log_number, we want all logs
|
||||
// with numbers `<= max_log_number` (includes this one) to be ignored
|
||||
edit->SetLogNumber(max_log_number + 1);
|
||||
if (flushed) {
|
||||
edit->SetLogNumber(max_log_number + 1);
|
||||
}
|
||||
// we must mark the next log number as used, even though it's
|
||||
// not actually used. that is because VersionSet assumes
|
||||
// VersionSet::next_file_number_ always to be strictly greater than any
|
||||
@@ -2048,6 +2137,7 @@ Status DBImpl::CompactFilesImpl(
|
||||
c->SetInputVersion(version);
|
||||
// deletion compaction currently not allowed in CompactFiles.
|
||||
assert(!c->deletion_compaction());
|
||||
running_compactions_.insert(c.get());
|
||||
|
||||
SequenceNumber earliest_write_conflict_snapshot;
|
||||
std::vector<SequenceNumber> snapshot_seqs =
|
||||
@@ -2103,6 +2193,8 @@ Status DBImpl::CompactFilesImpl(
|
||||
|
||||
ReleaseFileNumberFromPendingOutputs(pending_outputs_inserted_elem);
|
||||
|
||||
running_compactions_.erase(c.get());
|
||||
|
||||
if (status.ok()) {
|
||||
// Done
|
||||
} else if (status.IsShutdownInProgress()) {
|
||||
@@ -2212,6 +2304,20 @@ void DBImpl::NotifyOnCompactionCompleted(
|
||||
#endif // ROCKSDB_LITE
|
||||
}
|
||||
|
||||
bool DBImpl::NeedFlushOrCompaction(const MutableCFOptions& base_options,
|
||||
const MutableCFOptions& new_options) {
|
||||
return (base_options.disable_auto_compactions &&
|
||||
!new_options.disable_auto_compactions) ||
|
||||
base_options.level0_slowdown_writes_trigger <
|
||||
new_options.level0_slowdown_writes_trigger ||
|
||||
base_options.level0_stop_writes_trigger <
|
||||
new_options.level0_stop_writes_trigger ||
|
||||
base_options.soft_pending_compaction_bytes_limit <
|
||||
new_options.soft_pending_compaction_bytes_limit ||
|
||||
base_options.hard_pending_compaction_bytes_limit <
|
||||
new_options.hard_pending_compaction_bytes_limit;
|
||||
}
|
||||
|
||||
Status DBImpl::SetOptions(ColumnFamilyHandle* column_family,
|
||||
const std::unordered_map<std::string, std::string>& options_map) {
|
||||
#ifdef ROCKSDB_LITE
|
||||
@@ -2225,6 +2331,7 @@ Status DBImpl::SetOptions(ColumnFamilyHandle* column_family,
|
||||
return Status::InvalidArgument("empty input");
|
||||
}
|
||||
|
||||
MutableCFOptions prev_options = *cfd->GetLatestMutableCFOptions();
|
||||
MutableCFOptions new_options;
|
||||
Status s;
|
||||
Status persist_options_status;
|
||||
@@ -2233,8 +2340,15 @@ Status DBImpl::SetOptions(ColumnFamilyHandle* column_family,
|
||||
s = cfd->SetOptions(options_map);
|
||||
if (s.ok()) {
|
||||
new_options = *cfd->GetLatestMutableCFOptions();
|
||||
}
|
||||
if (s.ok()) {
|
||||
if (NeedFlushOrCompaction(prev_options, new_options)) {
|
||||
// Trigger possible flush/compactions. This has to be before we persist
|
||||
// options to file, otherwise there will be a deadlock with writer
|
||||
// thread.
|
||||
auto* old_sv =
|
||||
InstallSuperVersionAndScheduleWork(cfd, nullptr, new_options);
|
||||
delete old_sv;
|
||||
}
|
||||
|
||||
// Persist RocksDB options under the single write thread
|
||||
WriteThread::Writer w;
|
||||
write_thread_.EnterUnbatched(&w, &mutex_);
|
||||
@@ -2684,13 +2798,7 @@ Status DBImpl::EnableAutoCompaction(
|
||||
for (auto cf_ptr : column_family_handles) {
|
||||
Status status =
|
||||
this->SetOptions(cf_ptr, {{"disable_auto_compactions", "false"}});
|
||||
if (status.ok()) {
|
||||
ColumnFamilyData* cfd =
|
||||
reinterpret_cast<ColumnFamilyHandleImpl*>(cf_ptr)->cfd();
|
||||
InstrumentedMutexLock guard_lock(&mutex_);
|
||||
delete this->InstallSuperVersionAndScheduleWork(
|
||||
cfd, nullptr, *cfd->GetLatestMutableCFOptions());
|
||||
} else {
|
||||
if (!status.ok()) {
|
||||
s = status;
|
||||
}
|
||||
}
|
||||
@@ -2756,6 +2864,15 @@ void DBImpl::MaybeScheduleFlushOrCompaction() {
|
||||
}
|
||||
}
|
||||
|
||||
void DBImpl::SchedulePurge() {
|
||||
mutex_.AssertHeld();
|
||||
assert(opened_successfully_);
|
||||
|
||||
// Purge operations are put into High priority queue
|
||||
bg_purge_scheduled_++;
|
||||
env_->Schedule(&DBImpl::BGWorkPurge, this, Env::Priority::HIGH, nullptr);
|
||||
}
|
||||
|
||||
int DBImpl::BGCompactionsAllowed() const {
|
||||
if (write_controller_.NeedSpeedupCompaction()) {
|
||||
return db_options_.max_background_compactions;
|
||||
@@ -2810,6 +2927,14 @@ void DBImpl::SchedulePendingCompaction(ColumnFamilyData* cfd) {
|
||||
}
|
||||
}
|
||||
|
||||
void DBImpl::SchedulePendingPurge(std::string fname, FileType type,
|
||||
uint64_t number, uint32_t path_id,
|
||||
int job_id) {
|
||||
mutex_.AssertHeld();
|
||||
PurgeFileInfo file_info(fname, type, number, path_id, job_id);
|
||||
purge_queue_.push_back(std::move(file_info));
|
||||
}
|
||||
|
||||
void DBImpl::BGWorkFlush(void* db) {
|
||||
IOSTATS_SET_THREAD_POOL_ID(Env::Priority::HIGH);
|
||||
TEST_SYNC_POINT("DBImpl::BGWorkFlush");
|
||||
@@ -2825,6 +2950,12 @@ void DBImpl::BGWorkCompaction(void* arg) {
|
||||
reinterpret_cast<DBImpl*>(ca.db)->BackgroundCallCompaction(ca.m);
|
||||
}
|
||||
|
||||
void DBImpl::BGWorkPurge(void* db) {
|
||||
IOSTATS_SET_THREAD_POOL_ID(Env::Priority::HIGH);
|
||||
TEST_SYNC_POINT("DBImpl::BGWorkPurge");
|
||||
reinterpret_cast<DBImpl*>(db)->BackgroundCallPurge();
|
||||
}
|
||||
|
||||
void DBImpl::UnscheduleCallback(void* arg) {
|
||||
CompactionArg ca = *(reinterpret_cast<CompactionArg*>(arg));
|
||||
delete reinterpret_cast<CompactionArg*>(arg);
|
||||
@@ -2834,6 +2965,34 @@ void DBImpl::UnscheduleCallback(void* arg) {
|
||||
TEST_SYNC_POINT("DBImpl::UnscheduleCallback");
|
||||
}
|
||||
|
||||
void DBImpl::BackgroundCallPurge() {
|
||||
mutex_.Lock();
|
||||
|
||||
while (!purge_queue_.empty()) {
|
||||
auto purge_file = purge_queue_.begin();
|
||||
auto fname = purge_file->fname;
|
||||
auto type = purge_file->type;
|
||||
auto number = purge_file->number;
|
||||
auto path_id = purge_file->path_id;
|
||||
auto job_id = purge_file->job_id;
|
||||
purge_queue_.pop_front();
|
||||
|
||||
mutex_.Unlock();
|
||||
Status file_deletion_status;
|
||||
DeleteObsoleteFileImpl(file_deletion_status, job_id, fname, type, number,
|
||||
path_id);
|
||||
mutex_.Lock();
|
||||
}
|
||||
bg_purge_scheduled_--;
|
||||
|
||||
bg_cv_.SignalAll();
|
||||
// IMPORTANT:there should be no code after calling SignalAll. This call may
|
||||
// signal the DB destructor that it's OK to proceed with destruction. In
|
||||
// that case, all DB variables will be dealloacated and referencing them
|
||||
// will cause trouble.
|
||||
mutex_.Unlock();
|
||||
}
|
||||
|
||||
Status DBImpl::BackgroundFlush(bool* made_progress, JobContext* job_context,
|
||||
LogBuffer* log_buffer) {
|
||||
mutex_.AssertHeld();
|
||||
@@ -3043,6 +3202,7 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
|
||||
reinterpret_cast<ManualCompaction*>(arg);
|
||||
*made_progress = false;
|
||||
mutex_.AssertHeld();
|
||||
TEST_SYNC_POINT("DBImpl::BackgroundCompaction:Start");
|
||||
|
||||
bool is_manual = (manual_compaction != nullptr);
|
||||
|
||||
@@ -3157,6 +3317,10 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
|
||||
}
|
||||
}
|
||||
|
||||
if (c != nullptr) {
|
||||
running_compactions_.insert(c.get());
|
||||
}
|
||||
|
||||
if (!c) {
|
||||
// Nothing to do
|
||||
LogToBuffer(log_buffer, "Compaction nothing to do");
|
||||
@@ -3283,6 +3447,7 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
|
||||
NotifyOnCompactionCompleted(
|
||||
c->column_family_data(), c.get(), status,
|
||||
compaction_job_stats, job_context->job_id);
|
||||
running_compactions_.erase(c.get());
|
||||
}
|
||||
// this will unref its input_version and column_family_data
|
||||
c.reset();
|
||||
@@ -3335,6 +3500,7 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
|
||||
}
|
||||
m->in_progress = false; // not being processed anymore
|
||||
}
|
||||
TEST_SYNC_POINT("DBImpl::BackgroundCompaction:Finish");
|
||||
return status;
|
||||
}
|
||||
|
||||
@@ -3427,12 +3593,17 @@ bool DBImpl::MCOverlap(ManualCompaction* m, ManualCompaction* m1) {
|
||||
|
||||
namespace {
|
||||
struct IterState {
|
||||
IterState(DBImpl* _db, InstrumentedMutex* _mu, SuperVersion* _super_version)
|
||||
: db(_db), mu(_mu), super_version(_super_version) {}
|
||||
IterState(DBImpl* _db, InstrumentedMutex* _mu, SuperVersion* _super_version,
|
||||
bool _background_purge)
|
||||
: db(_db),
|
||||
mu(_mu),
|
||||
super_version(_super_version),
|
||||
background_purge(_background_purge) {}
|
||||
|
||||
DBImpl* db;
|
||||
InstrumentedMutex* mu;
|
||||
SuperVersion* super_version;
|
||||
bool background_purge;
|
||||
};
|
||||
|
||||
static void CleanupIteratorState(void* arg1, void* arg2) {
|
||||
@@ -3450,7 +3621,17 @@ static void CleanupIteratorState(void* arg1, void* arg2) {
|
||||
|
||||
delete state->super_version;
|
||||
if (job_context.HaveSomethingToDelete()) {
|
||||
state->db->PurgeObsoleteFiles(job_context);
|
||||
if (state->background_purge) {
|
||||
// PurgeObsoleteFiles here does not delete files. Instead, it adds the
|
||||
// files to be deleted to a job queue, and deletes it in a separate
|
||||
// background thread.
|
||||
state->db->PurgeObsoleteFiles(job_context, true /* schedule only */);
|
||||
state->mu->Lock();
|
||||
state->db->SchedulePurge();
|
||||
state->mu->Unlock();
|
||||
} else {
|
||||
state->db->PurgeObsoleteFiles(job_context);
|
||||
}
|
||||
}
|
||||
job_context.Clean();
|
||||
}
|
||||
@@ -3476,7 +3657,9 @@ InternalIterator* DBImpl::NewInternalIterator(const ReadOptions& read_options,
|
||||
super_version->current->AddIterators(read_options, env_options_,
|
||||
&merge_iter_builder);
|
||||
internal_iter = merge_iter_builder.Finish();
|
||||
IterState* cleanup = new IterState(this, &mutex_, super_version);
|
||||
IterState* cleanup =
|
||||
new IterState(this, &mutex_, super_version,
|
||||
read_options.background_purge_on_iterator_cleanup);
|
||||
internal_iter->RegisterCleanup(CleanupIteratorState, cleanup, nullptr);
|
||||
|
||||
return internal_iter;
|
||||
@@ -3724,227 +3907,6 @@ std::vector<Status> DBImpl::MultiGet(
|
||||
return stat_list;
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
Status DBImpl::AddFile(ColumnFamilyHandle* column_family,
|
||||
const std::string& file_path, bool move_file) {
|
||||
Status status;
|
||||
auto cfh = reinterpret_cast<ColumnFamilyHandleImpl*>(column_family);
|
||||
ColumnFamilyData* cfd = cfh->cfd();
|
||||
|
||||
ExternalSstFileInfo file_info;
|
||||
file_info.file_path = file_path;
|
||||
status = env_->GetFileSize(file_path, &file_info.file_size);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
|
||||
// Access the file using TableReader to extract
|
||||
// version, number of entries, smallest user key, largest user key
|
||||
std::unique_ptr<RandomAccessFile> sst_file;
|
||||
status = env_->NewRandomAccessFile(file_path, &sst_file, env_options_);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
std::unique_ptr<RandomAccessFileReader> sst_file_reader;
|
||||
sst_file_reader.reset(new RandomAccessFileReader(std::move(sst_file)));
|
||||
|
||||
std::unique_ptr<TableReader> table_reader;
|
||||
status = cfd->ioptions()->table_factory->NewTableReader(
|
||||
TableReaderOptions(*cfd->ioptions(), env_options_,
|
||||
cfd->internal_comparator()),
|
||||
std::move(sst_file_reader), file_info.file_size, &table_reader);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
|
||||
// Get the external sst file version from table properties
|
||||
const UserCollectedProperties& user_collected_properties =
|
||||
table_reader->GetTableProperties()->user_collected_properties;
|
||||
UserCollectedProperties::const_iterator external_sst_file_version_iter =
|
||||
user_collected_properties.find(ExternalSstFilePropertyNames::kVersion);
|
||||
if (external_sst_file_version_iter == user_collected_properties.end()) {
|
||||
return Status::InvalidArgument("Generated table version not found");
|
||||
}
|
||||
|
||||
file_info.version =
|
||||
DecodeFixed32(external_sst_file_version_iter->second.c_str());
|
||||
if (file_info.version == 1) {
|
||||
// version 1 imply that all sequence numbers in table equal 0
|
||||
file_info.sequence_number = 0;
|
||||
} else {
|
||||
return Status::InvalidArgument("Generated table version is not supported");
|
||||
}
|
||||
|
||||
// Get number of entries in table
|
||||
file_info.num_entries = table_reader->GetTableProperties()->num_entries;
|
||||
|
||||
ParsedInternalKey key;
|
||||
std::unique_ptr<InternalIterator> iter(
|
||||
table_reader->NewIterator(ReadOptions()));
|
||||
|
||||
// Get first (smallest) key from file
|
||||
iter->SeekToFirst();
|
||||
if (!ParseInternalKey(iter->key(), &key)) {
|
||||
return Status::Corruption("Generated table have corrupted keys");
|
||||
}
|
||||
if (key.sequence != 0) {
|
||||
return Status::Corruption("Generated table have non zero sequence number");
|
||||
}
|
||||
file_info.smallest_key = key.user_key.ToString();
|
||||
|
||||
// Get last (largest) key from file
|
||||
iter->SeekToLast();
|
||||
if (!ParseInternalKey(iter->key(), &key)) {
|
||||
return Status::Corruption("Generated table have corrupted keys");
|
||||
}
|
||||
if (key.sequence != 0) {
|
||||
return Status::Corruption("Generated table have non zero sequence number");
|
||||
}
|
||||
file_info.largest_key = key.user_key.ToString();
|
||||
|
||||
return AddFile(column_family, &file_info, move_file);
|
||||
}
|
||||
|
||||
Status DBImpl::AddFile(ColumnFamilyHandle* column_family,
|
||||
const ExternalSstFileInfo* file_info, bool move_file) {
|
||||
Status status;
|
||||
auto cfh = reinterpret_cast<ColumnFamilyHandleImpl*>(column_family);
|
||||
ColumnFamilyData* cfd = cfh->cfd();
|
||||
|
||||
if (file_info->num_entries == 0) {
|
||||
return Status::InvalidArgument("File contain no entries");
|
||||
}
|
||||
if (file_info->version != 1) {
|
||||
return Status::InvalidArgument("Generated table version is not supported");
|
||||
}
|
||||
// version 1 imply that file have only Put Operations with Sequence Number = 0
|
||||
|
||||
FileMetaData meta;
|
||||
meta.smallest =
|
||||
InternalKey(file_info->smallest_key, file_info->sequence_number,
|
||||
ValueType::kTypeValue);
|
||||
meta.largest = InternalKey(file_info->largest_key, file_info->sequence_number,
|
||||
ValueType::kTypeValue);
|
||||
if (!meta.smallest.Valid() || !meta.largest.Valid()) {
|
||||
return Status::Corruption("Generated table have corrupted keys");
|
||||
}
|
||||
meta.smallest_seqno = file_info->sequence_number;
|
||||
meta.largest_seqno = file_info->sequence_number;
|
||||
if (meta.smallest_seqno != 0 || meta.largest_seqno != 0) {
|
||||
return Status::InvalidArgument(
|
||||
"Non zero sequence numbers are not supported");
|
||||
}
|
||||
|
||||
// Generate a location for the new table
|
||||
std::list<uint64_t>::iterator pending_outputs_inserted_elem;
|
||||
{
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
pending_outputs_inserted_elem = CaptureCurrentFileNumberInPendingOutputs();
|
||||
meta.fd =
|
||||
FileDescriptor(versions_->NewFileNumber(), 0, file_info->file_size);
|
||||
}
|
||||
|
||||
std::string db_fname = TableFileName(
|
||||
db_options_.db_paths, meta.fd.GetNumber(), meta.fd.GetPathId());
|
||||
|
||||
if (move_file) {
|
||||
status = env_->LinkFile(file_info->file_path, db_fname);
|
||||
if (status.IsNotSupported()) {
|
||||
// Original file is on a different FS, use copy instead of hard linking
|
||||
status = CopyFile(env_, file_info->file_path, db_fname, 0);
|
||||
}
|
||||
} else {
|
||||
status = CopyFile(env_, file_info->file_path, db_fname, 0);
|
||||
}
|
||||
TEST_SYNC_POINT("DBImpl::AddFile:FileCopied");
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
|
||||
{
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
const MutableCFOptions mutable_cf_options =
|
||||
*cfd->GetLatestMutableCFOptions();
|
||||
|
||||
WriteThread::Writer w;
|
||||
write_thread_.EnterUnbatched(&w, &mutex_);
|
||||
|
||||
if (!snapshots_.empty()) {
|
||||
// Check that no snapshots are being held
|
||||
status =
|
||||
Status::NotSupported("Cannot add a file while holding snapshots");
|
||||
}
|
||||
|
||||
if (status.ok()) {
|
||||
// Verify that added file key range dont overlap with any keys in DB
|
||||
SuperVersion* sv = cfd->GetSuperVersion()->Ref();
|
||||
Arena arena;
|
||||
ReadOptions ro;
|
||||
ro.total_order_seek = true;
|
||||
ScopedArenaIterator iter(NewInternalIterator(ro, cfd, sv, &arena));
|
||||
|
||||
InternalKey range_start(file_info->smallest_key, kMaxSequenceNumber,
|
||||
kTypeValue);
|
||||
iter->Seek(range_start.Encode());
|
||||
status = iter->status();
|
||||
|
||||
if (status.ok() && iter->Valid()) {
|
||||
ParsedInternalKey seek_result;
|
||||
if (ParseInternalKey(iter->key(), &seek_result)) {
|
||||
auto* vstorage = cfd->current()->storage_info();
|
||||
if (vstorage->InternalComparator()->user_comparator()->Compare(
|
||||
seek_result.user_key, file_info->largest_key) <= 0) {
|
||||
status = Status::NotSupported("Cannot add overlapping range");
|
||||
}
|
||||
} else {
|
||||
status = Status::Corruption("DB have corrupted keys");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (status.ok()) {
|
||||
// Add file to L0
|
||||
VersionEdit edit;
|
||||
edit.SetColumnFamily(cfd->GetID());
|
||||
edit.AddFile(0, meta.fd.GetNumber(), meta.fd.GetPathId(),
|
||||
meta.fd.GetFileSize(), meta.smallest, meta.largest,
|
||||
meta.smallest_seqno, meta.largest_seqno,
|
||||
meta.marked_for_compaction);
|
||||
|
||||
status = versions_->LogAndApply(cfd, mutable_cf_options, &edit, &mutex_,
|
||||
directories_.GetDbDir());
|
||||
}
|
||||
write_thread_.ExitUnbatched(&w);
|
||||
|
||||
if (status.ok()) {
|
||||
delete InstallSuperVersionAndScheduleWork(cfd, nullptr,
|
||||
mutable_cf_options);
|
||||
}
|
||||
ReleaseFileNumberFromPendingOutputs(pending_outputs_inserted_elem);
|
||||
}
|
||||
|
||||
if (!status.ok()) {
|
||||
// We failed to add the file to the database
|
||||
Status s = env_->DeleteFile(db_fname);
|
||||
if (!s.ok()) {
|
||||
Log(InfoLogLevel::WARN_LEVEL, db_options_.info_log,
|
||||
"AddFile() clean up for file %s failed : %s", db_fname.c_str(),
|
||||
s.ToString().c_str());
|
||||
}
|
||||
} else if (status.ok() && move_file) {
|
||||
// The file was moved and added successfully, remove original file link
|
||||
Status s = env_->DeleteFile(file_info->file_path);
|
||||
if (!s.ok()) {
|
||||
Log(InfoLogLevel::WARN_LEVEL, db_options_.info_log,
|
||||
"%s was added to DB successfully but failed to remove original file "
|
||||
"link : %s",
|
||||
file_info->file_path.c_str(), s.ToString().c_str());
|
||||
}
|
||||
}
|
||||
return status;
|
||||
}
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
Status DBImpl::CreateColumnFamily(const ColumnFamilyOptions& cf_options,
|
||||
const std::string& column_family_name,
|
||||
ColumnFamilyHandle** handle) {
|
||||
@@ -4439,7 +4401,7 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options,
|
||||
w.status = WriteBatchInternal::InsertInto(
|
||||
&w, &column_family_memtables, &flush_scheduler_,
|
||||
write_options.ignore_missing_column_families, 0 /*log_number*/, this,
|
||||
true /*dont_filter_deletes*/, true /*concurrent_memtable_writes*/);
|
||||
true /*concurrent_memtable_writes*/);
|
||||
}
|
||||
|
||||
if (write_thread_.CompleteParallelWorker(&w)) {
|
||||
@@ -4510,11 +4472,17 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options,
|
||||
}
|
||||
}
|
||||
MaybeScheduleFlushOrCompaction();
|
||||
} else if (UNLIKELY(write_buffer_.ShouldFlush())) {
|
||||
} else if (UNLIKELY(write_buffer_manager_->ShouldFlush())) {
|
||||
// Before a new memtable is added in SwitchMemtable(),
|
||||
// write_buffer_manager_->ShouldFlush() will keep returning true. If another
|
||||
// thread is writing to another DB with the same write buffer, they may also
|
||||
// be flushed. We may end up with flushing much more DBs than needed. It's
|
||||
// suboptimal but still correct.
|
||||
Log(InfoLogLevel::INFO_LEVEL, db_options_.info_log,
|
||||
"Flushing column family with largest mem table size. Write buffer is "
|
||||
"using %" PRIu64 " bytes out of a total of %" PRIu64 ".",
|
||||
write_buffer_.memory_usage(), write_buffer_.buffer_size());
|
||||
write_buffer_manager_->memory_usage(),
|
||||
write_buffer_manager_->buffer_size());
|
||||
// no need to refcount because drop is happening in write thread, so can't
|
||||
// happen while we're in the write thread
|
||||
ColumnFamilyData* largest_cfd = nullptr;
|
||||
@@ -4733,7 +4701,7 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options,
|
||||
status = WriteBatchInternal::InsertInto(
|
||||
write_group, current_sequence, column_family_memtables_.get(),
|
||||
&flush_scheduler_, write_options.ignore_missing_column_families,
|
||||
0 /*log_number*/, this, false /*dont_filter_deletes*/);
|
||||
0 /*log_number*/, this);
|
||||
|
||||
if (status.ok()) {
|
||||
// There were no write failures. Set leader's status
|
||||
@@ -4760,8 +4728,7 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options,
|
||||
w.status = WriteBatchInternal::InsertInto(
|
||||
&w, &column_family_memtables, &flush_scheduler_,
|
||||
write_options.ignore_missing_column_families, 0 /*log_number*/,
|
||||
this, true /*dont_filter_deletes*/,
|
||||
true /*concurrent_memtable_writes*/);
|
||||
this, true /*concurrent_memtable_writes*/);
|
||||
}
|
||||
|
||||
// CompleteParallelWorker returns true if this thread should
|
||||
@@ -5606,6 +5573,10 @@ Status DB::CreateColumnFamily(const ColumnFamilyOptions& cf_options,
|
||||
Status DB::DropColumnFamily(ColumnFamilyHandle* column_family) {
|
||||
return Status::NotSupported("");
|
||||
}
|
||||
Status DB::DestroyColumnFamilyHandle(ColumnFamilyHandle* column_family) {
|
||||
delete column_family;
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
DB::~DB() { }
|
||||
|
||||
@@ -6005,8 +5976,10 @@ Status DBImpl::DeleteObsoleteOptionsFiles() {
|
||||
Status DBImpl::RenameTempFileToOptionsFile(const std::string& file_name) {
|
||||
#ifndef ROCKSDB_LITE
|
||||
Status s;
|
||||
|
||||
versions_->options_file_number_ = versions_->NewFileNumber();
|
||||
std::string options_file_name =
|
||||
OptionsFileName(GetName(), versions_->NewFileNumber());
|
||||
OptionsFileName(GetName(), versions_->options_file_number_);
|
||||
// Retry if the file name happen to conflict with an existing one.
|
||||
s = GetEnv()->RenameFile(file_name, options_file_name);
|
||||
|
||||
|
||||
+64
-11
@@ -31,13 +31,13 @@
|
||||
#include "db/wal_manager.h"
|
||||
#include "db/write_controller.h"
|
||||
#include "db/write_thread.h"
|
||||
#include "db/writebuffer.h"
|
||||
#include "memtable_list.h"
|
||||
#include "port/port.h"
|
||||
#include "rocksdb/db.h"
|
||||
#include "rocksdb/env.h"
|
||||
#include "rocksdb/memtablerep.h"
|
||||
#include "rocksdb/transaction_log.h"
|
||||
#include "rocksdb/write_buffer_manager.h"
|
||||
#include "table/scoped_arena_iterator.h"
|
||||
#include "util/autovector.h"
|
||||
#include "util/event_logger.h"
|
||||
@@ -258,10 +258,11 @@ class DBImpl : public DB {
|
||||
|
||||
using DB::AddFile;
|
||||
virtual Status AddFile(ColumnFamilyHandle* column_family,
|
||||
const ExternalSstFileInfo* file_info,
|
||||
const std::vector<ExternalSstFileInfo>& file_info_list,
|
||||
bool move_file) override;
|
||||
virtual Status AddFile(ColumnFamilyHandle* column_family,
|
||||
const std::string& file_path, bool move_file) override;
|
||||
const std::vector<std::string>& file_path_list,
|
||||
bool move_file) override;
|
||||
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
@@ -341,6 +342,8 @@ class DBImpl : public DB {
|
||||
|
||||
uint64_t TEST_LogfileNumber();
|
||||
|
||||
uint64_t TEST_total_log_size() const { return total_log_size_; }
|
||||
|
||||
// Returns column family name to ImmutableCFOptions map.
|
||||
Status TEST_GetAllImmutableCFOptions(
|
||||
std::unordered_map<std::string, const ImmutableCFOptions*>* iopts_map);
|
||||
@@ -358,7 +361,7 @@ class DBImpl : public DB {
|
||||
|
||||
#endif // NDEBUG
|
||||
|
||||
// Return maximum background compaction alowed to be scheduled based on
|
||||
// Return maximum background compaction allowed to be scheduled based on
|
||||
// compaction status.
|
||||
int BGCompactionsAllowed() const;
|
||||
|
||||
@@ -374,7 +377,10 @@ class DBImpl : public DB {
|
||||
// belong to live files are posibly removed. Also, removes all the
|
||||
// files in sst_delete_files and log_delete_files.
|
||||
// It is not necessary to hold the mutex when invoking this method.
|
||||
void PurgeObsoleteFiles(const JobContext& background_contet);
|
||||
void PurgeObsoleteFiles(const JobContext& background_contet,
|
||||
bool schedule_only = false);
|
||||
|
||||
void SchedulePurge();
|
||||
|
||||
ColumnFamilyHandle* DefaultColumnFamily() const override;
|
||||
|
||||
@@ -488,6 +494,8 @@ class DBImpl : public DB {
|
||||
void MarkLogAsHavingPrepSectionFlushed(uint64_t log);
|
||||
void MarkLogAsContainingPrepSection(uint64_t log);
|
||||
|
||||
Status NewDB();
|
||||
|
||||
protected:
|
||||
Env* const env_;
|
||||
const std::string dbname_;
|
||||
@@ -521,7 +529,7 @@ class DBImpl : public DB {
|
||||
Compaction *c, const Status &st,
|
||||
const CompactionJobStats& job_stats,
|
||||
int job_id);
|
||||
void NotifyOnMemTableSealed(ColumnFamilyData* cfd,
|
||||
void NotifyOnMemTableSealed(ColumnFamilyData* cfd,
|
||||
const MemTableInfo& mem_table_info);
|
||||
|
||||
void NewThreadStatusCfInfo(ColumnFamilyData* cfd) const;
|
||||
@@ -554,7 +562,7 @@ class DBImpl : public DB {
|
||||
|
||||
struct WriteContext;
|
||||
|
||||
Status NewDB();
|
||||
struct PurgeFileInfo;
|
||||
|
||||
// Recover the descriptor from persistent storage. May do a significant
|
||||
// amount of work to recover recently logged updates. Any changes to
|
||||
@@ -569,6 +577,10 @@ class DBImpl : public DB {
|
||||
|
||||
// Delete any unneeded files and stale in-memory entries.
|
||||
void DeleteObsoleteFiles();
|
||||
// Delete obsolete files and log status and information of file deletion
|
||||
void DeleteObsoleteFileImpl(Status file_deletion_status, int job_id,
|
||||
const std::string& fname, FileType type,
|
||||
uint64_t number, uint32_t path_id);
|
||||
|
||||
// Background process needs to call
|
||||
// auto x = CaptureCurrentFileNumberInPendingOutputs()
|
||||
@@ -623,11 +635,20 @@ class DBImpl : public DB {
|
||||
Status WaitForFlushMemTable(ColumnFamilyData* cfd);
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
// Finds the lowest level in the DB that the ingested file can be added to
|
||||
// REQUIRES: mutex_ held
|
||||
int PickLevelForIngestedFile(ColumnFamilyData* cfd,
|
||||
const ExternalSstFileInfo& file_info);
|
||||
|
||||
Status CompactFilesImpl(
|
||||
const CompactionOptions& compact_options, ColumnFamilyData* cfd,
|
||||
Version* version, const std::vector<std::string>& input_file_names,
|
||||
const int output_level, int output_path_id, JobContext* job_context,
|
||||
LogBuffer* log_buffer);
|
||||
Status ReadExternalSstFileInfo(ColumnFamilyHandle* column_family,
|
||||
const std::string& file_path,
|
||||
ExternalSstFileInfo* file_info);
|
||||
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
ColumnFamilyData* GetColumnFamilyDataByName(const std::string& cf_name);
|
||||
@@ -635,16 +656,25 @@ class DBImpl : public DB {
|
||||
void MaybeScheduleFlushOrCompaction();
|
||||
void SchedulePendingFlush(ColumnFamilyData* cfd);
|
||||
void SchedulePendingCompaction(ColumnFamilyData* cfd);
|
||||
void SchedulePendingPurge(std::string fname, FileType type, uint64_t number,
|
||||
uint32_t path_id, int job_id);
|
||||
static void BGWorkCompaction(void* arg);
|
||||
static void BGWorkFlush(void* db);
|
||||
static void BGWorkPurge(void* arg);
|
||||
static void UnscheduleCallback(void* arg);
|
||||
void BackgroundCallCompaction(void* arg);
|
||||
void BackgroundCallFlush();
|
||||
void BackgroundCallPurge();
|
||||
Status BackgroundCompaction(bool* madeProgress, JobContext* job_context,
|
||||
LogBuffer* log_buffer, void* m = 0);
|
||||
Status BackgroundFlush(bool* madeProgress, JobContext* job_context,
|
||||
LogBuffer* log_buffer);
|
||||
|
||||
// Compare options before and after to see whether flush or compaction is
|
||||
// needed immediately after dynamic option change.
|
||||
bool NeedFlushOrCompaction(const MutableCFOptions& base_options,
|
||||
const MutableCFOptions& new_options);
|
||||
|
||||
void PrintStatistics();
|
||||
|
||||
// dump rocksdb.stats to LOG
|
||||
@@ -690,9 +720,9 @@ class DBImpl : public DB {
|
||||
// * if AnyManualCompaction, whenever a compaction finishes, even if it hasn't
|
||||
// made any progress
|
||||
// * whenever a compaction made any progress
|
||||
// * whenever bg_flush_scheduled_ value decreases (i.e. whenever a flush is
|
||||
// done, even if it didn't make any progress)
|
||||
// * whenever there is an error in background flush or compaction
|
||||
// * whenever bg_flush_scheduled_ or bg_purge_scheduled_ value decreases
|
||||
// (i.e. whenever a flush is done, even if it didn't make any progress)
|
||||
// * whenever there is an error in background purge, flush or compaction
|
||||
InstrumentedCondVar bg_cv_;
|
||||
uint64_t logfile_number_;
|
||||
std::deque<uint64_t>
|
||||
@@ -785,7 +815,7 @@ class DBImpl : public DB {
|
||||
|
||||
Directories directories_;
|
||||
|
||||
WriteBuffer write_buffer_;
|
||||
WriteBufferManager* write_buffer_manager_;
|
||||
|
||||
WriteThread write_thread_;
|
||||
|
||||
@@ -812,6 +842,19 @@ class DBImpl : public DB {
|
||||
// State is protected with db mutex.
|
||||
std::list<uint64_t> pending_outputs_;
|
||||
|
||||
// PurgeFileInfo is a structure to hold information of files to be deleted in
|
||||
// purge_queue_
|
||||
struct PurgeFileInfo {
|
||||
std::string fname;
|
||||
FileType type;
|
||||
uint64_t number;
|
||||
uint32_t path_id;
|
||||
int job_id;
|
||||
PurgeFileInfo(std::string fn, FileType t, uint64_t num, uint32_t pid,
|
||||
int jid)
|
||||
: fname(fn), type(t), number(num), path_id(pid), job_id(jid) {}
|
||||
};
|
||||
|
||||
// flush_queue_ and compaction_queue_ hold column families that we need to
|
||||
// flush and compact, respectively.
|
||||
// A column family is inserted into flush_queue_ when it satisfies condition
|
||||
@@ -836,6 +879,9 @@ class DBImpl : public DB {
|
||||
// invariant(column family present in compaction_queue_ <==>
|
||||
// ColumnFamilyData::pending_compaction_ == true)
|
||||
std::deque<ColumnFamilyData*> compaction_queue_;
|
||||
|
||||
// A queue to store filenames of the files to be purged
|
||||
std::deque<PurgeFileInfo> purge_queue_;
|
||||
int unscheduled_flushes_;
|
||||
int unscheduled_compactions_;
|
||||
|
||||
@@ -851,6 +897,9 @@ class DBImpl : public DB {
|
||||
// stores the number of flushes are currently running
|
||||
int num_running_flushes_;
|
||||
|
||||
// number of background obsolete file purge jobs, submitted to the HIGH pool
|
||||
int bg_purge_scheduled_;
|
||||
|
||||
// Information for a manual compaction
|
||||
struct ManualCompaction {
|
||||
ColumnFamilyData* cfd;
|
||||
@@ -912,6 +961,10 @@ class DBImpl : public DB {
|
||||
// The options to access storage files
|
||||
const EnvOptions env_options_;
|
||||
|
||||
// A set of compactions that are running right now
|
||||
// REQUIRES: mutex held
|
||||
std::unordered_set<Compaction*> running_compactions_;
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
WalManager wal_manager_;
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
@@ -0,0 +1,419 @@
|
||||
// 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.
|
||||
|
||||
#include "db/db_impl.h"
|
||||
|
||||
#ifndef __STDC_FORMAT_MACROS
|
||||
#define __STDC_FORMAT_MACROS
|
||||
#endif
|
||||
|
||||
#include <inttypes.h>
|
||||
|
||||
#include "db/builder.h"
|
||||
#include "rocksdb/db.h"
|
||||
#include "rocksdb/env.h"
|
||||
#include "rocksdb/sst_file_writer.h"
|
||||
#include "table/table_builder.h"
|
||||
#include "util/file_reader_writer.h"
|
||||
#include "util/file_util.h"
|
||||
#include "util/sync_point.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
namespace {
|
||||
// RAII Timer
|
||||
class StatsTimer {
|
||||
public:
|
||||
explicit StatsTimer(Env* env, uint64_t* counter)
|
||||
: env_(env), counter_(counter), start_micros_(env_->NowMicros()) {}
|
||||
~StatsTimer() { *counter_ += env_->NowMicros() - start_micros_; }
|
||||
|
||||
private:
|
||||
Env* env_;
|
||||
uint64_t* counter_;
|
||||
uint64_t start_micros_;
|
||||
};
|
||||
} // anonymous namespace
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
|
||||
Status DBImpl::ReadExternalSstFileInfo(ColumnFamilyHandle* column_family,
|
||||
const std::string& file_path,
|
||||
ExternalSstFileInfo* file_info) {
|
||||
Status status;
|
||||
auto cfh = reinterpret_cast<ColumnFamilyHandleImpl*>(column_family);
|
||||
auto cfd = cfh->cfd();
|
||||
|
||||
file_info->file_path = file_path;
|
||||
status = env_->GetFileSize(file_path, &file_info->file_size);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
|
||||
// Access the file using TableReader to extract
|
||||
// version, number of entries, smallest user key, largest user key
|
||||
std::unique_ptr<RandomAccessFile> sst_file;
|
||||
status = env_->NewRandomAccessFile(file_path, &sst_file, env_options_);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
std::unique_ptr<RandomAccessFileReader> sst_file_reader;
|
||||
sst_file_reader.reset(new RandomAccessFileReader(std::move(sst_file)));
|
||||
|
||||
std::unique_ptr<TableReader> table_reader;
|
||||
status = cfd->ioptions()->table_factory->NewTableReader(
|
||||
TableReaderOptions(*cfd->ioptions(), env_options_,
|
||||
cfd->internal_comparator()),
|
||||
std::move(sst_file_reader), file_info->file_size, &table_reader);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
|
||||
// Get the external sst file version from table properties
|
||||
const UserCollectedProperties& user_collected_properties =
|
||||
table_reader->GetTableProperties()->user_collected_properties;
|
||||
UserCollectedProperties::const_iterator external_sst_file_version_iter =
|
||||
user_collected_properties.find(ExternalSstFilePropertyNames::kVersion);
|
||||
if (external_sst_file_version_iter == user_collected_properties.end()) {
|
||||
return Status::InvalidArgument("Generated table version not found");
|
||||
}
|
||||
|
||||
file_info->version =
|
||||
DecodeFixed32(external_sst_file_version_iter->second.c_str());
|
||||
if (file_info->version == 1) {
|
||||
// version 1 imply that all sequence numbers in table equal 0
|
||||
file_info->sequence_number = 0;
|
||||
} else {
|
||||
return Status::InvalidArgument("Generated table version is not supported");
|
||||
}
|
||||
// Get number of entries in table
|
||||
file_info->num_entries = table_reader->GetTableProperties()->num_entries;
|
||||
|
||||
ParsedInternalKey key;
|
||||
std::unique_ptr<InternalIterator> iter(
|
||||
table_reader->NewIterator(ReadOptions()));
|
||||
|
||||
// Get first (smallest) key from file
|
||||
iter->SeekToFirst();
|
||||
if (!ParseInternalKey(iter->key(), &key)) {
|
||||
return Status::Corruption("Generated table have corrupted keys");
|
||||
}
|
||||
if (key.sequence != 0) {
|
||||
return Status::Corruption("Generated table have non zero sequence number");
|
||||
}
|
||||
file_info->smallest_key = key.user_key.ToString();
|
||||
|
||||
// Get last (largest) key from file
|
||||
iter->SeekToLast();
|
||||
if (!ParseInternalKey(iter->key(), &key)) {
|
||||
return Status::Corruption("Generated table have corrupted keys");
|
||||
}
|
||||
if (key.sequence != 0) {
|
||||
return Status::Corruption("Generated table have non zero sequence number");
|
||||
}
|
||||
file_info->largest_key = key.user_key.ToString();
|
||||
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status DBImpl::AddFile(ColumnFamilyHandle* column_family,
|
||||
const std::vector<std::string>& file_path_list,
|
||||
bool move_file) {
|
||||
Status status;
|
||||
auto num_files = file_path_list.size();
|
||||
if (num_files == 0) {
|
||||
return Status::InvalidArgument("The list of files is empty");
|
||||
}
|
||||
|
||||
std::vector<ExternalSstFileInfo> file_infos(num_files);
|
||||
std::vector<ExternalSstFileInfo> file_info_list(num_files);
|
||||
for (size_t i = 0; i < num_files; i++) {
|
||||
status = ReadExternalSstFileInfo(column_family, file_path_list[i],
|
||||
&file_info_list[i]);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
}
|
||||
return AddFile(column_family, file_info_list, move_file);
|
||||
}
|
||||
|
||||
Status DBImpl::AddFile(ColumnFamilyHandle* column_family,
|
||||
const std::vector<ExternalSstFileInfo>& file_info_list,
|
||||
bool move_file) {
|
||||
Status status;
|
||||
auto cfh = reinterpret_cast<ColumnFamilyHandleImpl*>(column_family);
|
||||
ColumnFamilyData* cfd = cfh->cfd();
|
||||
|
||||
auto num_files = file_info_list.size();
|
||||
if (num_files == 0) {
|
||||
return Status::InvalidArgument("The list of files is empty");
|
||||
}
|
||||
|
||||
// Verify that passed files dont have overlapping ranges
|
||||
if (num_files > 1) {
|
||||
std::vector<const ExternalSstFileInfo*> sorted_file_info_list(num_files);
|
||||
for (size_t i = 0; i < num_files; i++) {
|
||||
sorted_file_info_list[i] = &file_info_list[i];
|
||||
}
|
||||
|
||||
auto* vstorage = cfd->current()->storage_info();
|
||||
std::sort(
|
||||
sorted_file_info_list.begin(), sorted_file_info_list.end(),
|
||||
[&vstorage, &file_info_list](const ExternalSstFileInfo* info1,
|
||||
const ExternalSstFileInfo* info2) {
|
||||
return vstorage->InternalComparator()->user_comparator()->Compare(
|
||||
info1->smallest_key, info2->smallest_key) < 0;
|
||||
});
|
||||
|
||||
for (size_t i = 0; i < num_files - 1; i++) {
|
||||
if (sorted_file_info_list[i]->largest_key >=
|
||||
sorted_file_info_list[i + 1]->smallest_key) {
|
||||
return Status::NotSupported("Cannot add overlapping range among files");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<uint64_t> micro_list(num_files, 0);
|
||||
std::vector<FileMetaData> meta_list(num_files);
|
||||
for (size_t i = 0; i < num_files; i++) {
|
||||
StatsTimer t(env_, µ_list[i]);
|
||||
if (file_info_list[i].num_entries == 0) {
|
||||
return Status::InvalidArgument("File contain no entries");
|
||||
}
|
||||
if (file_info_list[i].version != 1) {
|
||||
return Status::InvalidArgument(
|
||||
"Generated table version is not supported");
|
||||
}
|
||||
// version 1 imply that file have only Put Operations with Sequence Number =
|
||||
// 0
|
||||
|
||||
meta_list[i].smallest =
|
||||
InternalKey(file_info_list[i].smallest_key,
|
||||
file_info_list[i].sequence_number, ValueType::kTypeValue);
|
||||
meta_list[i].largest =
|
||||
InternalKey(file_info_list[i].largest_key,
|
||||
file_info_list[i].sequence_number, ValueType::kTypeValue);
|
||||
if (!meta_list[i].smallest.Valid() || !meta_list[i].largest.Valid()) {
|
||||
return Status::Corruption("Generated table have corrupted keys");
|
||||
}
|
||||
meta_list[i].smallest_seqno = file_info_list[i].sequence_number;
|
||||
meta_list[i].largest_seqno = file_info_list[i].sequence_number;
|
||||
if (meta_list[i].smallest_seqno != 0 || meta_list[i].largest_seqno != 0) {
|
||||
return Status::InvalidArgument(
|
||||
"Non zero sequence numbers are not supported");
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::list<uint64_t>::iterator> pending_outputs_inserted_elem_list(
|
||||
num_files);
|
||||
// Generate locations for the new tables
|
||||
{
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
for (size_t i = 0; i < num_files; i++) {
|
||||
StatsTimer t(env_, µ_list[i]);
|
||||
pending_outputs_inserted_elem_list[i] =
|
||||
CaptureCurrentFileNumberInPendingOutputs();
|
||||
meta_list[i].fd = FileDescriptor(versions_->NewFileNumber(), 0,
|
||||
file_info_list[i].file_size);
|
||||
}
|
||||
}
|
||||
|
||||
// Copy/Move external files into DB
|
||||
std::vector<std::string> db_fname_list(num_files);
|
||||
size_t j = 0;
|
||||
for (; j < num_files; j++) {
|
||||
StatsTimer t(env_, µ_list[j]);
|
||||
db_fname_list[j] =
|
||||
TableFileName(db_options_.db_paths, meta_list[j].fd.GetNumber(),
|
||||
meta_list[j].fd.GetPathId());
|
||||
if (move_file) {
|
||||
status = env_->LinkFile(file_info_list[j].file_path, db_fname_list[j]);
|
||||
if (status.IsNotSupported()) {
|
||||
// Original file is on a different FS, use copy instead of hard linking
|
||||
status =
|
||||
CopyFile(env_, file_info_list[j].file_path, db_fname_list[j], 0);
|
||||
}
|
||||
} else {
|
||||
status = CopyFile(env_, file_info_list[j].file_path, db_fname_list[j], 0);
|
||||
}
|
||||
TEST_SYNC_POINT("DBImpl::AddFile:FileCopied");
|
||||
if (!status.ok()) {
|
||||
for (size_t i = 0; i < j; i++) {
|
||||
Status s = env_->DeleteFile(db_fname_list[i]);
|
||||
if (!s.ok()) {
|
||||
Log(InfoLogLevel::WARN_LEVEL, db_options_.info_log,
|
||||
"AddFile() clean up for file %s failed : %s",
|
||||
db_fname_list[i].c_str(), s.ToString().c_str());
|
||||
}
|
||||
}
|
||||
return status;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
const MutableCFOptions mutable_cf_options =
|
||||
*cfd->GetLatestMutableCFOptions();
|
||||
|
||||
WriteThread::Writer w;
|
||||
write_thread_.EnterUnbatched(&w, &mutex_);
|
||||
|
||||
if (!snapshots_.empty()) {
|
||||
// Check that no snapshots are being held
|
||||
status =
|
||||
Status::NotSupported("Cannot add a file while holding snapshots");
|
||||
}
|
||||
|
||||
if (status.ok()) {
|
||||
// Verify that added file key range dont overlap with any keys in DB
|
||||
SuperVersion* sv = cfd->GetSuperVersion()->Ref();
|
||||
Arena arena;
|
||||
ReadOptions ro;
|
||||
ro.total_order_seek = true;
|
||||
ScopedArenaIterator iter(NewInternalIterator(ro, cfd, sv, &arena));
|
||||
|
||||
for (size_t i = 0; i < num_files; i++) {
|
||||
StatsTimer t(env_, µ_list[i]);
|
||||
InternalKey range_start(file_info_list[i].smallest_key,
|
||||
kMaxSequenceNumber, kTypeValue);
|
||||
iter->Seek(range_start.Encode());
|
||||
status = iter->status();
|
||||
|
||||
if (status.ok() && iter->Valid()) {
|
||||
ParsedInternalKey seek_result;
|
||||
if (ParseInternalKey(iter->key(), &seek_result)) {
|
||||
auto* vstorage = cfd->current()->storage_info();
|
||||
if (vstorage->InternalComparator()->user_comparator()->Compare(
|
||||
seek_result.user_key, file_info_list[i].largest_key) <= 0) {
|
||||
status = Status::NotSupported("Cannot add overlapping range");
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
status = Status::Corruption("DB have corrupted keys");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The level the file will be ingested into
|
||||
std::vector<int> target_level_list(num_files, 0);
|
||||
if (status.ok()) {
|
||||
// Add files to L0
|
||||
VersionEdit edit;
|
||||
edit.SetColumnFamily(cfd->GetID());
|
||||
for (size_t i = 0; i < num_files; i++) {
|
||||
StatsTimer t(env_, µ_list[i]);
|
||||
// Add file to the lowest possible level
|
||||
target_level_list[i] = PickLevelForIngestedFile(cfd, file_info_list[i]);
|
||||
edit.AddFile(target_level_list[i], meta_list[i].fd.GetNumber(),
|
||||
meta_list[i].fd.GetPathId(), meta_list[i].fd.GetFileSize(),
|
||||
meta_list[i].smallest, meta_list[i].largest,
|
||||
meta_list[i].smallest_seqno, meta_list[i].largest_seqno,
|
||||
meta_list[i].marked_for_compaction);
|
||||
}
|
||||
status = versions_->LogAndApply(cfd, mutable_cf_options, &edit, &mutex_,
|
||||
directories_.GetDbDir());
|
||||
}
|
||||
write_thread_.ExitUnbatched(&w);
|
||||
|
||||
if (status.ok()) {
|
||||
delete InstallSuperVersionAndScheduleWork(cfd, nullptr,
|
||||
mutable_cf_options);
|
||||
}
|
||||
for (size_t i = 0; i < num_files; i++) {
|
||||
// Update internal stats
|
||||
InternalStats::CompactionStats stats(1);
|
||||
stats.micros = micro_list[i];
|
||||
stats.bytes_written = meta_list[i].fd.GetFileSize();
|
||||
stats.num_output_files = 1;
|
||||
cfd->internal_stats()->AddCompactionStats(target_level_list[i], stats);
|
||||
cfd->internal_stats()->AddCFStats(InternalStats::BYTES_INGESTED_ADD_FILE,
|
||||
meta_list[i].fd.GetFileSize());
|
||||
ReleaseFileNumberFromPendingOutputs(
|
||||
pending_outputs_inserted_elem_list[i]);
|
||||
}
|
||||
}
|
||||
|
||||
if (!status.ok()) {
|
||||
// We failed to add the files to the database
|
||||
for (size_t i = 0; i < num_files; i++) {
|
||||
Status s = env_->DeleteFile(db_fname_list[i]);
|
||||
if (!s.ok()) {
|
||||
Log(InfoLogLevel::WARN_LEVEL, db_options_.info_log,
|
||||
"AddFile() clean up for file %s failed : %s",
|
||||
db_fname_list[i].c_str(), s.ToString().c_str());
|
||||
}
|
||||
}
|
||||
} else if (status.ok() && move_file) {
|
||||
// The files were moved and added successfully, remove original file links
|
||||
for (size_t i = 0; i < num_files; i++) {
|
||||
Status s = env_->DeleteFile(file_info_list[i].file_path);
|
||||
if (!s.ok()) {
|
||||
Log(InfoLogLevel::WARN_LEVEL, db_options_.info_log,
|
||||
"%s was added to DB successfully but failed to remove original "
|
||||
"file "
|
||||
"link : %s",
|
||||
file_info_list[i].file_path.c_str(), s.ToString().c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
// Finds the lowest level in the DB that the ingested file can be added to
|
||||
int DBImpl::PickLevelForIngestedFile(ColumnFamilyData* cfd,
|
||||
const ExternalSstFileInfo& file_info) {
|
||||
mutex_.AssertHeld();
|
||||
|
||||
int target_level = 0;
|
||||
auto* vstorage = cfd->current()->storage_info();
|
||||
auto* ucmp = vstorage->InternalComparator()->user_comparator();
|
||||
|
||||
Slice file_smallest_user_key(file_info.smallest_key);
|
||||
Slice file_largest_user_key(file_info.largest_key);
|
||||
|
||||
for (int lvl = cfd->NumberLevels() - 1; lvl >= vstorage->base_level();
|
||||
lvl--) {
|
||||
if (vstorage->OverlapInLevel(lvl, &file_smallest_user_key,
|
||||
&file_largest_user_key) == false) {
|
||||
// Make sure that the file dont overlap with the output of any
|
||||
// compaction running right now
|
||||
Slice compaction_smallest_user_key;
|
||||
Slice compaction_largest_user_key;
|
||||
bool overlap_with_compaction_output = false;
|
||||
for (Compaction* c : running_compactions_) {
|
||||
if (c->column_family_data()->GetID() != cfd->GetID() ||
|
||||
c->output_level() != lvl) {
|
||||
continue;
|
||||
}
|
||||
|
||||
compaction_smallest_user_key = c->GetSmallestUserKey();
|
||||
compaction_largest_user_key = c->GetLargestUserKey();
|
||||
|
||||
if (ucmp->Compare(file_smallest_user_key,
|
||||
compaction_largest_user_key) <= 0 &&
|
||||
ucmp->Compare(file_largest_user_key,
|
||||
compaction_smallest_user_key) >= 0) {
|
||||
overlap_with_compaction_output = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (overlap_with_compaction_output == false) {
|
||||
// Level lvl is the lowest level that dont have any files with key
|
||||
// range overlapping with our file key range and no compactions
|
||||
// planning to add overlapping files in it.
|
||||
target_level = lvl;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return target_level;
|
||||
}
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
} // namespace rocksdb
|
||||
+29
-58
@@ -16,6 +16,7 @@
|
||||
#include "db/dbformat.h"
|
||||
#include "db/filename.h"
|
||||
#include "db/merge_context.h"
|
||||
#include "db/merge_helper.h"
|
||||
#include "db/pinned_iterators_manager.h"
|
||||
#include "port/port.h"
|
||||
#include "rocksdb/env.h"
|
||||
@@ -109,7 +110,7 @@ class DBIter: public Iterator {
|
||||
env_(env),
|
||||
logger_(ioptions.info_log),
|
||||
user_comparator_(cmp),
|
||||
user_merge_operator_(ioptions.merge_operator),
|
||||
merge_operator_(ioptions.merge_operator),
|
||||
iter_(iter),
|
||||
sequence_(s),
|
||||
direction_(kForward),
|
||||
@@ -239,7 +240,7 @@ class DBIter: public Iterator {
|
||||
Env* const env_;
|
||||
Logger* logger_;
|
||||
const Comparator* const user_comparator_;
|
||||
const MergeOperator* const user_merge_operator_;
|
||||
const MergeOperator* const merge_operator_;
|
||||
InternalIterator* iter_;
|
||||
SequenceNumber const sequence_;
|
||||
|
||||
@@ -424,10 +425,10 @@ void DBIter::FindNextUserEntryInternal(bool skipping, bool prefix_check) {
|
||||
// POST: saved_value_ has the merged value for the user key
|
||||
// iter_ points to the next entry (or invalid)
|
||||
void DBIter::MergeValuesNewToOld() {
|
||||
if (!user_merge_operator_) {
|
||||
if (!merge_operator_) {
|
||||
Log(InfoLogLevel::ERROR_LEVEL,
|
||||
logger_, "Options::merge_operator is null.");
|
||||
status_ = Status::InvalidArgument("user_merge_operator_ must be set.");
|
||||
status_ = Status::InvalidArgument("merge_operator_ must be set.");
|
||||
valid_ = false;
|
||||
return;
|
||||
}
|
||||
@@ -456,15 +457,9 @@ void DBIter::MergeValuesNewToOld() {
|
||||
// final result in saved_value_. We are done!
|
||||
// ignore corruption if there is any.
|
||||
const Slice val = iter_->value();
|
||||
{
|
||||
StopWatchNano timer(env_, statistics_ != nullptr);
|
||||
PERF_TIMER_GUARD(merge_operator_time_nanos);
|
||||
user_merge_operator_->FullMerge(ikey.user_key, &val,
|
||||
merge_context_.GetOperands(),
|
||||
&saved_value_, logger_);
|
||||
RecordTick(statistics_, MERGE_OPERATION_TOTAL_TIME,
|
||||
timer.ElapsedNanos());
|
||||
}
|
||||
MergeHelper::TimedFullMerge(merge_operator_, ikey.user_key, &val,
|
||||
merge_context_.GetOperands(), &saved_value_,
|
||||
logger_, statistics_, env_);
|
||||
// iter_ is positioned after put
|
||||
iter_->Next();
|
||||
return;
|
||||
@@ -478,18 +473,13 @@ void DBIter::MergeValuesNewToOld() {
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
StopWatchNano timer(env_, statistics_ != nullptr);
|
||||
PERF_TIMER_GUARD(merge_operator_time_nanos);
|
||||
// we either exhausted all internal keys under this user key, or hit
|
||||
// a deletion marker.
|
||||
// feed null as the existing value to the merge operator, such that
|
||||
// client can differentiate this scenario and do things accordingly.
|
||||
user_merge_operator_->FullMerge(saved_key_.GetKey(), nullptr,
|
||||
merge_context_.GetOperands(), &saved_value_,
|
||||
logger_);
|
||||
RecordTick(statistics_, MERGE_OPERATION_TOTAL_TIME, timer.ElapsedNanos());
|
||||
}
|
||||
// we either exhausted all internal keys under this user key, or hit
|
||||
// a deletion marker.
|
||||
// feed null as the existing value to the merge operator, such that
|
||||
// client can differentiate this scenario and do things accordingly.
|
||||
MergeHelper::TimedFullMerge(merge_operator_, saved_key_.GetKey(), nullptr,
|
||||
merge_context_.GetOperands(), &saved_value_,
|
||||
logger_, statistics_, env_);
|
||||
}
|
||||
|
||||
void DBIter::Prev() {
|
||||
@@ -614,7 +604,7 @@ bool DBIter::FindValueForCurrentKey() {
|
||||
PERF_COUNTER_ADD(internal_delete_skipped_count, 1);
|
||||
break;
|
||||
case kTypeMerge:
|
||||
assert(user_merge_operator_ != nullptr);
|
||||
assert(merge_operator_ != nullptr);
|
||||
merge_context_.PushOperandBack(iter_->value());
|
||||
break;
|
||||
default:
|
||||
@@ -636,24 +626,15 @@ bool DBIter::FindValueForCurrentKey() {
|
||||
case kTypeMerge:
|
||||
current_entry_is_merged_ = true;
|
||||
if (last_not_merge_type == kTypeDeletion) {
|
||||
StopWatchNano timer(env_, statistics_ != nullptr);
|
||||
PERF_TIMER_GUARD(merge_operator_time_nanos);
|
||||
user_merge_operator_->FullMerge(saved_key_.GetKey(), nullptr,
|
||||
merge_context_.GetOperands(),
|
||||
&saved_value_, logger_);
|
||||
RecordTick(statistics_, MERGE_OPERATION_TOTAL_TIME,
|
||||
timer.ElapsedNanos());
|
||||
MergeHelper::TimedFullMerge(merge_operator_, saved_key_.GetKey(),
|
||||
nullptr, merge_context_.GetOperands(),
|
||||
&saved_value_, logger_, statistics_, env_);
|
||||
} else {
|
||||
assert(last_not_merge_type == kTypeValue);
|
||||
{
|
||||
StopWatchNano timer(env_, statistics_ != nullptr);
|
||||
PERF_TIMER_GUARD(merge_operator_time_nanos);
|
||||
user_merge_operator_->FullMerge(saved_key_.GetKey(), &pinned_value_,
|
||||
merge_context_.GetOperands(),
|
||||
&saved_value_, logger_);
|
||||
RecordTick(statistics_, MERGE_OPERATION_TOTAL_TIME,
|
||||
timer.ElapsedNanos());
|
||||
}
|
||||
MergeHelper::TimedFullMerge(merge_operator_, saved_key_.GetKey(),
|
||||
&pinned_value_,
|
||||
merge_context_.GetOperands(), &saved_value_,
|
||||
logger_, statistics_, env_);
|
||||
}
|
||||
break;
|
||||
case kTypeValue:
|
||||
@@ -708,14 +689,9 @@ bool DBIter::FindValueForCurrentKeyUsingSeek() {
|
||||
if (!iter_->Valid() ||
|
||||
!user_comparator_->Equal(ikey.user_key, saved_key_.GetKey()) ||
|
||||
ikey.type == kTypeDeletion || ikey.type == kTypeSingleDeletion) {
|
||||
{
|
||||
StopWatchNano timer(env_, statistics_ != nullptr);
|
||||
PERF_TIMER_GUARD(merge_operator_time_nanos);
|
||||
user_merge_operator_->FullMerge(saved_key_.GetKey(), nullptr,
|
||||
merge_context_.GetOperands(),
|
||||
&saved_value_, logger_);
|
||||
RecordTick(statistics_, MERGE_OPERATION_TOTAL_TIME, timer.ElapsedNanos());
|
||||
}
|
||||
MergeHelper::TimedFullMerge(merge_operator_, saved_key_.GetKey(), nullptr,
|
||||
merge_context_.GetOperands(), &saved_value_,
|
||||
logger_, statistics_, env_);
|
||||
// Make iter_ valid and point to saved_key_
|
||||
if (!iter_->Valid() ||
|
||||
!user_comparator_->Equal(ikey.user_key, saved_key_.GetKey())) {
|
||||
@@ -727,14 +703,9 @@ bool DBIter::FindValueForCurrentKeyUsingSeek() {
|
||||
}
|
||||
|
||||
const Slice& val = iter_->value();
|
||||
{
|
||||
StopWatchNano timer(env_, statistics_ != nullptr);
|
||||
PERF_TIMER_GUARD(merge_operator_time_nanos);
|
||||
user_merge_operator_->FullMerge(saved_key_.GetKey(), &val,
|
||||
merge_context_.GetOperands(), &saved_value_,
|
||||
logger_);
|
||||
RecordTick(statistics_, MERGE_OPERATION_TOTAL_TIME, timer.ElapsedNanos());
|
||||
}
|
||||
MergeHelper::TimedFullMerge(merge_operator_, saved_key_.GetKey(), &val,
|
||||
merge_context_.GetOperands(), &saved_value_,
|
||||
logger_, statistics_, env_);
|
||||
valid_ = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include "port/stack_trace.h"
|
||||
#include "rocksdb/iostats_context.h"
|
||||
#include "rocksdb/perf_context.h"
|
||||
#include "port/port.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
@@ -979,7 +980,7 @@ TEST_F(DBIteratorTest, PinnedDataIteratorRandomized) {
|
||||
|
||||
{
|
||||
// Test Seek to random keys
|
||||
printf("Testing seek on %zu keys\n", random_keys.size());
|
||||
printf("Testing seek on %" ROCKSDB_PRIszt " keys\n", random_keys.size());
|
||||
std::vector<Slice> keys_slices;
|
||||
std::vector<std::string> true_keys;
|
||||
for (auto& k : random_keys) {
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
// 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"
|
||||
#include "util/sync_point.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
class DBOptionsTest : public DBTestBase {
|
||||
public:
|
||||
DBOptionsTest() : DBTestBase("/db_options_test") {}
|
||||
};
|
||||
|
||||
// When write stalls, user can enable auto compaction to unblock writes.
|
||||
// However, we had an issue where the stalled write thread blocks the attempt
|
||||
// to persist auto compaction option, thus creating a deadlock. The test
|
||||
// verifies the issue is fixed.
|
||||
TEST_F(DBOptionsTest, EnableAutoCompactionToUnblockWrites) {
|
||||
Options options;
|
||||
options.disable_auto_compactions = true;
|
||||
options.write_buffer_size = 1000 * 1000; // 1M
|
||||
options.level0_file_num_compaction_trigger = 1;
|
||||
options.level0_slowdown_writes_trigger = 1;
|
||||
options.level0_stop_writes_trigger = 1;
|
||||
options.compression = kNoCompression;
|
||||
|
||||
SyncPoint::GetInstance()->LoadDependency(
|
||||
{{"DBImpl::DelayWrite:Wait",
|
||||
"DBOptionsTest::EnableAutoCompactionToUnblockWrites:1"},
|
||||
{"DBImpl::BackgroundCompaction:Finish",
|
||||
"DBOptionsTest::EnableAutoCompactionToUnblockWrites:1"}});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
// Stall writes.
|
||||
Reopen(options);
|
||||
env_->StartThread(
|
||||
[](void* arg) {
|
||||
std::string value(1000, 'v');
|
||||
auto* t = static_cast<DBOptionsTest*>(arg);
|
||||
for (int i = 0; i < 2000; i++) {
|
||||
ASSERT_OK(t->Put(t->Key(i), value));
|
||||
}
|
||||
},
|
||||
this);
|
||||
TEST_SYNC_POINT("DBOptionsTest::EnableAutoCompactionToUnblockWrites:1");
|
||||
ASSERT_TRUE(dbfull()->TEST_write_controler().IsStopped());
|
||||
ColumnFamilyHandle* handle = dbfull()->DefaultColumnFamily();
|
||||
// We will get a deadlock here if we hit the issue.
|
||||
dbfull()->EnableAutoCompaction({handle});
|
||||
env_->WaitForJoin();
|
||||
}
|
||||
|
||||
// Similar to EnableAutoCompactionAfterStallDeadlock. See comments there.
|
||||
TEST_F(DBOptionsTest, ToggleStopTriggerToUnblockWrites) {
|
||||
Options options;
|
||||
options.disable_auto_compactions = true;
|
||||
options.write_buffer_size = 1000 * 1000; // 1M
|
||||
options.level0_file_num_compaction_trigger = 1;
|
||||
options.level0_slowdown_writes_trigger = 1;
|
||||
options.level0_stop_writes_trigger = 1;
|
||||
options.compression = kNoCompression;
|
||||
|
||||
SyncPoint::GetInstance()->LoadDependency(
|
||||
{{"DBImpl::DelayWrite:Wait",
|
||||
"DBOptionsTest::ToggleStopTriggerToUnblockWrites:1"},
|
||||
{"DBImpl::BackgroundCompaction:Finish",
|
||||
"DBOptionsTest::ToggleStopTriggerToUnblockWrites:1"}});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
// Stall writes.
|
||||
Reopen(options);
|
||||
env_->StartThread(
|
||||
[](void* arg) {
|
||||
std::string value(1000, 'v');
|
||||
auto* t = static_cast<DBOptionsTest*>(arg);
|
||||
for (int i = 0; i < 2000; i++) {
|
||||
ASSERT_OK(t->Put(t->Key(i), value));
|
||||
}
|
||||
},
|
||||
this);
|
||||
TEST_SYNC_POINT("DBOptionsTest::ToggleStopTriggerToUnblockWrites:1");
|
||||
ASSERT_TRUE(dbfull()->TEST_write_controler().IsStopped());
|
||||
// We will get a deadlock here if we hit the issue.
|
||||
dbfull()->SetOptions({{"level0_stop_writes_trigger", "1000000"},
|
||||
{"level0_slowdown_writes_trigger", "1000000"}});
|
||||
env_->WaitForJoin();
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
rocksdb::port::InstallStackTraceHandler();
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
@@ -236,7 +236,7 @@ void GetExpectedTableProperties(TableProperties* expected_tp,
|
||||
expected_tp->data_size =
|
||||
kTableCount * (kKeysPerTable * (kKeySize + 8 + kValueSize));
|
||||
expected_tp->index_size =
|
||||
expected_tp->num_data_blocks * (kAvgSuccessorSize + 12);
|
||||
expected_tp->num_data_blocks * (kAvgSuccessorSize + 8);
|
||||
expected_tp->filter_size =
|
||||
kTableCount * (kKeysPerTable * kBloomBitsPerKey / 8);
|
||||
}
|
||||
|
||||
+455
-18
@@ -12,6 +12,7 @@
|
||||
#include "rocksdb/sst_file_manager.h"
|
||||
#include "rocksdb/sst_file_writer.h"
|
||||
#include "util/sst_file_manager_impl.h"
|
||||
#include "port/port.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
@@ -843,7 +844,7 @@ TEST_F(DBSSTTest, AddExternalSstFile) {
|
||||
|
||||
DestroyAndReopen(options);
|
||||
// Add file using file path
|
||||
s = db_->AddFile(file1);
|
||||
s = db_->AddFile(std::vector<std::string>(1, file1));
|
||||
ASSERT_TRUE(s.ok()) << s.ToString();
|
||||
ASSERT_EQ(db_->GetLatestSequenceNumber(), 0U);
|
||||
for (int k = 0; k < 100; k++) {
|
||||
@@ -853,23 +854,23 @@ TEST_F(DBSSTTest, AddExternalSstFile) {
|
||||
// Add file while holding a snapshot will fail
|
||||
const Snapshot* s1 = db_->GetSnapshot();
|
||||
if (s1 != nullptr) {
|
||||
ASSERT_NOK(db_->AddFile(&file2_info));
|
||||
ASSERT_NOK(db_->AddFile(std::vector<ExternalSstFileInfo>(1, file2_info)));
|
||||
db_->ReleaseSnapshot(s1);
|
||||
}
|
||||
// We can add the file after releaseing the snapshot
|
||||
ASSERT_OK(db_->AddFile(&file2_info));
|
||||
ASSERT_OK(db_->AddFile(std::vector<ExternalSstFileInfo>(1, file2_info)));
|
||||
|
||||
ASSERT_EQ(db_->GetLatestSequenceNumber(), 0U);
|
||||
for (int k = 0; k < 200; k++) {
|
||||
ASSERT_EQ(Get(Key(k)), Key(k) + "_val");
|
||||
}
|
||||
|
||||
// This file have overlapping values with the exisitng data
|
||||
s = db_->AddFile(file3);
|
||||
// This file has overlapping values with the exisitng data
|
||||
s = db_->AddFile(std::vector<std::string>(1, file3));
|
||||
ASSERT_FALSE(s.ok()) << s.ToString();
|
||||
|
||||
// This file have overlapping values with the exisitng data
|
||||
s = db_->AddFile(&file4_info);
|
||||
// This file has overlapping values with the exisitng data
|
||||
s = db_->AddFile(std::vector<ExternalSstFileInfo>(1, file4_info));
|
||||
ASSERT_FALSE(s.ok()) << s.ToString();
|
||||
|
||||
// Overwrite values of keys divisible by 5
|
||||
@@ -879,7 +880,7 @@ TEST_F(DBSSTTest, AddExternalSstFile) {
|
||||
ASSERT_NE(db_->GetLatestSequenceNumber(), 0U);
|
||||
|
||||
// Key range of file5 (400 => 499) dont overlap with any keys in DB
|
||||
ASSERT_OK(db_->AddFile(file5));
|
||||
ASSERT_OK(db_->AddFile(std::vector<std::string>(1, file5)));
|
||||
|
||||
// Make sure values are correct before and after flush/compaction
|
||||
for (int i = 0; i < 2; i++) {
|
||||
@@ -908,13 +909,13 @@ TEST_F(DBSSTTest, AddExternalSstFile) {
|
||||
}
|
||||
// We deleted range (400 => 499) but cannot add file5 because
|
||||
// of the range tombstones
|
||||
ASSERT_NOK(db_->AddFile(file5));
|
||||
ASSERT_NOK(db_->AddFile(std::vector<std::string>(1, file5)));
|
||||
|
||||
// Compacting the DB will remove the tombstones
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
||||
|
||||
// Now we can add the file
|
||||
ASSERT_OK(db_->AddFile(file5));
|
||||
ASSERT_OK(db_->AddFile(std::vector<std::string>(1, file5)));
|
||||
|
||||
// Verify values of file5 in DB
|
||||
for (int k = 400; k < 500; k++) {
|
||||
@@ -925,6 +926,230 @@ TEST_F(DBSSTTest, AddExternalSstFile) {
|
||||
kSkipFIFOCompaction));
|
||||
}
|
||||
|
||||
TEST_F(DBSSTTest, AddExternalSstFileList) {
|
||||
do {
|
||||
std::string sst_files_folder = test::TmpDir(env_) + "/sst_files/";
|
||||
env_->CreateDir(sst_files_folder);
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
|
||||
SstFileWriter sst_file_writer(EnvOptions(), options, options.comparator);
|
||||
|
||||
// file1.sst (0 => 99)
|
||||
std::string file1 = sst_files_folder + "file1.sst";
|
||||
ASSERT_OK(sst_file_writer.Open(file1));
|
||||
for (int k = 0; k < 100; k++) {
|
||||
ASSERT_OK(sst_file_writer.Add(Key(k), Key(k) + "_val"));
|
||||
}
|
||||
ExternalSstFileInfo file1_info;
|
||||
Status s = sst_file_writer.Finish(&file1_info);
|
||||
ASSERT_TRUE(s.ok()) << s.ToString();
|
||||
ASSERT_EQ(file1_info.file_path, file1);
|
||||
ASSERT_EQ(file1_info.num_entries, 100);
|
||||
ASSERT_EQ(file1_info.smallest_key, Key(0));
|
||||
ASSERT_EQ(file1_info.largest_key, Key(99));
|
||||
// sst_file_writer already finished, cannot add this value
|
||||
s = sst_file_writer.Add(Key(100), "bad_val");
|
||||
ASSERT_FALSE(s.ok()) << s.ToString();
|
||||
|
||||
// file2.sst (100 => 199)
|
||||
std::string file2 = sst_files_folder + "file2.sst";
|
||||
ASSERT_OK(sst_file_writer.Open(file2));
|
||||
for (int k = 100; k < 200; k++) {
|
||||
ASSERT_OK(sst_file_writer.Add(Key(k), Key(k) + "_val"));
|
||||
}
|
||||
// Cannot add this key because it's not after last added key
|
||||
s = sst_file_writer.Add(Key(99), "bad_val");
|
||||
ASSERT_FALSE(s.ok()) << s.ToString();
|
||||
ExternalSstFileInfo file2_info;
|
||||
s = sst_file_writer.Finish(&file2_info);
|
||||
ASSERT_TRUE(s.ok()) << s.ToString();
|
||||
ASSERT_EQ(file2_info.file_path, file2);
|
||||
ASSERT_EQ(file2_info.num_entries, 100);
|
||||
ASSERT_EQ(file2_info.smallest_key, Key(100));
|
||||
ASSERT_EQ(file2_info.largest_key, Key(199));
|
||||
|
||||
// file3.sst (195 => 199)
|
||||
// This file values overlap with file2 values
|
||||
std::string file3 = sst_files_folder + "file3.sst";
|
||||
ASSERT_OK(sst_file_writer.Open(file3));
|
||||
for (int k = 195; k < 200; k++) {
|
||||
ASSERT_OK(sst_file_writer.Add(Key(k), Key(k) + "_val_overlap"));
|
||||
}
|
||||
ExternalSstFileInfo file3_info;
|
||||
s = sst_file_writer.Finish(&file3_info);
|
||||
ASSERT_TRUE(s.ok()) << s.ToString();
|
||||
ASSERT_EQ(file3_info.file_path, file3);
|
||||
ASSERT_EQ(file3_info.num_entries, 5);
|
||||
ASSERT_EQ(file3_info.smallest_key, Key(195));
|
||||
ASSERT_EQ(file3_info.largest_key, Key(199));
|
||||
|
||||
// file4.sst (30 => 39)
|
||||
// This file values overlap with file1 values
|
||||
std::string file4 = sst_files_folder + "file4.sst";
|
||||
ASSERT_OK(sst_file_writer.Open(file4));
|
||||
for (int k = 30; k < 40; k++) {
|
||||
ASSERT_OK(sst_file_writer.Add(Key(k), Key(k) + "_val_overlap"));
|
||||
}
|
||||
ExternalSstFileInfo file4_info;
|
||||
s = sst_file_writer.Finish(&file4_info);
|
||||
ASSERT_TRUE(s.ok()) << s.ToString();
|
||||
ASSERT_EQ(file4_info.file_path, file4);
|
||||
ASSERT_EQ(file4_info.num_entries, 10);
|
||||
ASSERT_EQ(file4_info.smallest_key, Key(30));
|
||||
ASSERT_EQ(file4_info.largest_key, Key(39));
|
||||
|
||||
// file5.sst (200 => 299)
|
||||
std::string file5 = sst_files_folder + "file5.sst";
|
||||
ASSERT_OK(sst_file_writer.Open(file5));
|
||||
for (int k = 200; k < 300; k++) {
|
||||
ASSERT_OK(sst_file_writer.Add(Key(k), Key(k) + "_val"));
|
||||
}
|
||||
ExternalSstFileInfo file5_info;
|
||||
s = sst_file_writer.Finish(&file5_info);
|
||||
ASSERT_TRUE(s.ok()) << s.ToString();
|
||||
ASSERT_EQ(file5_info.file_path, file5);
|
||||
ASSERT_EQ(file5_info.num_entries, 100);
|
||||
ASSERT_EQ(file5_info.smallest_key, Key(200));
|
||||
ASSERT_EQ(file5_info.largest_key, Key(299));
|
||||
|
||||
// list 1 has internal key range conflict
|
||||
std::vector<std::string> file_list0({file1, file2});
|
||||
std::vector<std::string> file_list1({file3, file2, file1});
|
||||
std::vector<std::string> file_list2({file5});
|
||||
std::vector<std::string> file_list3({file3, file4});
|
||||
|
||||
std::vector<ExternalSstFileInfo> info_list0({file1_info, file2_info});
|
||||
std::vector<ExternalSstFileInfo> info_list1(
|
||||
{file3_info, file2_info, file1_info});
|
||||
std::vector<ExternalSstFileInfo> info_list2({file5_info});
|
||||
std::vector<ExternalSstFileInfo> info_list3({file3_info, file4_info});
|
||||
|
||||
DestroyAndReopen(options);
|
||||
|
||||
// This list of files have key ranges are overlapping with each other
|
||||
s = db_->AddFile(file_list1);
|
||||
ASSERT_FALSE(s.ok()) << s.ToString();
|
||||
s = db_->AddFile(info_list1);
|
||||
ASSERT_FALSE(s.ok()) << s.ToString();
|
||||
|
||||
// Add files using file path list
|
||||
s = db_->AddFile(file_list0);
|
||||
ASSERT_TRUE(s.ok()) << s.ToString();
|
||||
ASSERT_EQ(db_->GetLatestSequenceNumber(), 0U);
|
||||
for (int k = 0; k < 200; k++) {
|
||||
ASSERT_EQ(Get(Key(k)), Key(k) + "_val");
|
||||
}
|
||||
|
||||
// Add file while holding a snapshot will fail
|
||||
const Snapshot* s1 = db_->GetSnapshot();
|
||||
if (s1 != nullptr) {
|
||||
ASSERT_NOK(db_->AddFile(info_list2));
|
||||
db_->ReleaseSnapshot(s1);
|
||||
}
|
||||
// We can add the file after releaseing the snapshot
|
||||
ASSERT_OK(db_->AddFile(info_list2));
|
||||
ASSERT_EQ(db_->GetLatestSequenceNumber(), 0U);
|
||||
for (int k = 0; k < 300; k++) {
|
||||
ASSERT_EQ(Get(Key(k)), Key(k) + "_val");
|
||||
}
|
||||
|
||||
// This file list has overlapping values with the exisitng data
|
||||
s = db_->AddFile(file_list3);
|
||||
ASSERT_FALSE(s.ok()) << s.ToString();
|
||||
s = db_->AddFile(info_list3);
|
||||
ASSERT_FALSE(s.ok()) << s.ToString();
|
||||
|
||||
// Overwrite values of keys divisible by 5
|
||||
for (int k = 0; k < 200; k += 5) {
|
||||
ASSERT_OK(Put(Key(k), Key(k) + "_val_new"));
|
||||
}
|
||||
ASSERT_NE(db_->GetLatestSequenceNumber(), 0U);
|
||||
|
||||
// Make sure values are correct before and after flush/compaction
|
||||
for (int i = 0; i < 2; i++) {
|
||||
for (int k = 0; k < 200; k++) {
|
||||
std::string value = Key(k) + "_val";
|
||||
if (k % 5 == 0) {
|
||||
value += "_new";
|
||||
}
|
||||
ASSERT_EQ(Get(Key(k)), value);
|
||||
}
|
||||
for (int k = 200; k < 300; k++) {
|
||||
std::string value = Key(k) + "_val";
|
||||
ASSERT_EQ(Get(Key(k)), value);
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
||||
}
|
||||
|
||||
// Delete keys in range (200 => 299)
|
||||
for (int k = 200; k < 300; k++) {
|
||||
ASSERT_OK(Delete(Key(k)));
|
||||
}
|
||||
// We deleted range (200 => 299) but cannot add file5 because
|
||||
// of the range tombstones
|
||||
ASSERT_NOK(db_->AddFile(file_list2));
|
||||
|
||||
// Compacting the DB will remove the tombstones
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
||||
|
||||
// Now we can add the file
|
||||
ASSERT_OK(db_->AddFile(file_list2));
|
||||
|
||||
// Verify values of file5 in DB
|
||||
for (int k = 200; k < 300; k++) {
|
||||
std::string value = Key(k) + "_val";
|
||||
ASSERT_EQ(Get(Key(k)), value);
|
||||
}
|
||||
} while (ChangeOptions(kSkipPlainTable | kSkipUniversalCompaction |
|
||||
kSkipFIFOCompaction));
|
||||
}
|
||||
|
||||
TEST_F(DBSSTTest, AddExternalSstFileListAtomicity) {
|
||||
do {
|
||||
std::string sst_files_folder = test::TmpDir(env_) + "/sst_files/";
|
||||
env_->CreateDir(sst_files_folder);
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
|
||||
SstFileWriter sst_file_writer(EnvOptions(), options, options.comparator);
|
||||
|
||||
// files[0].sst (0 => 99)
|
||||
// files[1].sst (100 => 199)
|
||||
// ...
|
||||
// file[8].sst (800 => 899)
|
||||
int n = 9;
|
||||
std::vector<std::string> files(n);
|
||||
std::vector<ExternalSstFileInfo> files_info(n);
|
||||
for (int i = 0; i < n; i++) {
|
||||
files[i] = sst_files_folder + "file" + std::to_string(i) + ".sst";
|
||||
ASSERT_OK(sst_file_writer.Open(files[i]));
|
||||
for (int k = i * 100; k < (i + 1) * 100; k++) {
|
||||
ASSERT_OK(sst_file_writer.Add(Key(k), Key(k) + "_val"));
|
||||
}
|
||||
Status s = sst_file_writer.Finish(&files_info[i]);
|
||||
ASSERT_TRUE(s.ok()) << s.ToString();
|
||||
ASSERT_EQ(files_info[i].file_path, files[i]);
|
||||
ASSERT_EQ(files_info[i].num_entries, 100);
|
||||
ASSERT_EQ(files_info[i].smallest_key, Key(i * 100));
|
||||
ASSERT_EQ(files_info[i].largest_key, Key((i + 1) * 100 - 1));
|
||||
}
|
||||
files.push_back(sst_files_folder + "file" + std::to_string(n) + ".sst");
|
||||
auto s = db_->AddFile(files);
|
||||
ASSERT_NOK(s) << s.ToString();
|
||||
for (int k = 0; k < n * 100; k++) {
|
||||
ASSERT_EQ("NOT_FOUND", Get(Key(k)));
|
||||
}
|
||||
s = db_->AddFile(files_info);
|
||||
ASSERT_OK(s);
|
||||
for (int k = 0; k < n * 100; k++) {
|
||||
std::string value = Key(k) + "_val";
|
||||
ASSERT_EQ(Get(Key(k)), value);
|
||||
}
|
||||
} while (ChangeOptions(kSkipPlainTable | kSkipUniversalCompaction |
|
||||
kSkipFIFOCompaction));
|
||||
}
|
||||
// This test reporduce a bug that can happen in some cases if the DB started
|
||||
// purging obsolete files when we are adding an external sst file.
|
||||
// This situation may result in deleting the file while it's being added.
|
||||
@@ -963,7 +1188,7 @@ TEST_F(DBSSTTest, AddExternalSstFilePurgeObsoleteFilesBug) {
|
||||
});
|
||||
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
s = db_->AddFile(sst_file_path);
|
||||
s = db_->AddFile(std::vector<std::string>(1, sst_file_path));
|
||||
ASSERT_OK(s);
|
||||
|
||||
for (int i = 0; i < 500; i++) {
|
||||
@@ -1025,17 +1250,19 @@ TEST_F(DBSSTTest, AddExternalSstFileNoCopy) {
|
||||
ASSERT_EQ(file3_info.num_entries, 15);
|
||||
ASSERT_EQ(file3_info.smallest_key, Key(110));
|
||||
ASSERT_EQ(file3_info.largest_key, Key(124));
|
||||
|
||||
s = db_->AddFile(&file1_info, true /* move file */);
|
||||
s = db_->AddFile(std::vector<ExternalSstFileInfo>(1, file1_info),
|
||||
true /* move file */);
|
||||
ASSERT_TRUE(s.ok()) << s.ToString();
|
||||
ASSERT_EQ(Status::NotFound(), env_->FileExists(file1));
|
||||
|
||||
s = db_->AddFile(&file2_info, false /* copy file */);
|
||||
s = db_->AddFile(std::vector<ExternalSstFileInfo>(1, file2_info),
|
||||
false /* copy file */);
|
||||
ASSERT_TRUE(s.ok()) << s.ToString();
|
||||
ASSERT_OK(env_->FileExists(file2));
|
||||
|
||||
// This file have overlapping values with the exisitng data
|
||||
s = db_->AddFile(&file3_info, true /* move file */);
|
||||
s = db_->AddFile(std::vector<ExternalSstFileInfo>(1, file2_info),
|
||||
true /* move file */);
|
||||
ASSERT_FALSE(s.ok()) << s.ToString();
|
||||
ASSERT_OK(env_->FileExists(file3));
|
||||
|
||||
@@ -1101,7 +1328,8 @@ TEST_F(DBSSTTest, AddExternalSstFileMultiThreaded) {
|
||||
// sometimes we use copy, sometimes link .. the result should be the same
|
||||
bool move_file = (thread_id % 3 == 0);
|
||||
|
||||
Status s = db_->AddFile(file_names[file_idx], move_file);
|
||||
Status s = db_->AddFile(std::vector<std::string>(1, file_names[file_idx]),
|
||||
move_file);
|
||||
if (s.ok()) {
|
||||
files_added++;
|
||||
}
|
||||
@@ -1198,7 +1426,7 @@ TEST_F(DBSSTTest, AddExternalSstFileOverlappingRanges) {
|
||||
ASSERT_OK(s);
|
||||
|
||||
// Insert the generated file
|
||||
s = db_->AddFile(&file_info);
|
||||
s = db_->AddFile(std::vector<ExternalSstFileInfo>(1, file_info));
|
||||
|
||||
auto it = true_data.lower_bound(Key(range_start));
|
||||
if (it != true_data.end() && it->first <= Key(range_end)) {
|
||||
@@ -1228,7 +1456,7 @@ TEST_F(DBSSTTest, AddExternalSstFileOverlappingRanges) {
|
||||
}
|
||||
|
||||
printf(
|
||||
"Total: %zu ranges\n"
|
||||
"Total: %" ROCKSDB_PRIszt " ranges\n"
|
||||
"AddFile()|Success: %d ranges\n"
|
||||
"AddFile()|RangeConflict: %d ranges\n"
|
||||
"Put(): %d ranges\n",
|
||||
@@ -1243,6 +1471,215 @@ TEST_F(DBSSTTest, AddExternalSstFileOverlappingRanges) {
|
||||
kSkipFIFOCompaction));
|
||||
}
|
||||
|
||||
TEST_F(DBSSTTest, AddExternalSstFilePickedLevel) {
|
||||
std::string sst_files_folder = test::TmpDir(env_) + "/sst_files/";
|
||||
env_->CreateDir(sst_files_folder);
|
||||
Options options = CurrentOptions();
|
||||
options.disable_auto_compactions = false;
|
||||
options.level0_file_num_compaction_trigger = 4;
|
||||
options.num_levels = 4;
|
||||
options.env = env_;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
std::vector<std::vector<int>> file_to_keys;
|
||||
|
||||
// File 0 will go to last level (L3)
|
||||
file_to_keys.push_back({1, 10});
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, file_to_keys.back(),
|
||||
file_to_keys.size() - 1));
|
||||
EXPECT_EQ(FilesPerLevel(), "0,0,0,1");
|
||||
|
||||
// File 1 will go to level L2 (since it overlap with file 0 in L3)
|
||||
file_to_keys.push_back({2, 9});
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, file_to_keys.back(),
|
||||
file_to_keys.size() - 1));
|
||||
EXPECT_EQ(FilesPerLevel(), "0,0,1,1");
|
||||
|
||||
rocksdb::SyncPoint::GetInstance()->LoadDependency({
|
||||
{"DBSSTTest::AddExternalSstFilePickedLevel:0",
|
||||
"BackgroundCallCompaction:0"},
|
||||
{"DBImpl::BackgroundCompaction:Start",
|
||||
"DBSSTTest::AddExternalSstFilePickedLevel:1"},
|
||||
{"DBSSTTest::AddExternalSstFilePickedLevel:2",
|
||||
"DBImpl::BackgroundCompaction:NonTrivial:AfterRun"},
|
||||
});
|
||||
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
// Flush 4 files containing the same keys
|
||||
for (int i = 0; i < 4; i++) {
|
||||
ASSERT_OK(Put(Key(3), Key(3) + "put"));
|
||||
ASSERT_OK(Put(Key(8), Key(8) + "put"));
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
|
||||
// Wait for BackgroundCompaction() to be called
|
||||
TEST_SYNC_POINT("DBSSTTest::AddExternalSstFilePickedLevel:0");
|
||||
TEST_SYNC_POINT("DBSSTTest::AddExternalSstFilePickedLevel:1");
|
||||
|
||||
EXPECT_EQ(FilesPerLevel(), "4,0,1,1");
|
||||
|
||||
// This file overlaps with file 0 (L3), file 1 (L2) and the
|
||||
// output of compaction going to L1
|
||||
file_to_keys.push_back({4, 7});
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, file_to_keys.back(),
|
||||
file_to_keys.size() - 1));
|
||||
EXPECT_EQ(FilesPerLevel(), "5,0,1,1");
|
||||
|
||||
// This file does not overlap with any file or with the running compaction
|
||||
file_to_keys.push_back({9000, 9001});
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, file_to_keys.back(),
|
||||
file_to_keys.size() - 1));
|
||||
EXPECT_EQ(FilesPerLevel(), "5,0,1,2");
|
||||
|
||||
// Hold compaction from finishing
|
||||
TEST_SYNC_POINT("DBSSTTest::AddExternalSstFilePickedLevel:2");
|
||||
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
EXPECT_EQ(FilesPerLevel(), "1,1,1,2");
|
||||
|
||||
for (size_t file_id = 0; file_id < file_to_keys.size(); file_id++) {
|
||||
for (auto& key_id : file_to_keys[file_id]) {
|
||||
std::string k = Key(key_id);
|
||||
std::string v = k + ToString(file_id);
|
||||
if (key_id == 3 || key_id == 8) {
|
||||
v = k + "put";
|
||||
}
|
||||
|
||||
ASSERT_EQ(Get(k), v);
|
||||
}
|
||||
}
|
||||
|
||||
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
|
||||
TEST_F(DBSSTTest, AddExternalSstFilePickedLevelDynamic) {
|
||||
std::string sst_files_folder = test::TmpDir(env_) + "/sst_files/";
|
||||
env_->CreateDir(sst_files_folder);
|
||||
Options options = CurrentOptions();
|
||||
options.disable_auto_compactions = false;
|
||||
options.level0_file_num_compaction_trigger = 4;
|
||||
options.level_compaction_dynamic_level_bytes = true;
|
||||
options.num_levels = 4;
|
||||
options.env = env_;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
rocksdb::SyncPoint::GetInstance()->LoadDependency({
|
||||
{"DBSSTTest::AddExternalSstFilePickedLevelDynamic:0",
|
||||
"BackgroundCallCompaction:0"},
|
||||
{"DBImpl::BackgroundCompaction:Start",
|
||||
"DBSSTTest::AddExternalSstFilePickedLevelDynamic:1"},
|
||||
{"DBSSTTest::AddExternalSstFilePickedLevelDynamic:2",
|
||||
"DBImpl::BackgroundCompaction:NonTrivial:AfterRun"},
|
||||
});
|
||||
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
// Flush 4 files containing the same keys
|
||||
for (int i = 0; i < 4; i++) {
|
||||
for (int k = 20; k <= 30; k++) {
|
||||
ASSERT_OK(Put(Key(k), Key(k) + "put"));
|
||||
}
|
||||
for (int k = 50; k <= 60; k++) {
|
||||
ASSERT_OK(Put(Key(k), Key(k) + "put"));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
|
||||
// Wait for BackgroundCompaction() to be called
|
||||
TEST_SYNC_POINT("DBSSTTest::AddExternalSstFilePickedLevelDynamic:0");
|
||||
TEST_SYNC_POINT("DBSSTTest::AddExternalSstFilePickedLevelDynamic:1");
|
||||
std::vector<std::vector<int>> file_to_keys;
|
||||
|
||||
// This file overlaps with the output of the compaction (going to L3)
|
||||
// so the file will be added to L0 since L3 is the base level
|
||||
file_to_keys.push_back({31, 32, 33, 34});
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, file_to_keys.back(),
|
||||
file_to_keys.size() - 1));
|
||||
EXPECT_EQ(FilesPerLevel(), "5");
|
||||
|
||||
// This file does not overlap with the current running compactiong
|
||||
file_to_keys.push_back({9000, 9001});
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, file_to_keys.back(),
|
||||
file_to_keys.size() - 1));
|
||||
EXPECT_EQ(FilesPerLevel(), "5,0,0,1");
|
||||
|
||||
// Hold compaction from finishing
|
||||
TEST_SYNC_POINT("DBSSTTest::AddExternalSstFilePickedLevelDynamic:2");
|
||||
|
||||
// Output of the compaction will go to L3
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
EXPECT_EQ(FilesPerLevel(), "1,0,0,2");
|
||||
|
||||
Close();
|
||||
options.disable_auto_compactions = true;
|
||||
Reopen(options);
|
||||
|
||||
file_to_keys.push_back({1, 15, 19});
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, file_to_keys.back(),
|
||||
file_to_keys.size() - 1));
|
||||
ASSERT_EQ(FilesPerLevel(), "1,0,0,3");
|
||||
|
||||
file_to_keys.push_back({1000, 1001, 1002});
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, file_to_keys.back(),
|
||||
file_to_keys.size() - 1));
|
||||
ASSERT_EQ(FilesPerLevel(), "1,0,0,4");
|
||||
|
||||
file_to_keys.push_back({500, 600, 700});
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, file_to_keys.back(),
|
||||
file_to_keys.size() - 1));
|
||||
ASSERT_EQ(FilesPerLevel(), "1,0,0,5");
|
||||
|
||||
// File 5 overlaps with file 2 (L3 / base level)
|
||||
file_to_keys.push_back({2, 10});
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, file_to_keys.back(),
|
||||
file_to_keys.size() - 1));
|
||||
ASSERT_EQ(FilesPerLevel(), "2,0,0,5");
|
||||
|
||||
// File 6 overlaps with file 2 (L3 / base level) and file 5 (L0)
|
||||
file_to_keys.push_back({3, 9});
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, file_to_keys.back(),
|
||||
file_to_keys.size() - 1));
|
||||
ASSERT_EQ(FilesPerLevel(), "3,0,0,5");
|
||||
|
||||
// Verify data in files
|
||||
for (size_t file_id = 0; file_id < file_to_keys.size(); file_id++) {
|
||||
for (auto& key_id : file_to_keys[file_id]) {
|
||||
std::string k = Key(key_id);
|
||||
std::string v = k + ToString(file_id);
|
||||
|
||||
ASSERT_EQ(Get(k), v);
|
||||
}
|
||||
}
|
||||
|
||||
// Write range [5 => 10] to L0
|
||||
for (int i = 5; i <= 10; i++) {
|
||||
std::string k = Key(i);
|
||||
std::string v = k + "put";
|
||||
ASSERT_OK(Put(k, v));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_EQ(FilesPerLevel(), "4,0,0,5");
|
||||
|
||||
// File 7 overlaps with file 4 (L3)
|
||||
file_to_keys.push_back({650, 651, 652});
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, file_to_keys.back(),
|
||||
file_to_keys.size() - 1));
|
||||
ASSERT_EQ(FilesPerLevel(), "5,0,0,5");
|
||||
|
||||
for (size_t file_id = 0; file_id < file_to_keys.size(); file_id++) {
|
||||
for (auto& key_id : file_to_keys[file_id]) {
|
||||
std::string k = Key(key_id);
|
||||
std::string v = k + ToString(file_id);
|
||||
if (key_id >= 5 && key_id <= 10) {
|
||||
v = k + "put";
|
||||
}
|
||||
|
||||
ASSERT_EQ(Get(k), v);
|
||||
}
|
||||
}
|
||||
|
||||
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
// 1 Create some SST files by inserting K-V pairs into DB
|
||||
|
||||
+85
-132
@@ -1189,6 +1189,12 @@ bool MinLevelToCompress(CompressionType& type, Options& options, int wbits,
|
||||
} else if (LZ4_Supported()) {
|
||||
type = kLZ4Compression;
|
||||
fprintf(stderr, "using lz4\n");
|
||||
} else if (XPRESS_Supported()) {
|
||||
type = kXpressCompression;
|
||||
fprintf(stderr, "using xpress\n");
|
||||
} else if (ZSTD_Supported()) {
|
||||
type = kZSTDNotFinalCompression;
|
||||
fprintf(stderr, "using ZSTD\n");
|
||||
} else {
|
||||
fprintf(stderr, "skipping test, compression disabled\n");
|
||||
return false;
|
||||
@@ -2067,8 +2073,8 @@ TEST_F(DBTest, SnapshotFiles) {
|
||||
dbfull()->DisableFileDeletions();
|
||||
dbfull()->GetLiveFiles(files, &manifest_size);
|
||||
|
||||
// CURRENT, MANIFEST, *.sst files (one for each CF)
|
||||
ASSERT_EQ(files.size(), 4U);
|
||||
// CURRENT, MANIFEST, OPTIONS, *.sst files (one for each CF)
|
||||
ASSERT_EQ(files.size(), 5U);
|
||||
|
||||
uint64_t number = 0;
|
||||
FileType type;
|
||||
@@ -2285,130 +2291,6 @@ TEST_F(DBTest, FlushOneColumnFamily) {
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
TEST_F(DBTest, SharedWriteBuffer) {
|
||||
Options options = CurrentOptions();
|
||||
options.db_write_buffer_size = 100000; // this is the real limit
|
||||
options.write_buffer_size = 500000; // this is never hit
|
||||
CreateAndReopenWithCF({"pikachu", "dobrynia", "nikitich"}, options);
|
||||
|
||||
// Trigger a flush on CF "nikitich"
|
||||
ASSERT_OK(Put(0, Key(1), DummyString(1)));
|
||||
ASSERT_OK(Put(1, Key(1), DummyString(1)));
|
||||
ASSERT_OK(Put(3, Key(1), DummyString(90000)));
|
||||
ASSERT_OK(Put(2, Key(2), DummyString(20000)));
|
||||
ASSERT_OK(Put(2, Key(1), DummyString(1)));
|
||||
dbfull()->TEST_WaitForFlushMemTable(handles_[0]);
|
||||
dbfull()->TEST_WaitForFlushMemTable(handles_[1]);
|
||||
dbfull()->TEST_WaitForFlushMemTable(handles_[2]);
|
||||
dbfull()->TEST_WaitForFlushMemTable(handles_[3]);
|
||||
{
|
||||
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "default"),
|
||||
static_cast<uint64_t>(0));
|
||||
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "pikachu"),
|
||||
static_cast<uint64_t>(0));
|
||||
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "dobrynia"),
|
||||
static_cast<uint64_t>(0));
|
||||
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "nikitich"),
|
||||
static_cast<uint64_t>(1));
|
||||
}
|
||||
|
||||
// "dobrynia": 20KB
|
||||
// Flush 'dobrynia'
|
||||
ASSERT_OK(Put(3, Key(2), DummyString(40000)));
|
||||
ASSERT_OK(Put(2, Key(2), DummyString(70000)));
|
||||
ASSERT_OK(Put(0, Key(1), DummyString(1)));
|
||||
dbfull()->TEST_WaitForFlushMemTable(handles_[1]);
|
||||
dbfull()->TEST_WaitForFlushMemTable(handles_[2]);
|
||||
dbfull()->TEST_WaitForFlushMemTable(handles_[3]);
|
||||
{
|
||||
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "default"),
|
||||
static_cast<uint64_t>(0));
|
||||
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "pikachu"),
|
||||
static_cast<uint64_t>(0));
|
||||
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "dobrynia"),
|
||||
static_cast<uint64_t>(1));
|
||||
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "nikitich"),
|
||||
static_cast<uint64_t>(1));
|
||||
}
|
||||
|
||||
// "nikitich" still has data of 80KB
|
||||
// Inserting Data in "dobrynia" triggers "nikitich" flushing.
|
||||
ASSERT_OK(Put(3, Key(2), DummyString(40000)));
|
||||
ASSERT_OK(Put(2, Key(2), DummyString(40000)));
|
||||
ASSERT_OK(Put(0, Key(1), DummyString(1)));
|
||||
dbfull()->TEST_WaitForFlushMemTable(handles_[1]);
|
||||
dbfull()->TEST_WaitForFlushMemTable(handles_[2]);
|
||||
dbfull()->TEST_WaitForFlushMemTable(handles_[3]);
|
||||
{
|
||||
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "default"),
|
||||
static_cast<uint64_t>(0));
|
||||
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "pikachu"),
|
||||
static_cast<uint64_t>(0));
|
||||
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "dobrynia"),
|
||||
static_cast<uint64_t>(1));
|
||||
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "nikitich"),
|
||||
static_cast<uint64_t>(2));
|
||||
}
|
||||
|
||||
// "dobrynia" still has 40KB
|
||||
ASSERT_OK(Put(1, Key(2), DummyString(20000)));
|
||||
ASSERT_OK(Put(0, Key(1), DummyString(10000)));
|
||||
ASSERT_OK(Put(0, Key(1), DummyString(1)));
|
||||
dbfull()->TEST_WaitForFlushMemTable(handles_[0]);
|
||||
dbfull()->TEST_WaitForFlushMemTable(handles_[1]);
|
||||
dbfull()->TEST_WaitForFlushMemTable(handles_[2]);
|
||||
dbfull()->TEST_WaitForFlushMemTable(handles_[3]);
|
||||
// This should triggers no flush
|
||||
{
|
||||
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "default"),
|
||||
static_cast<uint64_t>(0));
|
||||
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "pikachu"),
|
||||
static_cast<uint64_t>(0));
|
||||
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "dobrynia"),
|
||||
static_cast<uint64_t>(1));
|
||||
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "nikitich"),
|
||||
static_cast<uint64_t>(2));
|
||||
}
|
||||
|
||||
// "default": 10KB, "pikachu": 20KB, "dobrynia": 40KB
|
||||
ASSERT_OK(Put(1, Key(2), DummyString(40000)));
|
||||
ASSERT_OK(Put(0, Key(1), DummyString(1)));
|
||||
dbfull()->TEST_WaitForFlushMemTable(handles_[0]);
|
||||
dbfull()->TEST_WaitForFlushMemTable(handles_[1]);
|
||||
dbfull()->TEST_WaitForFlushMemTable(handles_[2]);
|
||||
dbfull()->TEST_WaitForFlushMemTable(handles_[3]);
|
||||
// This should triggers flush of "pikachu"
|
||||
{
|
||||
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "default"),
|
||||
static_cast<uint64_t>(0));
|
||||
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "pikachu"),
|
||||
static_cast<uint64_t>(1));
|
||||
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "dobrynia"),
|
||||
static_cast<uint64_t>(1));
|
||||
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "nikitich"),
|
||||
static_cast<uint64_t>(2));
|
||||
}
|
||||
|
||||
// "default": 10KB, "dobrynia": 40KB
|
||||
// Some remaining writes so 'default', 'dobrynia' and 'nikitich' flush on
|
||||
// closure.
|
||||
ASSERT_OK(Put(3, Key(1), DummyString(1)));
|
||||
ReopenWithColumnFamilies({"default", "pikachu", "dobrynia", "nikitich"},
|
||||
options);
|
||||
{
|
||||
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "default"),
|
||||
static_cast<uint64_t>(1));
|
||||
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "pikachu"),
|
||||
static_cast<uint64_t>(1));
|
||||
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "dobrynia"),
|
||||
static_cast<uint64_t>(2));
|
||||
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "nikitich"),
|
||||
static_cast<uint64_t>(3));
|
||||
}
|
||||
}
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
TEST_F(DBTest, PurgeInfoLogs) {
|
||||
Options options = CurrentOptions();
|
||||
options.keep_log_file_num = 5;
|
||||
@@ -2766,12 +2648,12 @@ class ModelDB : public DB {
|
||||
#ifndef ROCKSDB_LITE
|
||||
using DB::AddFile;
|
||||
virtual Status AddFile(ColumnFamilyHandle* column_family,
|
||||
const ExternalSstFileInfo* file_path,
|
||||
const std::vector<ExternalSstFileInfo>& file_info_list,
|
||||
bool move_file) override {
|
||||
return Status::NotSupported("Not implemented.");
|
||||
}
|
||||
virtual Status AddFile(ColumnFamilyHandle* column_family,
|
||||
const std::string& file_path,
|
||||
const std::vector<std::string>& file_path_list,
|
||||
bool move_file) override {
|
||||
return Status::NotSupported("Not implemented.");
|
||||
}
|
||||
@@ -4182,7 +4064,7 @@ TEST_F(DBTest, DynamicLevelCompressionPerLevel) {
|
||||
options.level0_file_num_compaction_trigger = 2;
|
||||
options.level0_slowdown_writes_trigger = 2;
|
||||
options.level0_stop_writes_trigger = 2;
|
||||
options.target_file_size_base = 2048;
|
||||
options.target_file_size_base = 20480;
|
||||
options.level_compaction_dynamic_level_bytes = true;
|
||||
options.max_bytes_for_level_base = 102400;
|
||||
options.max_bytes_for_level_multiplier = 4;
|
||||
@@ -4210,7 +4092,8 @@ TEST_F(DBTest, DynamicLevelCompressionPerLevel) {
|
||||
ASSERT_EQ(NumTableFilesAtLevel(1), 0);
|
||||
ASSERT_EQ(NumTableFilesAtLevel(2), 0);
|
||||
ASSERT_EQ(NumTableFilesAtLevel(3), 0);
|
||||
ASSERT_GT(SizeAtLevel(0) + SizeAtLevel(4), 20U * 4000U);
|
||||
// Assuming each files' metadata is at least 50 bytes/
|
||||
ASSERT_GT(SizeAtLevel(0) + SizeAtLevel(4), 20U * 4000U + 50U * 4);
|
||||
|
||||
// Insert 400KB. Some data will be compressed
|
||||
for (int i = 21; i < 120; i++) {
|
||||
@@ -4220,7 +4103,8 @@ TEST_F(DBTest, DynamicLevelCompressionPerLevel) {
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
ASSERT_EQ(NumTableFilesAtLevel(1), 0);
|
||||
ASSERT_EQ(NumTableFilesAtLevel(2), 0);
|
||||
ASSERT_LT(SizeAtLevel(0) + SizeAtLevel(3) + SizeAtLevel(4), 120U * 4000U);
|
||||
ASSERT_LT(SizeAtLevel(0) + SizeAtLevel(3) + SizeAtLevel(4),
|
||||
120U * 4000U + 50U * 24);
|
||||
// Make sure data in files in L3 is not compacted by removing all files
|
||||
// in L4 and calculate number of rows
|
||||
ASSERT_OK(dbfull()->SetOptions({
|
||||
@@ -4240,7 +4124,7 @@ TEST_F(DBTest, DynamicLevelCompressionPerLevel) {
|
||||
num_keys++;
|
||||
}
|
||||
ASSERT_OK(iter->status());
|
||||
ASSERT_GT(SizeAtLevel(0) + SizeAtLevel(3), num_keys * 4000U);
|
||||
ASSERT_GT(SizeAtLevel(0) + SizeAtLevel(3), num_keys * 4000U + num_keys * 10U);
|
||||
}
|
||||
|
||||
TEST_F(DBTest, DynamicLevelCompressionPerLevel2) {
|
||||
@@ -4807,6 +4691,75 @@ TEST_F(DBTest, EncodeDecompressedBlockSizeTest) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBTest, CompressionStatsTest) {
|
||||
CompressionType type;
|
||||
|
||||
if (Snappy_Supported()) {
|
||||
type = kSnappyCompression;
|
||||
fprintf(stderr, "using snappy\n");
|
||||
} else if (Zlib_Supported()) {
|
||||
type = kZlibCompression;
|
||||
fprintf(stderr, "using zlib\n");
|
||||
} else if (BZip2_Supported()) {
|
||||
type = kBZip2Compression;
|
||||
fprintf(stderr, "using bzip2\n");
|
||||
} else if (LZ4_Supported()) {
|
||||
type = kLZ4Compression;
|
||||
fprintf(stderr, "using lz4\n");
|
||||
} else if (XPRESS_Supported()) {
|
||||
type = kXpressCompression;
|
||||
fprintf(stderr, "using xpress\n");
|
||||
} else if (ZSTD_Supported()) {
|
||||
type = kZSTDNotFinalCompression;
|
||||
fprintf(stderr, "using ZSTD\n");
|
||||
} else {
|
||||
fprintf(stderr, "skipping test, compression disabled\n");
|
||||
return;
|
||||
}
|
||||
|
||||
Options options = CurrentOptions();
|
||||
options.compression = type;
|
||||
options.statistics = rocksdb::CreateDBStatistics();
|
||||
options.statistics->stats_level_ = StatsLevel::kAll;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
int kNumKeysWritten = 100000;
|
||||
|
||||
// Check that compressions occur and are counted when compression is turned on
|
||||
Random rnd(301);
|
||||
for (int i = 0; i < kNumKeysWritten; ++i) {
|
||||
// compressible string
|
||||
ASSERT_OK(Put(Key(i), RandomString(&rnd, 128) + std::string(128, 'a')));
|
||||
}
|
||||
ASSERT_GT(options.statistics->getTickerCount(NUMBER_BLOCK_COMPRESSED), 0);
|
||||
|
||||
for (int i = 0; i < kNumKeysWritten; ++i) {
|
||||
auto r = Get(Key(i));
|
||||
}
|
||||
ASSERT_GT(options.statistics->getTickerCount(NUMBER_BLOCK_DECOMPRESSED), 0);
|
||||
|
||||
options.compression = kNoCompression;
|
||||
DestroyAndReopen(options);
|
||||
uint64_t currentCompressions =
|
||||
options.statistics->getTickerCount(NUMBER_BLOCK_COMPRESSED);
|
||||
uint64_t currentDecompressions =
|
||||
options.statistics->getTickerCount(NUMBER_BLOCK_DECOMPRESSED);
|
||||
|
||||
// Check that compressions do not occur when turned off
|
||||
for (int i = 0; i < kNumKeysWritten; ++i) {
|
||||
// compressible string
|
||||
ASSERT_OK(Put(Key(i), RandomString(&rnd, 128) + std::string(128, 'a')));
|
||||
}
|
||||
ASSERT_EQ(options.statistics->getTickerCount(NUMBER_BLOCK_COMPRESSED)
|
||||
- currentCompressions, 0);
|
||||
|
||||
for (int i = 0; i < kNumKeysWritten; ++i) {
|
||||
auto r = Get(Key(i));
|
||||
}
|
||||
ASSERT_EQ(options.statistics->getTickerCount(NUMBER_BLOCK_DECOMPRESSED)
|
||||
- currentDecompressions, 0);
|
||||
}
|
||||
|
||||
TEST_F(DBTest, MutexWaitStatsDisabledByDefault) {
|
||||
Options options = CurrentOptions();
|
||||
options.create_if_missing = true;
|
||||
|
||||
+284
@@ -6,7 +6,9 @@
|
||||
// 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 <atomic>
|
||||
#include <cstdlib>
|
||||
#include <functional>
|
||||
|
||||
#include "db/db_test_util.h"
|
||||
#include "port/stack_trace.h"
|
||||
@@ -144,6 +146,225 @@ TEST_F(DBTest2, CacheIndexAndFilterWithDBRestart) {
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
class DBTestSharedWriteBufferAcrossCFs
|
||||
: public DBTestBase,
|
||||
public testing::WithParamInterface<bool> {
|
||||
public:
|
||||
DBTestSharedWriteBufferAcrossCFs()
|
||||
: DBTestBase("/db_test_shared_write_buffer") {}
|
||||
void SetUp() override { use_old_interface_ = GetParam(); }
|
||||
bool use_old_interface_;
|
||||
};
|
||||
|
||||
TEST_P(DBTestSharedWriteBufferAcrossCFs, SharedWriteBufferAcrossCFs) {
|
||||
Options options = CurrentOptions();
|
||||
if (use_old_interface_) {
|
||||
options.db_write_buffer_size = 100000; // this is the real limit
|
||||
} else {
|
||||
options.write_buffer_manager.reset(new WriteBufferManager(100000));
|
||||
}
|
||||
options.write_buffer_size = 500000; // this is never hit
|
||||
CreateAndReopenWithCF({"pikachu", "dobrynia", "nikitich"}, options);
|
||||
|
||||
// Trigger a flush on CF "nikitich"
|
||||
ASSERT_OK(Put(0, Key(1), DummyString(1)));
|
||||
ASSERT_OK(Put(1, Key(1), DummyString(1)));
|
||||
ASSERT_OK(Put(3, Key(1), DummyString(90000)));
|
||||
ASSERT_OK(Put(2, Key(2), DummyString(20000)));
|
||||
ASSERT_OK(Put(2, Key(1), DummyString(1)));
|
||||
dbfull()->TEST_WaitForFlushMemTable(handles_[0]);
|
||||
dbfull()->TEST_WaitForFlushMemTable(handles_[1]);
|
||||
dbfull()->TEST_WaitForFlushMemTable(handles_[2]);
|
||||
dbfull()->TEST_WaitForFlushMemTable(handles_[3]);
|
||||
{
|
||||
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "default"),
|
||||
static_cast<uint64_t>(0));
|
||||
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "pikachu"),
|
||||
static_cast<uint64_t>(0));
|
||||
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "dobrynia"),
|
||||
static_cast<uint64_t>(0));
|
||||
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "nikitich"),
|
||||
static_cast<uint64_t>(1));
|
||||
}
|
||||
|
||||
// "dobrynia": 20KB
|
||||
// Flush 'dobrynia'
|
||||
ASSERT_OK(Put(3, Key(2), DummyString(40000)));
|
||||
ASSERT_OK(Put(2, Key(2), DummyString(70000)));
|
||||
ASSERT_OK(Put(0, Key(1), DummyString(1)));
|
||||
dbfull()->TEST_WaitForFlushMemTable(handles_[1]);
|
||||
dbfull()->TEST_WaitForFlushMemTable(handles_[2]);
|
||||
dbfull()->TEST_WaitForFlushMemTable(handles_[3]);
|
||||
{
|
||||
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "default"),
|
||||
static_cast<uint64_t>(0));
|
||||
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "pikachu"),
|
||||
static_cast<uint64_t>(0));
|
||||
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "dobrynia"),
|
||||
static_cast<uint64_t>(1));
|
||||
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "nikitich"),
|
||||
static_cast<uint64_t>(1));
|
||||
}
|
||||
|
||||
// "nikitich" still has data of 80KB
|
||||
// Inserting Data in "dobrynia" triggers "nikitich" flushing.
|
||||
ASSERT_OK(Put(3, Key(2), DummyString(40000)));
|
||||
ASSERT_OK(Put(2, Key(2), DummyString(40000)));
|
||||
ASSERT_OK(Put(0, Key(1), DummyString(1)));
|
||||
dbfull()->TEST_WaitForFlushMemTable(handles_[1]);
|
||||
dbfull()->TEST_WaitForFlushMemTable(handles_[2]);
|
||||
dbfull()->TEST_WaitForFlushMemTable(handles_[3]);
|
||||
{
|
||||
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "default"),
|
||||
static_cast<uint64_t>(0));
|
||||
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "pikachu"),
|
||||
static_cast<uint64_t>(0));
|
||||
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "dobrynia"),
|
||||
static_cast<uint64_t>(1));
|
||||
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "nikitich"),
|
||||
static_cast<uint64_t>(2));
|
||||
}
|
||||
|
||||
// "dobrynia" still has 40KB
|
||||
ASSERT_OK(Put(1, Key(2), DummyString(20000)));
|
||||
ASSERT_OK(Put(0, Key(1), DummyString(10000)));
|
||||
ASSERT_OK(Put(0, Key(1), DummyString(1)));
|
||||
dbfull()->TEST_WaitForFlushMemTable(handles_[0]);
|
||||
dbfull()->TEST_WaitForFlushMemTable(handles_[1]);
|
||||
dbfull()->TEST_WaitForFlushMemTable(handles_[2]);
|
||||
dbfull()->TEST_WaitForFlushMemTable(handles_[3]);
|
||||
// This should triggers no flush
|
||||
{
|
||||
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "default"),
|
||||
static_cast<uint64_t>(0));
|
||||
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "pikachu"),
|
||||
static_cast<uint64_t>(0));
|
||||
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "dobrynia"),
|
||||
static_cast<uint64_t>(1));
|
||||
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "nikitich"),
|
||||
static_cast<uint64_t>(2));
|
||||
}
|
||||
|
||||
// "default": 10KB, "pikachu": 20KB, "dobrynia": 40KB
|
||||
ASSERT_OK(Put(1, Key(2), DummyString(40000)));
|
||||
ASSERT_OK(Put(0, Key(1), DummyString(1)));
|
||||
dbfull()->TEST_WaitForFlushMemTable(handles_[0]);
|
||||
dbfull()->TEST_WaitForFlushMemTable(handles_[1]);
|
||||
dbfull()->TEST_WaitForFlushMemTable(handles_[2]);
|
||||
dbfull()->TEST_WaitForFlushMemTable(handles_[3]);
|
||||
// This should triggers flush of "pikachu"
|
||||
{
|
||||
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "default"),
|
||||
static_cast<uint64_t>(0));
|
||||
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "pikachu"),
|
||||
static_cast<uint64_t>(1));
|
||||
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "dobrynia"),
|
||||
static_cast<uint64_t>(1));
|
||||
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "nikitich"),
|
||||
static_cast<uint64_t>(2));
|
||||
}
|
||||
|
||||
// "default": 10KB, "dobrynia": 40KB
|
||||
// Some remaining writes so 'default', 'dobrynia' and 'nikitich' flush on
|
||||
// closure.
|
||||
ASSERT_OK(Put(3, Key(1), DummyString(1)));
|
||||
ReopenWithColumnFamilies({"default", "pikachu", "dobrynia", "nikitich"},
|
||||
options);
|
||||
{
|
||||
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "default"),
|
||||
static_cast<uint64_t>(1));
|
||||
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "pikachu"),
|
||||
static_cast<uint64_t>(1));
|
||||
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "dobrynia"),
|
||||
static_cast<uint64_t>(2));
|
||||
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "nikitich"),
|
||||
static_cast<uint64_t>(3));
|
||||
}
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(DBTestSharedWriteBufferAcrossCFs,
|
||||
DBTestSharedWriteBufferAcrossCFs, ::testing::Bool());
|
||||
|
||||
TEST_F(DBTest2, SharedWriteBufferLimitAcrossDB) {
|
||||
std::string dbname2 = test::TmpDir(env_) + "/db_shared_wb_db2";
|
||||
Options options = CurrentOptions();
|
||||
options.write_buffer_size = 500000; // this is never hit
|
||||
options.write_buffer_manager.reset(new WriteBufferManager(100000));
|
||||
CreateAndReopenWithCF({"cf1", "cf2"}, options);
|
||||
|
||||
ASSERT_OK(DestroyDB(dbname2, options));
|
||||
DB* db2 = nullptr;
|
||||
ASSERT_OK(DB::Open(options, dbname2, &db2));
|
||||
|
||||
WriteOptions wo;
|
||||
|
||||
// Trigger a flush on cf2
|
||||
ASSERT_OK(Put(0, Key(1), DummyString(1)));
|
||||
ASSERT_OK(Put(1, Key(1), DummyString(1)));
|
||||
ASSERT_OK(Put(2, Key(1), DummyString(90000)));
|
||||
|
||||
// Insert to DB2
|
||||
ASSERT_OK(db2->Put(wo, Key(2), DummyString(20000)));
|
||||
|
||||
ASSERT_OK(Put(2, Key(1), DummyString(1)));
|
||||
dbfull()->TEST_WaitForFlushMemTable(handles_[0]);
|
||||
dbfull()->TEST_WaitForFlushMemTable(handles_[1]);
|
||||
dbfull()->TEST_WaitForFlushMemTable(handles_[2]);
|
||||
static_cast<DBImpl*>(db2)->TEST_WaitForFlushMemTable();
|
||||
{
|
||||
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "default"),
|
||||
static_cast<uint64_t>(0));
|
||||
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "cf1"),
|
||||
static_cast<uint64_t>(0));
|
||||
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "cf2"),
|
||||
static_cast<uint64_t>(1));
|
||||
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db2, "default"),
|
||||
static_cast<uint64_t>(0));
|
||||
}
|
||||
|
||||
// db2: 20KB
|
||||
ASSERT_OK(db2->Put(wo, Key(2), DummyString(40000)));
|
||||
ASSERT_OK(db2->Put(wo, Key(3), DummyString(70000)));
|
||||
ASSERT_OK(db2->Put(wo, Key(1), DummyString(1)));
|
||||
dbfull()->TEST_WaitForFlushMemTable(handles_[0]);
|
||||
dbfull()->TEST_WaitForFlushMemTable(handles_[1]);
|
||||
dbfull()->TEST_WaitForFlushMemTable(handles_[2]);
|
||||
static_cast<DBImpl*>(db2)->TEST_WaitForFlushMemTable();
|
||||
{
|
||||
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "default"),
|
||||
static_cast<uint64_t>(0));
|
||||
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "cf1"),
|
||||
static_cast<uint64_t>(0));
|
||||
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "cf2"),
|
||||
static_cast<uint64_t>(1));
|
||||
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db2, "default"),
|
||||
static_cast<uint64_t>(1));
|
||||
}
|
||||
|
||||
//
|
||||
// Inserting Data in db2 and db_ triggers flushing in db_.
|
||||
ASSERT_OK(db2->Put(wo, Key(3), DummyString(70000)));
|
||||
ASSERT_OK(Put(2, Key(2), DummyString(45000)));
|
||||
ASSERT_OK(Put(0, Key(1), DummyString(1)));
|
||||
dbfull()->TEST_WaitForFlushMemTable(handles_[0]);
|
||||
dbfull()->TEST_WaitForFlushMemTable(handles_[1]);
|
||||
dbfull()->TEST_WaitForFlushMemTable(handles_[2]);
|
||||
static_cast<DBImpl*>(db2)->TEST_WaitForFlushMemTable();
|
||||
{
|
||||
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "default"),
|
||||
static_cast<uint64_t>(0));
|
||||
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "cf1"),
|
||||
static_cast<uint64_t>(0));
|
||||
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "cf2"),
|
||||
static_cast<uint64_t>(2));
|
||||
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db2, "default"),
|
||||
static_cast<uint64_t>(1));
|
||||
}
|
||||
|
||||
delete db2;
|
||||
ASSERT_OK(DestroyDB(dbname2, options));
|
||||
}
|
||||
|
||||
namespace {
|
||||
void ValidateKeyExistence(DB* db, const std::vector<Slice>& keys_must_exist,
|
||||
const std::vector<Slice>& keys_must_not_exist) {
|
||||
@@ -1219,7 +1440,70 @@ TEST_F(DBTest2, PersistentCache) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
void CountSyncPoint() {
|
||||
TEST_SYNC_POINT_CALLBACK("DBTest2::MarkedPoint", nullptr /* arg */);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST_F(DBTest2, SyncPointMarker) {
|
||||
std::atomic<int> sync_point_called(0);
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBTest2::MarkedPoint",
|
||||
[&](void* arg) { sync_point_called.fetch_add(1); });
|
||||
|
||||
// The first dependency enforces Marker can be loaded before MarkedPoint.
|
||||
// The second checks that thread 1's MarkedPoint should be disabled here.
|
||||
// Execution order:
|
||||
// | Thread 1 | Thread 2 |
|
||||
// | | Marker |
|
||||
// | MarkedPoint | |
|
||||
// | Thread1First | |
|
||||
// | | MarkedPoint |
|
||||
rocksdb::SyncPoint::GetInstance()->LoadDependencyAndMarkers(
|
||||
{{"DBTest2::SyncPointMarker:Thread1First", "DBTest2::MarkedPoint"}},
|
||||
{{"DBTest2::SyncPointMarker:Marker", "DBTest2::MarkedPoint"}});
|
||||
|
||||
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
std::function<void()> func1 = [&]() {
|
||||
CountSyncPoint();
|
||||
TEST_SYNC_POINT("DBTest2::SyncPointMarker:Thread1First");
|
||||
};
|
||||
|
||||
std::function<void()> func2 = [&]() {
|
||||
TEST_SYNC_POINT("DBTest2::SyncPointMarker:Marker");
|
||||
CountSyncPoint();
|
||||
};
|
||||
|
||||
auto thread1 = std::thread(func1);
|
||||
auto thread2 = std::thread(func2);
|
||||
thread1.join();
|
||||
thread2.join();
|
||||
|
||||
// Callback is only executed once
|
||||
ASSERT_EQ(sync_point_called.load(), 1);
|
||||
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST_F(DBTest2, MaxSuccessiveMergesInRecovery) {
|
||||
Options options;
|
||||
options = CurrentOptions(options);
|
||||
options.merge_operator = MergeOperators::CreatePutOperator();
|
||||
DestroyAndReopen(options);
|
||||
|
||||
db_->Put(WriteOptions(), "foo", "bar");
|
||||
ASSERT_OK(db_->Merge(WriteOptions(), "foo", "bar"));
|
||||
ASSERT_OK(db_->Merge(WriteOptions(), "foo", "bar"));
|
||||
ASSERT_OK(db_->Merge(WriteOptions(), "foo", "bar"));
|
||||
ASSERT_OK(db_->Merge(WriteOptions(), "foo", "bar"));
|
||||
ASSERT_OK(db_->Merge(WriteOptions(), "foo", "bar"));
|
||||
|
||||
options.max_successive_merges = 3;
|
||||
Reopen(options);
|
||||
}
|
||||
} // namespace rocksdb
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
+30
-8
@@ -91,10 +91,6 @@ bool DBTestBase::ShouldSkipOptions(int option_config, int skip_mask) {
|
||||
}
|
||||
#endif
|
||||
|
||||
if ((skip_mask & kSkipDeletesFilterFirst) &&
|
||||
option_config == kDeletesFilterFirst) {
|
||||
return true;
|
||||
}
|
||||
if ((skip_mask & kSkipUniversalCompaction) &&
|
||||
(option_config == kUniversalCompaction ||
|
||||
option_config == kUniversalCompactionMultiLevel)) {
|
||||
@@ -311,9 +307,6 @@ Options DBTestBase::CurrentOptions(
|
||||
options.report_bg_io_stats = true;
|
||||
// TODO(3.13) -- test more options
|
||||
break;
|
||||
case kDeletesFilterFirst:
|
||||
options.filter_deletes = true;
|
||||
break;
|
||||
case kUniversalCompaction:
|
||||
options.compaction_style = kCompactionStyleUniversal;
|
||||
options.num_levels = 1;
|
||||
@@ -449,7 +442,7 @@ void DBTestBase::Reopen(const Options& options) {
|
||||
|
||||
void DBTestBase::Close() {
|
||||
for (auto h : handles_) {
|
||||
delete h;
|
||||
db_->DestroyColumnFamilyHandle(h);
|
||||
}
|
||||
handles_.clear();
|
||||
delete db_;
|
||||
@@ -1082,6 +1075,35 @@ std::vector<std::uint64_t> DBTestBase::ListTableFiles(Env* env,
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
|
||||
Status DBTestBase::GenerateAndAddExternalFile(const Options options,
|
||||
std::vector<int> keys,
|
||||
size_t file_id) {
|
||||
std::string file_path =
|
||||
test::TmpDir(env_) + "/sst_files/" + ToString(file_id);
|
||||
SstFileWriter sst_file_writer(EnvOptions(), options, options.comparator);
|
||||
|
||||
Status s = sst_file_writer.Open(file_path);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
for (auto& entry : keys) {
|
||||
std::string k = Key(entry);
|
||||
std::string v = k + ToString(file_id);
|
||||
s = sst_file_writer.Add(k, v);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
}
|
||||
s = sst_file_writer.Finish();
|
||||
|
||||
if (s.ok()) {
|
||||
s = db_->AddFile(std::vector<std::string>(1, file_path));
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
uint64_t DBTestBase::GetNumberOfSstFilesForColumnFamily(
|
||||
DB* db, std::string column_family_name) {
|
||||
std::vector<LiveFileMetaData> metadata;
|
||||
|
||||
+16
-13
@@ -39,6 +39,7 @@
|
||||
#include "rocksdb/filter_policy.h"
|
||||
#include "rocksdb/options.h"
|
||||
#include "rocksdb/slice.h"
|
||||
#include "rocksdb/sst_file_writer.h"
|
||||
#include "rocksdb/statistics.h"
|
||||
#include "rocksdb/table.h"
|
||||
#include "rocksdb/utilities/checkpoint.h"
|
||||
@@ -550,19 +551,18 @@ class DBTestBase : public testing::Test {
|
||||
kWalDirAndMmapReads = 16,
|
||||
kManifestFileSize = 17,
|
||||
kPerfOptions = 18,
|
||||
kDeletesFilterFirst = 19,
|
||||
kHashSkipList = 20,
|
||||
kUniversalCompaction = 21,
|
||||
kUniversalCompactionMultiLevel = 22,
|
||||
kCompressedBlockCache = 23,
|
||||
kInfiniteMaxOpenFiles = 24,
|
||||
kxxHashChecksum = 25,
|
||||
kFIFOCompaction = 26,
|
||||
kOptimizeFiltersForHits = 27,
|
||||
kRowCache = 28,
|
||||
kRecycleLogFiles = 29,
|
||||
kConcurrentSkipList = 30,
|
||||
kEnd = 31,
|
||||
kHashSkipList = 19,
|
||||
kUniversalCompaction = 20,
|
||||
kUniversalCompactionMultiLevel = 21,
|
||||
kCompressedBlockCache = 22,
|
||||
kInfiniteMaxOpenFiles = 23,
|
||||
kxxHashChecksum = 24,
|
||||
kFIFOCompaction = 25,
|
||||
kOptimizeFiltersForHits = 26,
|
||||
kRowCache = 27,
|
||||
kRecycleLogFiles = 28,
|
||||
kConcurrentSkipList = 29,
|
||||
kEnd = 30,
|
||||
kLevelSubcompactions = 31,
|
||||
kUniversalSubcompactions = 32,
|
||||
kBlockBasedTableWithIndexRestartInterval = 33,
|
||||
@@ -798,6 +798,9 @@ class DBTestBase : public testing::Test {
|
||||
std::vector<std::uint64_t> ListTableFiles(Env* env, const std::string& path);
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
Status GenerateAndAddExternalFile(const Options options,
|
||||
std::vector<int> keys, size_t file_id);
|
||||
|
||||
uint64_t GetNumberOfSstFilesForColumnFamily(DB* db,
|
||||
std::string column_family_name);
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
@@ -127,7 +127,7 @@ TEST_P(DBTestUniversalCompaction, UniversalCompactionSingleSortedRun) {
|
||||
options.level0_file_num_compaction_trigger = 0;
|
||||
options.compaction_options_universal.size_ratio = 10;
|
||||
options.compaction_options_universal.min_merge_width = 2;
|
||||
options.compaction_options_universal.max_size_amplification_percent = 1;
|
||||
options.compaction_options_universal.max_size_amplification_percent = 0;
|
||||
|
||||
options.write_buffer_size = 105 << 10; // 105KB
|
||||
options.arena_block_size = 4 << 10;
|
||||
@@ -151,6 +151,9 @@ TEST_P(DBTestUniversalCompaction, UniversalCompactionSingleSortedRun) {
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
ASSERT_EQ(NumSortedRuns(0), 1);
|
||||
}
|
||||
ASSERT_OK(Put(Key(key_idx), ""));
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
ASSERT_EQ(NumSortedRuns(0), 1);
|
||||
}
|
||||
|
||||
TEST_P(DBTestUniversalCompaction, OptimizeFiltersForHits) {
|
||||
|
||||
+211
-2
@@ -9,6 +9,7 @@
|
||||
|
||||
#include "db/db_test_util.h"
|
||||
#include "port/stack_trace.h"
|
||||
#include "util/options_helper.h"
|
||||
#include "util/sync_point.h"
|
||||
|
||||
namespace rocksdb {
|
||||
@@ -156,6 +157,7 @@ TEST_F(DBWALTest, RecoverWithTableHandle) {
|
||||
Options options = CurrentOptions();
|
||||
options.create_if_missing = true;
|
||||
options.disable_auto_compactions = true;
|
||||
options.avoid_flush_during_recovery = false;
|
||||
DestroyAndReopen(options);
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
|
||||
@@ -382,6 +384,7 @@ TEST_F(DBWALTest, RecoverCheckFileAmount) {
|
||||
Options options = CurrentOptions();
|
||||
options.write_buffer_size = 100000;
|
||||
options.arena_block_size = 4 * 1024;
|
||||
options.avoid_flush_during_recovery = false;
|
||||
CreateAndReopenWithCF({"pikachu", "dobrynia", "nikitich"}, options);
|
||||
|
||||
ASSERT_OK(Put(0, Key(1), DummyString(1)));
|
||||
@@ -491,14 +494,14 @@ class RecoveryTestHelper {
|
||||
|
||||
shared_ptr<Cache> table_cache = NewLRUCache(50000, 16);
|
||||
EnvOptions env_options;
|
||||
WriteBuffer write_buffer(db_options.db_write_buffer_size);
|
||||
WriteBufferManager write_buffer_manager(db_options.db_write_buffer_size);
|
||||
|
||||
unique_ptr<VersionSet> versions;
|
||||
unique_ptr<WalManager> wal_manager;
|
||||
WriteController write_controller;
|
||||
|
||||
versions.reset(new VersionSet(test->dbname_, &db_options, env_options,
|
||||
table_cache.get(), &write_buffer,
|
||||
table_cache.get(), &write_buffer_manager,
|
||||
&write_controller));
|
||||
|
||||
wal_manager.reset(new WalManager(db_options, env_options));
|
||||
@@ -756,6 +759,212 @@ TEST_F(DBWALTest, kSkipAnyCorruptedRecords) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBWALTest, AvoidFlushDuringRecovery) {
|
||||
Options options = CurrentOptions();
|
||||
options.disable_auto_compactions = true;
|
||||
options.avoid_flush_during_recovery = false;
|
||||
|
||||
// Test with flush after recovery.
|
||||
Reopen(options);
|
||||
ASSERT_OK(Put("foo", "v1"));
|
||||
ASSERT_OK(Put("bar", "v2"));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(Put("foo", "v3"));
|
||||
ASSERT_OK(Put("bar", "v4"));
|
||||
ASSERT_EQ(1, TotalTableFiles());
|
||||
// Reopen DB. Check if WAL logs flushed.
|
||||
Reopen(options);
|
||||
ASSERT_EQ("v3", Get("foo"));
|
||||
ASSERT_EQ("v4", Get("bar"));
|
||||
ASSERT_EQ(2, TotalTableFiles());
|
||||
|
||||
// Test without flush after recovery.
|
||||
options.avoid_flush_during_recovery = true;
|
||||
DestroyAndReopen(options);
|
||||
ASSERT_OK(Put("foo", "v5"));
|
||||
ASSERT_OK(Put("bar", "v6"));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(Put("foo", "v7"));
|
||||
ASSERT_OK(Put("bar", "v8"));
|
||||
ASSERT_EQ(1, TotalTableFiles());
|
||||
// Reopen DB. WAL logs should not be flushed this time.
|
||||
Reopen(options);
|
||||
ASSERT_EQ("v7", Get("foo"));
|
||||
ASSERT_EQ("v8", Get("bar"));
|
||||
ASSERT_EQ(1, TotalTableFiles());
|
||||
|
||||
// Force flush with allow_2pc.
|
||||
options.avoid_flush_during_recovery = true;
|
||||
options.allow_2pc = true;
|
||||
ASSERT_OK(Put("foo", "v9"));
|
||||
ASSERT_OK(Put("bar", "v10"));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(Put("foo", "v11"));
|
||||
ASSERT_OK(Put("bar", "v12"));
|
||||
Reopen(options);
|
||||
ASSERT_EQ("v11", Get("foo"));
|
||||
ASSERT_EQ("v12", Get("bar"));
|
||||
ASSERT_EQ(2, TotalTableFiles());
|
||||
}
|
||||
|
||||
TEST_F(DBWALTest, RecoverWithoutFlush) {
|
||||
Options options = CurrentOptions();
|
||||
options.avoid_flush_during_recovery = true;
|
||||
options.create_if_missing = false;
|
||||
options.disable_auto_compactions = true;
|
||||
options.write_buffer_size = 64 * 1024 * 1024;
|
||||
|
||||
size_t count = RecoveryTestHelper::FillData(this, &options);
|
||||
auto validateData = [this, count]() {
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
ASSERT_NE(Get("key" + ToString(i)), "NOT_FOUND");
|
||||
}
|
||||
};
|
||||
Reopen(options);
|
||||
validateData();
|
||||
// Insert some data without flush
|
||||
ASSERT_OK(Put("foo", "foo_v1"));
|
||||
ASSERT_OK(Put("bar", "bar_v1"));
|
||||
Reopen(options);
|
||||
validateData();
|
||||
ASSERT_EQ(Get("foo"), "foo_v1");
|
||||
ASSERT_EQ(Get("bar"), "bar_v1");
|
||||
// Insert again and reopen
|
||||
ASSERT_OK(Put("foo", "foo_v2"));
|
||||
ASSERT_OK(Put("bar", "bar_v2"));
|
||||
Reopen(options);
|
||||
validateData();
|
||||
ASSERT_EQ(Get("foo"), "foo_v2");
|
||||
ASSERT_EQ(Get("bar"), "bar_v2");
|
||||
// manual flush and insert again
|
||||
Flush();
|
||||
ASSERT_EQ(Get("foo"), "foo_v2");
|
||||
ASSERT_EQ(Get("bar"), "bar_v2");
|
||||
ASSERT_OK(Put("foo", "foo_v3"));
|
||||
ASSERT_OK(Put("bar", "bar_v3"));
|
||||
Reopen(options);
|
||||
validateData();
|
||||
ASSERT_EQ(Get("foo"), "foo_v3");
|
||||
ASSERT_EQ(Get("bar"), "bar_v3");
|
||||
}
|
||||
|
||||
TEST_F(DBWALTest, RecoverWithoutFlushMultipleCF) {
|
||||
const std::string kSmallValue = "v";
|
||||
const std::string kLargeValue = DummyString(1024);
|
||||
Options options = CurrentOptions();
|
||||
options.avoid_flush_during_recovery = true;
|
||||
options.create_if_missing = false;
|
||||
options.disable_auto_compactions = true;
|
||||
|
||||
auto countWalFiles = [this]() {
|
||||
VectorLogPtr log_files;
|
||||
dbfull()->GetSortedWalFiles(log_files);
|
||||
return log_files.size();
|
||||
};
|
||||
|
||||
// Create DB with multiple column families and multiple log files.
|
||||
CreateAndReopenWithCF({"one", "two"}, options);
|
||||
ASSERT_OK(Put(0, "key1", kSmallValue));
|
||||
ASSERT_OK(Put(1, "key2", kLargeValue));
|
||||
Flush(1);
|
||||
ASSERT_EQ(1, countWalFiles());
|
||||
ASSERT_OK(Put(0, "key3", kSmallValue));
|
||||
ASSERT_OK(Put(2, "key4", kLargeValue));
|
||||
Flush(2);
|
||||
ASSERT_EQ(2, countWalFiles());
|
||||
|
||||
// Reopen, insert and flush.
|
||||
options.db_write_buffer_size = 64 * 1024 * 1024;
|
||||
ReopenWithColumnFamilies({"default", "one", "two"}, options);
|
||||
ASSERT_EQ(Get(0, "key1"), kSmallValue);
|
||||
ASSERT_EQ(Get(1, "key2"), kLargeValue);
|
||||
ASSERT_EQ(Get(0, "key3"), kSmallValue);
|
||||
ASSERT_EQ(Get(2, "key4"), kLargeValue);
|
||||
// Insert more data.
|
||||
ASSERT_OK(Put(0, "key5", kLargeValue));
|
||||
ASSERT_OK(Put(1, "key6", kLargeValue));
|
||||
ASSERT_EQ(3, countWalFiles());
|
||||
Flush(1);
|
||||
ASSERT_OK(Put(2, "key7", kLargeValue));
|
||||
ASSERT_EQ(4, countWalFiles());
|
||||
|
||||
// Reopen twice and validate.
|
||||
for (int i = 0; i < 2; i++) {
|
||||
ReopenWithColumnFamilies({"default", "one", "two"}, options);
|
||||
ASSERT_EQ(Get(0, "key1"), kSmallValue);
|
||||
ASSERT_EQ(Get(1, "key2"), kLargeValue);
|
||||
ASSERT_EQ(Get(0, "key3"), kSmallValue);
|
||||
ASSERT_EQ(Get(2, "key4"), kLargeValue);
|
||||
ASSERT_EQ(Get(0, "key5"), kLargeValue);
|
||||
ASSERT_EQ(Get(1, "key6"), kLargeValue);
|
||||
ASSERT_EQ(Get(2, "key7"), kLargeValue);
|
||||
ASSERT_EQ(4, countWalFiles());
|
||||
}
|
||||
}
|
||||
|
||||
// In this test we are trying to do the following:
|
||||
// 1. Create a DB with corrupted WAL log;
|
||||
// 2. Open with avoid_flush_during_recovery = true;
|
||||
// 3. Append more data without flushing, which creates new WAL log.
|
||||
// 4. Open again. See if it can correctly handle previous corruption.
|
||||
TEST_F(DBWALTest, RecoverFromCorruptedWALWithoutFlush) {
|
||||
const int jstart = RecoveryTestHelper::kWALFileOffset;
|
||||
const int jend = jstart + RecoveryTestHelper::kWALFilesCount;
|
||||
const int kAppendKeys = 100;
|
||||
Options options = CurrentOptions();
|
||||
options.avoid_flush_during_recovery = true;
|
||||
options.create_if_missing = false;
|
||||
options.disable_auto_compactions = true;
|
||||
options.write_buffer_size = 64 * 1024 * 1024;
|
||||
|
||||
auto getAll = [this]() {
|
||||
std::vector<std::pair<std::string, std::string>> data;
|
||||
ReadOptions ropt;
|
||||
Iterator* iter = dbfull()->NewIterator(ropt);
|
||||
for (iter->SeekToFirst(); iter->Valid(); iter->Next()) {
|
||||
data.push_back(
|
||||
std::make_pair(iter->key().ToString(), iter->value().ToString()));
|
||||
}
|
||||
delete iter;
|
||||
return data;
|
||||
};
|
||||
for (auto& mode : wal_recovery_mode_string_map) {
|
||||
options.wal_recovery_mode = mode.second;
|
||||
for (auto trunc : {true, false}) {
|
||||
for (int i = 0; i < 4; i++) {
|
||||
for (int j = jstart; j < jend; j++) {
|
||||
// Create corrupted WAL
|
||||
RecoveryTestHelper::FillData(this, &options);
|
||||
RecoveryTestHelper::CorruptWAL(this, options, /*off=*/i * .3,
|
||||
/*len%=*/.1, /*wal=*/j, trunc);
|
||||
// Skip the test if DB won't open.
|
||||
if (!TryReopen(options).ok()) {
|
||||
ASSERT_TRUE(options.wal_recovery_mode ==
|
||||
WALRecoveryMode::kAbsoluteConsistency ||
|
||||
(!trunc &&
|
||||
options.wal_recovery_mode ==
|
||||
WALRecoveryMode::kTolerateCorruptedTailRecords));
|
||||
continue;
|
||||
}
|
||||
ASSERT_OK(TryReopen(options));
|
||||
// Append some more data.
|
||||
for (int k = 0; k < kAppendKeys; k++) {
|
||||
std::string key = "extra_key" + ToString(k);
|
||||
std::string value = DummyString(RecoveryTestHelper::kValueSize);
|
||||
ASSERT_OK(Put(key, value));
|
||||
}
|
||||
// Save data for comparison.
|
||||
auto data = getAll();
|
||||
// Reopen. Verify data.
|
||||
ASSERT_OK(TryReopen(options));
|
||||
auto actual_data = getAll();
|
||||
ASSERT_EQ(data, actual_data);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
@@ -179,6 +179,10 @@ class InternalKey {
|
||||
Slice user_key() const { return ExtractUserKey(rep_); }
|
||||
size_t size() { return rep_.size(); }
|
||||
|
||||
void Set(const Slice& _user_key, SequenceNumber s, ValueType t) {
|
||||
SetFrom(ParsedInternalKey(_user_key, s, t));
|
||||
}
|
||||
|
||||
void SetFrom(const ParsedInternalKey& p) {
|
||||
rep_.clear();
|
||||
AppendInternalKey(&rep_, p);
|
||||
|
||||
+130
-8
@@ -9,20 +9,21 @@
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
|
||||
#include "rocksdb/db.h"
|
||||
#include <stdlib.h>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include "db/db_impl.h"
|
||||
#include "db/filename.h"
|
||||
#include "db/version_set.h"
|
||||
#include "db/write_batch_internal.h"
|
||||
#include "util/string_util.h"
|
||||
#include "util/testharness.h"
|
||||
#include "util/testutil.h"
|
||||
#include "rocksdb/db.h"
|
||||
#include "rocksdb/env.h"
|
||||
#include "rocksdb/transaction_log.h"
|
||||
#include <vector>
|
||||
#include <stdlib.h>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include "util/string_util.h"
|
||||
#include "util/sync_point.h"
|
||||
#include "util/testharness.h"
|
||||
#include "util/testutil.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
@@ -152,6 +153,15 @@ class DeleteFileTest : public testing::Test {
|
||||
ASSERT_EQ(required_manifest, manifest_cnt);
|
||||
}
|
||||
|
||||
static void DoSleep(void* arg) {
|
||||
auto test = reinterpret_cast<DeleteFileTest*>(arg);
|
||||
test->env_->SleepForMicroseconds(2 * 1000 * 1000);
|
||||
}
|
||||
|
||||
// An empty job to guard all jobs are processed
|
||||
static void GuardFinish(void* arg) {
|
||||
TEST_SYNC_POINT("DeleteFileTest::GuardFinish");
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(DeleteFileTest, AddKeysAndQueryLevels) {
|
||||
@@ -231,6 +241,118 @@ TEST_F(DeleteFileTest, PurgeObsoleteFilesTest) {
|
||||
CloseDB();
|
||||
}
|
||||
|
||||
TEST_F(DeleteFileTest, BackgroundPurgeTest) {
|
||||
std::string first("0"), last("999999");
|
||||
CompactRangeOptions compact_options;
|
||||
compact_options.change_level = true;
|
||||
compact_options.target_level = 2;
|
||||
Slice first_slice(first), last_slice(last);
|
||||
|
||||
// We keep an iterator alive
|
||||
Iterator* itr = 0;
|
||||
CreateTwoLevels();
|
||||
ReadOptions options;
|
||||
options.background_purge_on_iterator_cleanup = true;
|
||||
itr = db_->NewIterator(options);
|
||||
db_->CompactRange(compact_options, &first_slice, &last_slice);
|
||||
// 3 sst after compaction with live iterator
|
||||
CheckFileTypeCounts(dbname_, 0, 3, 1);
|
||||
test::SleepingBackgroundTask sleeping_task_before;
|
||||
env_->Schedule(&test::SleepingBackgroundTask::DoSleepTask,
|
||||
&sleeping_task_before, Env::Priority::HIGH);
|
||||
delete itr;
|
||||
test::SleepingBackgroundTask sleeping_task_after;
|
||||
env_->Schedule(&test::SleepingBackgroundTask::DoSleepTask,
|
||||
&sleeping_task_after, Env::Priority::HIGH);
|
||||
|
||||
// Make sure no purges are executed foreground
|
||||
CheckFileTypeCounts(dbname_, 0, 3, 1);
|
||||
sleeping_task_before.WakeUp();
|
||||
sleeping_task_before.WaitUntilDone();
|
||||
|
||||
// Make sure all background purges are executed
|
||||
sleeping_task_after.WakeUp();
|
||||
sleeping_task_after.WaitUntilDone();
|
||||
// 1 sst after iterator deletion
|
||||
CheckFileTypeCounts(dbname_, 0, 1, 1);
|
||||
|
||||
CloseDB();
|
||||
}
|
||||
|
||||
// This test is to reproduce a bug that read invalid ReadOption in iterator
|
||||
// cleanup function
|
||||
TEST_F(DeleteFileTest, BackgroundPurgeCopyOptions) {
|
||||
std::string first("0"), last("999999");
|
||||
CompactRangeOptions compact_options;
|
||||
compact_options.change_level = true;
|
||||
compact_options.target_level = 2;
|
||||
Slice first_slice(first), last_slice(last);
|
||||
|
||||
// We keep an iterator alive
|
||||
Iterator* itr = 0;
|
||||
CreateTwoLevels();
|
||||
ReadOptions* options = new ReadOptions();
|
||||
options->background_purge_on_iterator_cleanup = true;
|
||||
itr = db_->NewIterator(*options);
|
||||
// ReadOptions is deleted, but iterator cleanup function should not be
|
||||
// affected
|
||||
delete options;
|
||||
|
||||
db_->CompactRange(compact_options, &first_slice, &last_slice);
|
||||
// 3 sst after compaction with live iterator
|
||||
CheckFileTypeCounts(dbname_, 0, 3, 1);
|
||||
delete itr;
|
||||
|
||||
test::SleepingBackgroundTask sleeping_task_after;
|
||||
env_->Schedule(&test::SleepingBackgroundTask::DoSleepTask,
|
||||
&sleeping_task_after, Env::Priority::HIGH);
|
||||
|
||||
// Make sure all background purges are executed
|
||||
sleeping_task_after.WakeUp();
|
||||
sleeping_task_after.WaitUntilDone();
|
||||
// 1 sst after iterator deletion
|
||||
CheckFileTypeCounts(dbname_, 0, 1, 1);
|
||||
|
||||
CloseDB();
|
||||
}
|
||||
|
||||
TEST_F(DeleteFileTest, BackgroundPurgeTestMultipleJobs) {
|
||||
std::string first("0"), last("999999");
|
||||
CompactRangeOptions compact_options;
|
||||
compact_options.change_level = true;
|
||||
compact_options.target_level = 2;
|
||||
Slice first_slice(first), last_slice(last);
|
||||
|
||||
// We keep an iterator alive
|
||||
CreateTwoLevels();
|
||||
ReadOptions options;
|
||||
options.background_purge_on_iterator_cleanup = true;
|
||||
Iterator* itr1 = db_->NewIterator(options);
|
||||
CreateTwoLevels();
|
||||
Iterator* itr2 = db_->NewIterator(options);
|
||||
db_->CompactRange(compact_options, &first_slice, &last_slice);
|
||||
// 5 sst files after 2 compactions with 2 live iterators
|
||||
CheckFileTypeCounts(dbname_, 0, 5, 1);
|
||||
|
||||
// ~DBImpl should wait until all BGWorkPurge are finished
|
||||
rocksdb::SyncPoint::GetInstance()->LoadDependency(
|
||||
{{"DBImpl::~DBImpl:WaitJob", "DBImpl::BGWorkPurge"},
|
||||
{"DeleteFileTest::GuardFinish",
|
||||
"DeleteFileTest::BackgroundPurgeTestMultipleJobs:DBClose"}});
|
||||
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
delete itr1;
|
||||
env_->Schedule(&DeleteFileTest::DoSleep, this, Env::Priority::HIGH);
|
||||
delete itr2;
|
||||
env_->Schedule(&DeleteFileTest::GuardFinish, nullptr, Env::Priority::HIGH);
|
||||
CloseDB();
|
||||
|
||||
TEST_SYNC_POINT("DeleteFileTest::BackgroundPurgeTestMultipleJobs:DBClose");
|
||||
// 1 sst after iterator deletion
|
||||
CheckFileTypeCounts(dbname_, 0, 1, 1);
|
||||
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
|
||||
TEST_F(DeleteFileTest, DeleteFileWithIterator) {
|
||||
CreateTwoLevels();
|
||||
ReadOptions options;
|
||||
|
||||
@@ -292,9 +292,6 @@ Status FlushJob::WriteLevel0Table(const autovector<MemTable*>& mems,
|
||||
}
|
||||
base->Unref();
|
||||
|
||||
// re-acquire the most current version
|
||||
base = cfd_->current();
|
||||
|
||||
// Note that if file_size is zero, the file has been deleted and
|
||||
// should not be added to the manifest.
|
||||
if (s.ok() && meta->fd.GetFileSize() > 0) {
|
||||
|
||||
@@ -7,16 +7,16 @@
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
#include "db/flush_job.h"
|
||||
#include "db/column_family.h"
|
||||
#include "db/flush_job.h"
|
||||
#include "db/version_set.h"
|
||||
#include "db/writebuffer.h"
|
||||
#include "rocksdb/cache.h"
|
||||
#include "rocksdb/write_buffer_manager.h"
|
||||
#include "table/mock_table.h"
|
||||
#include "util/file_reader_writer.h"
|
||||
#include "util/string_util.h"
|
||||
#include "util/testharness.h"
|
||||
#include "util/testutil.h"
|
||||
#include "table/mock_table.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
@@ -29,9 +29,9 @@ class FlushJobTest : public testing::Test {
|
||||
: env_(Env::Default()),
|
||||
dbname_(test::TmpDir() + "/flush_job_test"),
|
||||
table_cache_(NewLRUCache(50000, 16)),
|
||||
write_buffer_(db_options_.db_write_buffer_size),
|
||||
write_buffer_manager_(db_options_.db_write_buffer_size),
|
||||
versions_(new VersionSet(dbname_, &db_options_, env_options_,
|
||||
table_cache_.get(), &write_buffer_,
|
||||
table_cache_.get(), &write_buffer_manager_,
|
||||
&write_controller_)),
|
||||
shutting_down_(false),
|
||||
mock_table_factory_(new mock::MockTableFactory()) {
|
||||
@@ -77,7 +77,7 @@ class FlushJobTest : public testing::Test {
|
||||
std::shared_ptr<Cache> table_cache_;
|
||||
WriteController write_controller_;
|
||||
DBOptions db_options_;
|
||||
WriteBuffer write_buffer_;
|
||||
WriteBufferManager write_buffer_manager_;
|
||||
ColumnFamilyOptions cf_options_;
|
||||
std::unique_ptr<VersionSet> versions_;
|
||||
InstrumentedMutex mutex_;
|
||||
|
||||
@@ -20,6 +20,8 @@ void FlushScheduler::ScheduleFlush(ColumnFamilyData* cfd) {
|
||||
}
|
||||
#endif // NDEBUG
|
||||
cfd->Ref();
|
||||
// Suppress false positive clang analyzer warnings.
|
||||
#ifndef __clang_analyzer__
|
||||
Node* node = new Node{cfd, head_.load(std::memory_order_relaxed)};
|
||||
while (!head_.compare_exchange_strong(
|
||||
node->next, node, std::memory_order_relaxed, std::memory_order_relaxed)) {
|
||||
@@ -28,6 +30,7 @@ void FlushScheduler::ScheduleFlush(ColumnFamilyData* cfd) {
|
||||
// inter-thread synchronization, so we don't even need release
|
||||
// semantics for this CAS
|
||||
}
|
||||
#endif // __clang_analyzer__
|
||||
}
|
||||
|
||||
ColumnFamilyData* FlushScheduler::TakeNextColumnFamily() {
|
||||
|
||||
+14
-6
@@ -770,7 +770,9 @@ void InternalStats::DumpCFStats(std::string* value) {
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t curr_ingest = cf_stats_value_[BYTES_FLUSHED];
|
||||
uint64_t flush_ingest = cf_stats_value_[BYTES_FLUSHED];
|
||||
uint64_t add_file_ingest = cf_stats_value_[BYTES_INGESTED_ADD_FILE];
|
||||
uint64_t curr_ingest = flush_ingest + add_file_ingest;
|
||||
// Cumulative summary
|
||||
double w_amp = stats_sum.bytes_written / static_cast<double>(curr_ingest + 1);
|
||||
uint64_t total_stall_count =
|
||||
@@ -785,8 +787,12 @@ void InternalStats::DumpCFStats(std::string* value) {
|
||||
stats_sum);
|
||||
value->append(buf);
|
||||
// Interval summary
|
||||
uint64_t interval_flush_ingest =
|
||||
flush_ingest - cf_stats_snapshot_.ingest_bytes_flush;
|
||||
uint64_t interval_add_file_inget =
|
||||
add_file_ingest - cf_stats_snapshot_.ingest_bytes_add_file;
|
||||
uint64_t interval_ingest =
|
||||
curr_ingest - cf_stats_snapshot_.ingest_bytes + 1;
|
||||
interval_flush_ingest + interval_add_file_inget + 1;
|
||||
CompactionStats interval_stats(stats_sum);
|
||||
interval_stats.Subtract(cf_stats_snapshot_.comp_stats);
|
||||
w_amp = interval_stats.bytes_written / static_cast<double>(interval_ingest);
|
||||
@@ -799,9 +805,10 @@ void InternalStats::DumpCFStats(std::string* value) {
|
||||
seconds_up, interval_seconds_up);
|
||||
value->append(buf);
|
||||
|
||||
snprintf(buf, sizeof(buf),
|
||||
"Flush(GB): cumulative %.3f, interval %.3f\n",
|
||||
curr_ingest / kGB, interval_ingest / kGB);
|
||||
snprintf(buf, sizeof(buf), "Flush(GB): cumulative %.3f, interval %.3f\n",
|
||||
flush_ingest / kGB, interval_flush_ingest / kGB);
|
||||
snprintf(buf, sizeof(buf), "AddFile(GB): cumulative %.3f, interval %.3f\n",
|
||||
add_file_ingest / kGB, interval_add_file_inget / kGB);
|
||||
value->append(buf);
|
||||
|
||||
// Compact
|
||||
@@ -873,7 +880,8 @@ void InternalStats::DumpCFStats(std::string* value) {
|
||||
total_stall_count - cf_stats_snapshot_.stall_count);
|
||||
value->append(buf);
|
||||
|
||||
cf_stats_snapshot_.ingest_bytes = curr_ingest;
|
||||
cf_stats_snapshot_.ingest_bytes_flush = flush_ingest;
|
||||
cf_stats_snapshot_.ingest_bytes_add_file = add_file_ingest;
|
||||
cf_stats_snapshot_.comp_stats = stats_sum;
|
||||
cf_stats_snapshot_.stall_count = total_stall_count;
|
||||
}
|
||||
|
||||
+6
-2
@@ -59,6 +59,7 @@ class InternalStats {
|
||||
HARD_PENDING_COMPACTION_BYTES_LIMIT,
|
||||
WRITE_STALLS_ENUM_MAX,
|
||||
BYTES_FLUSHED,
|
||||
BYTES_INGESTED_ADD_FILE,
|
||||
INTERNAL_CF_STATS_ENUM_MAX,
|
||||
};
|
||||
|
||||
@@ -244,7 +245,8 @@ class InternalStats {
|
||||
struct CFStatsSnapshot {
|
||||
// ColumnFamily-level stats
|
||||
CompactionStats comp_stats;
|
||||
uint64_t ingest_bytes; // Bytes written to L0
|
||||
uint64_t ingest_bytes_flush; // Bytes written to L0 (Flush)
|
||||
uint64_t ingest_bytes_add_file; // Bytes written to L0 (AddFile)
|
||||
uint64_t stall_count; // Stall count
|
||||
// Stats from compaction jobs - bytes written, bytes read, duration.
|
||||
uint64_t compact_bytes_write;
|
||||
@@ -254,7 +256,8 @@ class InternalStats {
|
||||
|
||||
CFStatsSnapshot()
|
||||
: comp_stats(0),
|
||||
ingest_bytes(0),
|
||||
ingest_bytes_flush(0),
|
||||
ingest_bytes_add_file(0),
|
||||
stall_count(0),
|
||||
compact_bytes_write(0),
|
||||
compact_bytes_read(0),
|
||||
@@ -372,6 +375,7 @@ class InternalStats {
|
||||
HARD_PENDING_COMPACTION_BYTES_LIMIT,
|
||||
WRITE_STALLS_ENUM_MAX,
|
||||
BYTES_FLUSHED,
|
||||
BYTES_INGESTED_ADD_FILE,
|
||||
INTERNAL_CF_STATS_ENUM_MAX,
|
||||
};
|
||||
|
||||
|
||||
@@ -76,6 +76,9 @@ struct JobContext {
|
||||
uint64_t prev_log_number;
|
||||
|
||||
uint64_t min_pending_output = 0;
|
||||
uint64_t prev_total_log_size = 0;
|
||||
size_t num_alive_log_files = 0;
|
||||
uint64_t size_log_to_delete = 0;
|
||||
|
||||
explicit JobContext(int _job_id, bool create_superversion = false) {
|
||||
job_id = _job_id;
|
||||
|
||||
+10
-9
@@ -91,7 +91,7 @@ bool Reader::ReadRecord(Slice* record, std::string* scratch,
|
||||
Slice fragment;
|
||||
while (true) {
|
||||
uint64_t physical_record_offset = end_of_buffer_offset_ - buffer_.size();
|
||||
size_t drop_size;
|
||||
size_t drop_size = 0;
|
||||
const unsigned int record_type = ReadPhysicalRecord(&fragment, &drop_size);
|
||||
switch (record_type) {
|
||||
case kFullType:
|
||||
@@ -199,10 +199,11 @@ bool Reader::ReadRecord(Slice* record, std::string* scratch,
|
||||
scratch->clear();
|
||||
return false;
|
||||
}
|
||||
if (record_type == kBadRecordLen)
|
||||
ReportCorruption(drop_size, "bad record length");
|
||||
else
|
||||
ReportCorruption(drop_size, "checksum mismatch");
|
||||
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;
|
||||
@@ -343,7 +344,7 @@ unsigned int Reader::ReadPhysicalRecord(Slice* result, size_t* drop_size) {
|
||||
if (buffer_.size() < (size_t)kHeaderSize) {
|
||||
int r;
|
||||
if (!ReadMore(drop_size, &r)) {
|
||||
return r;
|
||||
return r;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
@@ -362,11 +363,11 @@ unsigned int Reader::ReadPhysicalRecord(Slice* result, size_t* drop_size) {
|
||||
header_size = kRecyclableHeaderSize;
|
||||
// We need enough for the larger header
|
||||
if (buffer_.size() < (size_t)kRecyclableHeaderSize) {
|
||||
int r;
|
||||
if (!ReadMore(drop_size, &r)) {
|
||||
int r;
|
||||
if (!ReadMore(drop_size, &r)) {
|
||||
return r;
|
||||
}
|
||||
continue;
|
||||
continue;
|
||||
}
|
||||
const uint32_t log_num = DecodeFixed32(header + 7);
|
||||
if (log_num != log_number_) {
|
||||
|
||||
+42
-64
@@ -15,13 +15,14 @@
|
||||
|
||||
#include "db/dbformat.h"
|
||||
#include "db/merge_context.h"
|
||||
#include "db/merge_helper.h"
|
||||
#include "db/pinned_iterators_manager.h"
|
||||
#include "db/writebuffer.h"
|
||||
#include "rocksdb/comparator.h"
|
||||
#include "rocksdb/env.h"
|
||||
#include "rocksdb/iterator.h"
|
||||
#include "rocksdb/merge_operator.h"
|
||||
#include "rocksdb/slice_transform.h"
|
||||
#include "rocksdb/write_buffer_manager.h"
|
||||
#include "table/internal_iterator.h"
|
||||
#include "table/merger.h"
|
||||
#include "util/arena.h"
|
||||
@@ -34,35 +35,36 @@
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
MemTableOptions::MemTableOptions(
|
||||
const ImmutableCFOptions& ioptions,
|
||||
const MutableCFOptions& mutable_cf_options)
|
||||
: write_buffer_size(mutable_cf_options.write_buffer_size),
|
||||
arena_block_size(mutable_cf_options.arena_block_size),
|
||||
memtable_prefix_bloom_bits(mutable_cf_options.memtable_prefix_bloom_bits),
|
||||
memtable_prefix_bloom_probes(
|
||||
mutable_cf_options.memtable_prefix_bloom_probes),
|
||||
memtable_prefix_bloom_huge_page_tlb_size(
|
||||
mutable_cf_options.memtable_prefix_bloom_huge_page_tlb_size),
|
||||
inplace_update_support(ioptions.inplace_update_support),
|
||||
inplace_update_num_locks(mutable_cf_options.inplace_update_num_locks),
|
||||
inplace_callback(ioptions.inplace_callback),
|
||||
max_successive_merges(mutable_cf_options.max_successive_merges),
|
||||
filter_deletes(mutable_cf_options.filter_deletes),
|
||||
statistics(ioptions.statistics),
|
||||
merge_operator(ioptions.merge_operator),
|
||||
info_log(ioptions.info_log) {}
|
||||
MemTableOptions::MemTableOptions(const ImmutableCFOptions& ioptions,
|
||||
const MutableCFOptions& mutable_cf_options)
|
||||
: write_buffer_size(mutable_cf_options.write_buffer_size),
|
||||
arena_block_size(mutable_cf_options.arena_block_size),
|
||||
memtable_prefix_bloom_bits(
|
||||
static_cast<uint32_t>(
|
||||
static_cast<double>(mutable_cf_options.write_buffer_size) *
|
||||
mutable_cf_options.memtable_prefix_bloom_size_ratio) *
|
||||
8u),
|
||||
memtable_prefix_bloom_huge_page_tlb_size(
|
||||
mutable_cf_options.memtable_prefix_bloom_huge_page_tlb_size),
|
||||
inplace_update_support(ioptions.inplace_update_support),
|
||||
inplace_update_num_locks(mutable_cf_options.inplace_update_num_locks),
|
||||
inplace_callback(ioptions.inplace_callback),
|
||||
max_successive_merges(mutable_cf_options.max_successive_merges),
|
||||
statistics(ioptions.statistics),
|
||||
merge_operator(ioptions.merge_operator),
|
||||
info_log(ioptions.info_log) {}
|
||||
|
||||
MemTable::MemTable(const InternalKeyComparator& cmp,
|
||||
const ImmutableCFOptions& ioptions,
|
||||
const MutableCFOptions& mutable_cf_options,
|
||||
WriteBuffer* write_buffer, SequenceNumber earliest_seq)
|
||||
WriteBufferManager* write_buffer_manager,
|
||||
SequenceNumber earliest_seq)
|
||||
: comparator_(cmp),
|
||||
moptions_(ioptions, mutable_cf_options),
|
||||
refs_(0),
|
||||
kArenaBlockSize(OptimizeBlockSize(moptions_.arena_block_size)),
|
||||
arena_(moptions_.arena_block_size, 0),
|
||||
allocator_(&arena_, write_buffer),
|
||||
allocator_(&arena_, write_buffer_manager),
|
||||
table_(ioptions.memtable_factory->CreateMemTableRep(
|
||||
comparator_, &allocator_, ioptions.prefix_extractor,
|
||||
ioptions.info_log)),
|
||||
@@ -88,11 +90,9 @@ MemTable::MemTable(const InternalKeyComparator& cmp,
|
||||
|
||||
if (prefix_extractor_ && moptions_.memtable_prefix_bloom_bits > 0) {
|
||||
prefix_bloom_.reset(new DynamicBloom(
|
||||
&allocator_,
|
||||
moptions_.memtable_prefix_bloom_bits, ioptions.bloom_locality,
|
||||
moptions_.memtable_prefix_bloom_probes, nullptr,
|
||||
moptions_.memtable_prefix_bloom_huge_page_tlb_size,
|
||||
ioptions.info_log));
|
||||
&allocator_, moptions_.memtable_prefix_bloom_bits,
|
||||
ioptions.bloom_locality, 6 /* hard coded 6 probes */, nullptr,
|
||||
moptions_.memtable_prefix_bloom_huge_page_tlb_size, ioptions.info_log));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -352,7 +352,8 @@ uint64_t MemTable::ApproximateSize(const Slice& start_ikey,
|
||||
|
||||
void MemTable::Add(SequenceNumber s, ValueType type,
|
||||
const Slice& key, /* user key */
|
||||
const Slice& value, bool allow_concurrent) {
|
||||
const Slice& value, bool allow_concurrent,
|
||||
MemTablePostProcessInfo* post_process_info) {
|
||||
// Format of an entry is concatenation of:
|
||||
// key_size : varint32 of internal_key.size()
|
||||
// key bytes : char[internal_key.size()]
|
||||
@@ -406,13 +407,16 @@ void MemTable::Add(SequenceNumber s, ValueType type,
|
||||
}
|
||||
assert(first_seqno_.load() >= earliest_seqno_.load());
|
||||
}
|
||||
assert(post_process_info == nullptr);
|
||||
UpdateFlushState();
|
||||
} else {
|
||||
table_->InsertConcurrently(handle);
|
||||
|
||||
num_entries_.fetch_add(1, std::memory_order_relaxed);
|
||||
data_size_.fetch_add(encoded_len, std::memory_order_relaxed);
|
||||
assert(post_process_info != nullptr);
|
||||
post_process_info->num_entries++;
|
||||
post_process_info->data_size += encoded_len;
|
||||
if (type == kTypeDeletion) {
|
||||
num_deletes_.fetch_add(1, std::memory_order_relaxed);
|
||||
post_process_info->num_deletes++;
|
||||
}
|
||||
|
||||
if (prefix_bloom_) {
|
||||
@@ -432,8 +436,6 @@ void MemTable::Add(SequenceNumber s, ValueType type,
|
||||
!first_seqno_.compare_exchange_weak(cur_earliest_seqno, s)) {
|
||||
}
|
||||
}
|
||||
|
||||
UpdateFlushState();
|
||||
}
|
||||
|
||||
// Callback from MemTable::Get()
|
||||
@@ -490,22 +492,10 @@ static bool SaveValue(void* arg, const char* entry) {
|
||||
Slice v = GetLengthPrefixedSlice(key_ptr + key_length);
|
||||
*(s->status) = Status::OK();
|
||||
if (*(s->merge_in_progress)) {
|
||||
assert(merge_operator);
|
||||
bool merge_success = false;
|
||||
{
|
||||
StopWatchNano timer(s->env_, s->statistics != nullptr);
|
||||
PERF_TIMER_GUARD(merge_operator_time_nanos);
|
||||
merge_success = merge_operator->FullMerge(
|
||||
s->key->user_key(), &v, merge_context->GetOperands(), s->value,
|
||||
s->logger);
|
||||
RecordTick(s->statistics, MERGE_OPERATION_TOTAL_TIME,
|
||||
timer.ElapsedNanos());
|
||||
}
|
||||
if (!merge_success) {
|
||||
RecordTick(s->statistics, NUMBER_MERGE_FAILURES);
|
||||
*(s->status) =
|
||||
Status::Corruption("Error: Could not perform merge.");
|
||||
}
|
||||
*(s->status) = MergeHelper::TimedFullMerge(
|
||||
merge_operator, s->key->user_key(), &v,
|
||||
merge_context->GetOperands(), s->value, s->logger, s->statistics,
|
||||
s->env_);
|
||||
} else if (s->value != nullptr) {
|
||||
s->value->assign(v.data(), v.size());
|
||||
}
|
||||
@@ -518,23 +508,11 @@ static bool SaveValue(void* arg, const char* entry) {
|
||||
case kTypeDeletion:
|
||||
case kTypeSingleDeletion: {
|
||||
if (*(s->merge_in_progress)) {
|
||||
assert(merge_operator != nullptr);
|
||||
*(s->status) = Status::OK();
|
||||
bool merge_success = false;
|
||||
{
|
||||
StopWatchNano timer(s->env_, s->statistics != nullptr);
|
||||
PERF_TIMER_GUARD(merge_operator_time_nanos);
|
||||
merge_success = merge_operator->FullMerge(
|
||||
s->key->user_key(), nullptr, merge_context->GetOperands(),
|
||||
s->value, s->logger);
|
||||
RecordTick(s->statistics, MERGE_OPERATION_TOTAL_TIME,
|
||||
timer.ElapsedNanos());
|
||||
}
|
||||
if (!merge_success) {
|
||||
RecordTick(s->statistics, NUMBER_MERGE_FAILURES);
|
||||
*(s->status) =
|
||||
Status::Corruption("Error: Could not perform merge.");
|
||||
}
|
||||
*(s->status) = MergeHelper::TimedFullMerge(
|
||||
merge_operator, s->key->user_key(), nullptr,
|
||||
merge_context->GetOperands(), s->value, s->logger, s->statistics,
|
||||
s->env_);
|
||||
} else {
|
||||
*(s->status) = Status::NotFound();
|
||||
}
|
||||
|
||||
+24
-5
@@ -32,7 +32,6 @@ namespace rocksdb {
|
||||
class Mutex;
|
||||
class MemTableIterator;
|
||||
class MergeContext;
|
||||
class WriteBuffer;
|
||||
class InternalIterator;
|
||||
|
||||
struct MemTableOptions {
|
||||
@@ -42,7 +41,6 @@ struct MemTableOptions {
|
||||
size_t write_buffer_size;
|
||||
size_t arena_block_size;
|
||||
uint32_t memtable_prefix_bloom_bits;
|
||||
uint32_t memtable_prefix_bloom_probes;
|
||||
size_t memtable_prefix_bloom_huge_page_tlb_size;
|
||||
bool inplace_update_support;
|
||||
size_t inplace_update_num_locks;
|
||||
@@ -51,12 +49,20 @@ struct MemTableOptions {
|
||||
Slice delta_value,
|
||||
std::string* merged_value);
|
||||
size_t max_successive_merges;
|
||||
bool filter_deletes;
|
||||
Statistics* statistics;
|
||||
MergeOperator* merge_operator;
|
||||
Logger* info_log;
|
||||
};
|
||||
|
||||
// Batched counters to updated when inserting keys in one write batch.
|
||||
// In post process of the write batch, these can be updated together.
|
||||
// Only used in concurrent memtable insert case.
|
||||
struct MemTablePostProcessInfo {
|
||||
uint64_t data_size = 0;
|
||||
uint64_t num_entries = 0;
|
||||
uint64_t num_deletes = 0;
|
||||
};
|
||||
|
||||
// Note: Many of the methods in this class have comments indicating that
|
||||
// external synchromization is required as these methods are not thread-safe.
|
||||
// It is up to higher layers of code to decide how to prevent concurrent
|
||||
@@ -93,7 +99,8 @@ class MemTable {
|
||||
explicit MemTable(const InternalKeyComparator& comparator,
|
||||
const ImmutableCFOptions& ioptions,
|
||||
const MutableCFOptions& mutable_cf_options,
|
||||
WriteBuffer* write_buffer, SequenceNumber earliest_seq);
|
||||
WriteBufferManager* write_buffer_manager,
|
||||
SequenceNumber earliest_seq);
|
||||
|
||||
// Do not delete this MemTable unless Unref() indicates it not in use.
|
||||
~MemTable();
|
||||
@@ -159,7 +166,8 @@ class MemTable {
|
||||
// REQUIRES: if allow_concurrent = false, external synchronization to prevent
|
||||
// simultaneous operations on the same MemTable.
|
||||
void Add(SequenceNumber seq, ValueType type, const Slice& key,
|
||||
const Slice& value, bool allow_concurrent = false);
|
||||
const Slice& value, bool allow_concurrent = false,
|
||||
MemTablePostProcessInfo* post_process_info = nullptr);
|
||||
|
||||
// If memtable contains a value for key, store it in *value and return true.
|
||||
// If memtable contains a deletion for key, store a NotFound() error
|
||||
@@ -218,6 +226,17 @@ class MemTable {
|
||||
// key in the memtable.
|
||||
size_t CountSuccessiveMergeEntries(const LookupKey& key);
|
||||
|
||||
// Update counters and flush status after inserting a whole write batch
|
||||
// Used in concurrent memtable inserts.
|
||||
void BatchPostProcess(const MemTablePostProcessInfo& update_counters) {
|
||||
num_entries_.fetch_add(update_counters.num_entries,
|
||||
std::memory_order_relaxed);
|
||||
data_size_.fetch_add(update_counters.data_size, std::memory_order_relaxed);
|
||||
num_deletes_.fetch_add(update_counters.num_deletes,
|
||||
std::memory_order_relaxed);
|
||||
UpdateFlushState();
|
||||
}
|
||||
|
||||
// Get total number of entries in the mem table.
|
||||
// REQUIRES: external synchronization to prevent simultaneous
|
||||
// operations on the same MemTable (unless this Memtable is immutable).
|
||||
|
||||
+23
-12
@@ -10,36 +10,47 @@
|
||||
#include "db/memtable_allocator.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include "db/writebuffer.h"
|
||||
#include "rocksdb/write_buffer_manager.h"
|
||||
#include "util/arena.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
MemTableAllocator::MemTableAllocator(Allocator* allocator,
|
||||
WriteBuffer* write_buffer)
|
||||
: allocator_(allocator), write_buffer_(write_buffer), bytes_allocated_(0) {}
|
||||
WriteBufferManager* write_buffer_manager)
|
||||
: allocator_(allocator),
|
||||
write_buffer_manager_(write_buffer_manager),
|
||||
bytes_allocated_(0) {}
|
||||
|
||||
MemTableAllocator::~MemTableAllocator() { DoneAllocating(); }
|
||||
|
||||
char* MemTableAllocator::Allocate(size_t bytes) {
|
||||
assert(write_buffer_ != nullptr);
|
||||
bytes_allocated_.fetch_add(bytes, std::memory_order_relaxed);
|
||||
write_buffer_->ReserveMem(bytes);
|
||||
assert(write_buffer_manager_ != nullptr);
|
||||
if (write_buffer_manager_->enabled()) {
|
||||
bytes_allocated_.fetch_add(bytes, std::memory_order_relaxed);
|
||||
write_buffer_manager_->ReserveMem(bytes);
|
||||
}
|
||||
return allocator_->Allocate(bytes);
|
||||
}
|
||||
|
||||
char* MemTableAllocator::AllocateAligned(size_t bytes, size_t huge_page_size,
|
||||
Logger* logger) {
|
||||
assert(write_buffer_ != nullptr);
|
||||
bytes_allocated_.fetch_add(bytes, std::memory_order_relaxed);
|
||||
write_buffer_->ReserveMem(bytes);
|
||||
assert(write_buffer_manager_ != nullptr);
|
||||
if (write_buffer_manager_->enabled()) {
|
||||
bytes_allocated_.fetch_add(bytes, std::memory_order_relaxed);
|
||||
write_buffer_manager_->ReserveMem(bytes);
|
||||
}
|
||||
return allocator_->AllocateAligned(bytes, huge_page_size, logger);
|
||||
}
|
||||
|
||||
void MemTableAllocator::DoneAllocating() {
|
||||
if (write_buffer_ != nullptr) {
|
||||
write_buffer_->FreeMem(bytes_allocated_.load(std::memory_order_relaxed));
|
||||
write_buffer_ = nullptr;
|
||||
if (write_buffer_manager_ != nullptr) {
|
||||
if (write_buffer_manager_->enabled()) {
|
||||
write_buffer_manager_->FreeMem(
|
||||
bytes_allocated_.load(std::memory_order_relaxed));
|
||||
} else {
|
||||
assert(bytes_allocated_.load(std::memory_order_relaxed) == 0);
|
||||
}
|
||||
write_buffer_manager_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,21 +8,23 @@
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
//
|
||||
// This is used by the MemTable to allocate write buffer memory. It connects
|
||||
// to WriteBuffer so we can track and enforce overall write buffer limits.
|
||||
// to WriteBufferManager so we can track and enforce overall write buffer
|
||||
// limits.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include "rocksdb/write_buffer_manager.h"
|
||||
#include "util/allocator.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
class Logger;
|
||||
class WriteBuffer;
|
||||
|
||||
class MemTableAllocator : public Allocator {
|
||||
public:
|
||||
explicit MemTableAllocator(Allocator* allocator, WriteBuffer* write_buffer);
|
||||
explicit MemTableAllocator(Allocator* allocator,
|
||||
WriteBufferManager* write_buffer_manager);
|
||||
~MemTableAllocator();
|
||||
|
||||
// Allocator interface
|
||||
@@ -37,7 +39,7 @@ class MemTableAllocator : public Allocator {
|
||||
|
||||
private:
|
||||
Allocator* allocator_;
|
||||
WriteBuffer* write_buffer_;
|
||||
WriteBufferManager* write_buffer_manager_;
|
||||
std::atomic<size_t> bytes_allocated_;
|
||||
|
||||
// No copying allowed
|
||||
|
||||
+68
-42
@@ -11,14 +11,15 @@
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <string>
|
||||
#include "rocksdb/db.h"
|
||||
#include "db/memtable.h"
|
||||
#include "db/version_set.h"
|
||||
#include "rocksdb/db.h"
|
||||
#include "rocksdb/env.h"
|
||||
#include "rocksdb/iterator.h"
|
||||
#include "table/merger.h"
|
||||
#include "util/coding.h"
|
||||
#include "util/log_buffer.h"
|
||||
#include "util/sync_point.h"
|
||||
#include "util/thread_status_util.h"
|
||||
|
||||
namespace rocksdb {
|
||||
@@ -297,56 +298,81 @@ Status MemTableList::InstallMemtableFlushResults(
|
||||
// if some other thread is already committing, then return
|
||||
Status s;
|
||||
if (commit_in_progress_) {
|
||||
TEST_SYNC_POINT("MemTableList::InstallMemtableFlushResults:InProgress");
|
||||
return s;
|
||||
}
|
||||
|
||||
// Only a single thread can be executing this piece of code
|
||||
commit_in_progress_ = true;
|
||||
|
||||
// scan all memtables from the earliest, and commit those
|
||||
// (in that order) that have finished flushing. Memetables
|
||||
// are always committed in the order that they were created.
|
||||
while (!current_->memlist_.empty() && s.ok()) {
|
||||
MemTable* m = current_->memlist_.back(); // get the last element
|
||||
if (!m->flush_completed_) {
|
||||
// Retry until all completed flushes are committed. New flushes can finish
|
||||
// while the current thread is writing manifest where mutex is released.
|
||||
while (s.ok()) {
|
||||
auto& memlist = current_->memlist_;
|
||||
if (memlist.empty() || !memlist.back()->flush_completed_) {
|
||||
break;
|
||||
}
|
||||
|
||||
LogToBuffer(log_buffer, "[%s] Level-0 commit table #%" PRIu64 " started",
|
||||
cfd->GetName().c_str(), m->file_number_);
|
||||
|
||||
// this can release and reacquire the mutex.
|
||||
s = vset->LogAndApply(cfd, mutable_cf_options, &m->edit_, mu, db_directory);
|
||||
|
||||
// we will be changing the version in the next code path,
|
||||
// so we better create a new one, since versions are immutable
|
||||
InstallNewVersion();
|
||||
|
||||
// All the later memtables that have the same filenum
|
||||
// are part of the same batch. They can be committed now.
|
||||
uint64_t mem_id = 1; // how many memtables have been flushed.
|
||||
do {
|
||||
if (s.ok()) { // commit new state
|
||||
LogToBuffer(log_buffer, "[%s] Level-0 commit table #%" PRIu64
|
||||
": memtable #%" PRIu64 " done",
|
||||
cfd->GetName().c_str(), m->file_number_, mem_id);
|
||||
assert(m->file_number_ > 0);
|
||||
current_->Remove(m, to_delete);
|
||||
} else {
|
||||
// commit failed. setup state so that we can flush again.
|
||||
LogToBuffer(log_buffer, "Level-0 commit table #%" PRIu64
|
||||
": memtable #%" PRIu64 " failed",
|
||||
m->file_number_, mem_id);
|
||||
m->flush_completed_ = false;
|
||||
m->flush_in_progress_ = false;
|
||||
m->edit_.Clear();
|
||||
num_flush_not_started_++;
|
||||
m->file_number_ = 0;
|
||||
imm_flush_needed.store(true, std::memory_order_release);
|
||||
// scan all memtables from the earliest, and commit those
|
||||
// (in that order) that have finished flushing. Memetables
|
||||
// are always committed in the order that they were created.
|
||||
uint64_t batch_file_number = 0;
|
||||
size_t batch_count = 0;
|
||||
autovector<VersionEdit*> edit_list;
|
||||
// enumerate from the last (earliest) element to see how many batch finished
|
||||
for (auto it = memlist.rbegin(); it != memlist.rend(); ++it) {
|
||||
MemTable* m = *it;
|
||||
if (!m->flush_completed_) {
|
||||
break;
|
||||
}
|
||||
++mem_id;
|
||||
} while (!current_->memlist_.empty() && (nullptr != (m = current_->memlist_.back())) &&
|
||||
(m->file_number_ == file_number));
|
||||
if (it == memlist.rbegin() || batch_file_number != m->file_number_) {
|
||||
batch_file_number = m->file_number_;
|
||||
LogToBuffer(log_buffer,
|
||||
"[%s] Level-0 commit table #%" PRIu64 " started",
|
||||
cfd->GetName().c_str(), m->file_number_);
|
||||
edit_list.push_back(&m->edit_);
|
||||
}
|
||||
batch_count++;
|
||||
}
|
||||
|
||||
if (batch_count > 0) {
|
||||
// this can release and reacquire the mutex.
|
||||
s = vset->LogAndApply(cfd, mutable_cf_options, edit_list, mu,
|
||||
db_directory);
|
||||
|
||||
// we will be changing the version in the next code path,
|
||||
// so we better create a new one, since versions are immutable
|
||||
InstallNewVersion();
|
||||
|
||||
// All the later memtables that have the same filenum
|
||||
// are part of the same batch. They can be committed now.
|
||||
uint64_t mem_id = 1; // how many memtables have been flushed.
|
||||
if (s.ok()) { // commit new state
|
||||
while (batch_count-- > 0) {
|
||||
MemTable* m = current_->memlist_.back();
|
||||
LogToBuffer(log_buffer, "[%s] Level-0 commit table #%" PRIu64
|
||||
": memtable #%" PRIu64 " done",
|
||||
cfd->GetName().c_str(), m->file_number_, mem_id);
|
||||
assert(m->file_number_ > 0);
|
||||
current_->Remove(m, to_delete);
|
||||
++mem_id;
|
||||
}
|
||||
} else {
|
||||
for (auto it = current_->memlist_.rbegin(); batch_count-- > 0; it++) {
|
||||
MemTable* m = *it;
|
||||
// commit failed. setup state so that we can flush again.
|
||||
LogToBuffer(log_buffer, "Level-0 commit table #%" PRIu64
|
||||
": memtable #%" PRIu64 " failed",
|
||||
m->file_number_, mem_id);
|
||||
m->flush_completed_ = false;
|
||||
m->flush_in_progress_ = false;
|
||||
m->edit_.Clear();
|
||||
num_flush_not_started_++;
|
||||
m->file_number_ = 0;
|
||||
imm_flush_needed.store(true, std::memory_order_release);
|
||||
++mem_id;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
commit_in_progress_ = false;
|
||||
return s;
|
||||
|
||||
+11
-11
@@ -3,19 +3,19 @@
|
||||
// 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.
|
||||
|
||||
#include "db/memtable_list.h"
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include "db/memtable_list.h"
|
||||
#include "db/merge_context.h"
|
||||
#include "db/version_set.h"
|
||||
#include "db/write_controller.h"
|
||||
#include "db/writebuffer.h"
|
||||
#include "rocksdb/db.h"
|
||||
#include "rocksdb/status.h"
|
||||
#include "util/testutil.h"
|
||||
#include "rocksdb/write_buffer_manager.h"
|
||||
#include "util/string_util.h"
|
||||
#include "util/testharness.h"
|
||||
#include "util/testutil.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
@@ -59,12 +59,12 @@ class MemTableListTest : public testing::Test {
|
||||
DBOptions db_options;
|
||||
EnvOptions env_options;
|
||||
shared_ptr<Cache> table_cache(NewLRUCache(50000, 16));
|
||||
WriteBuffer write_buffer(db_options.db_write_buffer_size);
|
||||
WriteBufferManager write_buffer_manager(db_options.db_write_buffer_size);
|
||||
WriteController write_controller(10000000u);
|
||||
|
||||
CreateDB();
|
||||
VersionSet versions(dbname, &db_options, env_options, table_cache.get(),
|
||||
&write_buffer, &write_controller);
|
||||
&write_buffer_manager, &write_controller);
|
||||
|
||||
// Create mock default ColumnFamilyData
|
||||
ColumnFamilyOptions cf_options;
|
||||
@@ -126,7 +126,7 @@ TEST_F(MemTableListTest, GetTest) {
|
||||
options.memtable_factory = factory;
|
||||
ImmutableCFOptions ioptions(options);
|
||||
|
||||
WriteBuffer wb(options.db_write_buffer_size);
|
||||
WriteBufferManager wb(options.db_write_buffer_size);
|
||||
MemTable* mem =
|
||||
new MemTable(cmp, ioptions, MutableCFOptions(options, ioptions), &wb,
|
||||
kMaxSequenceNumber);
|
||||
@@ -163,7 +163,7 @@ TEST_F(MemTableListTest, GetTest) {
|
||||
SequenceNumber saved_seq = seq;
|
||||
|
||||
// Create another memtable and write some keys to it
|
||||
WriteBuffer wb2(options.db_write_buffer_size);
|
||||
WriteBufferManager wb2(options.db_write_buffer_size);
|
||||
MemTable* mem2 =
|
||||
new MemTable(cmp, ioptions, MutableCFOptions(options, ioptions), &wb2,
|
||||
kMaxSequenceNumber);
|
||||
@@ -228,7 +228,7 @@ TEST_F(MemTableListTest, GetFromHistoryTest) {
|
||||
options.memtable_factory = factory;
|
||||
ImmutableCFOptions ioptions(options);
|
||||
|
||||
WriteBuffer wb(options.db_write_buffer_size);
|
||||
WriteBufferManager wb(options.db_write_buffer_size);
|
||||
MemTable* mem =
|
||||
new MemTable(cmp, ioptions, MutableCFOptions(options, ioptions), &wb,
|
||||
kMaxSequenceNumber);
|
||||
@@ -303,7 +303,7 @@ TEST_F(MemTableListTest, GetFromHistoryTest) {
|
||||
ASSERT_EQ("value2.2", value);
|
||||
|
||||
// Create another memtable and write some keys to it
|
||||
WriteBuffer wb2(options.db_write_buffer_size);
|
||||
WriteBufferManager wb2(options.db_write_buffer_size);
|
||||
MemTable* mem2 =
|
||||
new MemTable(cmp, ioptions, MutableCFOptions(options, ioptions), &wb2,
|
||||
kMaxSequenceNumber);
|
||||
@@ -329,7 +329,7 @@ TEST_F(MemTableListTest, GetFromHistoryTest) {
|
||||
ASSERT_EQ(0, to_delete.size());
|
||||
|
||||
// Add a third memtable to push the first memtable out of the history
|
||||
WriteBuffer wb3(options.db_write_buffer_size);
|
||||
WriteBufferManager wb3(options.db_write_buffer_size);
|
||||
MemTable* mem3 =
|
||||
new MemTable(cmp, ioptions, MutableCFOptions(options, ioptions), &wb3,
|
||||
kMaxSequenceNumber);
|
||||
@@ -390,7 +390,7 @@ TEST_F(MemTableListTest, FlushPendingTest) {
|
||||
options.memtable_factory = factory;
|
||||
ImmutableCFOptions ioptions(options);
|
||||
InternalKeyComparator cmp(BytewiseComparator());
|
||||
WriteBuffer wb(options.db_write_buffer_size);
|
||||
WriteBufferManager wb(options.db_write_buffer_size);
|
||||
autovector<MemTable*> to_delete;
|
||||
|
||||
// Create MemTableList
|
||||
|
||||
@@ -28,13 +28,13 @@ int main() {
|
||||
|
||||
#include "db/dbformat.h"
|
||||
#include "db/memtable.h"
|
||||
#include "db/writebuffer.h"
|
||||
#include "port/port.h"
|
||||
#include "port/stack_trace.h"
|
||||
#include "rocksdb/comparator.h"
|
||||
#include "rocksdb/memtablerep.h"
|
||||
#include "rocksdb/options.h"
|
||||
#include "rocksdb/slice_transform.h"
|
||||
#include "rocksdb/write_buffer_manager.h"
|
||||
#include "util/arena.h"
|
||||
#include "util/mutexlock.h"
|
||||
#include "util/stop_watch.h"
|
||||
@@ -622,7 +622,7 @@ int main(int argc, char** argv) {
|
||||
rocksdb::BytewiseComparator());
|
||||
rocksdb::MemTable::KeyComparator key_comp(internal_key_comp);
|
||||
rocksdb::Arena arena;
|
||||
rocksdb::WriteBuffer wb(FLAGS_write_buffer_size);
|
||||
rocksdb::WriteBufferManager wb(FLAGS_write_buffer_size);
|
||||
rocksdb::MemTableAllocator memtable_allocator(&arena, &wb);
|
||||
uint64_t sequence;
|
||||
auto createMemtableRep = [&] {
|
||||
|
||||
+23
-24
@@ -18,31 +18,32 @@
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
// TODO(agiardullo): Clean up merge callsites to use this func
|
||||
Status MergeHelper::TimedFullMerge(const Slice& key, const Slice* value,
|
||||
Status MergeHelper::TimedFullMerge(const MergeOperator* merge_operator,
|
||||
const Slice& key, const Slice* value,
|
||||
const std::deque<std::string>& operands,
|
||||
const MergeOperator* merge_operator,
|
||||
Statistics* statistics, Env* env,
|
||||
Logger* logger, std::string* result) {
|
||||
std::string* result, Logger* logger,
|
||||
Statistics* statistics, Env* env) {
|
||||
assert(merge_operator != nullptr);
|
||||
|
||||
if (operands.size() == 0) {
|
||||
assert(value != nullptr && result != nullptr);
|
||||
result->assign(value->data(), value->size());
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
if (merge_operator == nullptr) {
|
||||
return Status::NotSupported("Provide a merge_operator when opening DB");
|
||||
bool success;
|
||||
{
|
||||
// Setup to time the merge
|
||||
StopWatchNano timer(env, statistics != nullptr);
|
||||
PERF_TIMER_GUARD(merge_operator_time_nanos);
|
||||
|
||||
// Do the merge
|
||||
success = merge_operator->FullMerge(key, value, operands, result, logger);
|
||||
|
||||
RecordTick(statistics, MERGE_OPERATION_TOTAL_TIME,
|
||||
statistics ? timer.ElapsedNanos() : 0);
|
||||
}
|
||||
|
||||
// Setup to time the merge
|
||||
StopWatchNano timer(env, statistics != nullptr);
|
||||
PERF_TIMER_GUARD(merge_operator_time_nanos);
|
||||
|
||||
// Do the merge
|
||||
bool success =
|
||||
merge_operator->FullMerge(key, value, operands, result, logger);
|
||||
|
||||
RecordTick(statistics, MERGE_OPERATION_TOTAL_TIME, timer.ElapsedNanosSafe());
|
||||
|
||||
if (!success) {
|
||||
RecordTick(statistics, NUMBER_MERGE_FAILURES);
|
||||
return Status::Corruption("Error: Could not perform merge.");
|
||||
@@ -140,9 +141,8 @@ Status MergeHelper::MergeUntil(InternalIterator* iter,
|
||||
const Slice val = iter->value();
|
||||
const Slice* val_ptr = (kTypeValue == ikey.type) ? &val : nullptr;
|
||||
std::string merge_result;
|
||||
s = TimedFullMerge(ikey.user_key, val_ptr, operands_,
|
||||
user_merge_operator_, stats_, env_, logger_,
|
||||
&merge_result);
|
||||
s = TimedFullMerge(user_merge_operator_, ikey.user_key, val_ptr,
|
||||
operands_, &merge_result, logger_, stats_, env_);
|
||||
|
||||
// We store the result in keys_.back() and operands_.back()
|
||||
// if nothing went wrong (i.e.: no operand corruption on disk)
|
||||
@@ -221,9 +221,8 @@ Status MergeHelper::MergeUntil(InternalIterator* iter,
|
||||
assert(operands_.size() >= 1);
|
||||
assert(operands_.size() == keys_.size());
|
||||
std::string merge_result;
|
||||
s = TimedFullMerge(orig_ikey.user_key, nullptr, operands_,
|
||||
user_merge_operator_, stats_, env_, logger_,
|
||||
&merge_result);
|
||||
s = TimedFullMerge(user_merge_operator_, orig_ikey.user_key, nullptr,
|
||||
operands_, &merge_result, logger_, stats_, env_);
|
||||
if (s.ok()) {
|
||||
// The original key encountered
|
||||
// We are certain that keys_ is not empty here (see assertions couple of
|
||||
@@ -257,7 +256,7 @@ Status MergeHelper::MergeUntil(InternalIterator* iter,
|
||||
std::deque<Slice>(operands_.begin(), operands_.end()),
|
||||
&merge_result, logger_);
|
||||
RecordTick(stats_, MERGE_OPERATION_TOTAL_TIME,
|
||||
timer.ElapsedNanosSafe());
|
||||
stats_ ? timer.ElapsedNanosSafe() : 0);
|
||||
}
|
||||
if (merge_success) {
|
||||
// Merging of operands (associative merge) was successful.
|
||||
|
||||
+4
-5
@@ -55,12 +55,11 @@ class MergeHelper {
|
||||
// Returns one of the following statuses:
|
||||
// - OK: Entries were successfully merged.
|
||||
// - Corruption: Merge operator reported unsuccessful merge.
|
||||
// - NotSupported: Merge operator is missing.
|
||||
static Status TimedFullMerge(const Slice& key, const Slice* value,
|
||||
static Status TimedFullMerge(const MergeOperator* merge_operator,
|
||||
const Slice& key, const Slice* value,
|
||||
const std::deque<std::string>& operands,
|
||||
const MergeOperator* merge_operator,
|
||||
Statistics* statistics, Env* env, Logger* logger,
|
||||
std::string* result);
|
||||
std::string* result, Logger* logger,
|
||||
Statistics* statistics, Env* env);
|
||||
|
||||
// Merge entries until we hit
|
||||
// - a corrupted key
|
||||
|
||||
@@ -331,20 +331,19 @@ class TestPlainTableFactory : public PlainTableFactory {
|
||||
TableProperties* props = nullptr;
|
||||
auto s =
|
||||
ReadTableProperties(file.get(), file_size, kPlainTableMagicNumber,
|
||||
table_reader_options.ioptions.env,
|
||||
table_reader_options.ioptions.info_log, &props);
|
||||
table_reader_options.ioptions, &props);
|
||||
EXPECT_TRUE(s.ok());
|
||||
|
||||
if (store_index_in_file_) {
|
||||
BlockHandle bloom_block_handle;
|
||||
s = FindMetaBlock(file.get(), file_size, kPlainTableMagicNumber,
|
||||
table_reader_options.ioptions.env,
|
||||
table_reader_options.ioptions,
|
||||
BloomBlockBuilder::kBloomBlock, &bloom_block_handle);
|
||||
EXPECT_TRUE(s.ok());
|
||||
|
||||
BlockHandle index_block_handle;
|
||||
s = FindMetaBlock(file.get(), file_size, kPlainTableMagicNumber,
|
||||
table_reader_options.ioptions.env,
|
||||
table_reader_options.ioptions,
|
||||
PlainTableIndexBuilder::kPlainTableIndexBlock,
|
||||
&index_block_handle);
|
||||
EXPECT_TRUE(s.ok());
|
||||
|
||||
+3
-4
@@ -43,8 +43,7 @@ DEFINE_int64(write_buffer_size, 33554432, "");
|
||||
DEFINE_int32(max_write_buffer_number, 2, "");
|
||||
DEFINE_int32(min_write_buffer_number_to_merge, 1, "");
|
||||
DEFINE_int32(skiplist_height, 4, "");
|
||||
DEFINE_int32(memtable_prefix_bloom_bits, 10000000, "");
|
||||
DEFINE_int32(memtable_prefix_bloom_probes, 10, "");
|
||||
DEFINE_double(memtable_prefix_bloom_size_ratio, 0.1, "");
|
||||
DEFINE_int32(memtable_prefix_bloom_huge_page_tlb_size, 2 * 1024 * 1024, "");
|
||||
DEFINE_int32(value_size, 40, "");
|
||||
|
||||
@@ -160,8 +159,8 @@ class PrefixTest : public testing::Test {
|
||||
options.min_write_buffer_number_to_merge =
|
||||
FLAGS_min_write_buffer_number_to_merge;
|
||||
|
||||
options.memtable_prefix_bloom_bits = FLAGS_memtable_prefix_bloom_bits;
|
||||
options.memtable_prefix_bloom_probes = FLAGS_memtable_prefix_bloom_probes;
|
||||
options.memtable_prefix_bloom_size_ratio =
|
||||
FLAGS_memtable_prefix_bloom_size_ratio;
|
||||
options.memtable_prefix_bloom_huge_page_tlb_size =
|
||||
FLAGS_memtable_prefix_bloom_huge_page_tlb_size;
|
||||
|
||||
|
||||
+267
-123
@@ -74,15 +74,16 @@
|
||||
#include "db/memtable.h"
|
||||
#include "db/table_cache.h"
|
||||
#include "db/version_edit.h"
|
||||
#include "db/writebuffer.h"
|
||||
#include "db/write_batch_internal.h"
|
||||
#include "rocksdb/comparator.h"
|
||||
#include "rocksdb/db.h"
|
||||
#include "rocksdb/env.h"
|
||||
#include "rocksdb/options.h"
|
||||
#include "rocksdb/immutable_options.h"
|
||||
#include "rocksdb/options.h"
|
||||
#include "rocksdb/write_buffer_manager.h"
|
||||
#include "table/scoped_arena_iterator.h"
|
||||
#include "util/file_reader_writer.h"
|
||||
#include "util/string_util.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
@@ -90,43 +91,114 @@ namespace {
|
||||
|
||||
class Repairer {
|
||||
public:
|
||||
Repairer(const std::string& dbname, const Options& options)
|
||||
Repairer(const std::string& dbname, const DBOptions& db_options,
|
||||
const std::vector<ColumnFamilyDescriptor>& column_families,
|
||||
const ColumnFamilyOptions& default_cf_opts,
|
||||
const ColumnFamilyOptions& unknown_cf_opts, bool create_unknown_cfs)
|
||||
: dbname_(dbname),
|
||||
env_(options.env),
|
||||
icmp_(options.comparator),
|
||||
options_(SanitizeOptions(dbname, &icmp_, options)),
|
||||
ioptions_(options_),
|
||||
env_(db_options.env),
|
||||
env_options_(),
|
||||
db_options_(SanitizeOptions(dbname_, db_options)),
|
||||
icmp_(default_cf_opts.comparator),
|
||||
default_cf_opts_(default_cf_opts),
|
||||
default_cf_iopts_(
|
||||
ImmutableCFOptions(Options(db_options_, default_cf_opts))),
|
||||
unknown_cf_opts_(unknown_cf_opts),
|
||||
create_unknown_cfs_(create_unknown_cfs),
|
||||
raw_table_cache_(
|
||||
// TableCache can be small since we expect each table to be opened
|
||||
// once.
|
||||
NewLRUCache(10, options_.table_cache_numshardbits)),
|
||||
NewLRUCache(10, db_options_.table_cache_numshardbits)),
|
||||
table_cache_(new TableCache(default_cf_iopts_, env_options_,
|
||||
raw_table_cache_.get())),
|
||||
wb_(db_options_.db_write_buffer_size),
|
||||
wc_(db_options_.delayed_write_rate),
|
||||
vset_(dbname_, &db_options_, env_options_, raw_table_cache_.get(), &wb_,
|
||||
&wc_),
|
||||
next_file_number_(1) {
|
||||
GetIntTblPropCollectorFactory(options, &int_tbl_prop_collector_factories_);
|
||||
for (const auto& cfd : column_families) {
|
||||
cf_name_to_opts_[cfd.name] = cfd.options;
|
||||
}
|
||||
}
|
||||
|
||||
table_cache_ =
|
||||
new TableCache(ioptions_, env_options_, raw_table_cache_.get());
|
||||
edit_ = new VersionEdit();
|
||||
const ColumnFamilyOptions* GetColumnFamilyOptions(
|
||||
const std::string& cf_name) {
|
||||
if (cf_name_to_opts_.find(cf_name) == cf_name_to_opts_.end()) {
|
||||
if (create_unknown_cfs_) {
|
||||
return &unknown_cf_opts_;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
return &cf_name_to_opts_[cf_name];
|
||||
}
|
||||
|
||||
// Adds a column family to the VersionSet with cf_options_ and updates
|
||||
// manifest.
|
||||
Status AddColumnFamily(const std::string& cf_name, uint32_t cf_id) {
|
||||
const auto* cf_opts = GetColumnFamilyOptions(cf_name);
|
||||
if (cf_opts == nullptr) {
|
||||
return Status::Corruption("Encountered unknown column family with name=" +
|
||||
cf_name + ", id=" + ToString(cf_id));
|
||||
}
|
||||
Options opts(db_options_, *cf_opts);
|
||||
MutableCFOptions mut_cf_opts(opts, ImmutableCFOptions(opts));
|
||||
|
||||
VersionEdit edit;
|
||||
edit.SetComparatorName(opts.comparator->Name());
|
||||
edit.SetLogNumber(0);
|
||||
edit.SetColumnFamily(cf_id);
|
||||
ColumnFamilyData* cfd;
|
||||
cfd = nullptr;
|
||||
edit.AddColumnFamily(cf_name);
|
||||
|
||||
mutex_.Lock();
|
||||
Status status = vset_.LogAndApply(cfd, mut_cf_opts, &edit, &mutex_,
|
||||
nullptr /* db_directory */,
|
||||
false /* new_descriptor_log */, cf_opts);
|
||||
mutex_.Unlock();
|
||||
return status;
|
||||
}
|
||||
|
||||
~Repairer() {
|
||||
delete table_cache_;
|
||||
raw_table_cache_.reset();
|
||||
delete edit_;
|
||||
}
|
||||
|
||||
Status Run() {
|
||||
Status status = FindFiles();
|
||||
if (status.ok()) {
|
||||
// Discard older manifests and start a fresh one
|
||||
for (size_t i = 0; i < manifests_.size(); i++) {
|
||||
ArchiveFile(dbname_ + "/" + manifests_[i]);
|
||||
}
|
||||
// Just create a DBImpl temporarily so we can reuse NewDB()
|
||||
DBImpl* db_impl = new DBImpl(db_options_, dbname_);
|
||||
status = db_impl->NewDB();
|
||||
delete db_impl;
|
||||
}
|
||||
if (status.ok()) {
|
||||
// Recover using the fresh manifest created by NewDB()
|
||||
status =
|
||||
vset_.Recover({{kDefaultColumnFamilyName, default_cf_opts_}}, false);
|
||||
}
|
||||
if (status.ok()) {
|
||||
// Need to scan existing SST files first so the column families are
|
||||
// created before we process WAL files
|
||||
ExtractMetaData();
|
||||
|
||||
// ExtractMetaData() uses table_fds_ to know which SST files' metadata to
|
||||
// extract -- we need to clear it here since metadata for existing SST
|
||||
// files has been extracted already
|
||||
table_fds_.clear();
|
||||
ConvertLogFilesToTables();
|
||||
ExtractMetaData();
|
||||
status = WriteDescriptor();
|
||||
status = AddTables();
|
||||
}
|
||||
if (status.ok()) {
|
||||
uint64_t bytes = 0;
|
||||
for (size_t i = 0; i < tables_.size(); i++) {
|
||||
bytes += tables_[i].meta.fd.GetFileSize();
|
||||
}
|
||||
Log(InfoLogLevel::WARN_LEVEL, options_.info_log,
|
||||
Log(InfoLogLevel::WARN_LEVEL, db_options_.info_log,
|
||||
"**** Repaired rocksdb %s; "
|
||||
"recovered %" ROCKSDB_PRIszt " files; %" PRIu64
|
||||
"bytes. "
|
||||
@@ -140,34 +212,41 @@ class Repairer {
|
||||
private:
|
||||
struct TableInfo {
|
||||
FileMetaData meta;
|
||||
uint32_t column_family_id;
|
||||
std::string column_family_name;
|
||||
SequenceNumber min_sequence;
|
||||
SequenceNumber max_sequence;
|
||||
};
|
||||
|
||||
std::string const dbname_;
|
||||
Env* const env_;
|
||||
const EnvOptions env_options_;
|
||||
const DBOptions db_options_;
|
||||
const InternalKeyComparator icmp_;
|
||||
std::vector<std::unique_ptr<IntTblPropCollectorFactory>>
|
||||
int_tbl_prop_collector_factories_;
|
||||
const Options options_;
|
||||
const ImmutableCFOptions ioptions_;
|
||||
const ColumnFamilyOptions default_cf_opts_;
|
||||
const ImmutableCFOptions default_cf_iopts_; // table_cache_ holds reference
|
||||
const ColumnFamilyOptions unknown_cf_opts_;
|
||||
const bool create_unknown_cfs_;
|
||||
std::shared_ptr<Cache> raw_table_cache_;
|
||||
TableCache* table_cache_;
|
||||
VersionEdit* edit_;
|
||||
WriteBufferManager wb_;
|
||||
WriteController wc_;
|
||||
VersionSet vset_;
|
||||
std::unordered_map<std::string, ColumnFamilyOptions> cf_name_to_opts_;
|
||||
InstrumentedMutex mutex_;
|
||||
|
||||
std::vector<std::string> manifests_;
|
||||
std::vector<FileDescriptor> table_fds_;
|
||||
std::vector<uint64_t> logs_;
|
||||
std::vector<TableInfo> tables_;
|
||||
uint64_t next_file_number_;
|
||||
const EnvOptions env_options_;
|
||||
|
||||
Status FindFiles() {
|
||||
std::vector<std::string> filenames;
|
||||
bool found_file = false;
|
||||
for (size_t path_id = 0; path_id < options_.db_paths.size(); path_id++) {
|
||||
for (size_t path_id = 0; path_id < db_options_.db_paths.size(); path_id++) {
|
||||
Status status =
|
||||
env_->GetChildren(options_.db_paths[path_id].path, &filenames);
|
||||
env_->GetChildren(db_options_.db_paths[path_id].path, &filenames);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
@@ -210,7 +289,7 @@ class Repairer {
|
||||
std::string logname = LogFileName(dbname_, logs_[i]);
|
||||
Status status = ConvertLogToTable(logs_[i]);
|
||||
if (!status.ok()) {
|
||||
Log(InfoLogLevel::WARN_LEVEL, options_.info_log,
|
||||
Log(InfoLogLevel::WARN_LEVEL, db_options_.info_log,
|
||||
"Log #%" PRIu64 ": ignoring conversion error: %s", logs_[i],
|
||||
status.ToString().c_str());
|
||||
}
|
||||
@@ -244,25 +323,26 @@ class Repairer {
|
||||
// Create the log reader.
|
||||
LogReporter reporter;
|
||||
reporter.env = env_;
|
||||
reporter.info_log = options_.info_log;
|
||||
reporter.info_log = db_options_.info_log;
|
||||
reporter.lognum = log;
|
||||
// We intentially make log::Reader do checksumming so that
|
||||
// We intentionally make log::Reader do checksumming so that
|
||||
// corruptions cause entire commits to be skipped instead of
|
||||
// propagating bad information (like overly large sequence
|
||||
// numbers).
|
||||
log::Reader reader(options_.info_log, std::move(lfile_reader), &reporter,
|
||||
log::Reader reader(db_options_.info_log, std::move(lfile_reader), &reporter,
|
||||
true /*enable checksum*/, 0 /*initial_offset*/, log);
|
||||
|
||||
// Initialize per-column family memtables
|
||||
for (auto* cfd : *vset_.GetColumnFamilySet()) {
|
||||
cfd->CreateNewMemtable(*cfd->GetLatestMutableCFOptions(),
|
||||
kMaxSequenceNumber);
|
||||
}
|
||||
auto cf_mems = new ColumnFamilyMemTablesImpl(vset_.GetColumnFamilySet());
|
||||
|
||||
// Read all the records and add to a memtable
|
||||
std::string scratch;
|
||||
Slice record;
|
||||
WriteBatch batch;
|
||||
WriteBuffer wb(options_.db_write_buffer_size);
|
||||
MemTable* mem =
|
||||
new MemTable(icmp_, ioptions_, MutableCFOptions(options_, ioptions_),
|
||||
&wb, kMaxSequenceNumber);
|
||||
auto cf_mems_default = new ColumnFamilyMemTablesDefault(mem);
|
||||
mem->Ref();
|
||||
int counter = 0;
|
||||
while (reader.ReadRecord(&record, &scratch)) {
|
||||
if (record.size() < WriteBatchInternal::kHeader) {
|
||||
@@ -271,47 +351,50 @@ class Repairer {
|
||||
continue;
|
||||
}
|
||||
WriteBatchInternal::SetContents(&batch, record);
|
||||
status = WriteBatchInternal::InsertInto(&batch, cf_mems_default, nullptr);
|
||||
status = WriteBatchInternal::InsertInto(&batch, cf_mems, nullptr);
|
||||
if (status.ok()) {
|
||||
counter += WriteBatchInternal::Count(&batch);
|
||||
} else {
|
||||
Log(InfoLogLevel::WARN_LEVEL,
|
||||
options_.info_log, "Log #%" PRIu64 ": ignoring %s", log,
|
||||
status.ToString().c_str());
|
||||
Log(InfoLogLevel::WARN_LEVEL, db_options_.info_log,
|
||||
"Log #%" PRIu64 ": ignoring %s", log, status.ToString().c_str());
|
||||
status = Status::OK(); // Keep going with rest of file
|
||||
}
|
||||
}
|
||||
|
||||
// Do not record a version edit for this conversion to a Table
|
||||
// since ExtractMetaData() will also generate edits.
|
||||
FileMetaData meta;
|
||||
meta.fd = FileDescriptor(next_file_number_++, 0, 0);
|
||||
{
|
||||
// Dump a table for each column family with entries in this log file.
|
||||
for (auto* cfd : *vset_.GetColumnFamilySet()) {
|
||||
// Do not record a version edit for this conversion to a Table
|
||||
// since ExtractMetaData() will also generate edits.
|
||||
MemTable* mem = cfd->mem();
|
||||
if (mem->IsEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
FileMetaData meta;
|
||||
meta.fd = FileDescriptor(next_file_number_++, 0, 0);
|
||||
ReadOptions ro;
|
||||
ro.total_order_seek = true;
|
||||
Arena arena;
|
||||
ScopedArenaIterator iter(mem->NewIterator(ro, &arena));
|
||||
MutableCFOptions mutable_cf_options(options_, ioptions_);
|
||||
status = BuildTable(
|
||||
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,
|
||||
nullptr /* internal_stats */, TableFileCreationReason::kRecovery);
|
||||
}
|
||||
delete mem->Unref();
|
||||
delete cf_mems_default;
|
||||
mem = nullptr;
|
||||
if (status.ok()) {
|
||||
if (meta.fd.GetFileSize() > 0) {
|
||||
table_fds_.push_back(meta.fd);
|
||||
dbname_, env_, *cfd->ioptions(), *cfd->GetLatestMutableCFOptions(),
|
||||
env_options_, table_cache_, iter.get(), &meta,
|
||||
cfd->internal_comparator(), cfd->int_tbl_prop_collector_factories(),
|
||||
cfd->GetID(), cfd->GetName(), {}, kMaxSequenceNumber, kNoCompression,
|
||||
CompressionOptions(), false, nullptr /* internal_stats */,
|
||||
TableFileCreationReason::kRecovery);
|
||||
Log(InfoLogLevel::INFO_LEVEL, db_options_.info_log,
|
||||
"Log #%" PRIu64 ": %d ops saved to Table #%" PRIu64 " %s", log,
|
||||
counter, meta.fd.GetNumber(), status.ToString().c_str());
|
||||
if (status.ok()) {
|
||||
if (meta.fd.GetFileSize() > 0) {
|
||||
table_fds_.push_back(meta.fd);
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Log(InfoLogLevel::INFO_LEVEL, options_.info_log,
|
||||
"Log #%" PRIu64 ": %d ops saved to Table #%" PRIu64 " %s",
|
||||
log, counter, meta.fd.GetNumber(), status.ToString().c_str());
|
||||
delete cf_mems;
|
||||
return status;
|
||||
}
|
||||
|
||||
@@ -322,13 +405,12 @@ class Repairer {
|
||||
Status status = ScanTable(&t);
|
||||
if (!status.ok()) {
|
||||
std::string fname = TableFileName(
|
||||
options_.db_paths, t.meta.fd.GetNumber(), t.meta.fd.GetPathId());
|
||||
db_options_.db_paths, t.meta.fd.GetNumber(), t.meta.fd.GetPathId());
|
||||
char file_num_buf[kFormatFileNumberBufSize];
|
||||
FormatFileNumber(t.meta.fd.GetNumber(), t.meta.fd.GetPathId(),
|
||||
file_num_buf, sizeof(file_num_buf));
|
||||
Log(InfoLogLevel::WARN_LEVEL, options_.info_log,
|
||||
"Table #%s: ignoring %s", file_num_buf,
|
||||
status.ToString().c_str());
|
||||
Log(InfoLogLevel::WARN_LEVEL, db_options_.info_log,
|
||||
"Table #%s: ignoring %s", file_num_buf, status.ToString().c_str());
|
||||
ArchiveFile(fname);
|
||||
} else {
|
||||
tables_.push_back(t);
|
||||
@@ -337,16 +419,52 @@ class Repairer {
|
||||
}
|
||||
|
||||
Status ScanTable(TableInfo* t) {
|
||||
std::string fname = TableFileName(options_.db_paths, t->meta.fd.GetNumber(),
|
||||
t->meta.fd.GetPathId());
|
||||
std::string fname = TableFileName(
|
||||
db_options_.db_paths, t->meta.fd.GetNumber(), t->meta.fd.GetPathId());
|
||||
int counter = 0;
|
||||
uint64_t file_size;
|
||||
Status status = env_->GetFileSize(fname, &file_size);
|
||||
t->meta.fd = FileDescriptor(t->meta.fd.GetNumber(), t->meta.fd.GetPathId(),
|
||||
file_size);
|
||||
std::shared_ptr<const TableProperties> props;
|
||||
if (status.ok()) {
|
||||
status = table_cache_->GetTableProperties(env_options_, icmp_, t->meta.fd,
|
||||
&props);
|
||||
}
|
||||
if (status.ok()) {
|
||||
t->column_family_id = static_cast<uint32_t>(props->column_family_id);
|
||||
if (t->column_family_id ==
|
||||
TablePropertiesCollectorFactory::Context::kUnknownColumnFamily) {
|
||||
Log(InfoLogLevel::WARN_LEVEL, db_options_.info_log,
|
||||
"Table #%" PRIu64
|
||||
": column family unknown (probably due to legacy format); "
|
||||
"adding to default column family id 0.",
|
||||
t->meta.fd.GetNumber());
|
||||
t->column_family_id = 0;
|
||||
}
|
||||
|
||||
if (vset_.GetColumnFamilySet()->GetColumnFamily(t->column_family_id) ==
|
||||
nullptr) {
|
||||
status =
|
||||
AddColumnFamily(props->column_family_name, t->column_family_id);
|
||||
}
|
||||
}
|
||||
ColumnFamilyData* cfd = nullptr;
|
||||
if (status.ok()) {
|
||||
cfd = vset_.GetColumnFamilySet()->GetColumnFamily(t->column_family_id);
|
||||
if (cfd->GetName() != props->column_family_name) {
|
||||
Log(InfoLogLevel::ERROR_LEVEL, db_options_.info_log,
|
||||
"Table #%" PRIu64
|
||||
": inconsistent column family name '%s'; expected '%s' for column "
|
||||
"family id %" PRIu32 ".",
|
||||
t->meta.fd.GetNumber(), props->column_family_name.c_str(),
|
||||
cfd->GetName().c_str(), t->column_family_id);
|
||||
status = Status::Corruption(dbname_, "inconsistent column family name");
|
||||
}
|
||||
}
|
||||
if (status.ok()) {
|
||||
InternalIterator* iter = table_cache_->NewIterator(
|
||||
ReadOptions(), env_options_, icmp_, t->meta.fd);
|
||||
ReadOptions(), env_options_, cfd->internal_comparator(), t->meta.fd);
|
||||
bool empty = true;
|
||||
ParsedInternalKey parsed;
|
||||
t->min_sequence = 0;
|
||||
@@ -354,9 +472,9 @@ class Repairer {
|
||||
for (iter->SeekToFirst(); iter->Valid(); iter->Next()) {
|
||||
Slice key = iter->key();
|
||||
if (!ParseInternalKey(key, &parsed)) {
|
||||
Log(InfoLogLevel::ERROR_LEVEL,
|
||||
options_.info_log, "Table #%" PRIu64 ": unparsable key %s",
|
||||
t->meta.fd.GetNumber(), EscapeString(key).c_str());
|
||||
Log(InfoLogLevel::ERROR_LEVEL, db_options_.info_log,
|
||||
"Table #%" PRIu64 ": unparsable key %s", t->meta.fd.GetNumber(),
|
||||
EscapeString(key).c_str());
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -377,70 +495,51 @@ class Repairer {
|
||||
status = iter->status();
|
||||
}
|
||||
delete iter;
|
||||
|
||||
Log(InfoLogLevel::INFO_LEVEL, db_options_.info_log,
|
||||
"Table #%" PRIu64 ": %d entries %s", t->meta.fd.GetNumber(), counter,
|
||||
status.ToString().c_str());
|
||||
}
|
||||
Log(InfoLogLevel::INFO_LEVEL,
|
||||
options_.info_log, "Table #%" PRIu64 ": %d entries %s",
|
||||
t->meta.fd.GetNumber(), counter, status.ToString().c_str());
|
||||
return status;
|
||||
}
|
||||
|
||||
Status WriteDescriptor() {
|
||||
std::string tmp = TempFileName(dbname_, 1);
|
||||
unique_ptr<WritableFile> file;
|
||||
EnvOptions env_options = env_->OptimizeForManifestWrite(env_options_);
|
||||
Status status = env_->NewWritableFile(tmp, &file, env_options);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
|
||||
Status AddTables() {
|
||||
std::unordered_map<uint32_t, std::vector<const TableInfo*>> cf_id_to_tables;
|
||||
SequenceNumber max_sequence = 0;
|
||||
for (size_t i = 0; i < tables_.size(); i++) {
|
||||
cf_id_to_tables[tables_[i].column_family_id].push_back(&tables_[i]);
|
||||
if (max_sequence < tables_[i].max_sequence) {
|
||||
max_sequence = tables_[i].max_sequence;
|
||||
}
|
||||
}
|
||||
vset_.SetLastSequence(max_sequence);
|
||||
|
||||
edit_->SetComparatorName(icmp_.user_comparator()->Name());
|
||||
edit_->SetLogNumber(0);
|
||||
edit_->SetNextFile(next_file_number_);
|
||||
edit_->SetLastSequence(max_sequence);
|
||||
for (const auto& cf_id_and_tables : cf_id_to_tables) {
|
||||
auto* cfd =
|
||||
vset_.GetColumnFamilySet()->GetColumnFamily(cf_id_and_tables.first);
|
||||
VersionEdit edit;
|
||||
edit.SetComparatorName(cfd->user_comparator()->Name());
|
||||
edit.SetLogNumber(0);
|
||||
edit.SetNextFile(next_file_number_);
|
||||
edit.SetColumnFamily(cfd->GetID());
|
||||
|
||||
for (size_t i = 0; i < tables_.size(); i++) {
|
||||
// TODO(opt): separate out into multiple levels
|
||||
const TableInfo& t = tables_[i];
|
||||
edit_->AddFile(0, t.meta.fd.GetNumber(), t.meta.fd.GetPathId(),
|
||||
t.meta.fd.GetFileSize(), t.meta.smallest, t.meta.largest,
|
||||
t.min_sequence, t.max_sequence,
|
||||
t.meta.marked_for_compaction);
|
||||
}
|
||||
|
||||
//fprintf(stderr, "NewDescriptor:\n%s\n", edit_.DebugString().c_str());
|
||||
{
|
||||
unique_ptr<WritableFileWriter> file_writer(
|
||||
new WritableFileWriter(std::move(file), env_options));
|
||||
log::Writer log(std::move(file_writer), 0, false);
|
||||
std::string record;
|
||||
edit_->EncodeTo(&record);
|
||||
status = log.AddRecord(record);
|
||||
}
|
||||
|
||||
if (!status.ok()) {
|
||||
env_->DeleteFile(tmp);
|
||||
} else {
|
||||
// Discard older manifests
|
||||
for (size_t i = 0; i < manifests_.size(); i++) {
|
||||
ArchiveFile(dbname_ + "/" + manifests_[i]);
|
||||
for (const auto* table : cf_id_and_tables.second) {
|
||||
edit.AddFile(0, table->meta.fd.GetNumber(), table->meta.fd.GetPathId(),
|
||||
table->meta.fd.GetFileSize(), table->meta.smallest,
|
||||
table->meta.largest, table->min_sequence,
|
||||
table->max_sequence, table->meta.marked_for_compaction);
|
||||
}
|
||||
|
||||
// Install new manifest
|
||||
status = env_->RenameFile(tmp, DescriptorFileName(dbname_, 1));
|
||||
if (status.ok()) {
|
||||
status = SetCurrentFile(env_, dbname_, 1, nullptr);
|
||||
} else {
|
||||
env_->DeleteFile(tmp);
|
||||
mutex_.Lock();
|
||||
Status status = vset_.LogAndApply(
|
||||
cfd, *cfd->GetLatestMutableCFOptions(), &edit, &mutex_,
|
||||
nullptr /* db_directory */, false /* new_descriptor_log */);
|
||||
mutex_.Unlock();
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
}
|
||||
return status;
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
void ArchiveFile(const std::string& fname) {
|
||||
@@ -459,15 +558,60 @@ class Repairer {
|
||||
new_file.append("/");
|
||||
new_file.append((slash == nullptr) ? fname.c_str() : slash + 1);
|
||||
Status s = env_->RenameFile(fname, new_file);
|
||||
Log(InfoLogLevel::INFO_LEVEL,
|
||||
options_.info_log, "Archiving %s: %s\n",
|
||||
Log(InfoLogLevel::INFO_LEVEL, db_options_.info_log, "Archiving %s: %s\n",
|
||||
fname.c_str(), s.ToString().c_str());
|
||||
}
|
||||
};
|
||||
} // namespace
|
||||
|
||||
Status GetDefaultCFOptions(
|
||||
const std::vector<ColumnFamilyDescriptor>& column_families,
|
||||
ColumnFamilyOptions* res) {
|
||||
assert(res != nullptr);
|
||||
auto iter = std::find_if(column_families.begin(), column_families.end(),
|
||||
[](const ColumnFamilyDescriptor& cfd) {
|
||||
return cfd.name == kDefaultColumnFamilyName;
|
||||
});
|
||||
if (iter == column_families.end()) {
|
||||
return Status::InvalidArgument(
|
||||
"column_families", "Must contain entry for default column family");
|
||||
}
|
||||
*res = iter->options;
|
||||
return Status::OK();
|
||||
}
|
||||
} // anonymous namespace
|
||||
|
||||
Status RepairDB(const std::string& dbname, const DBOptions& db_options,
|
||||
const std::vector<ColumnFamilyDescriptor>& column_families) {
|
||||
ColumnFamilyOptions default_cf_opts;
|
||||
Status status = GetDefaultCFOptions(column_families, &default_cf_opts);
|
||||
if (status.ok()) {
|
||||
Repairer repairer(dbname, db_options, column_families, default_cf_opts,
|
||||
ColumnFamilyOptions() /* unknown_cf_opts */,
|
||||
false /* create_unknown_cfs */);
|
||||
status = repairer.Run();
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
Status RepairDB(const std::string& dbname, const DBOptions& db_options,
|
||||
const std::vector<ColumnFamilyDescriptor>& column_families,
|
||||
const ColumnFamilyOptions& unknown_cf_opts) {
|
||||
ColumnFamilyOptions default_cf_opts;
|
||||
Status status = GetDefaultCFOptions(column_families, &default_cf_opts);
|
||||
if (status.ok()) {
|
||||
Repairer repairer(dbname, db_options, column_families, default_cf_opts,
|
||||
unknown_cf_opts, true /* create_unknown_cfs */);
|
||||
status = repairer.Run();
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
Status RepairDB(const std::string& dbname, const Options& options) {
|
||||
Repairer repairer(dbname, options);
|
||||
DBOptions db_options(options);
|
||||
ColumnFamilyOptions cf_options(options);
|
||||
Repairer repairer(dbname, db_options, {}, cf_options /* default_cf_opts */,
|
||||
cf_options /* unknown_cf_opts */,
|
||||
true /* create_unknown_cfs */);
|
||||
return repairer.Run();
|
||||
}
|
||||
|
||||
|
||||
+105
-6
@@ -9,9 +9,11 @@
|
||||
|
||||
#include "db/db_impl.h"
|
||||
#include "db/db_test_util.h"
|
||||
#include "rocksdb/comparator.h"
|
||||
#include "rocksdb/db.h"
|
||||
#include "rocksdb/transaction_log.h"
|
||||
#include "util/file_util.h"
|
||||
#include "util/string_util.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
@@ -49,7 +51,7 @@ TEST_F(RepairTest, LostManifest) {
|
||||
Close();
|
||||
ASSERT_OK(env_->FileExists(manifest_path));
|
||||
ASSERT_OK(env_->DeleteFile(manifest_path));
|
||||
RepairDB(dbname_, CurrentOptions());
|
||||
ASSERT_OK(RepairDB(dbname_, CurrentOptions()));
|
||||
Reopen(CurrentOptions());
|
||||
|
||||
ASSERT_EQ(Get("key"), "val");
|
||||
@@ -70,7 +72,7 @@ TEST_F(RepairTest, CorruptManifest) {
|
||||
Close();
|
||||
ASSERT_OK(env_->FileExists(manifest_path));
|
||||
CreateFile(env_, manifest_path, "blah");
|
||||
RepairDB(dbname_, CurrentOptions());
|
||||
ASSERT_OK(RepairDB(dbname_, CurrentOptions()));
|
||||
Reopen(CurrentOptions());
|
||||
|
||||
ASSERT_EQ(Get("key"), "val");
|
||||
@@ -96,7 +98,7 @@ TEST_F(RepairTest, IncompleteManifest) {
|
||||
ASSERT_OK(env_->FileExists(new_manifest_path));
|
||||
// Replace the manifest with one that is only aware of the first SST file.
|
||||
CopyFile(orig_manifest_path + ".tmp", new_manifest_path);
|
||||
RepairDB(dbname_, CurrentOptions());
|
||||
ASSERT_OK(RepairDB(dbname_, CurrentOptions()));
|
||||
Reopen(CurrentOptions());
|
||||
|
||||
ASSERT_EQ(Get("key"), "val");
|
||||
@@ -115,7 +117,7 @@ TEST_F(RepairTest, LostSst) {
|
||||
ASSERT_OK(env_->DeleteFile(sst_path));
|
||||
|
||||
Close();
|
||||
RepairDB(dbname_, CurrentOptions());
|
||||
ASSERT_OK(RepairDB(dbname_, CurrentOptions()));
|
||||
Reopen(CurrentOptions());
|
||||
|
||||
// Exactly one of the key-value pairs should be in the DB now.
|
||||
@@ -134,7 +136,7 @@ TEST_F(RepairTest, CorruptSst) {
|
||||
CreateFile(env_, sst_path, "blah");
|
||||
|
||||
Close();
|
||||
RepairDB(dbname_, CurrentOptions());
|
||||
ASSERT_OK(RepairDB(dbname_, CurrentOptions()));
|
||||
Reopen(CurrentOptions());
|
||||
|
||||
// Exactly one of the key-value pairs should be in the DB now.
|
||||
@@ -159,7 +161,7 @@ TEST_F(RepairTest, UnflushedSst) {
|
||||
Close();
|
||||
ASSERT_OK(env_->FileExists(manifest_path));
|
||||
ASSERT_OK(env_->DeleteFile(manifest_path));
|
||||
RepairDB(dbname_, CurrentOptions());
|
||||
ASSERT_OK(RepairDB(dbname_, CurrentOptions()));
|
||||
Reopen(CurrentOptions());
|
||||
|
||||
ASSERT_OK(dbfull()->GetSortedWalFiles(wal_files));
|
||||
@@ -169,6 +171,103 @@ TEST_F(RepairTest, UnflushedSst) {
|
||||
ASSERT_EQ(Get("key"), "val");
|
||||
}
|
||||
|
||||
TEST_F(RepairTest, RepairMultipleColumnFamilies) {
|
||||
// Verify repair logic associates SST files with their original column
|
||||
// families.
|
||||
const int kNumCfs = 3;
|
||||
const int kEntriesPerCf = 2;
|
||||
DestroyAndReopen(CurrentOptions());
|
||||
CreateAndReopenWithCF({"pikachu1", "pikachu2"}, CurrentOptions());
|
||||
for (int i = 0; i < kNumCfs; ++i) {
|
||||
for (int j = 0; j < kEntriesPerCf; ++j) {
|
||||
Put(i, "key" + ToString(j), "val" + ToString(j));
|
||||
if (j == kEntriesPerCf - 1 && i == kNumCfs - 1) {
|
||||
// Leave one unflushed so we can verify WAL entries are properly
|
||||
// associated with column families.
|
||||
continue;
|
||||
}
|
||||
Flush(i);
|
||||
}
|
||||
}
|
||||
|
||||
// Need to get path before Close() deletes db_, but delete it after Close() to
|
||||
// ensure Close() doesn't re-create the manifest.
|
||||
std::string manifest_path =
|
||||
DescriptorFileName(dbname_, dbfull()->TEST_Current_Manifest_FileNo());
|
||||
Close();
|
||||
ASSERT_OK(env_->FileExists(manifest_path));
|
||||
ASSERT_OK(env_->DeleteFile(manifest_path));
|
||||
|
||||
ASSERT_OK(RepairDB(dbname_, CurrentOptions()));
|
||||
|
||||
ReopenWithColumnFamilies({"default", "pikachu1", "pikachu2"},
|
||||
CurrentOptions());
|
||||
for (int i = 0; i < kNumCfs; ++i) {
|
||||
for (int j = 0; j < kEntriesPerCf; ++j) {
|
||||
ASSERT_EQ(Get(i, "key" + ToString(j)), "val" + ToString(j));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(RepairTest, RepairColumnFamilyOptions) {
|
||||
// Verify repair logic uses correct ColumnFamilyOptions when repairing a
|
||||
// database with different options for column families.
|
||||
const int kNumCfs = 2;
|
||||
const int kEntriesPerCf = 2;
|
||||
|
||||
Options opts(CurrentOptions()), rev_opts(CurrentOptions());
|
||||
opts.comparator = BytewiseComparator();
|
||||
rev_opts.comparator = ReverseBytewiseComparator();
|
||||
|
||||
DestroyAndReopen(opts);
|
||||
CreateColumnFamilies({"reverse"}, rev_opts);
|
||||
ReopenWithColumnFamilies({"default", "reverse"},
|
||||
std::vector<Options>{opts, rev_opts});
|
||||
for (int i = 0; i < kNumCfs; ++i) {
|
||||
for (int j = 0; j < kEntriesPerCf; ++j) {
|
||||
Put(i, "key" + ToString(j), "val" + ToString(j));
|
||||
if (i == kNumCfs - 1 && j == kEntriesPerCf - 1) {
|
||||
// Leave one unflushed so we can verify RepairDB's flush logic
|
||||
continue;
|
||||
}
|
||||
Flush(i);
|
||||
}
|
||||
}
|
||||
Close();
|
||||
|
||||
// RepairDB() records the comparator in the manifest, and DB::Open would fail
|
||||
// if a different comparator were used.
|
||||
ASSERT_OK(RepairDB(dbname_, opts, {{"default", opts}, {"reverse", rev_opts}},
|
||||
opts /* unknown_cf_opts */));
|
||||
ASSERT_OK(TryReopenWithColumnFamilies({"default", "reverse"},
|
||||
std::vector<Options>{opts, rev_opts}));
|
||||
for (int i = 0; i < kNumCfs; ++i) {
|
||||
for (int j = 0; j < kEntriesPerCf; ++j) {
|
||||
ASSERT_EQ(Get(i, "key" + ToString(j)), "val" + ToString(j));
|
||||
}
|
||||
}
|
||||
|
||||
// Examine table properties to verify RepairDB() used the right options when
|
||||
// converting WAL->SST
|
||||
TablePropertiesCollection fname_to_props;
|
||||
db_->GetPropertiesOfAllTables(handles_[1], &fname_to_props);
|
||||
ASSERT_EQ(fname_to_props.size(), 2U);
|
||||
for (const auto& fname_and_props : fname_to_props) {
|
||||
ASSERT_EQ(InternalKeyComparator(rev_opts.comparator).Name(),
|
||||
fname_and_props.second->comparator_name);
|
||||
}
|
||||
|
||||
// Also check comparator when it's provided via "unknown" CF options
|
||||
ASSERT_OK(RepairDB(dbname_, opts, {{"default", opts}},
|
||||
rev_opts /* unknown_cf_opts */));
|
||||
ASSERT_OK(TryReopenWithColumnFamilies({"default", "reverse"},
|
||||
std::vector<Options>{opts, rev_opts}));
|
||||
for (int i = 0; i < kNumCfs; ++i) {
|
||||
for (int j = 0; j < kEntriesPerCf; ++j) {
|
||||
ASSERT_EQ(Get(i, "key" + ToString(j)), "val" + ToString(j));
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace rocksdb
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
-24
@@ -1,24 +0,0 @@
|
||||
// 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.
|
||||
|
||||
#include "rocksdb/slice.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
Slice::Slice(const SliceParts& parts, std::string* buf) {
|
||||
size_t length = 0;
|
||||
for (int i = 0; i < parts.num_parts; ++i) {
|
||||
length += parts.parts[i].size();
|
||||
}
|
||||
buf->reserve(length);
|
||||
|
||||
for (int i = 0; i < parts.num_parts; ++i) {
|
||||
buf->append(parts.parts[i].data(), parts.parts[i].size());
|
||||
}
|
||||
data_ = buf->data();
|
||||
size_ = buf->size();
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
@@ -276,7 +276,7 @@ void TestCustomizedTablePropertiesCollector(
|
||||
new test::StringSource(fwf->contents())));
|
||||
TableProperties* props;
|
||||
Status s = ReadTableProperties(fake_file_reader.get(), fwf->contents().size(),
|
||||
magic_number, Env::Default(), nullptr, &props);
|
||||
magic_number, ioptions, &props);
|
||||
std::unique_ptr<TableProperties> props_guard(props);
|
||||
ASSERT_OK(s);
|
||||
|
||||
@@ -417,7 +417,7 @@ void TestInternalKeyPropertiesCollector(
|
||||
TableProperties* props;
|
||||
Status s =
|
||||
ReadTableProperties(reader.get(), fwf->contents().size(), magic_number,
|
||||
Env::Default(), nullptr, &props);
|
||||
ioptions, &props);
|
||||
ASSERT_OK(s);
|
||||
|
||||
std::unique_ptr<TableProperties> props_guard(props);
|
||||
|
||||
+10
-19
@@ -82,30 +82,24 @@ bool VersionEdit::EncodeTo(std::string* dst) const {
|
||||
PutLengthPrefixedSlice(dst, comparator_);
|
||||
}
|
||||
if (has_log_number_) {
|
||||
PutVarint32(dst, kLogNumber);
|
||||
PutVarint64(dst, log_number_);
|
||||
PutVarint32Varint64(dst, kLogNumber, log_number_);
|
||||
}
|
||||
if (has_prev_log_number_) {
|
||||
PutVarint32(dst, kPrevLogNumber);
|
||||
PutVarint64(dst, prev_log_number_);
|
||||
PutVarint32Varint64(dst, kPrevLogNumber, prev_log_number_);
|
||||
}
|
||||
if (has_next_file_number_) {
|
||||
PutVarint32(dst, kNextFileNumber);
|
||||
PutVarint64(dst, next_file_number_);
|
||||
PutVarint32Varint64(dst, kNextFileNumber, next_file_number_);
|
||||
}
|
||||
if (has_last_sequence_) {
|
||||
PutVarint32(dst, kLastSequence);
|
||||
PutVarint64(dst, last_sequence_);
|
||||
PutVarint32Varint64(dst, kLastSequence, last_sequence_);
|
||||
}
|
||||
if (has_max_column_family_) {
|
||||
PutVarint32(dst, kMaxColumnFamily);
|
||||
PutVarint32(dst, max_column_family_);
|
||||
PutVarint32Varint32(dst, kMaxColumnFamily, max_column_family_);
|
||||
}
|
||||
|
||||
for (const auto& deleted : deleted_files_) {
|
||||
PutVarint32(dst, kDeletedFile);
|
||||
PutVarint32(dst, deleted.first /* level */);
|
||||
PutVarint64(dst, deleted.second /* file number */);
|
||||
PutVarint32Varint32Varint64(dst, kDeletedFile, deleted.first /* level */,
|
||||
deleted.second /* file number */);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < new_files_.size(); i++) {
|
||||
@@ -124,8 +118,7 @@ bool VersionEdit::EncodeTo(std::string* dst) const {
|
||||
} else {
|
||||
PutVarint32(dst, kNewFile3);
|
||||
}
|
||||
PutVarint32(dst, new_files_[i].first); // level
|
||||
PutVarint64(dst, f.fd.GetNumber());
|
||||
PutVarint32Varint64(dst, new_files_[i].first /* level */, f.fd.GetNumber());
|
||||
if (f.fd.GetPathId() != 0 && !has_customized_fields) {
|
||||
// kNewFile3
|
||||
PutVarint32(dst, f.fd.GetPathId());
|
||||
@@ -133,8 +126,7 @@ bool VersionEdit::EncodeTo(std::string* dst) const {
|
||||
PutVarint64(dst, f.fd.GetFileSize());
|
||||
PutLengthPrefixedSlice(dst, f.smallest.Encode());
|
||||
PutLengthPrefixedSlice(dst, f.largest.Encode());
|
||||
PutVarint64(dst, f.smallest_seqno);
|
||||
PutVarint64(dst, f.largest_seqno);
|
||||
PutVarint64Varint64(dst, f.smallest_seqno, f.largest_seqno);
|
||||
if (has_customized_fields) {
|
||||
// Customized fields' format:
|
||||
// +-----------------------------+
|
||||
@@ -181,8 +173,7 @@ bool VersionEdit::EncodeTo(std::string* dst) const {
|
||||
|
||||
// 0 is default and does not need to be explicitly written
|
||||
if (column_family_ != 0) {
|
||||
PutVarint32(dst, kColumnFamily);
|
||||
PutVarint32(dst, column_family_);
|
||||
PutVarint32Varint32(dst, kColumnFamily, column_family_);
|
||||
}
|
||||
|
||||
if (is_column_family_add_) {
|
||||
|
||||
+1
-1
@@ -193,7 +193,7 @@ class VersionEdit {
|
||||
f.smallest_seqno = smallest_seqno;
|
||||
f.largest_seqno = largest_seqno;
|
||||
f.marked_for_compaction = marked_for_compaction;
|
||||
new_files_.emplace_back(level, f);
|
||||
new_files_.emplace_back(level, std::move(f));
|
||||
}
|
||||
|
||||
void AddFile(int level, const FileMetaData& f) {
|
||||
|
||||
+53
-48
@@ -30,11 +30,12 @@
|
||||
#include "db/log_writer.h"
|
||||
#include "db/memtable.h"
|
||||
#include "db/merge_context.h"
|
||||
#include "db/merge_helper.h"
|
||||
#include "db/table_cache.h"
|
||||
#include "db/version_builder.h"
|
||||
#include "db/writebuffer.h"
|
||||
#include "rocksdb/env.h"
|
||||
#include "rocksdb/merge_operator.h"
|
||||
#include "rocksdb/write_buffer_manager.h"
|
||||
#include "table/format.h"
|
||||
#include "table/get_context.h"
|
||||
#include "table/internal_iterator.h"
|
||||
@@ -596,8 +597,7 @@ Status Version::GetTableProperties(std::shared_ptr<const TableProperties>* tp,
|
||||
new RandomAccessFileReader(std::move(file)));
|
||||
s = ReadTableProperties(
|
||||
file_reader.get(), file_meta->fd.GetFileSize(),
|
||||
Footer::kInvalidTableMagicNumber /* table's magic number */, vset_->env_,
|
||||
ioptions->info_log, &raw_table_properties);
|
||||
Footer::kInvalidTableMagicNumber /* table's magic number */, *ioptions, &raw_table_properties);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
@@ -973,22 +973,9 @@ void Version::Get(const ReadOptions& read_options, const LookupKey& k,
|
||||
}
|
||||
// merge_operands are in saver and we hit the beginning of the key history
|
||||
// do a final merge of nullptr and operands;
|
||||
bool merge_success = false;
|
||||
{
|
||||
StopWatchNano timer(env_, db_statistics_ != nullptr);
|
||||
PERF_TIMER_GUARD(merge_operator_time_nanos);
|
||||
merge_success = merge_operator_->FullMerge(
|
||||
user_key, nullptr, merge_context->GetOperands(), value, info_log_);
|
||||
RecordTick(db_statistics_, MERGE_OPERATION_TOTAL_TIME,
|
||||
timer.ElapsedNanos());
|
||||
}
|
||||
if (merge_success) {
|
||||
*status = Status::OK();
|
||||
} else {
|
||||
RecordTick(db_statistics_, NUMBER_MERGE_FAILURES);
|
||||
*status = Status::Corruption("could not perform end-of-key merge for ",
|
||||
user_key);
|
||||
}
|
||||
*status = MergeHelper::TimedFullMerge(merge_operator_, user_key, nullptr,
|
||||
merge_context->GetOperands(), value,
|
||||
info_log_, db_statistics_, env_);
|
||||
} else {
|
||||
if (key_exists != nullptr) {
|
||||
*key_exists = false;
|
||||
@@ -2089,20 +2076,20 @@ struct VersionSet::ManifestWriter {
|
||||
bool done;
|
||||
InstrumentedCondVar cv;
|
||||
ColumnFamilyData* cfd;
|
||||
VersionEdit* edit;
|
||||
const autovector<VersionEdit*>& edit_list;
|
||||
|
||||
explicit ManifestWriter(InstrumentedMutex* mu, ColumnFamilyData* _cfd,
|
||||
VersionEdit* e)
|
||||
: done(false), cv(mu), cfd(_cfd), edit(e) {}
|
||||
const autovector<VersionEdit*>& e)
|
||||
: done(false), cv(mu), cfd(_cfd), edit_list(e) {}
|
||||
};
|
||||
|
||||
VersionSet::VersionSet(const std::string& dbname, const DBOptions* db_options,
|
||||
const EnvOptions& storage_options, Cache* table_cache,
|
||||
WriteBuffer* write_buffer,
|
||||
WriteBufferManager* write_buffer_manager,
|
||||
WriteController* write_controller)
|
||||
: column_family_set_(new ColumnFamilySet(
|
||||
dbname, db_options, storage_options, table_cache,
|
||||
write_buffer, write_controller)),
|
||||
: column_family_set_(
|
||||
new ColumnFamilySet(dbname, db_options, storage_options, table_cache,
|
||||
write_buffer_manager, write_controller)),
|
||||
env_(db_options->env),
|
||||
dbname_(dbname),
|
||||
db_options_(db_options),
|
||||
@@ -2162,20 +2149,34 @@ void VersionSet::AppendVersion(ColumnFamilyData* column_family_data,
|
||||
|
||||
Status VersionSet::LogAndApply(ColumnFamilyData* column_family_data,
|
||||
const MutableCFOptions& mutable_cf_options,
|
||||
VersionEdit* edit, InstrumentedMutex* mu,
|
||||
Directory* db_directory, bool new_descriptor_log,
|
||||
const autovector<VersionEdit*>& edit_list,
|
||||
InstrumentedMutex* mu, Directory* db_directory,
|
||||
bool new_descriptor_log,
|
||||
const ColumnFamilyOptions* new_cf_options) {
|
||||
mu->AssertHeld();
|
||||
// num of edits
|
||||
auto num_edits = edit_list.size();
|
||||
if (num_edits == 0) {
|
||||
return Status::OK();
|
||||
} else if (num_edits > 1) {
|
||||
#ifndef NDEBUG
|
||||
// no group commits for column family add or drop
|
||||
for (auto& edit : edit_list) {
|
||||
assert(!edit->IsColumnFamilyManipulation());
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// column_family_data can be nullptr only if this is column_family_add.
|
||||
// in that case, we also need to specify ColumnFamilyOptions
|
||||
if (column_family_data == nullptr) {
|
||||
assert(edit->is_column_family_add_);
|
||||
assert(num_edits == 1);
|
||||
assert(edit_list[0]->is_column_family_add_);
|
||||
assert(new_cf_options != nullptr);
|
||||
}
|
||||
|
||||
// queue our request
|
||||
ManifestWriter w(mu, column_family_data, edit);
|
||||
ManifestWriter w(mu, column_family_data, edit_list);
|
||||
manifest_writers_.push_back(&w);
|
||||
while (!w.done && &w != manifest_writers_.front()) {
|
||||
w.cv.Wait();
|
||||
@@ -2195,7 +2196,7 @@ Status VersionSet::LogAndApply(ColumnFamilyData* column_family_data,
|
||||
return Status::ShutdownInProgress();
|
||||
}
|
||||
|
||||
std::vector<VersionEdit*> batch_edits;
|
||||
autovector<VersionEdit*> batch_edits;
|
||||
Version* v = nullptr;
|
||||
std::unique_ptr<BaseReferencedVersionBuilder> builder_guard(nullptr);
|
||||
|
||||
@@ -2203,24 +2204,26 @@ Status VersionSet::LogAndApply(ColumnFamilyData* column_family_data,
|
||||
ManifestWriter* last_writer = &w;
|
||||
assert(!manifest_writers_.empty());
|
||||
assert(manifest_writers_.front() == &w);
|
||||
if (edit->IsColumnFamilyManipulation()) {
|
||||
if (w.edit_list.front()->IsColumnFamilyManipulation()) {
|
||||
// no group commits for column family add or drop
|
||||
LogAndApplyCFHelper(edit);
|
||||
batch_edits.push_back(edit);
|
||||
LogAndApplyCFHelper(w.edit_list.front());
|
||||
batch_edits.push_back(w.edit_list.front());
|
||||
} else {
|
||||
v = new Version(column_family_data, this, current_version_number_++);
|
||||
builder_guard.reset(new BaseReferencedVersionBuilder(column_family_data));
|
||||
auto* builder = builder_guard->version_builder();
|
||||
for (const auto& writer : manifest_writers_) {
|
||||
if (writer->edit->IsColumnFamilyManipulation() ||
|
||||
if (writer->edit_list.front()->IsColumnFamilyManipulation() ||
|
||||
writer->cfd->GetID() != column_family_data->GetID()) {
|
||||
// no group commits for column family add or drop
|
||||
// also, group commits across column families are not supported
|
||||
break;
|
||||
}
|
||||
last_writer = writer;
|
||||
LogAndApplyHelper(column_family_data, builder, v, last_writer->edit, mu);
|
||||
batch_edits.push_back(last_writer->edit);
|
||||
for (const auto& edit : writer->edit_list) {
|
||||
LogAndApplyHelper(column_family_data, builder, v, edit, mu);
|
||||
batch_edits.push_back(edit);
|
||||
}
|
||||
}
|
||||
builder->SaveTo(v->storage_info());
|
||||
}
|
||||
@@ -2243,7 +2246,8 @@ Status VersionSet::LogAndApply(ColumnFamilyData* column_family_data,
|
||||
if (new_descriptor_log) {
|
||||
// if we're writing out new snapshot make sure to persist max column family
|
||||
if (column_family_set_->GetMaxColumnFamily() > 0) {
|
||||
edit->SetMaxColumnFamily(column_family_set_->GetMaxColumnFamily());
|
||||
w.edit_list.front()->SetMaxColumnFamily(
|
||||
column_family_set_->GetMaxColumnFamily());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2254,7 +2258,7 @@ Status VersionSet::LogAndApply(ColumnFamilyData* column_family_data,
|
||||
mu->Unlock();
|
||||
|
||||
TEST_SYNC_POINT("VersionSet::LogAndApply:WriteManifest");
|
||||
if (!edit->IsColumnFamilyManipulation() &&
|
||||
if (!w.edit_list.front()->IsColumnFamilyManipulation() &&
|
||||
db_options_->max_open_files == -1) {
|
||||
// unlimited table cache. Pre-load table handle now.
|
||||
// Need to do it out of the mutex.
|
||||
@@ -2280,12 +2284,13 @@ Status VersionSet::LogAndApply(ColumnFamilyData* column_family_data,
|
||||
|
||||
unique_ptr<WritableFileWriter> file_writer(
|
||||
new WritableFileWriter(std::move(descriptor_file), opt_env_opts));
|
||||
descriptor_log_.reset(new log::Writer(std::move(file_writer), 0, false));
|
||||
descriptor_log_.reset(
|
||||
new log::Writer(std::move(file_writer), 0, false));
|
||||
s = WriteSnapshot(descriptor_log_.get());
|
||||
}
|
||||
}
|
||||
|
||||
if (!edit->IsColumnFamilyManipulation()) {
|
||||
if (!w.edit_list.front()->IsColumnFamilyManipulation()) {
|
||||
// This is cpu-heavy operations, which should be called outside mutex.
|
||||
v->PrepareApply(mutable_cf_options, true);
|
||||
}
|
||||
@@ -2327,7 +2332,7 @@ Status VersionSet::LogAndApply(ColumnFamilyData* column_family_data,
|
||||
new_manifest_file_size = descriptor_log_->file()->GetFileSize();
|
||||
}
|
||||
|
||||
if (edit->is_column_family_drop_) {
|
||||
if (w.edit_list.front()->is_column_family_drop_) {
|
||||
TEST_SYNC_POINT("VersionSet::LogAndApply::ColumnFamilyDrop:0");
|
||||
TEST_SYNC_POINT("VersionSet::LogAndApply::ColumnFamilyDrop:1");
|
||||
TEST_SYNC_POINT("VersionSet::LogAndApply::ColumnFamilyDrop:2");
|
||||
@@ -2347,12 +2352,12 @@ Status VersionSet::LogAndApply(ColumnFamilyData* column_family_data,
|
||||
|
||||
// Install the new version
|
||||
if (s.ok()) {
|
||||
if (edit->is_column_family_add_) {
|
||||
if (w.edit_list.front()->is_column_family_add_) {
|
||||
// no group commit on column family add
|
||||
assert(batch_edits.size() == 1);
|
||||
assert(new_cf_options != nullptr);
|
||||
CreateColumnFamily(*new_cf_options, edit);
|
||||
} else if (edit->is_column_family_drop_) {
|
||||
CreateColumnFamily(*new_cf_options, w.edit_list.front());
|
||||
} else if (w.edit_list.front()->is_column_family_drop_) {
|
||||
assert(batch_edits.size() == 1);
|
||||
column_family_data->SetDropped();
|
||||
if (column_family_data->Unref()) {
|
||||
@@ -2375,7 +2380,7 @@ Status VersionSet::LogAndApply(ColumnFamilyData* column_family_data,
|
||||
|
||||
manifest_file_number_ = pending_manifest_file_number_;
|
||||
manifest_file_size_ = new_manifest_file_size;
|
||||
prev_log_number_ = edit->prev_log_number_;
|
||||
prev_log_number_ = w.edit_list.front()->prev_log_number_;
|
||||
} else {
|
||||
std::string version_edits;
|
||||
for (auto& e : batch_edits) {
|
||||
@@ -2740,7 +2745,7 @@ Status VersionSet::Recover(
|
||||
}
|
||||
}
|
||||
|
||||
for (auto builder : builders) {
|
||||
for (auto& builder : builders) {
|
||||
delete builder.second;
|
||||
}
|
||||
|
||||
@@ -2833,7 +2838,7 @@ Status VersionSet::ReduceNumberOfLevels(const std::string& dbname,
|
||||
std::shared_ptr<Cache> tc(NewLRUCache(options->max_open_files - 10,
|
||||
options->table_cache_numshardbits));
|
||||
WriteController wc(options->delayed_write_rate);
|
||||
WriteBuffer wb(options->db_write_buffer_size);
|
||||
WriteBufferManager wb(options->db_write_buffer_size);
|
||||
VersionSet versions(dbname, options, env_options, tc.get(), &wb, &wc);
|
||||
Status status;
|
||||
|
||||
|
||||
+19
-3
@@ -55,9 +55,8 @@ class LookupKey;
|
||||
class MemTable;
|
||||
class Version;
|
||||
class VersionSet;
|
||||
class WriteBuffer;
|
||||
class WriteBufferManager;
|
||||
class MergeContext;
|
||||
class ColumnFamilyData;
|
||||
class ColumnFamilySet;
|
||||
class TableCache;
|
||||
class MergeIteratorBuilder;
|
||||
@@ -576,7 +575,8 @@ class VersionSet {
|
||||
public:
|
||||
VersionSet(const std::string& dbname, const DBOptions* db_options,
|
||||
const EnvOptions& env_options, Cache* table_cache,
|
||||
WriteBuffer* write_buffer, WriteController* write_controller);
|
||||
WriteBufferManager* write_buffer_manager,
|
||||
WriteController* write_controller);
|
||||
~VersionSet();
|
||||
|
||||
// Apply *edit to the current version to form a new descriptor that
|
||||
@@ -590,6 +590,19 @@ class VersionSet {
|
||||
const MutableCFOptions& mutable_cf_options, VersionEdit* edit,
|
||||
InstrumentedMutex* mu, Directory* db_directory = nullptr,
|
||||
bool new_descriptor_log = false,
|
||||
const ColumnFamilyOptions* column_family_options = nullptr) {
|
||||
autovector<VersionEdit*> edit_list;
|
||||
edit_list.push_back(edit);
|
||||
return LogAndApply(column_family_data, mutable_cf_options, edit_list, mu,
|
||||
db_directory, new_descriptor_log, column_family_options);
|
||||
}
|
||||
// The batch version. If edit_list.size() > 1, caller must ensure that
|
||||
// no edit in the list column family add or drop
|
||||
Status LogAndApply(
|
||||
ColumnFamilyData* column_family_data,
|
||||
const MutableCFOptions& mutable_cf_options,
|
||||
const autovector<VersionEdit*>& edit_list, InstrumentedMutex* mu,
|
||||
Directory* db_directory = nullptr, bool new_descriptor_log = false,
|
||||
const ColumnFamilyOptions* column_family_options = nullptr);
|
||||
|
||||
// Recover the last saved descriptor from persistent storage.
|
||||
@@ -627,6 +640,8 @@ class VersionSet {
|
||||
// Return the current manifest file number
|
||||
uint64_t manifest_file_number() const { return manifest_file_number_; }
|
||||
|
||||
uint64_t options_file_number() const { return options_file_number_; }
|
||||
|
||||
uint64_t pending_manifest_file_number() const {
|
||||
return pending_manifest_file_number_;
|
||||
}
|
||||
@@ -743,6 +758,7 @@ class VersionSet {
|
||||
const DBOptions* const db_options_;
|
||||
std::atomic<uint64_t> next_file_number_;
|
||||
uint64_t manifest_file_number_;
|
||||
uint64_t options_file_number_;
|
||||
uint64_t pending_manifest_file_number_;
|
||||
std::atomic<uint64_t> last_sequence_;
|
||||
uint64_t prev_log_number_; // 0 or backing store for memtable being compacted
|
||||
|
||||
@@ -10,12 +10,12 @@
|
||||
|
||||
#include "rocksdb/cache.h"
|
||||
#include "rocksdb/write_batch.h"
|
||||
#include "rocksdb/write_buffer_manager.h"
|
||||
|
||||
#include "db/wal_manager.h"
|
||||
#include "db/log_writer.h"
|
||||
#include "db/column_family.h"
|
||||
#include "db/version_set.h"
|
||||
#include "db/writebuffer.h"
|
||||
#include "util/file_reader_writer.h"
|
||||
#include "util/mock_env.h"
|
||||
#include "util/string_util.h"
|
||||
@@ -34,7 +34,7 @@ class WalManagerTest : public testing::Test {
|
||||
: env_(new MockEnv(Env::Default())),
|
||||
dbname_(test::TmpDir() + "/wal_manager_test"),
|
||||
table_cache_(NewLRUCache(50000, 16)),
|
||||
write_buffer_(db_options_.db_write_buffer_size),
|
||||
write_buffer_manager_(db_options_.db_write_buffer_size),
|
||||
current_log_number_(0) {
|
||||
DestroyDB(dbname_, Options());
|
||||
}
|
||||
@@ -48,7 +48,7 @@ class WalManagerTest : public testing::Test {
|
||||
db_options_.env = env_.get();
|
||||
|
||||
versions_.reset(new VersionSet(dbname_, &db_options_, env_options_,
|
||||
table_cache_.get(), &write_buffer_,
|
||||
table_cache_.get(), &write_buffer_manager_,
|
||||
&write_controller_));
|
||||
|
||||
wal_manager_.reset(new WalManager(db_options_, env_options_));
|
||||
@@ -104,7 +104,7 @@ class WalManagerTest : public testing::Test {
|
||||
EnvOptions env_options_;
|
||||
std::shared_ptr<Cache> table_cache_;
|
||||
DBOptions db_options_;
|
||||
WriteBuffer write_buffer_;
|
||||
WriteBufferManager write_buffer_manager_;
|
||||
std::unique_ptr<VersionSet> versions_;
|
||||
std::unique_ptr<WalManager> wal_manager_;
|
||||
|
||||
@@ -128,7 +128,7 @@ TEST_F(WalManagerTest, ReadFirstRecordCache) {
|
||||
unique_ptr<WritableFileWriter> file_writer(
|
||||
new WritableFileWriter(std::move(file), EnvOptions()));
|
||||
log::Writer writer(std::move(file_writer), 1,
|
||||
db_options_.recycle_log_file_num > 0);
|
||||
db_options_.recycle_log_file_num > 0);
|
||||
WriteBatch batch;
|
||||
batch.Put("foo", "bar");
|
||||
WriteBatchInternal::SetSequence(&batch, 10);
|
||||
|
||||
+58
-48
@@ -31,6 +31,7 @@
|
||||
|
||||
#include "rocksdb/write_batch.h"
|
||||
|
||||
#include <map>
|
||||
#include <stack>
|
||||
#include <stdexcept>
|
||||
#include <vector>
|
||||
@@ -40,6 +41,7 @@
|
||||
#include "db/dbformat.h"
|
||||
#include "db/flush_scheduler.h"
|
||||
#include "db/memtable.h"
|
||||
#include "db/merge_context.h"
|
||||
#include "db/snapshot_impl.h"
|
||||
#include "db/write_batch_internal.h"
|
||||
#include "rocksdb/merge_operator.h"
|
||||
@@ -691,8 +693,10 @@ class MemTableInserter : public WriteBatch::Handler {
|
||||
// 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_;
|
||||
bool* has_valid_writes_;
|
||||
typedef std::map<MemTable*, MemTablePostProcessInfo> MemPostInfoMap;
|
||||
MemPostInfoMap mem_post_info_map_;
|
||||
// current recovered transaction we are rebuilding (recovery)
|
||||
WriteBatch* rebuilding_trx_;
|
||||
|
||||
@@ -701,8 +705,8 @@ class MemTableInserter : public WriteBatch::Handler {
|
||||
FlushScheduler* flush_scheduler,
|
||||
bool ignore_missing_column_families,
|
||||
uint64_t recovering_log_number, DB* db,
|
||||
const bool dont_filter_deletes,
|
||||
bool concurrent_memtable_writes)
|
||||
bool concurrent_memtable_writes,
|
||||
bool* has_valid_writes = nullptr)
|
||||
: sequence_(sequence),
|
||||
cf_mems_(cf_mems),
|
||||
flush_scheduler_(flush_scheduler),
|
||||
@@ -710,19 +714,22 @@ class MemTableInserter : public WriteBatch::Handler {
|
||||
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),
|
||||
has_valid_writes_(has_valid_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_; }
|
||||
|
||||
void PostProcess() {
|
||||
for (auto& pair : mem_post_info_map_) {
|
||||
pair.first->BatchPostProcess(pair.second);
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
@@ -752,6 +759,10 @@ class MemTableInserter : public WriteBatch::Handler {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (has_valid_writes_ != nullptr) {
|
||||
*has_valid_writes_ = true;
|
||||
}
|
||||
|
||||
if (log_number_ref_ > 0) {
|
||||
cf_mems_->GetMemTable()->RefLogContainingPrepSection(log_number_ref_);
|
||||
}
|
||||
@@ -775,7 +786,8 @@ class MemTableInserter : public WriteBatch::Handler {
|
||||
MemTable* mem = cf_mems_->GetMemTable();
|
||||
auto* moptions = mem->GetMemTableOptions();
|
||||
if (!moptions->inplace_update_support) {
|
||||
mem->Add(sequence_, kTypeValue, key, value, concurrent_memtable_writes_);
|
||||
mem->Add(sequence_, kTypeValue, key, value, concurrent_memtable_writes_,
|
||||
get_post_process_info(mem));
|
||||
} else if (moptions->inplace_callback == nullptr) {
|
||||
assert(!concurrent_memtable_writes_);
|
||||
mem->Update(sequence_, key, value);
|
||||
@@ -826,24 +838,8 @@ class MemTableInserter : public WriteBatch::Handler {
|
||||
Status DeleteImpl(uint32_t column_family_id, const Slice& key,
|
||||
ValueType delete_type) {
|
||||
MemTable* mem = cf_mems_->GetMemTable();
|
||||
auto* moptions = mem->GetMemTableOptions();
|
||||
if (!dont_filter_deletes_ && moptions->filter_deletes) {
|
||||
assert(!concurrent_memtable_writes_);
|
||||
SnapshotImpl read_from_snapshot;
|
||||
read_from_snapshot.number_ = sequence_;
|
||||
ReadOptions ropts;
|
||||
ropts.snapshot = &read_from_snapshot;
|
||||
std::string value;
|
||||
auto cf_handle = cf_mems_->GetColumnFamilyHandle();
|
||||
if (cf_handle == nullptr) {
|
||||
cf_handle = db_->DefaultColumnFamily();
|
||||
}
|
||||
if (!db_->KeyMayExist(ropts, cf_handle, key, &value)) {
|
||||
RecordTick(moptions->statistics, NUMBER_FILTERED_DELETES);
|
||||
return Status::OK();
|
||||
}
|
||||
}
|
||||
mem->Add(sequence_, delete_type, key, Slice(), concurrent_memtable_writes_);
|
||||
mem->Add(sequence_, delete_type, key, Slice(), concurrent_memtable_writes_,
|
||||
get_post_process_info(mem));
|
||||
sequence_++;
|
||||
CheckMemtableFull();
|
||||
return Status::OK();
|
||||
@@ -936,20 +932,13 @@ class MemTableInserter : public WriteBatch::Handler {
|
||||
std::deque<std::string> operands;
|
||||
operands.push_front(value.ToString());
|
||||
std::string new_value;
|
||||
bool merge_success = false;
|
||||
{
|
||||
StopWatchNano timer(Env::Default(), moptions->statistics != nullptr);
|
||||
PERF_TIMER_GUARD(merge_operator_time_nanos);
|
||||
merge_success = merge_operator->FullMerge(
|
||||
key, &get_value_slice, operands, &new_value, moptions->info_log);
|
||||
RecordTick(moptions->statistics, MERGE_OPERATION_TOTAL_TIME,
|
||||
timer.ElapsedNanos());
|
||||
}
|
||||
|
||||
if (!merge_success) {
|
||||
// Failed to merge!
|
||||
RecordTick(moptions->statistics, NUMBER_MERGE_FAILURES);
|
||||
Status merge_status = MergeHelper::TimedFullMerge(
|
||||
merge_operator, key, &get_value_slice, operands, &new_value,
|
||||
moptions->info_log, moptions->statistics, Env::Default());
|
||||
|
||||
if (!merge_status.ok()) {
|
||||
// Failed to merge!
|
||||
// Store the delta in memtable
|
||||
perform_merge = false;
|
||||
} else {
|
||||
@@ -996,6 +985,9 @@ class MemTableInserter : public WriteBatch::Handler {
|
||||
|
||||
// we are now iterating through a prepared section
|
||||
rebuilding_trx_ = new WriteBatch();
|
||||
if (has_valid_writes_ != nullptr) {
|
||||
*has_valid_writes_ = true;
|
||||
}
|
||||
} else {
|
||||
// in non-recovery we ignore prepare markers
|
||||
// and insert the values directly. making sure we have a
|
||||
@@ -1041,7 +1033,7 @@ class MemTableInserter : public WriteBatch::Handler {
|
||||
// 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
|
||||
// all insertes must reference this trx log number
|
||||
log_number_ref_ = trx->log_number_;
|
||||
s = trx->batch_->Iterate(this);
|
||||
log_number_ref_ = 0;
|
||||
@@ -1049,6 +1041,9 @@ class MemTableInserter : public WriteBatch::Handler {
|
||||
if (s.ok()) {
|
||||
db_->DeleteRecoveredTransaction(name.ToString());
|
||||
}
|
||||
if (has_valid_writes_ != nullptr) {
|
||||
*has_valid_writes_ = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// in non recovery we simply ignore this tag
|
||||
@@ -1075,6 +1070,15 @@ class MemTableInserter : public WriteBatch::Handler {
|
||||
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
private:
|
||||
MemTablePostProcessInfo* get_post_process_info(MemTable* mem) {
|
||||
if (!concurrent_memtable_writes_) {
|
||||
// No need to batch counters locally if we don't use concurrent mode.
|
||||
return nullptr;
|
||||
}
|
||||
return &mem_post_info_map_[mem];
|
||||
}
|
||||
};
|
||||
|
||||
// This function can only be called in these conditions:
|
||||
@@ -1086,10 +1090,10 @@ Status WriteBatchInternal::InsertInto(
|
||||
const autovector<WriteThread::Writer*>& writers, SequenceNumber sequence,
|
||||
ColumnFamilyMemTables* memtables, FlushScheduler* flush_scheduler,
|
||||
bool ignore_missing_column_families, uint64_t log_number, DB* db,
|
||||
const bool dont_filter_deletes, bool concurrent_memtable_writes) {
|
||||
bool concurrent_memtable_writes) {
|
||||
MemTableInserter inserter(sequence, memtables, flush_scheduler,
|
||||
ignore_missing_column_families, log_number, db,
|
||||
dont_filter_deletes, concurrent_memtable_writes);
|
||||
concurrent_memtable_writes);
|
||||
for (size_t i = 0; i < writers.size(); i++) {
|
||||
auto w = writers[i];
|
||||
if (!w->ShouldWriteToMemtable()) {
|
||||
@@ -1109,30 +1113,36 @@ Status WriteBatchInternal::InsertInto(WriteThread::Writer* writer,
|
||||
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);
|
||||
concurrent_memtable_writes);
|
||||
assert(writer->ShouldWriteToMemtable());
|
||||
inserter.set_log_number_ref(writer->log_ref);
|
||||
return writer->batch->Iterate(&inserter);
|
||||
Status s = writer->batch->Iterate(&inserter);
|
||||
if (concurrent_memtable_writes) {
|
||||
inserter.PostProcess();
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
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) {
|
||||
uint64_t log_number, DB* db, bool concurrent_memtable_writes,
|
||||
SequenceNumber* last_seq_used, bool* has_valid_writes) {
|
||||
MemTableInserter inserter(WriteBatchInternal::Sequence(batch), memtables,
|
||||
flush_scheduler, ignore_missing_column_families,
|
||||
log_number, db, dont_filter_deletes,
|
||||
concurrent_memtable_writes);
|
||||
log_number, db, concurrent_memtable_writes,
|
||||
has_valid_writes);
|
||||
Status s = batch->Iterate(&inserter);
|
||||
if (last_seq_used != nullptr) {
|
||||
*last_seq_used = inserter.get_final_sequence();
|
||||
}
|
||||
if (concurrent_memtable_writes) {
|
||||
inserter.PostProcess();
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
|
||||
@@ -129,9 +129,6 @@ class WriteBatchInternal {
|
||||
|
||||
// Inserts batches[i] into memtable, for i in 0..num_batches-1 inclusive.
|
||||
//
|
||||
// If dont_filter_deletes is false AND options.filter_deletes is true
|
||||
// AND db->KeyMayExist is false, then a Delete won't modify the memtable.
|
||||
//
|
||||
// If ignore_missing_column_families == true. WriteBatch
|
||||
// referencing non-existing column family will be ignored.
|
||||
// If ignore_missing_column_families == false, processing of the
|
||||
@@ -153,7 +150,6 @@ class WriteBatchInternal {
|
||||
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);
|
||||
|
||||
// Convenience form of InsertInto when you have only one batch
|
||||
@@ -163,16 +159,15 @@ class WriteBatchInternal {
|
||||
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);
|
||||
SequenceNumber* last_seq_used = nullptr,
|
||||
bool* has_valid_writes = nullptr);
|
||||
|
||||
static Status InsertInto(WriteThread::Writer* writer,
|
||||
ColumnFamilyMemTables* memtables,
|
||||
FlushScheduler* flush_scheduler,
|
||||
bool ignore_missing_column_families = false,
|
||||
uint64_t log_number = 0, DB* db = nullptr,
|
||||
const bool dont_filter_deletes = true,
|
||||
bool concurrent_memtable_writes = false);
|
||||
|
||||
static void Append(WriteBatch* dst, const WriteBatch* src);
|
||||
|
||||
@@ -10,13 +10,13 @@
|
||||
#include "rocksdb/db.h"
|
||||
|
||||
#include <memory>
|
||||
#include "db/memtable.h"
|
||||
#include "db/column_family.h"
|
||||
#include "db/memtable.h"
|
||||
#include "db/write_batch_internal.h"
|
||||
#include "db/writebuffer.h"
|
||||
#include "rocksdb/env.h"
|
||||
#include "rocksdb/memtablerep.h"
|
||||
#include "rocksdb/utilities/write_batch_with_index.h"
|
||||
#include "rocksdb/write_buffer_manager.h"
|
||||
#include "table/scoped_arena_iterator.h"
|
||||
#include "util/logging.h"
|
||||
#include "util/string_util.h"
|
||||
@@ -30,7 +30,7 @@ static std::string PrintContents(WriteBatch* b) {
|
||||
Options options;
|
||||
options.memtable_factory = factory;
|
||||
ImmutableCFOptions ioptions(options);
|
||||
WriteBuffer wb(options.db_write_buffer_size);
|
||||
WriteBufferManager wb(options.db_write_buffer_size);
|
||||
MemTable* mem =
|
||||
new MemTable(cmp, ioptions, MutableCFOptions(options, ioptions), &wb,
|
||||
kMaxSequenceNumber);
|
||||
|
||||
+1
-1
@@ -193,7 +193,7 @@ class WriteThread {
|
||||
// writer. If w has become the leader of a write batch group, returns
|
||||
// STATE_GROUP_LEADER. If w has been made part of a sequential batch
|
||||
// group and the leader has performed the write, returns STATE_DONE.
|
||||
// If w has been made part of a parallel batch group and is reponsible
|
||||
// If w has been made part of a parallel batch group and is responsible
|
||||
// for updating the memtable, returns STATE_PARALLEL_FOLLOWER.
|
||||
//
|
||||
// The db mutex SHOULD NOT be held when calling this function, because
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
include ../make_config.mk
|
||||
|
||||
ifndef DISABLE_JEMALLOC
|
||||
ifdef JEMALLOC
|
||||
PLATFORM_CXXFLAGS += "-DROCKSDB_JEMALLOC"
|
||||
endif
|
||||
EXEC_LDFLAGS := $(JEMALLOC_LIB) $(EXEC_LDFLAGS) -lpthread
|
||||
PLATFORM_CXXFLAGS += $(JEMALLOC_INCLUDE)
|
||||
endif
|
||||
|
||||
.PHONY: clean librocksdb
|
||||
|
||||
all: simple_example column_families_example compact_files_example c_simple_example optimistic_transaction_example transaction_example
|
||||
|
||||
+5
-13
@@ -34,13 +34,9 @@ class Cache;
|
||||
// Create a new cache with a fixed size capacity. The cache is sharded
|
||||
// to 2^num_shard_bits shards, by hash of the key. The total capacity
|
||||
// is divided and evenly assigned to each shard.
|
||||
//
|
||||
// The parameter num_shard_bits defaults to 4, and strict_capacity_limit
|
||||
// defaults to false.
|
||||
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);
|
||||
extern std::shared_ptr<Cache> NewLRUCache(size_t capacity,
|
||||
int num_shard_bits = 6,
|
||||
bool strict_capacity_limit = false);
|
||||
|
||||
class Cache {
|
||||
public:
|
||||
@@ -112,8 +108,8 @@ class Cache {
|
||||
// capacity.
|
||||
virtual void SetStrictCapacityLimit(bool strict_capacity_limit) = 0;
|
||||
|
||||
// Set whether to return error on insertion when cache reaches its full
|
||||
// capacity.
|
||||
// Get the flag whether to return error on insertion when cache reaches its
|
||||
// full capacity.
|
||||
virtual bool HasStrictCapacityLimit() const = 0;
|
||||
|
||||
// returns the maximum configured capacity of the cache
|
||||
@@ -148,10 +144,6 @@ class Cache {
|
||||
virtual void EraseUnRefEntries() = 0;
|
||||
|
||||
private:
|
||||
void LRU_Remove(Handle* e);
|
||||
void LRU_Append(Handle* e);
|
||||
void Unref(Handle* e);
|
||||
|
||||
// No copying allowed
|
||||
Cache(const Cache&);
|
||||
Cache& operator=(const Cache&);
|
||||
|
||||
+96
-26
@@ -12,15 +12,16 @@
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
#include "rocksdb/immutable_options.h"
|
||||
#include "rocksdb/iterator.h"
|
||||
#include "rocksdb/listener.h"
|
||||
#include "rocksdb/metadata.h"
|
||||
#include "rocksdb/options.h"
|
||||
#include "rocksdb/snapshot.h"
|
||||
#include "rocksdb/sst_file_writer.h"
|
||||
#include "rocksdb/thread_status.h"
|
||||
#include "rocksdb/transaction_log.h"
|
||||
#include "rocksdb/types.h"
|
||||
@@ -146,7 +147,9 @@ class DB {
|
||||
// in rocksdb::kDefaultColumnFamilyName.
|
||||
// If everything is OK, handles will on return be the same size
|
||||
// as column_families --- handles[i] will be a handle that you
|
||||
// will use to operate on column family column_family[i]
|
||||
// will use to operate on column family column_family[i].
|
||||
// Before delete DB, you have to close All column families by calling
|
||||
// DestroyColumnFamilyHandle() with all the handles.
|
||||
static Status Open(const DBOptions& db_options, const std::string& name,
|
||||
const std::vector<ColumnFamilyDescriptor>& column_families,
|
||||
std::vector<ColumnFamilyHandle*>* handles, DB** dbptr);
|
||||
@@ -172,6 +175,11 @@ class DB {
|
||||
// only records a drop record in the manifest and prevents the column
|
||||
// family from flushing and compacting.
|
||||
virtual Status DropColumnFamily(ColumnFamilyHandle* column_family);
|
||||
// Close a column family specified by column_family handle and destroy
|
||||
// the column family handle specified to avoid double deletion. This call
|
||||
// deletes the column family handle by default. Use this method to
|
||||
// close column family instead of deleting column family handle directly
|
||||
virtual Status DestroyColumnFamilyHandle(ColumnFamilyHandle* column_family);
|
||||
|
||||
// Set the database entry for "key" to "value".
|
||||
// If "key" already exists, it will be overwritten.
|
||||
@@ -580,14 +588,14 @@ class DB {
|
||||
}
|
||||
|
||||
#if defined(__GNUC__) || defined(__clang__)
|
||||
__attribute__((deprecated))
|
||||
__attribute__((__deprecated__))
|
||||
#elif _WIN32
|
||||
__declspec(deprecated)
|
||||
#endif
|
||||
virtual Status
|
||||
CompactRange(ColumnFamilyHandle* column_family, const Slice* begin,
|
||||
const Slice* end, bool change_level = false,
|
||||
int target_level = -1, uint32_t target_path_id = 0) {
|
||||
virtual Status
|
||||
CompactRange(ColumnFamilyHandle* column_family, const Slice* begin,
|
||||
const Slice* end, bool change_level = false,
|
||||
int target_level = -1, uint32_t target_path_id = 0) {
|
||||
CompactRangeOptions options;
|
||||
options.change_level = change_level;
|
||||
options.target_level = target_level;
|
||||
@@ -595,14 +603,13 @@ class DB {
|
||||
return CompactRange(options, column_family, begin, end);
|
||||
}
|
||||
#if defined(__GNUC__) || defined(__clang__)
|
||||
__attribute__((deprecated))
|
||||
__attribute__((__deprecated__))
|
||||
#elif _WIN32
|
||||
__declspec(deprecated)
|
||||
#endif
|
||||
virtual Status
|
||||
CompactRange(const Slice* begin, const Slice* end,
|
||||
bool change_level = false, int target_level = -1,
|
||||
uint32_t target_path_id = 0) {
|
||||
virtual Status
|
||||
CompactRange(const Slice* begin, const Slice* end, bool change_level = false,
|
||||
int target_level = -1, uint32_t target_path_id = 0) {
|
||||
CompactRangeOptions options;
|
||||
options.change_level = change_level;
|
||||
options.target_level = target_level;
|
||||
@@ -792,31 +799,77 @@ class DB {
|
||||
GetColumnFamilyMetaData(DefaultColumnFamily(), metadata);
|
||||
}
|
||||
|
||||
// Load table file located at "file_path" into "column_family", a pointer to
|
||||
// ExternalSstFileInfo can be used instead of "file_path" to do a blind add
|
||||
// that wont need to read the file, move_file can be set to true to
|
||||
// move the file instead of copying it.
|
||||
// Batch load table files whose paths stored in "file_path_list" into
|
||||
// "column_family", a vector of ExternalSstFileInfo can be used
|
||||
// instead of "file_path_list" to do a blind batch add that wont
|
||||
// need to read the file, move_file can be set to true to
|
||||
// move the files instead of copying them.
|
||||
//
|
||||
// Current Requirements:
|
||||
// (1) Key range in loaded table file don't overlap with
|
||||
// (1) The key ranges of the files don't overlap with each other
|
||||
// (1) The key range of any file in list doesn't overlap with
|
||||
// existing keys or tombstones in DB.
|
||||
// (2) No other writes happen during AddFile call, otherwise
|
||||
// DB may get corrupted.
|
||||
// (3) No snapshots are held.
|
||||
// (2) No snapshots are held.
|
||||
//
|
||||
// Notes: We will try to ingest the files to the lowest possible level
|
||||
// even if the file compression dont match the level compression
|
||||
virtual Status AddFile(ColumnFamilyHandle* column_family,
|
||||
const std::string& file_path,
|
||||
const std::vector<std::string>& file_path_list,
|
||||
bool move_file = false) = 0;
|
||||
virtual Status AddFile(const std::string& file_path, bool move_file = false) {
|
||||
return AddFile(DefaultColumnFamily(), file_path, move_file);
|
||||
virtual Status AddFile(const std::vector<std::string>& file_path_list,
|
||||
bool move_file = false) {
|
||||
return AddFile(DefaultColumnFamily(), file_path_list, move_file);
|
||||
}
|
||||
#if defined(__GNUC__) || defined(__clang__)
|
||||
__attribute__((__deprecated__))
|
||||
#elif _WIN32
|
||||
__declspec(deprecated)
|
||||
#endif
|
||||
virtual Status
|
||||
AddFile(ColumnFamilyHandle* column_family, const std::string& file_path,
|
||||
bool move_file = false) {
|
||||
return AddFile(column_family, std::vector<std::string>(1, file_path),
|
||||
move_file);
|
||||
}
|
||||
#if defined(__GNUC__) || defined(__clang__)
|
||||
__attribute__((__deprecated__))
|
||||
#elif _WIN32
|
||||
__declspec(deprecated)
|
||||
#endif
|
||||
virtual Status
|
||||
AddFile(const std::string& file_path, bool move_file = false) {
|
||||
return AddFile(DefaultColumnFamily(),
|
||||
std::vector<std::string>(1, file_path), move_file);
|
||||
}
|
||||
|
||||
// Load table file with information "file_info" into "column_family"
|
||||
virtual Status AddFile(ColumnFamilyHandle* column_family,
|
||||
const ExternalSstFileInfo* file_info,
|
||||
const std::vector<ExternalSstFileInfo>& file_info_list,
|
||||
bool move_file = false) = 0;
|
||||
virtual Status AddFile(const ExternalSstFileInfo* file_info,
|
||||
virtual Status AddFile(const std::vector<ExternalSstFileInfo>& file_info_list,
|
||||
bool move_file = false) {
|
||||
return AddFile(DefaultColumnFamily(), file_info, move_file);
|
||||
return AddFile(DefaultColumnFamily(), file_info_list, move_file);
|
||||
}
|
||||
#if defined(__GNUC__) || defined(__clang__)
|
||||
__attribute__((__deprecated__))
|
||||
#elif _WIN32
|
||||
__declspec(deprecated)
|
||||
#endif
|
||||
virtual Status
|
||||
AddFile(ColumnFamilyHandle* column_family,
|
||||
const ExternalSstFileInfo* file_info, bool move_file = false) {
|
||||
return AddFile(column_family,
|
||||
std::vector<ExternalSstFileInfo>(1, *file_info), move_file);
|
||||
}
|
||||
#if defined(__GNUC__) || defined(__clang__)
|
||||
__attribute__((__deprecated__))
|
||||
#elif _WIN32
|
||||
__declspec(deprecated)
|
||||
#endif
|
||||
virtual Status
|
||||
AddFile(const ExternalSstFileInfo* file_info, bool move_file = false) {
|
||||
return AddFile(DefaultColumnFamily(),
|
||||
std::vector<ExternalSstFileInfo>(1, *file_info), move_file);
|
||||
}
|
||||
|
||||
#endif // ROCKSDB_LITE
|
||||
@@ -858,7 +911,24 @@ Status DestroyDB(const std::string& name, const Options& options);
|
||||
// resurrect as much of the contents of the database as possible.
|
||||
// Some data may be lost, so be careful when calling this function
|
||||
// on a database that contains important information.
|
||||
//
|
||||
// With this API, we will warn and skip data associated with column families not
|
||||
// specified in column_families.
|
||||
//
|
||||
// @param column_families Descriptors for known column families
|
||||
Status RepairDB(const std::string& dbname, const DBOptions& db_options,
|
||||
const std::vector<ColumnFamilyDescriptor>& column_families);
|
||||
|
||||
// @param unknown_cf_opts Options for column families encountered during the
|
||||
// repair that were not specified in column_families.
|
||||
Status RepairDB(const std::string& dbname, const DBOptions& db_options,
|
||||
const std::vector<ColumnFamilyDescriptor>& column_families,
|
||||
const ColumnFamilyOptions& unknown_cf_opts);
|
||||
|
||||
// @param options These options will be used for the database and for ALL column
|
||||
// families encountered during the repair
|
||||
Status RepairDB(const std::string& dbname, const Options& options);
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
@@ -733,7 +733,7 @@ extern void Error(const shared_ptr<Logger>& info_log, const char* format, ...);
|
||||
extern void Fatal(const shared_ptr<Logger>& info_log, const char* format, ...);
|
||||
|
||||
// Log the specified data to *info_log if info_log is non-nullptr.
|
||||
// The default info log level is InfoLogLevel::ERROR.
|
||||
// The default info log level is InfoLogLevel::INFO_LEVEL.
|
||||
extern void Log(const shared_ptr<Logger>& info_log, const char* format, ...)
|
||||
# if defined(__GNUC__) || defined(__clang__)
|
||||
__attribute__((__format__ (__printf__, 2, 3)))
|
||||
@@ -745,7 +745,7 @@ extern void LogFlush(Logger *info_log);
|
||||
extern void Log(const InfoLogLevel log_level, Logger* info_log,
|
||||
const char* format, ...);
|
||||
|
||||
// The default info log level is InfoLogLevel::ERROR.
|
||||
// The default info log level is InfoLogLevel::INFO_LEVEL.
|
||||
extern void Log(Logger* info_log, const char* format, ...)
|
||||
# if defined(__GNUC__) || defined(__clang__)
|
||||
__attribute__((__format__ (__printf__, 2, 3)))
|
||||
|
||||
@@ -20,7 +20,7 @@ typedef std::unordered_map<std::string, std::shared_ptr<const TableProperties>>
|
||||
class DB;
|
||||
class Status;
|
||||
struct CompactionJobStats;
|
||||
enum CompressionType : char;
|
||||
enum CompressionType : unsigned char;
|
||||
|
||||
enum class TableFileCreationReason {
|
||||
kFlush,
|
||||
|
||||
+44
-27
@@ -17,9 +17,10 @@
|
||||
#include <limits>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "rocksdb/version.h"
|
||||
#include "rocksdb/listener.h"
|
||||
#include "rocksdb/universal_compaction.h"
|
||||
#include "rocksdb/version.h"
|
||||
#include "rocksdb/write_buffer_manager.h"
|
||||
|
||||
#ifdef max
|
||||
#undef max
|
||||
@@ -52,7 +53,7 @@ class WalFilter;
|
||||
// sequence of key,value pairs. Each block may be compressed before
|
||||
// being stored in a file. The following enum describes which
|
||||
// compression method (if any) is used to compress a block.
|
||||
enum CompressionType : char {
|
||||
enum CompressionType : unsigned char {
|
||||
// NOTE: do not change the values of existing entries, as these are
|
||||
// part of the persistent format on disk.
|
||||
kNoCompression = 0x0,
|
||||
@@ -66,7 +67,7 @@ enum CompressionType : char {
|
||||
kZSTDNotFinalCompression = 0x40,
|
||||
|
||||
// kDisableCompressionOption is used to disable some compression options.
|
||||
kDisableCompressionOption = -1,
|
||||
kDisableCompressionOption = 0xff,
|
||||
};
|
||||
|
||||
enum CompactionStyle : char {
|
||||
@@ -450,8 +451,8 @@ struct ColumnFamilyOptions {
|
||||
// (max_bytes_for_level_base) * (max_bytes_for_level_multiplier ^ (L-1))
|
||||
// For example, if max_bytes_for_level_base is 200MB, and if
|
||||
// max_bytes_for_level_multiplier is 10, total data size for level-1
|
||||
// will be 2GB, total file size for level-2 will be 20GB,
|
||||
// and total file size for level-3 will be 200GB.
|
||||
// will be 200MB, total file size for level-2 will be 2GB,
|
||||
// and total file size for level-3 will be 20GB.
|
||||
//
|
||||
// Default: 256MB.
|
||||
//
|
||||
@@ -632,16 +633,6 @@ struct ColumnFamilyOptions {
|
||||
// The options for FIFO compaction style
|
||||
CompactionOptionsFIFO compaction_options_fifo;
|
||||
|
||||
// Use KeyMayExist API to filter deletes when this is true.
|
||||
// If KeyMayExist returns false, i.e. the key definitely does not exist, then
|
||||
// the delete is a noop. KeyMayExist only incurs in-memory look up.
|
||||
// This optimization avoids writing the delete to storage when appropriate.
|
||||
//
|
||||
// Default: false
|
||||
//
|
||||
// Dynamically changeable through SetOptions() API
|
||||
bool filter_deletes;
|
||||
|
||||
// An iteration->Next() sequentially skips over keys with the same
|
||||
// user-key unless this option is set. This number specifies the number
|
||||
// of keys (with the same userkey) that will be sequentially
|
||||
@@ -745,16 +736,15 @@ struct ColumnFamilyOptions {
|
||||
Slice delta_value,
|
||||
std::string* merged_value);
|
||||
|
||||
// if prefix_extractor is set and bloom_bits is not 0, create prefix bloom
|
||||
// for memtable
|
||||
// if prefix_extractor is set and memtable_prefix_bloom_size_ratio is not 0,
|
||||
// create prefix bloom for memtable with the size of
|
||||
// write_buffer_size * memtable_prefix_bloom_size_ratio.
|
||||
// If it is larger than 0.25, it is santinized to 0.25.
|
||||
//
|
||||
// Default: 0 (disable)
|
||||
//
|
||||
// Dynamically changeable through SetOptions() API
|
||||
uint32_t memtable_prefix_bloom_bits;
|
||||
|
||||
// number of hash probes per key
|
||||
//
|
||||
// Dynamically changeable through SetOptions() API
|
||||
uint32_t memtable_prefix_bloom_probes;
|
||||
double memtable_prefix_bloom_size_ratio;
|
||||
|
||||
// Page size for huge page TLB for bloom in memtable. If <=0, not allocate
|
||||
// from huge page TLB but from malloc.
|
||||
@@ -1149,6 +1139,22 @@ struct DBOptions {
|
||||
// Default: 0 (disabled)
|
||||
size_t db_write_buffer_size;
|
||||
|
||||
// The memory usage of memtable will report to this object. The same object
|
||||
// can be passed into multiple DBs and it will track the sum of size of all
|
||||
// the DBs. If the total size of all live memtables of all the DBs exceeds
|
||||
// a limit, a flush will be triggered in the next DB to which the next write
|
||||
// is issued.
|
||||
//
|
||||
// If the object is only passed to on DB, the behavior is the same as
|
||||
// db_write_buffer_size. When write_buffer_manager is set, the value set will
|
||||
// override db_write_buffer_size.
|
||||
//
|
||||
// This feature is disabled by default. Specify a non-zero value
|
||||
// to enable it.
|
||||
//
|
||||
// Default: null
|
||||
std::shared_ptr<WriteBufferManager> write_buffer_manager;
|
||||
|
||||
// Specify the file access pattern once a compaction is started.
|
||||
// It will be applied to all input files of a compaction.
|
||||
// Default: NORMAL
|
||||
@@ -1267,8 +1273,6 @@ struct DBOptions {
|
||||
// It is strongly recommended to set enable_write_thread_adaptive_yield
|
||||
// if you are going to use this feature.
|
||||
//
|
||||
// THIS FEATURE IS NOT STABLE YET.
|
||||
//
|
||||
// Default: false
|
||||
bool allow_concurrent_memtable_write;
|
||||
|
||||
@@ -1277,8 +1281,6 @@ struct DBOptions {
|
||||
// This can substantially improve throughput for concurrent workloads,
|
||||
// regardless of whether allow_concurrent_memtable_write is enabled.
|
||||
//
|
||||
// THIS FEATURE IS NOT STABLE YET.
|
||||
//
|
||||
// Default: false
|
||||
bool enable_write_thread_adaptive_yield;
|
||||
|
||||
@@ -1342,6 +1344,15 @@ struct DBOptions {
|
||||
// when printing to LOG.
|
||||
// DEFAULT: false
|
||||
bool dump_malloc_stats;
|
||||
|
||||
// By default RocksDB replay WAL logs and flush them on DB open, which may
|
||||
// create very small SST files. If this option is enabled, RocksDB will try
|
||||
// to avoid (but not guarantee not to) flush during recovery. Also, existing
|
||||
// WAL logs will be kept, so that if crash happened before flush, we still
|
||||
// have logs to recover from.
|
||||
//
|
||||
// DEFAULT: false
|
||||
bool avoid_flush_during_recovery;
|
||||
};
|
||||
|
||||
// Options to control the behavior of a database (passed to DB::Open)
|
||||
@@ -1493,6 +1504,12 @@ struct ReadOptions {
|
||||
// Default: false
|
||||
bool pin_data;
|
||||
|
||||
// If true, when PurgeObsoleteFile is called in CleanupIteratorState, we
|
||||
// schedule a background job in the flush job queue and delete obsolete files
|
||||
// in background.
|
||||
// Default: false
|
||||
bool background_purge_on_iterator_cleanup;
|
||||
|
||||
// If non-zero, NewIterator will create a new table reader which
|
||||
// performs reads of the given size. Using a large size (> 2MB) can
|
||||
// improve the performance of forward iteration on spinning disks.
|
||||
|
||||
@@ -13,14 +13,14 @@ namespace rocksdb {
|
||||
|
||||
// How much perf stats to collect. Affects perf_context and iostats_context.
|
||||
|
||||
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
|
||||
enum PerfLevel : unsigned char {
|
||||
kUninitialized = 0, // unknown setting
|
||||
kDisable = 1, // disable perf stats
|
||||
kEnableCount = 2, // enable only count stats
|
||||
kEnableTimeExceptForMutex = 3, // Other than count stats, also enable time
|
||||
// stats except for mutexes
|
||||
kEnableTime = 3, // enable count and time stats
|
||||
kOutOfBounds = 4 // N.B. Must always be the last value!
|
||||
kEnableTime = 4, // enable count and time stats
|
||||
kOutOfBounds = 5 // N.B. Must always be the last value!
|
||||
};
|
||||
|
||||
// set the perf stats level for current thread
|
||||
|
||||
@@ -179,6 +179,11 @@ enum Tickers : uint32_t {
|
||||
NUMBER_SUPERVERSION_ACQUIRES,
|
||||
NUMBER_SUPERVERSION_RELEASES,
|
||||
NUMBER_SUPERVERSION_CLEANUPS,
|
||||
|
||||
// # of compressions/decompressions executed
|
||||
NUMBER_BLOCK_COMPRESSED,
|
||||
NUMBER_BLOCK_DECOMPRESSED,
|
||||
|
||||
NUMBER_BLOCK_NOT_COMPRESSED,
|
||||
MERGE_OPERATION_TOTAL_TIME,
|
||||
FILTER_OPERATION_TOTAL_TIME,
|
||||
@@ -211,6 +216,8 @@ const std::vector<std::pair<Tickers, std::string>> TickersNameMap = {
|
||||
{BLOCK_CACHE_BYTES_READ, "rocksdb.block.cache.bytes.read"},
|
||||
{BLOCK_CACHE_BYTES_WRITE, "rocksdb.block.cache.bytes.write"},
|
||||
{BLOOM_FILTER_USEFUL, "rocksdb.bloom.filter.useful"},
|
||||
{PERSISTENT_CACHE_HIT, "rocksdb.persistent.cache.hit"},
|
||||
{PERSISTENT_CACHE_MISS, "rocksdb.persistent.cache.miss"},
|
||||
{MEMTABLE_HIT, "rocksdb.memtable.hit"},
|
||||
{MEMTABLE_MISS, "rocksdb.memtable.miss"},
|
||||
{GET_HIT_L0, "rocksdb.l0.hit"},
|
||||
@@ -260,15 +267,18 @@ const std::vector<std::pair<Tickers, std::string>> TickersNameMap = {
|
||||
{WAL_FILE_BYTES, "rocksdb.wal.bytes"},
|
||||
{WRITE_DONE_BY_SELF, "rocksdb.write.self"},
|
||||
{WRITE_DONE_BY_OTHER, "rocksdb.write.other"},
|
||||
{WRITE_TIMEDOUT, "rocksdb.write.timeout"},
|
||||
{WRITE_WITH_WAL, "rocksdb.write.wal"},
|
||||
{FLUSH_WRITE_BYTES, "rocksdb.flush.write.bytes"},
|
||||
{COMPACT_READ_BYTES, "rocksdb.compact.read.bytes"},
|
||||
{COMPACT_WRITE_BYTES, "rocksdb.compact.write.bytes"},
|
||||
{FLUSH_WRITE_BYTES, "rocksdb.flush.write.bytes"},
|
||||
{NUMBER_DIRECT_LOAD_TABLE_PROPERTIES,
|
||||
"rocksdb.number.direct.load.table.properties"},
|
||||
{NUMBER_SUPERVERSION_ACQUIRES, "rocksdb.number.superversion_acquires"},
|
||||
{NUMBER_SUPERVERSION_RELEASES, "rocksdb.number.superversion_releases"},
|
||||
{NUMBER_SUPERVERSION_CLEANUPS, "rocksdb.number.superversion_cleanups"},
|
||||
{NUMBER_BLOCK_COMPRESSED, "rocksdb.number.block.compressed"},
|
||||
{NUMBER_BLOCK_DECOMPRESSED, "rocksdb.number.block.decompressed"},
|
||||
{NUMBER_BLOCK_NOT_COMPRESSED, "rocksdb.number.block.not_compressed"},
|
||||
{MERGE_OPERATION_TOTAL_TIME, "rocksdb.merge.operation.time.nanos"},
|
||||
{FILTER_OPERATION_TOTAL_TIME, "rocksdb.filter.operation.time.nanos"},
|
||||
@@ -313,6 +323,14 @@ enum Histograms : uint32_t {
|
||||
BYTES_PER_READ,
|
||||
BYTES_PER_WRITE,
|
||||
BYTES_PER_MULTIGET,
|
||||
|
||||
// number of bytes compressed/decompressed
|
||||
// number of bytes is when uncompressed; i.e. before/after respectively
|
||||
BYTES_COMPRESSED,
|
||||
BYTES_DECOMPRESSED,
|
||||
COMPRESSION_TIMES_NANOS,
|
||||
DECOMPRESSION_TIMES_NANOS,
|
||||
|
||||
HISTOGRAM_ENUM_MAX, // TODO(ldemailly): enforce HistogramsNameMap match
|
||||
};
|
||||
|
||||
@@ -343,6 +361,10 @@ const std::vector<std::pair<Histograms, std::string>> HistogramsNameMap = {
|
||||
{BYTES_PER_READ, "rocksdb.bytes.per.read"},
|
||||
{BYTES_PER_WRITE, "rocksdb.bytes.per.write"},
|
||||
{BYTES_PER_MULTIGET, "rocksdb.bytes.per.multiget"},
|
||||
{BYTES_COMPRESSED, "rocksdb.bytes.compressed"},
|
||||
{BYTES_DECOMPRESSED, "rocksdb.bytes.decompressed"},
|
||||
{COMPRESSION_TIMES_NANOS, "rocksdb.compression.times.nanos"},
|
||||
{DECOMPRESSION_TIMES_NANOS, "rocksdb.decompression.times.nanos"},
|
||||
};
|
||||
|
||||
struct HistogramData {
|
||||
@@ -357,9 +379,12 @@ enum StatsLevel {
|
||||
// Collect all stats except the counters requiring to get time inside the
|
||||
// mutex lock.
|
||||
kExceptTimeForMutex,
|
||||
// Collect all stats expect time inside mutex lock AND time spent on
|
||||
// compression
|
||||
kExceptDetailedTimers,
|
||||
// Collect all stats, including measuring duration of mutex operations.
|
||||
// If getting time is expensive on the platform to run, it can
|
||||
// reduce scalability to more threads, especialy for writes.
|
||||
// reduce scalability to more threads, especially for writes.
|
||||
kAll,
|
||||
};
|
||||
|
||||
|
||||
@@ -161,6 +161,11 @@ struct BlockBasedTableOptions {
|
||||
// Default: false
|
||||
bool skip_table_builder_flush = false;
|
||||
|
||||
// Verify that decompressing the compressed block gives back the input. This
|
||||
// is a verification mode that we use to detect bugs in compression
|
||||
// algorithms.
|
||||
bool verify_compression = false;
|
||||
|
||||
// We currently have three versions:
|
||||
// 0 -- This version is currently written out by all RocksDB's versions by
|
||||
// default. Can be read by really old RocksDB's. Doesn't support changing
|
||||
|
||||
@@ -21,7 +21,7 @@ class Logger;
|
||||
struct Options;
|
||||
class Snapshot;
|
||||
|
||||
enum CompressionType : char;
|
||||
enum CompressionType : unsigned char;
|
||||
|
||||
// Options to control the behavior of a database (passed to
|
||||
// DB::Open). A LevelDBOptions object can be initialized as though
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
#include "rocksdb/cache.h"
|
||||
#include "rocksdb/slice.h"
|
||||
#include "rocksdb/status.h"
|
||||
#include "util/lru_cache_handle.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
|
||||
@@ -40,6 +40,11 @@ class StackableDB : public DB {
|
||||
return db_->DropColumnFamily(column_family);
|
||||
}
|
||||
|
||||
virtual Status DestroyColumnFamilyHandle(
|
||||
ColumnFamilyHandle* column_family) override {
|
||||
return db_->DestroyColumnFamilyHandle(column_family);
|
||||
}
|
||||
|
||||
using DB::Put;
|
||||
virtual Status Put(const WriteOptions& options,
|
||||
ColumnFamilyHandle* column_family, const Slice& key,
|
||||
@@ -65,14 +70,14 @@ class StackableDB : public DB {
|
||||
|
||||
using DB::AddFile;
|
||||
virtual Status AddFile(ColumnFamilyHandle* column_family,
|
||||
const ExternalSstFileInfo* file_info,
|
||||
const std::vector<ExternalSstFileInfo>& file_info_list,
|
||||
bool move_file) override {
|
||||
return db_->AddFile(column_family, file_info, move_file);
|
||||
return db_->AddFile(column_family, file_info_list, move_file);
|
||||
}
|
||||
virtual Status AddFile(ColumnFamilyHandle* column_family,
|
||||
const std::string& file_path,
|
||||
const std::vector<std::string>& file_path_list,
|
||||
bool move_file) override {
|
||||
return db_->AddFile(column_family, file_path, move_file);
|
||||
return db_->AddFile(column_family, file_path_list, move_file);
|
||||
}
|
||||
|
||||
using DB::KeyMayExist;
|
||||
|
||||
@@ -356,7 +356,7 @@ class Transaction {
|
||||
virtual void SetLockTimeout(int64_t timeout) = 0;
|
||||
|
||||
// Return the WriteOptions that will be used during Commit()
|
||||
virtual const WriteOptions* GetWriteOptions() = 0;
|
||||
virtual WriteOptions* GetWriteOptions() = 0;
|
||||
|
||||
// Reset the WriteOptions that will be used during Commit().
|
||||
virtual void SetWriteOptions(const WriteOptions& write_options) = 0;
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
#pragma once
|
||||
|
||||
#define ROCKSDB_MAJOR 4
|
||||
#define ROCKSDB_MINOR 9
|
||||
#define ROCKSDB_PATCH 0
|
||||
#define ROCKSDB_MINOR 10
|
||||
#define ROCKSDB_PATCH 2
|
||||
|
||||
// Do not use these. We made the mistake of declaring macros starting with
|
||||
// double underscore. Now we have to live with our choice. We'll deprecate these
|
||||
|
||||
@@ -125,7 +125,7 @@ class WriteBatch : public WriteBatchBase {
|
||||
// most recent call to SetSavePoint() and removes the most recent save point.
|
||||
// If there is no previous call to SetSavePoint(), Status::NotFound()
|
||||
// will be returned.
|
||||
// Oterwise returns Status::OK().
|
||||
// Otherwise returns Status::OK().
|
||||
Status RollbackToSavePoint() override;
|
||||
|
||||
// Support for iterating over the contents of a batch.
|
||||
|
||||
@@ -7,21 +7,28 @@
|
||||
// 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.
|
||||
//
|
||||
// WriteBuffer is for managing memory allocation for one or more MemTables.
|
||||
// WriteBufferManager is for managing memory allocation for one or more
|
||||
// MemTables.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <cstddef>
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
class WriteBuffer {
|
||||
class WriteBufferManager {
|
||||
public:
|
||||
explicit WriteBuffer(size_t _buffer_size)
|
||||
// _buffer_size = 0 indicates no limit. Memory won't be tracked,
|
||||
// memory_usage() won't be valid and ShouldFlush() will always return true.
|
||||
explicit WriteBufferManager(size_t _buffer_size)
|
||||
: buffer_size_(_buffer_size), memory_used_(0) {}
|
||||
|
||||
~WriteBuffer() {}
|
||||
~WriteBufferManager() {}
|
||||
|
||||
bool enabled() const { return buffer_size_ != 0; }
|
||||
|
||||
// Only valid if enabled()
|
||||
size_t memory_usage() const {
|
||||
return memory_used_.load(std::memory_order_relaxed);
|
||||
}
|
||||
@@ -29,15 +36,19 @@ class WriteBuffer {
|
||||
|
||||
// Should only be called from write thread
|
||||
bool ShouldFlush() const {
|
||||
return buffer_size() > 0 && memory_usage() >= buffer_size();
|
||||
return enabled() && memory_usage() >= buffer_size();
|
||||
}
|
||||
|
||||
// Should only be called from write thread
|
||||
void ReserveMem(size_t mem) {
|
||||
memory_used_.fetch_add(mem, std::memory_order_relaxed);
|
||||
if (enabled()) {
|
||||
memory_used_.fetch_add(mem, std::memory_order_relaxed);
|
||||
}
|
||||
}
|
||||
void FreeMem(size_t mem) {
|
||||
memory_used_.fetch_sub(mem, std::memory_order_relaxed);
|
||||
if (enabled()) {
|
||||
memory_used_.fetch_sub(mem, std::memory_order_relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
@@ -45,8 +56,7 @@ class WriteBuffer {
|
||||
std::atomic<size_t> memory_used_;
|
||||
|
||||
// No copying allowed
|
||||
WriteBuffer(const WriteBuffer&);
|
||||
void operator=(const WriteBuffer&);
|
||||
WriteBufferManager(const WriteBufferManager&) = delete;
|
||||
WriteBufferManager& operator=(const WriteBufferManager&) = delete;
|
||||
};
|
||||
|
||||
} // namespace rocksdb
|
||||
@@ -1,3 +1,5 @@
|
||||
cmake_minimum_required(VERSION 2.6)
|
||||
|
||||
set(JNI_NATIVE_SOURCES
|
||||
rocksjni/backupenginejni.cc
|
||||
rocksjni/backupablejni.cc
|
||||
|
||||
@@ -569,8 +569,7 @@ public class DbBenchmark {
|
||||
(Integer)flags_.get(Flag.bloom_locality));
|
||||
options.setMinWriteBufferNumberToMerge(
|
||||
(Integer)flags_.get(Flag.min_write_buffer_number_to_merge));
|
||||
options.setMemtablePrefixBloomBits(
|
||||
(Integer)flags_.get(Flag.memtable_bloom_bits));
|
||||
options.setMemtablePrefixBloomSizeRatio((Double) flags_.get(Flag.memtable_bloom_size_ratio));
|
||||
options.setNumLevels(
|
||||
(Integer)flags_.get(Flag.num_levels));
|
||||
options.setTargetFileSizeBase(
|
||||
@@ -599,8 +598,6 @@ public class DbBenchmark {
|
||||
(Boolean)flags_.get(Flag.disable_auto_compactions));
|
||||
options.setSourceCompactionFactor(
|
||||
(Integer)flags_.get(Flag.source_compaction_factor));
|
||||
options.setFilterDeletes(
|
||||
(Boolean)flags_.get(Flag.filter_deletes));
|
||||
options.setMaxSuccessiveMerges(
|
||||
(Integer)flags_.get(Flag.max_successive_merges));
|
||||
options.setWalTtlSeconds((Long)flags_.get(Flag.wal_ttl_seconds));
|
||||
@@ -1195,10 +1192,10 @@ public class DbBenchmark {
|
||||
return Integer.parseInt(value);
|
||||
}
|
||||
},
|
||||
memtable_bloom_bits(0,"Bloom filter bits per key for memtable.\n" +
|
||||
"\tNegative means no bloom filter.") {
|
||||
memtable_bloom_size_ratio(0, "Ratio of memtable used by the bloom filter.\n"
|
||||
+ "\t0 means no bloom filter.") {
|
||||
@Override public Object parseValue(String value) {
|
||||
return Integer.parseInt(value);
|
||||
return Double.parseDouble(value);
|
||||
}
|
||||
},
|
||||
cache_numshardbits(-1,"Number of shards for the block cache\n" +
|
||||
|
||||
Loaded 100 of 194 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user