mirror of
https://github.com/facebook/rocksdb.git
synced 2026-11-06 11:02:36 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
28d3aa3667 | ||
|
|
79c2075b53 | ||
|
|
0640c28723 | ||
|
|
718309432b | ||
|
|
bc672aefb3 | ||
|
|
b565228e1e | ||
|
|
dc3cdfede9 | ||
|
|
8856b22edd | ||
|
|
4159f5b87b | ||
|
|
a69bc91e37 | ||
|
|
03d433ee65 | ||
|
|
c00948d5e1 | ||
|
|
05e2831966 | ||
|
|
436ed904da | ||
|
|
b0f1927dbb | ||
|
|
ec70fea4c4 | ||
|
|
501591c423 | ||
|
|
91cb82f34e | ||
|
|
09f5a4b486 | ||
|
|
6199cba998 | ||
|
|
0a019d74a0 | ||
|
|
4cbc4e6f88 | ||
|
|
619167ee66 | ||
|
|
472e64d39e | ||
|
|
c9cd404bcd | ||
|
|
4fb09c6871 | ||
|
|
dadc429767 | ||
|
|
62a8fd154a | ||
|
|
48da7a9cad | ||
|
|
72cab88959 | ||
|
|
dec2c9f564 | ||
|
|
0b1ffe2e1d | ||
|
|
674b1181cf | ||
|
|
782a1590f9 | ||
|
|
de85e4cadf | ||
|
|
530534fceb | ||
|
|
7015fd81c4 | ||
|
|
7160f5d80c | ||
|
|
e1d3c7dbe4 | ||
|
|
3bdec09cb7 | ||
|
|
15325bf55b | ||
|
|
04251e1e3a | ||
|
|
18cc5018b7 | ||
|
|
bf03f59c11 | ||
|
|
df719d4964 | ||
|
|
4d6d47688c | ||
|
|
71b438c4a6 | ||
|
|
eade498bda | ||
|
|
760e9a94de | ||
|
|
69bb210d58 | ||
|
|
4eabbdb7ec | ||
|
|
4b8bb62f0a | ||
|
|
176f0bedce | ||
|
|
bb1c74ce18 | ||
|
|
a66b8157df | ||
|
|
f06be62fd2 | ||
|
|
2dc3910b5e | ||
|
|
12e030a992 | ||
|
|
c89369f57c | ||
|
|
4716ab4d16 | ||
|
|
25d600569d | ||
|
|
1369f015ee | ||
|
|
1a08d0beb5 | ||
|
|
40f562e747 | ||
|
|
d59d90bb1f | ||
|
|
46296cc869 | ||
|
|
bee8d033f4 | ||
|
|
5fec963877 | ||
|
|
cccd2199a6 | ||
|
|
20f2b54252 | ||
|
|
7842920be5 | ||
|
|
a84df655f3 | ||
|
|
d6ce0f7c61 | ||
|
|
ab455ce495 | ||
|
|
3eddd1abe9 | ||
|
|
73faa3d41d | ||
|
|
bffaf0a8b2 | ||
|
|
821cff114e | ||
|
|
8b7be1808c | ||
|
|
d03f110904 | ||
|
|
75222d130e | ||
|
|
47f1e72125 | ||
|
|
51440f83ec | ||
|
|
4949ef08db | ||
|
|
735df66552 | ||
|
|
e409d3d745 | ||
|
|
62c3a95796 | ||
|
|
d9b3338ebe | ||
|
|
75d7075a8a | ||
|
|
406a5682eb | ||
|
|
d8c8f08c12 | ||
|
|
643bbbf081 | ||
|
|
21f2492ac0 | ||
|
|
f02ce0c651 | ||
|
|
133130a4f5 | ||
|
|
de4d172d0f | ||
|
|
6df589b446 | ||
|
|
2e764f06ea | ||
|
|
7322c74012 | ||
|
|
31e60e2a77 | ||
|
|
7647df8f9e | ||
|
|
b2785472c8 | ||
|
|
3ce3bb3da2 | ||
|
|
bb808eaddb | ||
|
|
0b3172d071 | ||
|
|
211a195d41 | ||
|
|
5dc174e11a | ||
|
|
2d0b9e5f0a | ||
|
|
3af668ed17 | ||
|
|
408cc4b8e0 | ||
|
|
78382d4ba7 | ||
|
|
ca8b85ac04 | ||
|
|
0483dab2ab | ||
|
|
8afafc2783 | ||
|
|
fe5c6321cb | ||
|
|
3083ed2129 | ||
|
|
8d8d4e45ba | ||
|
|
ab946af08a | ||
|
|
fc83821270 | ||
|
|
898e803fc5 | ||
|
|
ac81130fa0 | ||
|
|
ec7a944360 | ||
|
|
4266d4fd90 | ||
|
|
d333820bad | ||
|
|
495ce6018a | ||
|
|
2ecac9f96d | ||
|
|
a187e66ad0 | ||
|
|
16c197627a | ||
|
|
4c181f08bc | ||
|
|
bc7a7a400c | ||
|
|
832271f6b1 | ||
|
|
dc9d70de65 | ||
|
|
d5a0c0e69b | ||
|
|
ebfdb3c7f6 | ||
|
|
9ffc8ba024 | ||
|
|
a3da590226 | ||
|
|
21cd6b7ad8 | ||
|
|
a0635ba3f6 | ||
|
|
c815351038 | ||
|
|
ec4ff4e99c | ||
|
|
672dda9b3b | ||
|
|
3ab8ffd4dd | ||
|
|
a81ac24127 | ||
|
|
6d299b70b8 | ||
|
|
a0580205c8 | ||
|
|
328ad902ab | ||
|
|
9c38ce1d02 | ||
|
|
8d26799fef | ||
|
|
32198343ff | ||
|
|
6116ccc232 | ||
|
|
d90cee9fd3 | ||
|
|
ea6d3a8ac0 | ||
|
|
5c224d1b70 | ||
|
|
dc81efe415 | ||
|
|
687214f878 | ||
|
|
2abb592688 | ||
|
|
309a9d0760 | ||
|
|
7fee8775a4 | ||
|
|
7a3577519f | ||
|
|
4cb4d546cd | ||
|
|
e2c1d4b57f | ||
|
|
eaf61ba9f3 | ||
|
|
f16c0b289c | ||
|
|
d1a978ae3d | ||
|
|
3e08175418 | ||
|
|
84a9c6a53a | ||
|
|
eeb44366ba |
No files matched your search
@@ -33,6 +33,8 @@ package/
|
||||
.phutil_module_cache
|
||||
unity
|
||||
tags
|
||||
rocksdb_dump
|
||||
rocksdb_undump
|
||||
|
||||
java/out
|
||||
java/target
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
## RocksDB dump format
|
||||
|
||||
The version 1 RocksDB dump format is fairly simple:
|
||||
|
||||
1) The dump starts with the magic 8 byte identifier "ROCKDUMP"
|
||||
|
||||
2) The magic is followed by an 8 byte big-endian version which is 0x00000001.
|
||||
|
||||
3) Next are arbitrarily sized chunks of bytes prepended by 4 byte little endian number indicating how large each chunk is.
|
||||
|
||||
4) The first chunk is special and is a json string indicating some things about the creation of this dump. It contains the following keys:
|
||||
* database-path: The path of the database this dump was created from.
|
||||
* hostname: The hostname of the machine where the dump was created.
|
||||
* creation-time: Unix seconds since epoc when this dump was created.
|
||||
|
||||
5) Following the info dump the slices paired into are key/value pairs.
|
||||
+28
-3
@@ -1,11 +1,36 @@
|
||||
# Rocksdb Change Log
|
||||
|
||||
## 3.11.0 (5/19/2015)
|
||||
## 3.12.0 (7/2/2015)
|
||||
### New Features
|
||||
* Added experimental support for optimistic transactions. See include/rocksdb/utilities/optimistic_transaction.h for more info.
|
||||
* Added a new way to report QPS from db_bench (check out --report_file and --report_interval_seconds)
|
||||
* Added a cache for individual rows. See DBOptions::row_cache for more info.
|
||||
* Several new features on EventListener (see include/rocksdb/listener.h):
|
||||
- OnCompationCompleted() now returns per-compaciton job statistics, defined in include/rocksdb/compaction_job_stats.h.
|
||||
- Added OnTableFileCreated() and OnTableFileDeleted().
|
||||
|
||||
### Public API changes
|
||||
* EventListener::OnFlushCompleted() now passes FlushJobInfo instead of a list of parameters.
|
||||
* DB::GetDbIdentity() is now a const function. If this function is overridden in your application, be sure to also make GetDbIdentity() const to avoid compile error.
|
||||
* Move listeners from ColumnFamilyOptions to DBOptions.
|
||||
* Add max_write_buffer_number_to_maintain option
|
||||
* DB::CompactRange()'s parameter reduce_level is changed to change_level, to allow users to move levels to lower levels if allowed. It can be used to migrate a DB from options.level_compaction_dynamic_level_bytes=false to options.level_compaction_dynamic_level_bytes.true.
|
||||
* Change default value for options.compaction_filter_factory and options.compaction_filter_factory_v2 to nullptr instead of DefaultCompactionFilterFactory and DefaultCompactionFilterFactoryV2.
|
||||
* If CancelAllBackgroundWork is called without doing a flush after doing loads with WAL disabled, the changes which haven't been flushed before the call to CancelAllBackgroundWork will be lost.
|
||||
* WBWIIterator::Entry() now returns WriteEntry instead of `const WriteEntry&`
|
||||
* 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.
|
||||
* 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.
|
||||
* Add BackupEngineImpl.options_.max_background_operations to specify the maximum number of operations that may be performed in parallel. Add support for parallelized backup and restore.
|
||||
|
||||
## 3.11.0 (5/19/2015)
|
||||
### New Features
|
||||
* Added a new API Cache::SetCapacity(size_t capacity) to dynamically change the maximum configured capacity of the cache. If the new capacity is less than the existing cache usage, the implementation will try to lower the usage by evicting the necessary number of elements following a strict LRU policy.
|
||||
|
||||
### New Features
|
||||
* Added an experimental API for handling flashcache devices (blacklists background threads from caching their reads) -- NewFlashcacheAwareEnv
|
||||
* If universal compaction is used and options.num_levels > 1, compact files are tried to be stored in none-L0 with smaller files based on options.target_file_size_base. The limitation of DB size when using universal compaction is greatly mitigated by using more levels. You can set num_levels = 1 to make universal compaction behave as before. If you set num_levels > 1 and want to roll back to a previous version, you need to compact all files to a big file in level 0 (by setting target_file_size_base to be large and CompactRange(<cf_handle>, nullptr, nullptr, true, 0) and reopen the DB with the same version to rewrite the manifest, and then you can open it using previous releases.
|
||||
* More information about rocksdb background threads are available in Env::GetThreadList(), including the number of bytes read / written by a compaction job, mem-table size and current number of bytes written by a flush job and many more. Check include/rocksdb/thread_status.h for more detail.
|
||||
|
||||
+2
-2
@@ -28,7 +28,7 @@ your make commands, like this: `PORTABLE=1 make static_lib`
|
||||
data compression.
|
||||
|
||||
* All our tools depend on:
|
||||
- [gflags](https://code.google.com/p/gflags/) - a library that handles
|
||||
- [gflags](https://gflags.github.io/gflags/) - a library that handles
|
||||
command line flags processing. You can compile rocksdb library even
|
||||
if you don't have gflags installed.
|
||||
|
||||
@@ -75,7 +75,7 @@ your make commands, like this: `PORTABLE=1 make static_lib`
|
||||
* Update XCode: run `xcode-select --install` (or install it from XCode App's settting).
|
||||
* Install via [homebrew](http://brew.sh/).
|
||||
* If you're first time developer in MacOS, you still need to run: `xcode-select --install` in your command line.
|
||||
* run `brew tap homebrew/dupes; brew install gcc47 --use-llvm` to install gcc 4.7 (or higher).
|
||||
* run `brew tap homebrew/versions; brew install gcc47 --use-llvm` to install gcc 4.7 (or higher).
|
||||
* run `brew install rocksdb`
|
||||
|
||||
* **iOS**:
|
||||
|
||||
@@ -30,7 +30,7 @@ quoted_perl_command = $(subst ','\'',$(perl_command))
|
||||
# We use this debug level when developing RocksDB.
|
||||
# * DEBUG_LEVEL=0; this is the debug level we use for release. If you're
|
||||
# running rocksdb in production you most definitely want to compile RocksDB
|
||||
# with debug level 0. To compile with level 0, run `make shared_lib`,
|
||||
# with debug level 0. To compile with level 0, run `make shared_lib`,
|
||||
# `make install-shared`, `make static_lib`, `make install-static` or
|
||||
# `make install`
|
||||
DEBUG_LEVEL=1
|
||||
@@ -231,6 +231,7 @@ TESTS = \
|
||||
dynamic_bloom_test \
|
||||
c_test \
|
||||
cache_test \
|
||||
checkpoint_test \
|
||||
coding_test \
|
||||
corruption_test \
|
||||
crc32c_test \
|
||||
@@ -287,7 +288,10 @@ TESTS = \
|
||||
thread_list_test \
|
||||
sst_dump_test \
|
||||
compact_files_test \
|
||||
perf_context_test
|
||||
perf_context_test \
|
||||
optimistic_transaction_test \
|
||||
write_callback_test \
|
||||
compaction_job_stats_test
|
||||
|
||||
SUBSET := $(shell echo $(TESTS) |sed s/^.*$(ROCKSDBTESTS_START)/$(ROCKSDBTESTS_START)/)
|
||||
|
||||
@@ -296,7 +300,9 @@ TOOLS = \
|
||||
db_sanity_test \
|
||||
db_stress \
|
||||
ldb \
|
||||
db_repl_stress
|
||||
db_repl_stress \
|
||||
rocksdb_dump \
|
||||
rocksdb_undump
|
||||
|
||||
BENCHMARKS = db_bench table_reader_bench cache_bench memtablerep_bench
|
||||
|
||||
@@ -342,7 +348,8 @@ $(SHARED3): $(SHARED4)
|
||||
endif
|
||||
|
||||
$(SHARED4):
|
||||
$(CXX) $(PLATFORM_SHARED_LDFLAGS)$(SHARED3) $(CXXFLAGS) $(PLATFORM_SHARED_CFLAGS) $(LIB_SOURCES) $(LDFLAGS) -o $@
|
||||
$(CXX) $(PLATFORM_SHARED_LDFLAGS)$(SHARED3) $(CXXFLAGS) $(PLATFORM_SHARED_CFLAGS) $(LIB_SOURCES) \
|
||||
$(LDFLAGS) -o $@
|
||||
|
||||
endif # PLATFORM_SHARED_EXT
|
||||
|
||||
@@ -362,11 +369,11 @@ dbg: $(LIBRARY) $(BENCHMARKS) $(TOOLS) $(TESTS)
|
||||
# creates static library and programs
|
||||
release:
|
||||
$(MAKE) clean
|
||||
OPT="-DNDEBUG -O2" $(MAKE) static_lib $(TOOLS) db_bench -j32
|
||||
OPT="-DNDEBUG -O2" $(MAKE) static_lib $(TOOLS) db_bench
|
||||
|
||||
coverage:
|
||||
$(MAKE) clean
|
||||
COVERAGEFLAGS="-fprofile-arcs -ftest-coverage" LDFLAGS+="-lgcov" $(MAKE) all check -j32
|
||||
COVERAGEFLAGS="-fprofile-arcs -ftest-coverage" LDFLAGS+="-lgcov" $(MAKE) all check
|
||||
cd coverage && ./coverage_test.sh
|
||||
# Delete intermediate files
|
||||
find . -type f -regex ".*\.\(\(gcda\)\|\(gcno\)\)" -exec rm {} \;
|
||||
@@ -497,18 +504,21 @@ CLEAN_FILES += t LOG $(TMPD)
|
||||
watch-log:
|
||||
watch --interval=0 'sort -k7,7nr -k4,4gr LOG|$(quoted_perl_command)'
|
||||
|
||||
# If GNU parallel is installed, run the tests in parallel,
|
||||
# If J != 1 and GNU parallel is installed, run the tests in parallel,
|
||||
# via the check_0 rule above. Otherwise, run them sequentially.
|
||||
check: all
|
||||
$(AM_V_GEN)case $$(parallel --gnu --help 2>/dev/null) in \
|
||||
*'GNU Parallel'*) \
|
||||
t=$$($(test_names)); \
|
||||
$(MAKE) T="$$t" TMPD=$(TMPD) check_0;; \
|
||||
*) \
|
||||
for t in $(TESTS); do \
|
||||
echo "===== Running $$t"; ./$$t || exit 1; done;; \
|
||||
esac
|
||||
$(AM_V_GEN)if test "$(J)" != 1 \
|
||||
&& (parallel --gnu --help 2>/dev/null) | \
|
||||
grep -q 'GNU Parallel'; \
|
||||
then \
|
||||
t=$$($(test_names)); \
|
||||
$(MAKE) T="$$t" TMPD=$(TMPD) check_0; \
|
||||
else \
|
||||
for t in $(TESTS); do \
|
||||
echo "===== Running $$t"; ./$$t || exit 1; done; \
|
||||
fi
|
||||
rm -rf $(TMPD)
|
||||
sh tools/rocksdb_dump_test.sh
|
||||
|
||||
check_some: $(SUBSET) ldb_tests
|
||||
for t in $(SUBSET); do echo "===== Running $$t"; ./$$t || exit 1; done
|
||||
@@ -689,6 +699,9 @@ prefix_test: db/prefix_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
backupable_db_test: utilities/backupable/backupable_db_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
checkpoint_test: utilities/checkpoint/checkpoint_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
document_db_test: utilities/document/document_db_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
@@ -710,6 +723,9 @@ flush_job_test: db/flush_job_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
compaction_job_test: db/compaction_job_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
compaction_job_stats_test: db/compaction_job_stats_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
wal_manager_test: db/wal_manager_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
@@ -782,6 +798,12 @@ deletefile_test: db/deletefile_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
geodb_test: utilities/geodb/geodb_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
rocksdb_dump: tools/dump/rocksdb_dump.o $(LIBOBJECTS)
|
||||
$(AM_LINK)
|
||||
|
||||
rocksdb_undump: tools/dump/rocksdb_undump.o $(LIBOBJECTS)
|
||||
$(AM_LINK)
|
||||
|
||||
cuckoo_table_builder_test: table/cuckoo_table_builder_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
@@ -797,9 +819,6 @@ listener_test: db/listener_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
thread_list_test: util/thread_list_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
compactor_test: utilities/compaction/compactor_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
compact_files_test: db/compact_files_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
@@ -815,6 +834,9 @@ sst_dump_test: util/sst_dump_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
memenv_test : util/memenv_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
optimistic_transaction_test: utilities/transactions/optimistic_transaction_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
mock_env_test : util/mock_env_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
@@ -830,6 +852,9 @@ auto_roll_logger_test: util/auto_roll_logger_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
memtable_list_test: db/memtable_list_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
write_callback_test: db/write_callback_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
sst_dump: tools/sst_dump.o $(LIBOBJECTS)
|
||||
$(AM_LINK)
|
||||
|
||||
@@ -939,7 +964,7 @@ rocksdbjavastatic: $(java_libobjects) libz.a libbz2.a libsnappy.a liblz4.a
|
||||
$(CXX) $(CXXFLAGS) -I./java/. $(JAVA_INCLUDE) -shared -fPIC \
|
||||
-o ./java/target/$(ROCKSDBJNILIB) $(JNI_NATIVE_SOURCES) \
|
||||
$(java_libobjects) $(COVERAGEFLAGS) \
|
||||
libz.a libbz2.a libsnappy.a liblz4.a
|
||||
libz.a libbz2.a libsnappy.a liblz4.a $(LDFLAGS)
|
||||
cd java/target;strip -S -x $(ROCKSDBJNILIB)
|
||||
cd java;jar -cf target/$(ROCKSDB_JAR) HISTORY*.md
|
||||
cd java/target;jar -uf $(ROCKSDB_JAR) $(ROCKSDBJNILIB)
|
||||
|
||||
@@ -8,6 +8,7 @@ Some examples of the features disabled by ROCKSDB_LITE:
|
||||
* No support for replication (which we provide in form of TrasactionalIterator)
|
||||
* No advanced monitoring tools
|
||||
* No special-purpose memtables that are highly optimized for specific use cases
|
||||
* No Transactions
|
||||
|
||||
When adding a new big feature to RocksDB, please add ROCKSDB_LITE compile guard if:
|
||||
* Nobody from mobile really needs your feature,
|
||||
|
||||
@@ -15,7 +15,7 @@ Two different use cases at Linkedin are using RocksDB as a storage engine:
|
||||
Learn more about those use cases in a Tech Talk by Ankit Gupta and Naveen Somasundaram: http://www.youtube.com/watch?v=plqVp_OnSzg
|
||||
|
||||
## Yahoo
|
||||
Yahoo is using RocksDB as a storage engine for their biggest distributed data store Sherpa.
|
||||
Yahoo is using RocksDB as a storage engine for their biggest distributed data store Sherpa. Learn more about it here: http://yahooeng.tumblr.com/post/120730204806/sherpa-scales-new-heights
|
||||
|
||||
## CockroachDB
|
||||
CockroachDB is an open-source geo-replicated transactional database (still in development). They are using RocksDB as their storage engine. Check out their github: https://github.com/cockroachdb/cockroach
|
||||
|
||||
@@ -92,7 +92,7 @@ CROSS_COMPILE=
|
||||
PLATFORM_CCFLAGS=
|
||||
PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS"
|
||||
PLATFORM_SHARED_EXT="so"
|
||||
PLATFORM_SHARED_LDFLAGS="-shared -Wl,-soname -Wl,"
|
||||
PLATFORM_SHARED_LDFLAGS="-Wl,--no-as-needed -shared -Wl,-soname -Wl,"
|
||||
PLATFORM_SHARED_CFLAGS="-fPIC"
|
||||
PLATFORM_SHARED_VERSIONED=true
|
||||
|
||||
@@ -157,6 +157,7 @@ case "$TARGET_OS" in
|
||||
;;
|
||||
Cygwin)
|
||||
PLATFORM=CYGWIN
|
||||
PLATFORM_SHARED_CFLAGS=""
|
||||
PLATFORM_CXXFLAGS="-std=gnu++11"
|
||||
COMMON_FLAGS="$COMMON_FLAGS -DCYGWIN"
|
||||
if [ -z "$USE_CLANG" ]; then
|
||||
@@ -293,6 +294,18 @@ EOF
|
||||
JAVA_LDFLAGS="$JAVA_LDFLAGS -ltcmalloc"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Test whether malloc_usable_size is available
|
||||
$CXX $CFLAGS -x c++ - -o /dev/null 2>/dev/null <<EOF
|
||||
#include <malloc.h>
|
||||
int main() {
|
||||
size_t res = malloc_usable_size(0);
|
||||
return 0;
|
||||
}
|
||||
EOF
|
||||
if [ "$?" = 0 ]; then
|
||||
COMMON_FLAGS="$COMMON_FLAGS -DROCKSDB_MALLOC_USABLE_SIZE"
|
||||
fi
|
||||
fi
|
||||
|
||||
# TODO(tec): Fix -Wshorten-64-to-32 errors on FreeBSD and enable the warning.
|
||||
|
||||
Executable
+2
@@ -0,0 +1,2 @@
|
||||
#!/bin/bash
|
||||
docker run -v $PWD:/rocks -w /rocks buildpack-deps make
|
||||
Executable
+55
@@ -0,0 +1,55 @@
|
||||
#!/bin/sh
|
||||
|
||||
# fail early
|
||||
set -e
|
||||
|
||||
if test -z $ROCKSDB_PATH; then
|
||||
ROCKSDB_PATH=~/rocksdb
|
||||
fi
|
||||
source $ROCKSDB_PATH/build_tools/fbcode_config4.8.1.sh
|
||||
|
||||
EXTRA_LDFLAGS=""
|
||||
|
||||
if test -z $ALLOC; then
|
||||
# default
|
||||
ALLOC=tcmalloc
|
||||
elif [[ $ALLOC == "jemalloc" ]]; then
|
||||
ALLOC=system
|
||||
EXTRA_LDFLAGS+=" -Wl,--whole-archive $JEMALLOC_LIB -Wl,--no-whole-archive"
|
||||
fi
|
||||
|
||||
# we need to force mongo to use static library, not shared
|
||||
STATIC_LIB_DEP_DIR='build/static_library_dependencies'
|
||||
test -d $STATIC_LIB_DEP_DIR || mkdir $STATIC_LIB_DEP_DIR
|
||||
test -h $STATIC_LIB_DEP_DIR/`basename $SNAPPY_LIBS` || ln -s $SNAPPY_LIBS $STATIC_LIB_DEP_DIR
|
||||
test -h $STATIC_LIB_DEP_DIR/`basename $LZ4_LIBS` || ln -s $LZ4_LIBS $STATIC_LIB_DEP_DIR
|
||||
|
||||
EXTRA_LDFLAGS+=" -L $STATIC_LIB_DEP_DIR"
|
||||
|
||||
set -x
|
||||
|
||||
EXTRA_CMD=""
|
||||
if ! test -e version.json; then
|
||||
# this is Mongo 3.0
|
||||
EXTRA_CMD="--rocksdb \
|
||||
--variant-dir=linux2/norm
|
||||
--cxx=${CXX} \
|
||||
--cc=${CC} \
|
||||
--use-system-zlib" # add this line back to normal code path
|
||||
# when https://jira.mongodb.org/browse/SERVER-19123 is resolved
|
||||
fi
|
||||
|
||||
scons \
|
||||
LINKFLAGS="$EXTRA_LDFLAGS $EXEC_LDFLAGS $PLATFORM_LDFLAGS" \
|
||||
CCFLAGS="$CXXFLAGS -L $STATIC_LIB_DEP_DIR" \
|
||||
LIBS="lz4 gcc stdc++" \
|
||||
LIBPATH="$ROCKSDB_PATH" \
|
||||
CPPPATH="$ROCKSDB_PATH/include" \
|
||||
-j32 \
|
||||
--allocator=$ALLOC \
|
||||
--nostrip \
|
||||
--opt=on \
|
||||
--disable-minimum-compiler-version-enforcement \
|
||||
--use-system-snappy \
|
||||
--disable-warnings-as-errors \
|
||||
$EXTRA_CMD $*
|
||||
@@ -18,19 +18,6 @@ GLIBC_REV=7397bed99280af5d9543439cdb7d018af7542720
|
||||
GLIBC_INCLUDE="/mnt/gvfs/third-party2/glibc/$GLIBC_REV/2.20/gcc-4.9-glibc-2.20/99df8fc/include"
|
||||
GLIBC_LIBS=" -L /mnt/gvfs/third-party2/glibc/$GLIBC_REV/2.20/gcc-4.9-glibc-2.20/99df8fc/lib"
|
||||
|
||||
# snappy and zlib depend are bundled with MongoDB so we wan't to pick up the bundled headers when
|
||||
# building for it and disable block compressors supported by RocksDB but not used by MongoDB.
|
||||
|
||||
if [[ -n $ROCKSDB_FOR_MONGO ]]; then
|
||||
|
||||
MONGO_SRC="$ROCKSDB_FOR_MONGO/src/third_party"
|
||||
SNAPPY_INCLUDE=" -I $MONGO_SRC/snappy-1.1.2"
|
||||
CFLAGS+=" -DSNAPPY"
|
||||
ZLIB_INCLUDE=" -I $MONGO_SRC/zlib-1.2.8"
|
||||
CFLAGS+=" -DZLIB"
|
||||
|
||||
else
|
||||
|
||||
SNAPPY_INCLUDE=" -I /mnt/gvfs/third-party2/snappy/b0f269b3ca47770121aa159b99e1d8d2ab260e1f/1.0.3/gcc-4.9-glibc-2.20/c32916f/include/"
|
||||
|
||||
if test -z $PIC_BUILD; then
|
||||
@@ -56,7 +43,6 @@ if test -z $PIC_BUILD; then
|
||||
LZ4_LIBS=" /mnt/gvfs/third-party2/lz4/79d2943e2dd7208a3e0b06cf95e9f85f05fe9e1b/r124/gcc-4.9-glibc-2.20/4230243/lib/liblz4.a"
|
||||
CFLAGS+=" -DLZ4"
|
||||
fi
|
||||
fi
|
||||
|
||||
# location of gflags headers and libraries
|
||||
GFLAGS_INCLUDE=" -I /mnt/gvfs/third-party2/gflags/0fa60e2b88de3e469db6c482d6e6dac72f5d65f9/1.6/gcc-4.9-glibc-2.20/4230243/include/"
|
||||
@@ -125,7 +111,7 @@ else
|
||||
fi
|
||||
|
||||
CFLAGS+=" $DEPS_INCLUDE"
|
||||
CFLAGS+=" -DROCKSDB_PLATFORM_POSIX -DROCKSDB_FALLOCATE_PRESENT"
|
||||
CFLAGS+=" -DROCKSDB_PLATFORM_POSIX -DROCKSDB_FALLOCATE_PRESENT -DROCKSDB_MALLOC_USABLE_SIZE"
|
||||
CXXFLAGS+=" $CFLAGS"
|
||||
|
||||
EXEC_LDFLAGS=" $SNAPPY_LIBS $ZLIB_LIBS $BZIP_LIBS $LZ4_LIBS $GFLAGS_LIBS $NUMA_LIB"
|
||||
|
||||
@@ -87,13 +87,14 @@ else
|
||||
fi
|
||||
|
||||
CFLAGS+=" $DEPS_INCLUDE"
|
||||
CFLAGS+=" -DROCKSDB_PLATFORM_POSIX -DROCKSDB_FALLOCATE_PRESENT"
|
||||
CFLAGS+=" -DROCKSDB_PLATFORM_POSIX -DROCKSDB_FALLOCATE_PRESENT -DROCKSDB_MALLOC_USABLE_SIZE"
|
||||
CFLAGS+=" -DSNAPPY -DGFLAGS=google -DZLIB -DBZIP2 -DLZ4 -DNUMA"
|
||||
CXXFLAGS+=" $CFLAGS"
|
||||
|
||||
EXEC_LDFLAGS=" $SNAPPY_LIBS $ZLIB_LIBS $BZIP_LIBS $LZ4_LIBS $GFLAGS_LIBS $NUMA_LIB"
|
||||
EXEC_LDFLAGS+=" -Wl,--dynamic-linker,/usr/local/fbcode/gcc-4.8.1-glibc-2.17/lib/ld.so"
|
||||
EXEC_LDFLAGS+=" $LIBUNWIND"
|
||||
EXEC_LDFLAGS+=" -Wl,-rpath=/usr/local/fbcode/gcc-4.8.1-glibc-2.17/lib"
|
||||
|
||||
PLATFORM_LDFLAGS="$LIBGCC_LIBS $GLIBC_LIBS $STDLIBS -lgcc -lstdc++"
|
||||
|
||||
|
||||
@@ -222,6 +222,7 @@ Status BuildTable(
|
||||
}
|
||||
if (s.ok()) {
|
||||
meta->fd.file_size = builder->FileSize();
|
||||
meta->marked_for_compaction = builder->NeedCompact();
|
||||
assert(meta->fd.GetFileSize() > 0);
|
||||
if (table_properties) {
|
||||
*table_properties = builder->GetTableProperties();
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
#include "rocksdb/slice_transform.h"
|
||||
#include "rocksdb/table.h"
|
||||
#include "rocksdb/utilities/backupable_db.h"
|
||||
#include "utilities/merge_operators.h"
|
||||
|
||||
using rocksdb::Cache;
|
||||
using rocksdb::ColumnFamilyDescriptor;
|
||||
@@ -53,6 +54,7 @@ using rocksdb::FlushOptions;
|
||||
using rocksdb::Iterator;
|
||||
using rocksdb::Logger;
|
||||
using rocksdb::MergeOperator;
|
||||
using rocksdb::MergeOperators;
|
||||
using rocksdb::NewBloomFilterPolicy;
|
||||
using rocksdb::NewLRUCache;
|
||||
using rocksdb::Options;
|
||||
@@ -63,6 +65,7 @@ using rocksdb::Range;
|
||||
using rocksdb::ReadOptions;
|
||||
using rocksdb::SequentialFile;
|
||||
using rocksdb::Slice;
|
||||
using rocksdb::SliceParts;
|
||||
using rocksdb::SliceTransform;
|
||||
using rocksdb::Snapshot;
|
||||
using rocksdb::Status;
|
||||
@@ -74,6 +77,7 @@ using rocksdb::BackupEngine;
|
||||
using rocksdb::BackupableDBOptions;
|
||||
using rocksdb::BackupInfo;
|
||||
using rocksdb::RestoreOptions;
|
||||
using rocksdb::CompactRangeOptions;
|
||||
|
||||
using std::shared_ptr;
|
||||
|
||||
@@ -602,6 +606,10 @@ void rocksdb_close(rocksdb_t* db) {
|
||||
delete db;
|
||||
}
|
||||
|
||||
void rocksdb_options_set_uint64add_merge_operator(rocksdb_options_t* opt) {
|
||||
opt->rep.merge_operator = rocksdb::MergeOperators::CreateUInt64AddOperator();
|
||||
}
|
||||
|
||||
rocksdb_t* rocksdb_open_column_families(
|
||||
const rocksdb_options_t* db_options,
|
||||
const char* name,
|
||||
@@ -830,6 +838,69 @@ char* rocksdb_get_cf(
|
||||
return result;
|
||||
}
|
||||
|
||||
void rocksdb_multi_get(
|
||||
rocksdb_t* db,
|
||||
const rocksdb_readoptions_t* options,
|
||||
size_t num_keys, const char* const* keys_list,
|
||||
const size_t* keys_list_sizes,
|
||||
char** values_list, size_t* values_list_sizes,
|
||||
char** errs) {
|
||||
std::vector<Slice> keys(num_keys);
|
||||
for (size_t i = 0; i < num_keys; i++) {
|
||||
keys[i] = Slice(keys_list[i], keys_list_sizes[i]);
|
||||
}
|
||||
std::vector<std::string> values(num_keys);
|
||||
std::vector<Status> statuses = db->rep->MultiGet(options->rep, keys, &values);
|
||||
for (size_t i = 0; i < num_keys; i++) {
|
||||
if (statuses[i].ok()) {
|
||||
values_list[i] = CopyString(values[i]);
|
||||
values_list_sizes[i] = values[i].size();
|
||||
errs[i] = nullptr;
|
||||
} else {
|
||||
values_list[i] = nullptr;
|
||||
values_list_sizes[i] = 0;
|
||||
if (!statuses[i].IsNotFound()) {
|
||||
errs[i] = strdup(statuses[i].ToString().c_str());
|
||||
} else {
|
||||
errs[i] = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void rocksdb_multi_get_cf(
|
||||
rocksdb_t* db,
|
||||
const rocksdb_readoptions_t* options,
|
||||
const rocksdb_column_family_handle_t* const* column_families,
|
||||
size_t num_keys, const char* const* keys_list,
|
||||
const size_t* keys_list_sizes,
|
||||
char** values_list, size_t* values_list_sizes,
|
||||
char** errs) {
|
||||
std::vector<Slice> keys(num_keys);
|
||||
std::vector<ColumnFamilyHandle*> cfs(num_keys);
|
||||
for (size_t i = 0; i < num_keys; i++) {
|
||||
keys[i] = Slice(keys_list[i], keys_list_sizes[i]);
|
||||
cfs[i] = column_families[i]->rep;
|
||||
}
|
||||
std::vector<std::string> values(num_keys);
|
||||
std::vector<Status> statuses = db->rep->MultiGet(options->rep, cfs, keys, &values);
|
||||
for (size_t i = 0; i < num_keys; i++) {
|
||||
if (statuses[i].ok()) {
|
||||
values_list[i] = CopyString(values[i]);
|
||||
values_list_sizes[i] = values[i].size();
|
||||
errs[i] = nullptr;
|
||||
} else {
|
||||
values_list[i] = nullptr;
|
||||
values_list_sizes[i] = 0;
|
||||
if (!statuses[i].IsNotFound()) {
|
||||
errs[i] = strdup(statuses[i].ToString().c_str());
|
||||
} else {
|
||||
errs[i] = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
rocksdb_iterator_t* rocksdb_create_iterator(
|
||||
rocksdb_t* db,
|
||||
const rocksdb_readoptions_t* options) {
|
||||
@@ -936,6 +1007,7 @@ void rocksdb_compact_range(
|
||||
const char* limit_key, size_t limit_key_len) {
|
||||
Slice a, b;
|
||||
db->rep->CompactRange(
|
||||
CompactRangeOptions(),
|
||||
// Pass nullptr Slice if corresponding "const char*" is nullptr
|
||||
(start_key ? (a = Slice(start_key, start_key_len), &a) : nullptr),
|
||||
(limit_key ? (b = Slice(limit_key, limit_key_len), &b) : nullptr));
|
||||
@@ -948,7 +1020,7 @@ void rocksdb_compact_range_cf(
|
||||
const char* limit_key, size_t limit_key_len) {
|
||||
Slice a, b;
|
||||
db->rep->CompactRange(
|
||||
column_family->rep,
|
||||
CompactRangeOptions(), column_family->rep,
|
||||
// Pass nullptr Slice if corresponding "const char*" is nullptr
|
||||
(start_key ? (a = Slice(start_key, start_key_len), &a) : nullptr),
|
||||
(limit_key ? (b = Slice(limit_key, limit_key_len), &b) : nullptr));
|
||||
@@ -1071,6 +1143,43 @@ void rocksdb_writebatch_put_cf(
|
||||
b->rep.Put(column_family->rep, Slice(key, klen), Slice(val, vlen));
|
||||
}
|
||||
|
||||
void rocksdb_writebatch_putv(
|
||||
rocksdb_writebatch_t* b,
|
||||
int num_keys, const char* const* keys_list,
|
||||
const size_t* keys_list_sizes,
|
||||
int num_values, const char* const* values_list,
|
||||
const size_t* values_list_sizes) {
|
||||
std::vector<Slice> key_slices(num_keys);
|
||||
for (int i = 0; i < num_keys; i++) {
|
||||
key_slices[i] = Slice(keys_list[i], keys_list_sizes[i]);
|
||||
}
|
||||
std::vector<Slice> value_slices(num_values);
|
||||
for (int i = 0; i < num_values; i++) {
|
||||
value_slices[i] = Slice(values_list[i], values_list_sizes[i]);
|
||||
}
|
||||
b->rep.Put(SliceParts(key_slices.data(), num_keys),
|
||||
SliceParts(value_slices.data(), num_values));
|
||||
}
|
||||
|
||||
void rocksdb_writebatch_putv_cf(
|
||||
rocksdb_writebatch_t* b,
|
||||
rocksdb_column_family_handle_t* column_family,
|
||||
int num_keys, const char* const* keys_list,
|
||||
const size_t* keys_list_sizes,
|
||||
int num_values, const char* const* values_list,
|
||||
const size_t* values_list_sizes) {
|
||||
std::vector<Slice> key_slices(num_keys);
|
||||
for (int i = 0; i < num_keys; i++) {
|
||||
key_slices[i] = Slice(keys_list[i], keys_list_sizes[i]);
|
||||
}
|
||||
std::vector<Slice> value_slices(num_values);
|
||||
for (int i = 0; i < num_values; i++) {
|
||||
value_slices[i] = Slice(values_list[i], values_list_sizes[i]);
|
||||
}
|
||||
b->rep.Put(column_family->rep, SliceParts(key_slices.data(), num_keys),
|
||||
SliceParts(value_slices.data(), num_values));
|
||||
}
|
||||
|
||||
void rocksdb_writebatch_merge(
|
||||
rocksdb_writebatch_t* b,
|
||||
const char* key, size_t klen,
|
||||
@@ -1086,6 +1195,43 @@ void rocksdb_writebatch_merge_cf(
|
||||
b->rep.Merge(column_family->rep, Slice(key, klen), Slice(val, vlen));
|
||||
}
|
||||
|
||||
void rocksdb_writebatch_mergev(
|
||||
rocksdb_writebatch_t* b,
|
||||
int num_keys, const char* const* keys_list,
|
||||
const size_t* keys_list_sizes,
|
||||
int num_values, const char* const* values_list,
|
||||
const size_t* values_list_sizes) {
|
||||
std::vector<Slice> key_slices(num_keys);
|
||||
for (int i = 0; i < num_keys; i++) {
|
||||
key_slices[i] = Slice(keys_list[i], keys_list_sizes[i]);
|
||||
}
|
||||
std::vector<Slice> value_slices(num_values);
|
||||
for (int i = 0; i < num_values; i++) {
|
||||
value_slices[i] = Slice(values_list[i], values_list_sizes[i]);
|
||||
}
|
||||
b->rep.Merge(SliceParts(key_slices.data(), num_keys),
|
||||
SliceParts(value_slices.data(), num_values));
|
||||
}
|
||||
|
||||
void rocksdb_writebatch_mergev_cf(
|
||||
rocksdb_writebatch_t* b,
|
||||
rocksdb_column_family_handle_t* column_family,
|
||||
int num_keys, const char* const* keys_list,
|
||||
const size_t* keys_list_sizes,
|
||||
int num_values, const char* const* values_list,
|
||||
const size_t* values_list_sizes) {
|
||||
std::vector<Slice> key_slices(num_keys);
|
||||
for (int i = 0; i < num_keys; i++) {
|
||||
key_slices[i] = Slice(keys_list[i], keys_list_sizes[i]);
|
||||
}
|
||||
std::vector<Slice> value_slices(num_values);
|
||||
for (int i = 0; i < num_values; i++) {
|
||||
value_slices[i] = Slice(values_list[i], values_list_sizes[i]);
|
||||
}
|
||||
b->rep.Merge(column_family->rep, SliceParts(key_slices.data(), num_keys),
|
||||
SliceParts(value_slices.data(), num_values));
|
||||
}
|
||||
|
||||
void rocksdb_writebatch_delete(
|
||||
rocksdb_writebatch_t* b,
|
||||
const char* key, size_t klen) {
|
||||
@@ -1099,6 +1245,35 @@ void rocksdb_writebatch_delete_cf(
|
||||
b->rep.Delete(column_family->rep, Slice(key, klen));
|
||||
}
|
||||
|
||||
void rocksdb_writebatch_deletev(
|
||||
rocksdb_writebatch_t* b,
|
||||
int num_keys, const char* const* keys_list,
|
||||
const size_t* keys_list_sizes) {
|
||||
std::vector<Slice> key_slices(num_keys);
|
||||
for (int i = 0; i < num_keys; i++) {
|
||||
key_slices[i] = Slice(keys_list[i], keys_list_sizes[i]);
|
||||
}
|
||||
b->rep.Delete(SliceParts(key_slices.data(), num_keys));
|
||||
}
|
||||
|
||||
void rocksdb_writebatch_deletev_cf(
|
||||
rocksdb_writebatch_t* b,
|
||||
rocksdb_column_family_handle_t* column_family,
|
||||
int num_keys, const char* const* keys_list,
|
||||
const size_t* keys_list_sizes) {
|
||||
std::vector<Slice> key_slices(num_keys);
|
||||
for (int i = 0; i < num_keys; i++) {
|
||||
key_slices[i] = Slice(keys_list[i], keys_list_sizes[i]);
|
||||
}
|
||||
b->rep.Delete(column_family->rep, SliceParts(key_slices.data(), num_keys));
|
||||
}
|
||||
|
||||
void rocksdb_writebatch_put_log_data(
|
||||
rocksdb_writebatch_t* b,
|
||||
const char* blob, size_t len) {
|
||||
b->rep.PutLogData(Slice(blob, len));
|
||||
}
|
||||
|
||||
void rocksdb_writebatch_iterate(
|
||||
rocksdb_writebatch_t* b,
|
||||
void* state,
|
||||
@@ -1186,6 +1361,26 @@ void rocksdb_block_based_options_set_whole_key_filtering(
|
||||
options->rep.whole_key_filtering = v;
|
||||
}
|
||||
|
||||
void rocksdb_block_based_options_set_format_version(
|
||||
rocksdb_block_based_table_options_t* options, int v) {
|
||||
options->rep.format_version = v;
|
||||
}
|
||||
|
||||
void rocksdb_block_based_options_set_index_type(
|
||||
rocksdb_block_based_table_options_t* options, int v) {
|
||||
options->rep.index_type = static_cast<BlockBasedTableOptions::IndexType>(v);
|
||||
}
|
||||
|
||||
void rocksdb_block_based_options_set_hash_index_allow_collision(
|
||||
rocksdb_block_based_table_options_t* options, unsigned char v) {
|
||||
options->rep.hash_index_allow_collision = v;
|
||||
}
|
||||
|
||||
void rocksdb_block_based_options_set_cache_index_and_filter_blocks(
|
||||
rocksdb_block_based_table_options_t* options, unsigned char v) {
|
||||
options->rep.cache_index_and_filter_blocks = v;
|
||||
}
|
||||
|
||||
void rocksdb_options_set_block_based_table_factory(
|
||||
rocksdb_options_t *opt,
|
||||
rocksdb_block_based_table_options_t* table_options) {
|
||||
@@ -1568,6 +1763,11 @@ void rocksdb_options_set_min_write_buffer_number_to_merge(rocksdb_options_t* opt
|
||||
opt->rep.min_write_buffer_number_to_merge = n;
|
||||
}
|
||||
|
||||
void rocksdb_options_set_max_write_buffer_number_to_maintain(
|
||||
rocksdb_options_t* opt, int n) {
|
||||
opt->rep.max_write_buffer_number_to_maintain = n;
|
||||
}
|
||||
|
||||
void rocksdb_options_set_max_background_compactions(rocksdb_options_t* opt, int n) {
|
||||
opt->rep.max_background_compactions = n;
|
||||
}
|
||||
@@ -1732,10 +1932,17 @@ void rocksdb_options_set_fifo_compaction_options(
|
||||
opt->rep.compaction_options_fifo = fifo->rep;
|
||||
}
|
||||
|
||||
char *rocksdb_options_statistics_get_string(rocksdb_options_t *opt) {
|
||||
rocksdb::Statistics *statistics = opt->rep.statistics.get();
|
||||
if (statistics) {
|
||||
return strdup(statistics->ToString().c_str());
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/*
|
||||
TODO:
|
||||
DB::OpenForReadOnly
|
||||
DB::MultiGet
|
||||
DB::KeyMayExist
|
||||
DB::GetOptions
|
||||
DB::GetSortedWalFiles
|
||||
@@ -2100,6 +2307,27 @@ rocksdb_slicetransform_t* rocksdb_slicetransform_create_fixed_prefix(size_t pref
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
rocksdb_slicetransform_t* rocksdb_slicetransform_create_noop() {
|
||||
struct Wrapper : public rocksdb_slicetransform_t {
|
||||
const SliceTransform* rep_;
|
||||
~Wrapper() { delete rep_; }
|
||||
const char* Name() const override { return rep_->Name(); }
|
||||
Slice Transform(const Slice& src) const override {
|
||||
return rep_->Transform(src);
|
||||
}
|
||||
bool InDomain(const Slice& src) const override {
|
||||
return rep_->InDomain(src);
|
||||
}
|
||||
bool InRange(const Slice& src) const override { return rep_->InRange(src); }
|
||||
static void DoNothing(void*) { }
|
||||
};
|
||||
Wrapper* wrapper = new Wrapper;
|
||||
wrapper->rep_ = rocksdb::NewNoopTransform();
|
||||
wrapper->state_ = nullptr;
|
||||
wrapper->destructor_ = &Wrapper::DoNothing;
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
rocksdb_universal_compaction_options_t* rocksdb_universal_compaction_options_create() {
|
||||
rocksdb_universal_compaction_options_t* result = new rocksdb_universal_compaction_options_t;
|
||||
result->rep = new rocksdb::CompactionOptionsUniversal;
|
||||
@@ -2217,4 +2445,4 @@ extern void rocksdb_livefiles_destroy(
|
||||
|
||||
} // end extern "C"
|
||||
|
||||
#endif // ROCKSDB_LITE
|
||||
#endif // !ROCKSDB_LITE
|
||||
+82
-1
@@ -2,6 +2,8 @@
|
||||
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. */
|
||||
|
||||
#ifndef ROCKSDB_LITE // Lite does not support C API
|
||||
|
||||
#include "rocksdb/c.h"
|
||||
|
||||
#include <stddef.h>
|
||||
@@ -465,6 +467,24 @@ int main(int argc, char** argv) {
|
||||
rocksdb_writebatch_destroy(wb);
|
||||
}
|
||||
|
||||
StartPhase("writebatch_vectors");
|
||||
{
|
||||
rocksdb_writebatch_t* wb = rocksdb_writebatch_create();
|
||||
const char* k_list[2] = { "z", "ap" };
|
||||
const size_t k_sizes[2] = { 1, 2 };
|
||||
const char* v_list[3] = { "x", "y", "z" };
|
||||
const size_t v_sizes[3] = { 1, 1, 1 };
|
||||
rocksdb_writebatch_putv(wb, 2, k_list, k_sizes, 3, v_list, v_sizes);
|
||||
rocksdb_write(db, woptions, wb, &err);
|
||||
CheckNoError(err);
|
||||
CheckGet(db, roptions, "zap", "xyz");
|
||||
rocksdb_writebatch_delete(wb, "zap", 3);
|
||||
rocksdb_write(db, woptions, wb, &err);
|
||||
CheckNoError(err);
|
||||
CheckGet(db, roptions, "zap", NULL);
|
||||
rocksdb_writebatch_destroy(wb);
|
||||
}
|
||||
|
||||
StartPhase("writebatch_rep");
|
||||
{
|
||||
rocksdb_writebatch_t* wb1 = rocksdb_writebatch_create();
|
||||
@@ -505,6 +525,33 @@ int main(int argc, char** argv) {
|
||||
rocksdb_iter_destroy(iter);
|
||||
}
|
||||
|
||||
StartPhase("multiget");
|
||||
{
|
||||
const char* keys[3] = { "box", "foo", "notfound" };
|
||||
const size_t keys_sizes[3] = { 3, 3, 8 };
|
||||
char* vals[3];
|
||||
size_t vals_sizes[3];
|
||||
char* errs[3];
|
||||
rocksdb_multi_get(db, roptions, 3, keys, keys_sizes, vals, vals_sizes, errs);
|
||||
|
||||
int i;
|
||||
for (i = 0; i < 3; i++) {
|
||||
CheckEqual(NULL, errs[i], 0);
|
||||
switch (i) {
|
||||
case 0:
|
||||
CheckEqual("c", vals[i], vals_sizes[i]);
|
||||
break;
|
||||
case 1:
|
||||
CheckEqual("hello", vals[i], vals_sizes[i]);
|
||||
break;
|
||||
case 2:
|
||||
CheckEqual(NULL, vals[i], vals_sizes[i]);
|
||||
break;
|
||||
}
|
||||
Free(&vals[i]);
|
||||
}
|
||||
}
|
||||
|
||||
StartPhase("approximate_sizes");
|
||||
{
|
||||
int i;
|
||||
@@ -778,12 +825,36 @@ int main(int argc, char** argv) {
|
||||
CheckGetCF(db, roptions, handles[1], "box", "c");
|
||||
rocksdb_writebatch_destroy(wb);
|
||||
|
||||
const char* keys[3] = { "box", "box", "barfooxx" };
|
||||
const rocksdb_column_family_handle_t* get_handles[3] = { handles[0], handles[1], handles[1] };
|
||||
const size_t keys_sizes[3] = { 3, 3, 8 };
|
||||
char* vals[3];
|
||||
size_t vals_sizes[3];
|
||||
char* errs[3];
|
||||
rocksdb_multi_get_cf(db, roptions, get_handles, 3, keys, keys_sizes, vals, vals_sizes, errs);
|
||||
|
||||
int i;
|
||||
for (i = 0; i < 3; i++) {
|
||||
CheckEqual(NULL, errs[i], 0);
|
||||
switch (i) {
|
||||
case 0:
|
||||
CheckEqual(NULL, vals[i], vals_sizes[i]); // wrong cf
|
||||
break;
|
||||
case 1:
|
||||
CheckEqual("c", vals[i], vals_sizes[i]); // bingo
|
||||
break;
|
||||
case 2:
|
||||
CheckEqual(NULL, vals[i], vals_sizes[i]); // normal not found
|
||||
break;
|
||||
}
|
||||
Free(&vals[i]);
|
||||
}
|
||||
|
||||
rocksdb_iterator_t* iter = rocksdb_create_iterator_cf(db, roptions, handles[1]);
|
||||
CheckCondition(!rocksdb_iter_valid(iter));
|
||||
rocksdb_iter_seek_to_first(iter);
|
||||
CheckCondition(rocksdb_iter_valid(iter));
|
||||
|
||||
int i;
|
||||
for (i = 0; rocksdb_iter_valid(iter) != 0; rocksdb_iter_next(iter)) {
|
||||
i++;
|
||||
}
|
||||
@@ -938,3 +1009,13 @@ int main(int argc, char** argv) {
|
||||
fprintf(stderr, "PASS\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
#else
|
||||
#include <stdio.h>
|
||||
|
||||
int main() {
|
||||
fprintf(stderr, "SKIPPED\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif // !ROCKSDB_LITE
|
||||
+62
-133
@@ -25,46 +25,18 @@
|
||||
#include "db/version_set.h"
|
||||
#include "db/writebuffer.h"
|
||||
#include "db/internal_stats.h"
|
||||
#include "db/job_context.h"
|
||||
#include "db/table_properties_collector.h"
|
||||
#include "db/version_set.h"
|
||||
#include "db/write_controller.h"
|
||||
#include "util/autovector.h"
|
||||
#include "util/compression.h"
|
||||
#include "util/hash_skiplist_rep.h"
|
||||
#include "util/options_helper.h"
|
||||
#include "util/thread_status_util.h"
|
||||
#include "util/xfunc.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
namespace {
|
||||
// This function computes the amount of time in microseconds by which a write
|
||||
// should be delayed based on the number of level-0 files according to the
|
||||
// following formula:
|
||||
// if n < bottom, return 0;
|
||||
// if n >= top, return 1000;
|
||||
// otherwise, let r = (n - bottom) /
|
||||
// (top - bottom)
|
||||
// and return r^2 * 1000.
|
||||
// The goal of this formula is to gradually increase the rate at which writes
|
||||
// are slowed. We also tried linear delay (r * 1000), but it seemed to do
|
||||
// slightly worse. There is no other particular reason for choosing quadratic.
|
||||
uint64_t SlowdownAmount(int n, double bottom, double top) {
|
||||
uint64_t delay;
|
||||
if (n >= top) {
|
||||
delay = 1000;
|
||||
} else if (n < bottom) {
|
||||
delay = 0;
|
||||
} else {
|
||||
// If we are here, we know that:
|
||||
// level0_start_slowdown <= n < level0_slowdown
|
||||
// since the previous two conditions are false.
|
||||
double how_much = static_cast<double>(n - bottom) / (top - bottom);
|
||||
delay = std::max(how_much * how_much * 1000, 100.0);
|
||||
}
|
||||
assert(delay <= 1000);
|
||||
return delay;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
ColumnFamilyHandleImpl::ColumnFamilyHandleImpl(
|
||||
ColumnFamilyData* column_family_data, DBImpl* db, InstrumentedMutex* mutex)
|
||||
: cfd_(column_family_data), db_(db), mutex_(mutex) {
|
||||
@@ -87,6 +59,7 @@ ColumnFamilyHandleImpl::~ColumnFamilyHandleImpl() {
|
||||
if (job_context.HaveSomethingToDelete()) {
|
||||
db_->PurgeObsoleteFiles(job_context);
|
||||
}
|
||||
job_context.Clean();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,6 +89,28 @@ void GetIntTblPropCollectorFactory(
|
||||
new InternalKeyPropertiesCollectorFactory);
|
||||
}
|
||||
|
||||
Status CheckCompressionSupported(const ColumnFamilyOptions& cf_options) {
|
||||
if (!cf_options.compression_per_level.empty()) {
|
||||
for (size_t level = 0; level < cf_options.compression_per_level.size();
|
||||
++level) {
|
||||
if (!CompressionTypeSupported(cf_options.compression_per_level[level])) {
|
||||
return Status::InvalidArgument(
|
||||
"Compression type " +
|
||||
CompressionTypeToString(cf_options.compression_per_level[level]) +
|
||||
" is not linked with the binary.");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (!CompressionTypeSupported(cf_options.compression)) {
|
||||
return Status::InvalidArgument(
|
||||
"Compression type " +
|
||||
CompressionTypeToString(cf_options.compression) +
|
||||
" is not linked with the binary.");
|
||||
}
|
||||
}
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
ColumnFamilyOptions SanitizeOptions(const DBOptions& db_options,
|
||||
const InternalKeyComparator* icmp,
|
||||
const ColumnFamilyOptions& src) {
|
||||
@@ -136,15 +131,31 @@ ColumnFamilyOptions SanitizeOptions(const DBOptions& db_options,
|
||||
result.min_write_buffer_number_to_merge =
|
||||
std::min(result.min_write_buffer_number_to_merge,
|
||||
result.max_write_buffer_number - 1);
|
||||
if (result.num_levels < 1) {
|
||||
result.num_levels = 1;
|
||||
}
|
||||
if (result.compaction_style == kCompactionStyleLevel &&
|
||||
result.num_levels < 2) {
|
||||
result.num_levels = 2;
|
||||
}
|
||||
if (result.max_mem_compaction_level >= result.num_levels) {
|
||||
result.max_mem_compaction_level = result.num_levels - 1;
|
||||
}
|
||||
if (result.soft_rate_limit > result.hard_rate_limit) {
|
||||
result.soft_rate_limit = result.hard_rate_limit;
|
||||
}
|
||||
if (result.max_write_buffer_number < 2) {
|
||||
result.max_write_buffer_number = 2;
|
||||
}
|
||||
if (result.max_write_buffer_number_to_maintain < 0) {
|
||||
result.max_write_buffer_number_to_maintain = result.max_write_buffer_number;
|
||||
}
|
||||
XFUNC_TEST("memtablelist_history", "transaction_xftest_SanitizeOptions",
|
||||
xf_transaction_set_memtable_history1,
|
||||
xf_transaction_set_memtable_history,
|
||||
&result.max_write_buffer_number_to_maintain);
|
||||
XFUNC_TEST("memtablelist_history_clear", "transaction_xftest_SanitizeOptions",
|
||||
xf_transaction_clear_memtable_history1,
|
||||
xf_transaction_clear_memtable_history,
|
||||
&result.max_write_buffer_number_to_maintain);
|
||||
|
||||
if (!result.prefix_extractor) {
|
||||
assert(result.memtable_factory);
|
||||
Slice name = result.memtable_factory->Name();
|
||||
@@ -154,24 +165,6 @@ ColumnFamilyOptions SanitizeOptions(const DBOptions& db_options,
|
||||
}
|
||||
}
|
||||
|
||||
if (!src.compression_per_level.empty()) {
|
||||
for (size_t level = 0; level < src.compression_per_level.size(); ++level) {
|
||||
if (!CompressionTypeSupported(src.compression_per_level[level])) {
|
||||
Log(InfoLogLevel::WARN_LEVEL, db_options.info_log,
|
||||
"Compression type chosen for level %zu is not supported: %s. "
|
||||
"RocksDB "
|
||||
"will not compress data on level %zu.",
|
||||
level, CompressionTypeToString(src.compression_per_level[level]),
|
||||
level);
|
||||
}
|
||||
}
|
||||
} else if (!CompressionTypeSupported(src.compression)) {
|
||||
Log(InfoLogLevel::WARN_LEVEL, db_options.info_log,
|
||||
"Compression type chosen is not supported: %s. RocksDB will not "
|
||||
"compress data.",
|
||||
CompressionTypeToString(src.compression));
|
||||
}
|
||||
|
||||
if (result.compaction_style == kCompactionStyleFIFO) {
|
||||
result.num_levels = 1;
|
||||
// since we delete level0 files in FIFO compaction when there are too many
|
||||
@@ -302,7 +295,8 @@ ColumnFamilyData::ColumnFamilyData(
|
||||
mutable_cf_options_(options_, ioptions_),
|
||||
write_buffer_(write_buffer),
|
||||
mem_(nullptr),
|
||||
imm_(options_.min_write_buffer_number_to_merge),
|
||||
imm_(options_.min_write_buffer_number_to_merge,
|
||||
options_.max_write_buffer_number_to_maintain),
|
||||
super_version_(nullptr),
|
||||
super_version_number_(0),
|
||||
local_sv_(new ThreadLocalPtr(&SuperVersionUnrefHandle)),
|
||||
@@ -352,7 +346,7 @@ ColumnFamilyData::ColumnFamilyData(
|
||||
if (column_family_set_->NumberOfColumnFamilies() < 10) {
|
||||
Log(InfoLogLevel::INFO_LEVEL, ioptions_.info_log,
|
||||
"--------------- Options for column family [%s]:\n", name.c_str());
|
||||
options_.Dump(ioptions_.info_log);
|
||||
options_.DumpCFOptions(ioptions_.info_log);
|
||||
} else {
|
||||
Log(InfoLogLevel::INFO_LEVEL, ioptions_.info_log,
|
||||
"\t(skipping printing options)\n");
|
||||
@@ -435,16 +429,15 @@ void ColumnFamilyData::RecalculateWriteStallConditions(
|
||||
auto* vstorage = current_->storage_info();
|
||||
const double score = vstorage->max_compaction_score();
|
||||
const int max_level = vstorage->max_compaction_score_level();
|
||||
|
||||
auto write_controller = column_family_set_->write_controller_;
|
||||
|
||||
if (imm()->size() >= mutable_cf_options.max_write_buffer_number) {
|
||||
if (imm()->NumNotFlushed() >= mutable_cf_options.max_write_buffer_number) {
|
||||
write_controller_token_ = write_controller->GetStopToken();
|
||||
internal_stats_->AddCFStats(InternalStats::MEMTABLE_COMPACTION, 1);
|
||||
Log(InfoLogLevel::WARN_LEVEL, ioptions_.info_log,
|
||||
"[%s] Stopping writes because we have %d immutable memtables "
|
||||
"(waiting for flush), max_write_buffer_number is set to %d",
|
||||
name_.c_str(), imm()->size(),
|
||||
name_.c_str(), imm()->NumNotFlushed(),
|
||||
mutable_cf_options.max_write_buffer_number);
|
||||
} else if (vstorage->l0_delay_trigger_count() >=
|
||||
mutable_cf_options.level0_stop_writes_trigger) {
|
||||
@@ -456,37 +449,18 @@ void ColumnFamilyData::RecalculateWriteStallConditions(
|
||||
} else if (mutable_cf_options.level0_slowdown_writes_trigger >= 0 &&
|
||||
vstorage->l0_delay_trigger_count() >=
|
||||
mutable_cf_options.level0_slowdown_writes_trigger) {
|
||||
uint64_t slowdown =
|
||||
SlowdownAmount(vstorage->l0_delay_trigger_count(),
|
||||
mutable_cf_options.level0_slowdown_writes_trigger,
|
||||
mutable_cf_options.level0_stop_writes_trigger);
|
||||
write_controller_token_ = write_controller->GetDelayToken(slowdown);
|
||||
internal_stats_->AddCFStats(InternalStats::LEVEL0_SLOWDOWN, slowdown);
|
||||
write_controller_token_ = write_controller->GetDelayToken();
|
||||
internal_stats_->AddCFStats(InternalStats::LEVEL0_SLOWDOWN, 1);
|
||||
Log(InfoLogLevel::WARN_LEVEL, ioptions_.info_log,
|
||||
"[%s] Stalling writes because we have %d level-0 files (%" PRIu64
|
||||
"us)",
|
||||
name_.c_str(), vstorage->l0_delay_trigger_count(), slowdown);
|
||||
} else if (mutable_cf_options.hard_rate_limit > 1.0 &&
|
||||
score > mutable_cf_options.hard_rate_limit) {
|
||||
uint64_t kHardLimitSlowdown = 1000;
|
||||
write_controller_token_ =
|
||||
write_controller->GetDelayToken(kHardLimitSlowdown);
|
||||
internal_stats_->RecordLevelNSlowdown(max_level, false);
|
||||
Log(InfoLogLevel::WARN_LEVEL, ioptions_.info_log,
|
||||
"[%s] Stalling writes because we hit hard limit on level %d. "
|
||||
"(%" PRIu64 "us)",
|
||||
name_.c_str(), max_level, kHardLimitSlowdown);
|
||||
"[%s] Stalling writes because we have %d level-0 files",
|
||||
name_.c_str(), vstorage->l0_delay_trigger_count());
|
||||
} else if (mutable_cf_options.soft_rate_limit > 0.0 &&
|
||||
score > mutable_cf_options.soft_rate_limit) {
|
||||
uint64_t slowdown = SlowdownAmount(score,
|
||||
mutable_cf_options.soft_rate_limit,
|
||||
mutable_cf_options.hard_rate_limit);
|
||||
write_controller_token_ = write_controller->GetDelayToken(slowdown);
|
||||
write_controller_token_ = write_controller->GetDelayToken();
|
||||
internal_stats_->RecordLevelNSlowdown(max_level, true);
|
||||
Log(InfoLogLevel::WARN_LEVEL, ioptions_.info_log,
|
||||
"[%s] Stalling writes because we hit soft limit on level %d (%" PRIu64
|
||||
"us)",
|
||||
name_.c_str(), max_level, slowdown);
|
||||
"[%s] Stalling writes because we hit soft limit on level %d",
|
||||
name_.c_str(), max_level);
|
||||
} else {
|
||||
write_controller_token_.reset();
|
||||
}
|
||||
@@ -506,18 +480,18 @@ uint64_t ColumnFamilyData::GetNumLiveVersions() const {
|
||||
}
|
||||
|
||||
MemTable* ColumnFamilyData::ConstructNewMemtable(
|
||||
const MutableCFOptions& mutable_cf_options) {
|
||||
const MutableCFOptions& mutable_cf_options, SequenceNumber earliest_seq) {
|
||||
assert(current_ != nullptr);
|
||||
return new MemTable(internal_comparator_, ioptions_,
|
||||
mutable_cf_options, write_buffer_);
|
||||
return new MemTable(internal_comparator_, ioptions_, mutable_cf_options,
|
||||
write_buffer_, earliest_seq);
|
||||
}
|
||||
|
||||
void ColumnFamilyData::CreateNewMemtable(
|
||||
const MutableCFOptions& mutable_cf_options) {
|
||||
const MutableCFOptions& mutable_cf_options, SequenceNumber earliest_seq) {
|
||||
if (mem_ != nullptr) {
|
||||
delete mem_->Unref();
|
||||
}
|
||||
SetMemtable(ConstructNewMemtable(mutable_cf_options));
|
||||
SetMemtable(ConstructNewMemtable(mutable_cf_options, earliest_seq));
|
||||
mem_->Ref();
|
||||
}
|
||||
|
||||
@@ -627,51 +601,6 @@ bool ColumnFamilyData::ReturnThreadLocalSuperVersion(SuperVersion* sv) {
|
||||
return false;
|
||||
}
|
||||
|
||||
void ColumnFamilyData::NotifyOnCompactionCompleted(
|
||||
DB* db, Compaction* c, const Status& status) {
|
||||
#ifndef ROCKSDB_LITE
|
||||
auto listeners = ioptions()->listeners;
|
||||
assert(listeners.size() > 0U);
|
||||
CompactionJobInfo info;
|
||||
info.cf_name = c->column_family_data()->GetName();
|
||||
info.status = status;
|
||||
info.output_level = c->output_level();
|
||||
for (size_t i = 0; i < c->num_input_levels(); ++i) {
|
||||
for (const auto fmd : *c->inputs(i)) {
|
||||
info.input_files.push_back(
|
||||
TableFileName(options_.db_paths,
|
||||
fmd->fd.GetNumber(),
|
||||
fmd->fd.GetPathId()));
|
||||
}
|
||||
}
|
||||
for (const auto newf : c->edit()->GetNewFiles()) {
|
||||
info.output_files.push_back(
|
||||
TableFileName(options_.db_paths,
|
||||
newf.second.fd.GetNumber(),
|
||||
newf.second.fd.GetPathId()));
|
||||
}
|
||||
for (auto listener : listeners) {
|
||||
listener->OnCompactionCompleted(db, info);
|
||||
}
|
||||
#endif // ROCKSDB_LITE
|
||||
}
|
||||
|
||||
void ColumnFamilyData::NotifyOnFlushCompleted(
|
||||
DB* db, const std::string& file_path,
|
||||
bool triggered_flush_slowdown,
|
||||
bool triggered_flush_stop) {
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
auto listeners = ioptions()->listeners;
|
||||
for (auto listener : listeners) {
|
||||
listener->OnFlushCompleted(
|
||||
db, GetName(), file_path,
|
||||
// Use path 0 as fulled memtables are first flushed into path 0.
|
||||
triggered_flush_slowdown, triggered_flush_stop);
|
||||
}
|
||||
#endif // ROCKSDB_LITE
|
||||
}
|
||||
|
||||
SuperVersion* ColumnFamilyData::InstallSuperVersion(
|
||||
SuperVersion* new_superversion, InstrumentedMutex* db_mutex) {
|
||||
db_mutex->AssertHeld();
|
||||
|
||||
+12
-13
@@ -14,15 +14,16 @@
|
||||
#include <vector>
|
||||
#include <atomic>
|
||||
|
||||
#include "rocksdb/options.h"
|
||||
#include "rocksdb/db.h"
|
||||
#include "rocksdb/env.h"
|
||||
#include "db/memtable_list.h"
|
||||
#include "db/write_batch_internal.h"
|
||||
#include "db/write_controller.h"
|
||||
#include "db/table_cache.h"
|
||||
#include "db/table_properties_collector.h"
|
||||
#include "db/flush_scheduler.h"
|
||||
#include "rocksdb/compaction_job_stats.h"
|
||||
#include "rocksdb/db.h"
|
||||
#include "rocksdb/env.h"
|
||||
#include "rocksdb/options.h"
|
||||
#include "util/instrumented_mutex.h"
|
||||
#include "util/mutable_cf_options.h"
|
||||
#include "util/thread_local.h"
|
||||
@@ -129,6 +130,8 @@ struct SuperVersion {
|
||||
autovector<MemTable*> to_delete;
|
||||
};
|
||||
|
||||
extern Status CheckCompressionSupported(const ColumnFamilyOptions& cf_options);
|
||||
|
||||
extern ColumnFamilyOptions SanitizeOptions(const DBOptions& db_options,
|
||||
const InternalKeyComparator* icmp,
|
||||
const ColumnFamilyOptions& src);
|
||||
@@ -223,10 +226,13 @@ class ColumnFamilyData {
|
||||
Version* dummy_versions() { return dummy_versions_; }
|
||||
void SetCurrent(Version* current);
|
||||
uint64_t GetNumLiveVersions() const; // REQUIRE: DB mutex held
|
||||
|
||||
MemTable* ConstructNewMemtable(const MutableCFOptions& mutable_cf_options);
|
||||
void SetMemtable(MemTable* new_mem) { mem_ = new_mem; }
|
||||
void CreateNewMemtable(const MutableCFOptions& mutable_cf_options);
|
||||
|
||||
// See Memtable constructor for explanation of earliest_seq param.
|
||||
MemTable* ConstructNewMemtable(const MutableCFOptions& mutable_cf_options,
|
||||
SequenceNumber earliest_seq);
|
||||
void CreateNewMemtable(const MutableCFOptions& mutable_cf_options,
|
||||
SequenceNumber earliest_seq);
|
||||
|
||||
TableCache* table_cache() const { return table_cache_.get(); }
|
||||
|
||||
@@ -292,13 +298,6 @@ class ColumnFamilyData {
|
||||
|
||||
void ResetThreadLocalSuperVersions();
|
||||
|
||||
void NotifyOnCompactionCompleted(DB* db, Compaction* c, const Status& status);
|
||||
|
||||
void NotifyOnFlushCompleted(
|
||||
DB* db, const std::string& file_path,
|
||||
bool triggered_flush_slowdown,
|
||||
bool triggered_flush_stop);
|
||||
|
||||
// Protected by DB mutex
|
||||
void set_pending_flush(bool value) { pending_flush_ = value; }
|
||||
void set_pending_compaction(bool value) { pending_compaction_ = value; }
|
||||
|
||||
+134
-60
@@ -98,10 +98,12 @@ class ColumnFamilyTest : public testing::Test {
|
||||
&db_);
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE // ReadOnlyDB is not supported
|
||||
void AssertOpenReadOnly(std::vector<std::string> cf,
|
||||
std::vector<ColumnFamilyOptions> options = {}) {
|
||||
ASSERT_OK(OpenReadOnly(cf, options));
|
||||
}
|
||||
#endif // !ROCKSDB_LITE
|
||||
|
||||
|
||||
void Open(std::vector<std::string> cf,
|
||||
@@ -121,7 +123,7 @@ class ColumnFamilyTest : public testing::Test {
|
||||
#ifndef CYGWIN
|
||||
return std::stoi(value);
|
||||
#else
|
||||
return std::strtol(value.c_str(), 0);
|
||||
return std::strtol(value.c_str(), 0 /* off */, 10 /* base */);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -186,10 +188,28 @@ class ColumnFamilyTest : public testing::Test {
|
||||
}
|
||||
|
||||
void WaitForFlush(int cf) {
|
||||
#ifndef ROCKSDB_LITE // TEST functions are not supported in lite
|
||||
ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable(handles_[cf]));
|
||||
#endif // !ROCKSDB_LITE
|
||||
}
|
||||
|
||||
void WaitForCompaction() { ASSERT_OK(dbfull()->TEST_WaitForCompact()); }
|
||||
void WaitForCompaction() {
|
||||
#ifndef ROCKSDB_LITE // TEST functions are not supported in lite
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
#endif // !ROCKSDB_LITE
|
||||
}
|
||||
|
||||
uint64_t MaxTotalInMemoryState() {
|
||||
#ifndef ROCKSDB_LITE
|
||||
return dbfull()->TEST_MaxTotalInMemoryState();
|
||||
#else
|
||||
return 0;
|
||||
#endif // !ROCKSDB_LITE
|
||||
}
|
||||
|
||||
void AssertMaxTotalInMemoryState(uint64_t value) {
|
||||
ASSERT_EQ(value, MaxTotalInMemoryState());
|
||||
}
|
||||
|
||||
Status Put(int cf, const std::string& key, const std::string& value) {
|
||||
return db_->Put(WriteOptions(), handles_[cf], Slice(key), Slice(value));
|
||||
@@ -215,11 +235,13 @@ class ColumnFamilyTest : public testing::Test {
|
||||
}
|
||||
|
||||
void CompactAll(int cf) {
|
||||
ASSERT_OK(db_->CompactRange(handles_[cf], nullptr, nullptr));
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), handles_[cf], nullptr,
|
||||
nullptr));
|
||||
}
|
||||
|
||||
void Compact(int cf, const Slice& start, const Slice& limit) {
|
||||
ASSERT_OK(db_->CompactRange(handles_[cf], &start, &limit));
|
||||
ASSERT_OK(
|
||||
db_->CompactRange(CompactRangeOptions(), handles_[cf], &start, &limit));
|
||||
}
|
||||
|
||||
int NumTableFilesAtLevel(int level, int cf) {
|
||||
@@ -227,6 +249,7 @@ class ColumnFamilyTest : public testing::Test {
|
||||
"rocksdb.num-files-at-level" + ToString(level));
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
// Return spread of files per level
|
||||
std::string FilesPerLevel(int cf) {
|
||||
std::string result;
|
||||
@@ -243,12 +266,27 @@ class ColumnFamilyTest : public testing::Test {
|
||||
result.resize(last_non_zero_offset);
|
||||
return result;
|
||||
}
|
||||
#endif
|
||||
|
||||
void AssertFilesPerLevel(const std::string& value, int cf) {
|
||||
#ifndef ROCKSDB_LITE
|
||||
ASSERT_EQ(value, FilesPerLevel(cf));
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE // GetLiveFilesMetaData is not supported
|
||||
int CountLiveFiles() {
|
||||
std::vector<LiveFileMetaData> metadata;
|
||||
db_->GetLiveFilesMetaData(&metadata);
|
||||
return static_cast<int>(metadata.size());
|
||||
}
|
||||
#endif // !ROCKSDB_LITE
|
||||
|
||||
void AssertCountLiveFiles(int expected_value) {
|
||||
#ifndef ROCKSDB_LITE
|
||||
ASSERT_EQ(expected_value, CountLiveFiles());
|
||||
#endif
|
||||
}
|
||||
|
||||
// Do n memtable flushes, each of which produces an sstable
|
||||
// covering the range [small,large].
|
||||
@@ -261,6 +299,7 @@ class ColumnFamilyTest : public testing::Test {
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE // GetSortedWalFiles is not supported
|
||||
int CountLiveLogFiles() {
|
||||
int micros_wait_for_log_deletion = 20000;
|
||||
env_->SleepForMicroseconds(micros_wait_for_log_deletion);
|
||||
@@ -287,15 +326,25 @@ class ColumnFamilyTest : public testing::Test {
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
return 0;
|
||||
}
|
||||
#endif // !ROCKSDB_LITE
|
||||
|
||||
void AssertCountLiveLogFiles(int value) {
|
||||
#ifndef ROCKSDB_LITE // GetSortedWalFiles is not supported
|
||||
ASSERT_EQ(value, CountLiveLogFiles());
|
||||
#endif // !ROCKSDB_LITE
|
||||
}
|
||||
|
||||
void AssertNumberOfImmutableMemtables(std::vector<int> num_per_cf) {
|
||||
assert(num_per_cf.size() == handles_.size());
|
||||
|
||||
#ifndef ROCKSDB_LITE // GetProperty is not supported in lite
|
||||
for (size_t i = 0; i < num_per_cf.size(); ++i) {
|
||||
ASSERT_EQ(num_per_cf[i], GetProperty(static_cast<int>(i),
|
||||
"rocksdb.num-immutable-mem-table"));
|
||||
}
|
||||
#endif // !ROCKSDB_LITE
|
||||
}
|
||||
|
||||
void CopyFile(const std::string& source, const std::string& destination,
|
||||
@@ -408,10 +457,10 @@ TEST_F(ColumnFamilyTest, DropTest) {
|
||||
}
|
||||
ASSERT_EQ("bar1", Get(1, "1"));
|
||||
|
||||
ASSERT_EQ(CountLiveFiles(), 1);
|
||||
AssertCountLiveFiles(1);
|
||||
DropColumnFamilies({1});
|
||||
// make sure that all files are deleted when we drop the column family
|
||||
ASSERT_EQ(CountLiveFiles(), 0);
|
||||
AssertCountLiveFiles(0);
|
||||
Destroy();
|
||||
}
|
||||
}
|
||||
@@ -552,10 +601,9 @@ TEST_F(ColumnFamilyTest, FlushTest) {
|
||||
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
uint64_t max_total_in_memory_state =
|
||||
dbfull()->TEST_MaxTotalInMemoryState();
|
||||
MaxTotalInMemoryState();
|
||||
Flush(i);
|
||||
ASSERT_EQ(dbfull()->TEST_MaxTotalInMemoryState(),
|
||||
max_total_in_memory_state);
|
||||
AssertMaxTotalInMemoryState(max_total_in_memory_state);
|
||||
}
|
||||
ASSERT_OK(Put(1, "foofoo", "bar"));
|
||||
ASSERT_OK(Put(0, "foofoo", "bar"));
|
||||
@@ -590,7 +638,7 @@ TEST_F(ColumnFamilyTest, LogDeletionTest) {
|
||||
// Each bracket is one log file. if number is in (), it means
|
||||
// we don't need it anymore (it's been flushed)
|
||||
// []
|
||||
ASSERT_EQ(CountLiveLogFiles(), 0);
|
||||
AssertCountLiveLogFiles(0);
|
||||
PutRandomData(0, 1, 100);
|
||||
// [0]
|
||||
PutRandomData(1, 1, 100);
|
||||
@@ -598,53 +646,53 @@ TEST_F(ColumnFamilyTest, LogDeletionTest) {
|
||||
PutRandomData(1, 1000, 100);
|
||||
WaitForFlush(1);
|
||||
// [0, (1)] [1]
|
||||
ASSERT_EQ(CountLiveLogFiles(), 2);
|
||||
AssertCountLiveLogFiles(2);
|
||||
PutRandomData(0, 1, 100);
|
||||
// [0, (1)] [0, 1]
|
||||
ASSERT_EQ(CountLiveLogFiles(), 2);
|
||||
AssertCountLiveLogFiles(2);
|
||||
PutRandomData(2, 1, 100);
|
||||
// [0, (1)] [0, 1, 2]
|
||||
PutRandomData(2, 1000, 100);
|
||||
WaitForFlush(2);
|
||||
// [0, (1)] [0, 1, (2)] [2]
|
||||
ASSERT_EQ(CountLiveLogFiles(), 3);
|
||||
AssertCountLiveLogFiles(3);
|
||||
PutRandomData(2, 1000, 100);
|
||||
WaitForFlush(2);
|
||||
// [0, (1)] [0, 1, (2)] [(2)] [2]
|
||||
ASSERT_EQ(CountLiveLogFiles(), 4);
|
||||
AssertCountLiveLogFiles(4);
|
||||
PutRandomData(3, 1, 100);
|
||||
// [0, (1)] [0, 1, (2)] [(2)] [2, 3]
|
||||
PutRandomData(1, 1, 100);
|
||||
// [0, (1)] [0, 1, (2)] [(2)] [1, 2, 3]
|
||||
ASSERT_EQ(CountLiveLogFiles(), 4);
|
||||
AssertCountLiveLogFiles(4);
|
||||
PutRandomData(1, 1000, 100);
|
||||
WaitForFlush(1);
|
||||
// [0, (1)] [0, (1), (2)] [(2)] [(1), 2, 3] [1]
|
||||
ASSERT_EQ(CountLiveLogFiles(), 5);
|
||||
AssertCountLiveLogFiles(5);
|
||||
PutRandomData(0, 1000, 100);
|
||||
WaitForFlush(0);
|
||||
// [(0), (1)] [(0), (1), (2)] [(2)] [(1), 2, 3] [1, (0)] [0]
|
||||
// delete obsolete logs -->
|
||||
// [(1), 2, 3] [1, (0)] [0]
|
||||
ASSERT_EQ(CountLiveLogFiles(), 3);
|
||||
AssertCountLiveLogFiles(3);
|
||||
PutRandomData(0, 1000, 100);
|
||||
WaitForFlush(0);
|
||||
// [(1), 2, 3] [1, (0)], [(0)] [0]
|
||||
ASSERT_EQ(CountLiveLogFiles(), 4);
|
||||
AssertCountLiveLogFiles(4);
|
||||
PutRandomData(1, 1000, 100);
|
||||
WaitForFlush(1);
|
||||
// [(1), 2, 3] [(1), (0)] [(0)] [0, (1)] [1]
|
||||
ASSERT_EQ(CountLiveLogFiles(), 5);
|
||||
AssertCountLiveLogFiles(5);
|
||||
PutRandomData(2, 1000, 100);
|
||||
WaitForFlush(2);
|
||||
// [(1), (2), 3] [(1), (0)] [(0)] [0, (1)] [1, (2)], [2]
|
||||
ASSERT_EQ(CountLiveLogFiles(), 6);
|
||||
AssertCountLiveLogFiles(6);
|
||||
PutRandomData(3, 1000, 100);
|
||||
WaitForFlush(3);
|
||||
// [(1), (2), (3)] [(1), (0)] [(0)] [0, (1)] [1, (2)], [2, (3)] [3]
|
||||
// delete obsolete logs -->
|
||||
// [0, (1)] [1, (2)], [2, (3)] [3]
|
||||
ASSERT_EQ(CountLiveLogFiles(), 4);
|
||||
AssertCountLiveLogFiles(4);
|
||||
Close();
|
||||
}
|
||||
|
||||
@@ -663,15 +711,19 @@ TEST_F(ColumnFamilyTest, DifferentWriteBufferSizes) {
|
||||
default_cf.write_buffer_size = 100000;
|
||||
default_cf.max_write_buffer_number = 10;
|
||||
default_cf.min_write_buffer_number_to_merge = 1;
|
||||
default_cf.max_write_buffer_number_to_maintain = 0;
|
||||
one.write_buffer_size = 200000;
|
||||
one.max_write_buffer_number = 10;
|
||||
one.min_write_buffer_number_to_merge = 2;
|
||||
one.max_write_buffer_number_to_maintain = 1;
|
||||
two.write_buffer_size = 1000000;
|
||||
two.max_write_buffer_number = 10;
|
||||
two.min_write_buffer_number_to_merge = 3;
|
||||
two.max_write_buffer_number_to_maintain = 2;
|
||||
three.write_buffer_size = 90000;
|
||||
three.max_write_buffer_number = 10;
|
||||
three.min_write_buffer_number_to_merge = 4;
|
||||
three.max_write_buffer_number_to_maintain = -1;
|
||||
|
||||
Reopen({default_cf, one, two, three});
|
||||
|
||||
@@ -679,72 +731,73 @@ TEST_F(ColumnFamilyTest, DifferentWriteBufferSizes) {
|
||||
PutRandomData(0, 100, 1000);
|
||||
WaitForFlush(0);
|
||||
AssertNumberOfImmutableMemtables({0, 0, 0, 0});
|
||||
ASSERT_EQ(CountLiveLogFiles(), 1);
|
||||
AssertCountLiveLogFiles(1);
|
||||
PutRandomData(1, 200, 1000);
|
||||
env_->SleepForMicroseconds(micros_wait_for_flush);
|
||||
AssertNumberOfImmutableMemtables({0, 1, 0, 0});
|
||||
ASSERT_EQ(CountLiveLogFiles(), 2);
|
||||
AssertCountLiveLogFiles(2);
|
||||
PutRandomData(2, 1000, 1000);
|
||||
env_->SleepForMicroseconds(micros_wait_for_flush);
|
||||
AssertNumberOfImmutableMemtables({0, 1, 1, 0});
|
||||
ASSERT_EQ(CountLiveLogFiles(), 3);
|
||||
AssertCountLiveLogFiles(3);
|
||||
PutRandomData(2, 1000, 1000);
|
||||
env_->SleepForMicroseconds(micros_wait_for_flush);
|
||||
AssertNumberOfImmutableMemtables({0, 1, 2, 0});
|
||||
ASSERT_EQ(CountLiveLogFiles(), 4);
|
||||
AssertCountLiveLogFiles(4);
|
||||
PutRandomData(3, 90, 1000);
|
||||
env_->SleepForMicroseconds(micros_wait_for_flush);
|
||||
AssertNumberOfImmutableMemtables({0, 1, 2, 1});
|
||||
ASSERT_EQ(CountLiveLogFiles(), 5);
|
||||
AssertCountLiveLogFiles(5);
|
||||
PutRandomData(3, 90, 1000);
|
||||
env_->SleepForMicroseconds(micros_wait_for_flush);
|
||||
AssertNumberOfImmutableMemtables({0, 1, 2, 2});
|
||||
ASSERT_EQ(CountLiveLogFiles(), 6);
|
||||
AssertCountLiveLogFiles(6);
|
||||
PutRandomData(3, 90, 1000);
|
||||
env_->SleepForMicroseconds(micros_wait_for_flush);
|
||||
AssertNumberOfImmutableMemtables({0, 1, 2, 3});
|
||||
ASSERT_EQ(CountLiveLogFiles(), 7);
|
||||
AssertCountLiveLogFiles(7);
|
||||
PutRandomData(0, 100, 1000);
|
||||
WaitForFlush(0);
|
||||
AssertNumberOfImmutableMemtables({0, 1, 2, 3});
|
||||
ASSERT_EQ(CountLiveLogFiles(), 8);
|
||||
AssertCountLiveLogFiles(8);
|
||||
PutRandomData(2, 100, 10000);
|
||||
WaitForFlush(2);
|
||||
AssertNumberOfImmutableMemtables({0, 1, 0, 3});
|
||||
ASSERT_EQ(CountLiveLogFiles(), 9);
|
||||
AssertCountLiveLogFiles(9);
|
||||
PutRandomData(3, 90, 1000);
|
||||
WaitForFlush(3);
|
||||
AssertNumberOfImmutableMemtables({0, 1, 0, 0});
|
||||
ASSERT_EQ(CountLiveLogFiles(), 10);
|
||||
AssertCountLiveLogFiles(10);
|
||||
PutRandomData(3, 90, 1000);
|
||||
env_->SleepForMicroseconds(micros_wait_for_flush);
|
||||
AssertNumberOfImmutableMemtables({0, 1, 0, 1});
|
||||
ASSERT_EQ(CountLiveLogFiles(), 11);
|
||||
AssertCountLiveLogFiles(11);
|
||||
PutRandomData(1, 200, 1000);
|
||||
WaitForFlush(1);
|
||||
AssertNumberOfImmutableMemtables({0, 0, 0, 1});
|
||||
ASSERT_EQ(CountLiveLogFiles(), 5);
|
||||
AssertCountLiveLogFiles(5);
|
||||
PutRandomData(3, 240, 1000);
|
||||
WaitForFlush(3);
|
||||
PutRandomData(3, 300, 1000);
|
||||
WaitForFlush(3);
|
||||
AssertNumberOfImmutableMemtables({0, 0, 0, 0});
|
||||
ASSERT_EQ(CountLiveLogFiles(), 12);
|
||||
AssertCountLiveLogFiles(12);
|
||||
PutRandomData(0, 100, 1000);
|
||||
WaitForFlush(0);
|
||||
AssertNumberOfImmutableMemtables({0, 0, 0, 0});
|
||||
ASSERT_EQ(CountLiveLogFiles(), 12);
|
||||
AssertCountLiveLogFiles(12);
|
||||
PutRandomData(2, 3*100, 10000);
|
||||
WaitForFlush(2);
|
||||
AssertNumberOfImmutableMemtables({0, 0, 0, 0});
|
||||
ASSERT_EQ(CountLiveLogFiles(), 12);
|
||||
AssertCountLiveLogFiles(12);
|
||||
PutRandomData(1, 2*200, 1000);
|
||||
WaitForFlush(1);
|
||||
AssertNumberOfImmutableMemtables({0, 0, 0, 0});
|
||||
ASSERT_EQ(CountLiveLogFiles(), 7);
|
||||
AssertCountLiveLogFiles(7);
|
||||
Close();
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE // Cuckoo is not supported in lite
|
||||
TEST_F(ColumnFamilyTest, MemtableNotSupportSnapshot) {
|
||||
Open();
|
||||
auto* s1 = dbfull()->GetSnapshot();
|
||||
@@ -765,6 +818,7 @@ TEST_F(ColumnFamilyTest, MemtableNotSupportSnapshot) {
|
||||
ASSERT_TRUE(s3 == nullptr);
|
||||
Close();
|
||||
}
|
||||
#endif // !ROCKSDB_LITE
|
||||
|
||||
TEST_F(ColumnFamilyTest, DifferentMergeOperators) {
|
||||
Open();
|
||||
@@ -813,6 +867,7 @@ TEST_F(ColumnFamilyTest, DifferentCompactionStyles) {
|
||||
default_cf.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
|
||||
one.compaction_style = kCompactionStyleUniversal;
|
||||
|
||||
one.num_levels = 1;
|
||||
// trigger compaction if there are >= 4 files
|
||||
one.level0_file_num_compaction_trigger = 4;
|
||||
@@ -830,14 +885,14 @@ TEST_F(ColumnFamilyTest, DifferentCompactionStyles) {
|
||||
for (int i = 0; i < one.level0_file_num_compaction_trigger - 1; ++i) {
|
||||
PutRandomData(1, 11, 10000);
|
||||
WaitForFlush(1);
|
||||
ASSERT_EQ(ToString(i + 1), FilesPerLevel(1));
|
||||
AssertFilesPerLevel(ToString(i + 1), 1);
|
||||
}
|
||||
|
||||
// SETUP column family "two" -- level style with 4 levels
|
||||
for (int i = 0; i < two.level0_file_num_compaction_trigger - 1; ++i) {
|
||||
PutRandomData(2, 15, 10000);
|
||||
WaitForFlush(2);
|
||||
ASSERT_EQ(ToString(i + 1), FilesPerLevel(2));
|
||||
AssertFilesPerLevel(ToString(i + 1), 2);
|
||||
}
|
||||
|
||||
// TRIGGER compaction "one"
|
||||
@@ -850,16 +905,17 @@ TEST_F(ColumnFamilyTest, DifferentCompactionStyles) {
|
||||
WaitForCompaction();
|
||||
|
||||
// VERIFY compaction "one"
|
||||
ASSERT_EQ("1", FilesPerLevel(1));
|
||||
AssertFilesPerLevel("1", 1);
|
||||
|
||||
// VERIFY compaction "two"
|
||||
ASSERT_EQ("0,1", FilesPerLevel(2));
|
||||
AssertFilesPerLevel("0,1", 2);
|
||||
CompactAll(2);
|
||||
ASSERT_EQ("0,1", FilesPerLevel(2));
|
||||
AssertFilesPerLevel("0,1", 2);
|
||||
|
||||
Close();
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE // Tailing interator not supported
|
||||
namespace {
|
||||
std::string IterStatus(Iterator* iter) {
|
||||
std::string result;
|
||||
@@ -916,7 +972,9 @@ TEST_F(ColumnFamilyTest, NewIteratorsTest) {
|
||||
Destroy();
|
||||
}
|
||||
}
|
||||
#endif // !ROCKSDB_LITE
|
||||
|
||||
#ifndef ROCKSDB_LITE // ReadOnlyDB is not supported
|
||||
TEST_F(ColumnFamilyTest, ReadOnlyDBTest) {
|
||||
Open();
|
||||
CreateColumnFamiliesAndReopen({"one", "two", "three", "four"});
|
||||
@@ -966,6 +1024,7 @@ TEST_F(ColumnFamilyTest, ReadOnlyDBTest) {
|
||||
s = OpenReadOnly({"one", "four"});
|
||||
ASSERT_TRUE(!s.ok());
|
||||
}
|
||||
#endif // !ROCKSDB_LITE
|
||||
|
||||
TEST_F(ColumnFamilyTest, DontRollEmptyLogs) {
|
||||
Open();
|
||||
@@ -981,7 +1040,7 @@ TEST_F(ColumnFamilyTest, DontRollEmptyLogs) {
|
||||
}
|
||||
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
dbfull()->TEST_WaitForFlushMemTable(handles_[i]);
|
||||
WaitForFlush(i);
|
||||
}
|
||||
int total_new_writable_files =
|
||||
env_->GetNumberOfNewWritableFileCalls() - num_writable_file_start;
|
||||
@@ -1005,7 +1064,8 @@ TEST_F(ColumnFamilyTest, FlushStaleColumnFamilies) {
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
PutRandomData(0, 100, 1000); // flush
|
||||
WaitForFlush(0);
|
||||
ASSERT_EQ(i + 1, CountLiveFiles());
|
||||
|
||||
AssertCountLiveFiles(i + 1);
|
||||
}
|
||||
// third flush. now, CF [two] should be detected as stale and flushed
|
||||
// column family 1 should not be flushed since it's empty
|
||||
@@ -1014,7 +1074,7 @@ TEST_F(ColumnFamilyTest, FlushStaleColumnFamilies) {
|
||||
WaitForFlush(2);
|
||||
// 3 files for default column families, 1 file for column family [two], zero
|
||||
// files for column family [one], because it's empty
|
||||
ASSERT_EQ(4, CountLiveFiles());
|
||||
AssertCountLiveFiles(4);
|
||||
Close();
|
||||
}
|
||||
|
||||
@@ -1029,21 +1089,35 @@ TEST_F(ColumnFamilyTest, CreateMissingColumnFamilies) {
|
||||
|
||||
TEST_F(ColumnFamilyTest, SanitizeOptions) {
|
||||
DBOptions db_options;
|
||||
for (int i = 1; i <= 3; i++) {
|
||||
for (int j = 1; j <= 3; j++) {
|
||||
for (int k = 1; k <= 3; k++) {
|
||||
ColumnFamilyOptions original;
|
||||
original.level0_stop_writes_trigger = i;
|
||||
original.level0_slowdown_writes_trigger = j;
|
||||
original.level0_file_num_compaction_trigger = k;
|
||||
ColumnFamilyOptions result =
|
||||
SanitizeOptions(db_options, nullptr, original);
|
||||
ASSERT_TRUE(result.level0_stop_writes_trigger >=
|
||||
result.level0_slowdown_writes_trigger);
|
||||
ASSERT_TRUE(result.level0_slowdown_writes_trigger >=
|
||||
result.level0_file_num_compaction_trigger);
|
||||
ASSERT_TRUE(result.level0_file_num_compaction_trigger ==
|
||||
original.level0_file_num_compaction_trigger);
|
||||
for (int s = kCompactionStyleLevel; s <= kCompactionStyleUniversal; ++s) {
|
||||
for (int l = 0; l <= 2; l++) {
|
||||
for (int i = 1; i <= 3; i++) {
|
||||
for (int j = 1; j <= 3; j++) {
|
||||
for (int k = 1; k <= 3; k++) {
|
||||
ColumnFamilyOptions original;
|
||||
original.compaction_style = static_cast<CompactionStyle>(s);
|
||||
original.num_levels = l;
|
||||
original.level0_stop_writes_trigger = i;
|
||||
original.level0_slowdown_writes_trigger = j;
|
||||
original.level0_file_num_compaction_trigger = k;
|
||||
ColumnFamilyOptions result =
|
||||
SanitizeOptions(db_options, nullptr, original);
|
||||
ASSERT_TRUE(result.level0_stop_writes_trigger >=
|
||||
result.level0_slowdown_writes_trigger);
|
||||
ASSERT_TRUE(result.level0_slowdown_writes_trigger >=
|
||||
result.level0_file_num_compaction_trigger);
|
||||
ASSERT_TRUE(result.level0_file_num_compaction_trigger ==
|
||||
original.level0_file_num_compaction_trigger);
|
||||
if (s == kCompactionStyleLevel) {
|
||||
ASSERT_GE(result.num_levels, 2);
|
||||
} else {
|
||||
ASSERT_GE(result.num_levels, 1);
|
||||
if (original.num_levels >= 1) {
|
||||
ASSERT_EQ(result.num_levels, original.num_levels);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
|
||||
#include "rocksdb/db.h"
|
||||
#include "rocksdb/env.h"
|
||||
#include "util/string_util.h"
|
||||
#include "util/testharness.h"
|
||||
|
||||
namespace rocksdb {
|
||||
@@ -31,12 +32,9 @@ class FlushedFileCollector : public EventListener {
|
||||
~FlushedFileCollector() {}
|
||||
|
||||
virtual void OnFlushCompleted(
|
||||
DB* db, const std::string& column_family_name,
|
||||
const std::string& file_path,
|
||||
bool triggered_writes_slowdown,
|
||||
bool triggered_writes_stop) {
|
||||
DB* db, const FlushJobInfo& info) override {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
flushed_files_.push_back(file_path);
|
||||
flushed_files_.push_back(info.file_path);
|
||||
}
|
||||
|
||||
std::vector<std::string> GetFlushedFiles() {
|
||||
@@ -79,9 +77,8 @@ TEST_F(CompactFilesTest, ObsoleteFiles) {
|
||||
|
||||
// create couple files
|
||||
for (int i = 1000; i < 2000; ++i) {
|
||||
db->Put(WriteOptions(),
|
||||
std::to_string(i),
|
||||
std::string(kWriteBufferSize / 10, 'a' + (i % 26)));
|
||||
db->Put(WriteOptions(), ToString(i),
|
||||
std::string(kWriteBufferSize / 10, 'a' + (i % 26)));
|
||||
}
|
||||
|
||||
auto l0_files = collector->GetFlushedFiles();
|
||||
|
||||
+45
-2
@@ -16,6 +16,7 @@
|
||||
#include <inttypes.h>
|
||||
#include <vector>
|
||||
|
||||
#include "rocksdb/compaction_filter.h"
|
||||
#include "db/column_family.h"
|
||||
#include "util/logging.h"
|
||||
#include "util/sync_point.h"
|
||||
@@ -148,9 +149,25 @@ bool Compaction::IsTrivialMove() const {
|
||||
// Otherwise, the move could create a parent file that will require
|
||||
// a very expensive merge later on.
|
||||
// If start_level_== output_level_, the purpose is to force compaction
|
||||
// filter to be applied to that level, and thus cannot be a trivia move.
|
||||
// filter to be applied to that level, and thus cannot be a trivial move.
|
||||
|
||||
// Check if start level have files with overlapping ranges
|
||||
if (start_level_ == 0 &&
|
||||
input_version_->storage_info()->level0_non_overlapping() == false) {
|
||||
// We cannot move files from L0 to L1 if the files are overlapping
|
||||
return false;
|
||||
}
|
||||
|
||||
if (is_manual_compaction_ &&
|
||||
(cfd_->ioptions()->compaction_filter != nullptr ||
|
||||
cfd_->ioptions()->compaction_filter_factory != nullptr ||
|
||||
cfd_->ioptions()->compaction_filter_factory_v2 != nullptr)) {
|
||||
// This is a manual compaction and we have a compaction filter that should
|
||||
// be executed, we cannot do a trivial move
|
||||
return false;
|
||||
}
|
||||
|
||||
return (start_level_ != output_level_ && num_input_levels() == 1 &&
|
||||
num_input_files(0) == 1 &&
|
||||
input(0, 0)->fd.GetPathId() == GetOutputPathId() &&
|
||||
InputCompressionMatchesOutput() &&
|
||||
TotalFileSize(grandparents_) <= max_grandparent_overlap_bytes_);
|
||||
@@ -340,4 +357,30 @@ uint64_t Compaction::OutputFilePreallocationSize() {
|
||||
return preallocation_size * 1.1;
|
||||
}
|
||||
|
||||
std::unique_ptr<CompactionFilter> Compaction::CreateCompactionFilter() const {
|
||||
if (!cfd_->ioptions()->compaction_filter_factory) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
CompactionFilter::Context context;
|
||||
context.is_full_compaction = is_full_compaction_;
|
||||
context.is_manual_compaction = is_manual_compaction_;
|
||||
return cfd_->ioptions()->compaction_filter_factory->CreateCompactionFilter(
|
||||
context);
|
||||
}
|
||||
|
||||
std::unique_ptr<CompactionFilterV2>
|
||||
Compaction::CreateCompactionFilterV2() const {
|
||||
if (!cfd_->ioptions()->compaction_filter_factory_v2) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
CompactionFilterContext context;
|
||||
context.is_full_compaction = is_full_compaction_;
|
||||
context.is_manual_compaction = is_manual_compaction_;
|
||||
return
|
||||
cfd_->ioptions()->compaction_filter_factory_v2->CreateCompactionFilterV2(
|
||||
context);
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
@@ -29,6 +29,8 @@ struct CompactionInputFiles {
|
||||
class Version;
|
||||
class ColumnFamilyData;
|
||||
class VersionStorageInfo;
|
||||
class CompactionFilter;
|
||||
class CompactionFilterV2;
|
||||
|
||||
// A Compaction encapsulates information about a compaction.
|
||||
class Compaction {
|
||||
@@ -179,6 +181,12 @@ class Compaction {
|
||||
// to pick up the next file to be compacted from files_by_size_
|
||||
void ResetNextCompactionIndex();
|
||||
|
||||
// Create a CompactionFilter from compaction_filter_factory
|
||||
std::unique_ptr<CompactionFilter> CreateCompactionFilter() const;
|
||||
|
||||
// Create a CompactionFilterV2 from compaction_filter_factory_v2
|
||||
std::unique_ptr<CompactionFilterV2> CreateCompactionFilterV2() const;
|
||||
|
||||
private:
|
||||
// mark (or clear) all files that are being compacted
|
||||
void MarkFilesBeingCompacted(bool mark_as_compacted);
|
||||
|
||||
+188
-100
@@ -22,7 +22,7 @@
|
||||
#include "db/builder.h"
|
||||
#include "db/db_iter.h"
|
||||
#include "db/dbformat.h"
|
||||
#include "db/event_logger_helpers.h"
|
||||
#include "db/event_helpers.h"
|
||||
#include "db/filename.h"
|
||||
#include "db/log_reader.h"
|
||||
#include "db/log_writer.h"
|
||||
@@ -66,6 +66,7 @@ struct CompactionJob::CompactionState {
|
||||
uint64_t file_size;
|
||||
InternalKey smallest, largest;
|
||||
SequenceNumber smallest_seqno, largest_seqno;
|
||||
bool need_compaction;
|
||||
};
|
||||
std::vector<Output> outputs;
|
||||
|
||||
@@ -83,22 +84,6 @@ struct CompactionJob::CompactionState {
|
||||
num_input_records(0),
|
||||
num_output_records(0) {}
|
||||
|
||||
// Create a client visible context of this compaction
|
||||
CompactionFilter::Context GetFilterContextV1() {
|
||||
CompactionFilter::Context context;
|
||||
context.is_full_compaction = compaction->IsFullCompaction();
|
||||
context.is_manual_compaction = compaction->IsManualCompaction();
|
||||
return context;
|
||||
}
|
||||
|
||||
// Create a client visible context of this compaction
|
||||
CompactionFilterContext GetFilterContext() {
|
||||
CompactionFilterContext context;
|
||||
context.is_full_compaction = compaction->IsFullCompaction();
|
||||
context.is_manual_compaction = compaction->IsManualCompaction();
|
||||
return context;
|
||||
}
|
||||
|
||||
std::vector<std::string> key_str_buf_;
|
||||
std::vector<std::string> existing_value_str_buf_;
|
||||
// new_value_buf_ will only be appended if a value changes
|
||||
@@ -205,10 +190,13 @@ CompactionJob::CompactionJob(
|
||||
std::vector<SequenceNumber> existing_snapshots,
|
||||
std::shared_ptr<Cache> table_cache,
|
||||
std::function<uint64_t()> yield_callback, EventLogger* event_logger,
|
||||
bool paranoid_file_checks)
|
||||
bool paranoid_file_checks, const std::string& dbname,
|
||||
CompactionJobStats* compaction_job_stats)
|
||||
: job_id_(job_id),
|
||||
compact_(new CompactionState(compaction)),
|
||||
compaction_job_stats_(compaction_job_stats),
|
||||
compaction_stats_(1),
|
||||
dbname_(dbname),
|
||||
db_options_(db_options),
|
||||
env_options_(env_options),
|
||||
env_(db_options.env),
|
||||
@@ -247,11 +235,15 @@ void CompactionJob::ReportStartedCompaction(
|
||||
(static_cast<uint64_t>(compact_->compaction->start_level()) << 32) +
|
||||
compact_->compaction->output_level());
|
||||
|
||||
// In the current design, a CompactionJob is always created
|
||||
// for non-trivial compaction.
|
||||
assert(compaction->IsTrivialMove() == false ||
|
||||
compaction->IsManualCompaction() == true);
|
||||
|
||||
ThreadStatusUtil::SetThreadOperationProperty(
|
||||
ThreadStatus::COMPACTION_PROP_FLAGS,
|
||||
compaction->IsManualCompaction() +
|
||||
(compaction->IsDeletionCompaction() << 1) +
|
||||
(compaction->IsTrivialMove() << 2));
|
||||
compaction->IsManualCompaction() +
|
||||
(compaction->IsDeletionCompaction() << 1));
|
||||
|
||||
ThreadStatusUtil::SetThreadOperationProperty(
|
||||
ThreadStatus::COMPACTION_TOTAL_INPUT_BYTES,
|
||||
@@ -268,6 +260,11 @@ void CompactionJob::ReportStartedCompaction(
|
||||
// to ensure GetThreadList() can always show them all together.
|
||||
ThreadStatusUtil::SetThreadOperation(
|
||||
ThreadStatus::OP_COMPACTION);
|
||||
|
||||
if (compaction_job_stats_) {
|
||||
compaction_job_stats_->is_manual_compaction =
|
||||
compaction->IsManualCompaction();
|
||||
}
|
||||
}
|
||||
|
||||
void CompactionJob::Prepare() {
|
||||
@@ -348,11 +345,7 @@ Status CompactionJob::Run() {
|
||||
Status status;
|
||||
ParsedInternalKey ikey;
|
||||
std::unique_ptr<CompactionFilterV2> compaction_filter_from_factory_v2 =
|
||||
nullptr;
|
||||
auto context = compact_->GetFilterContext();
|
||||
compaction_filter_from_factory_v2 =
|
||||
cfd->ioptions()->compaction_filter_factory_v2->CreateCompactionFilterV2(
|
||||
context);
|
||||
compact_->compaction->CreateCompactionFilterV2();
|
||||
auto compaction_filter_v2 = compaction_filter_from_factory_v2.get();
|
||||
|
||||
int64_t imm_micros = 0; // Micros spent doing imm_ compactions
|
||||
@@ -498,10 +491,6 @@ Status CompactionJob::Run() {
|
||||
}
|
||||
|
||||
compaction_stats_.micros = env_->NowMicros() - start_micros - imm_micros;
|
||||
compaction_stats_.files_in_leveln =
|
||||
static_cast<int>(compact_->compaction->num_input_files(0));
|
||||
compaction_stats_.files_in_levelnp1 =
|
||||
static_cast<int>(compact_->compaction->num_input_files(1));
|
||||
MeasureTime(stats_, COMPACTION_TIME, compaction_stats_.micros);
|
||||
|
||||
size_t num_output_files = compact_->outputs.size();
|
||||
@@ -510,19 +499,9 @@ Status CompactionJob::Run() {
|
||||
assert(num_output_files > 0);
|
||||
--num_output_files;
|
||||
}
|
||||
compaction_stats_.files_out_levelnp1 = static_cast<int>(num_output_files);
|
||||
compaction_stats_.num_output_files = static_cast<int>(num_output_files);
|
||||
|
||||
for (size_t i = 0; i < compact_->compaction->num_input_files(0); i++) {
|
||||
compaction_stats_.bytes_readn +=
|
||||
compact_->compaction->input(0, i)->fd.GetFileSize();
|
||||
compaction_stats_.num_input_records +=
|
||||
static_cast<uint64_t>(compact_->compaction->input(0, i)->num_entries);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < compact_->compaction->num_input_files(1); i++) {
|
||||
compaction_stats_.bytes_readnp1 +=
|
||||
compact_->compaction->input(1, i)->fd.GetFileSize();
|
||||
}
|
||||
UpdateCompactionInputStats();
|
||||
|
||||
for (size_t i = 0; i < num_output_files; i++) {
|
||||
compaction_stats_.bytes_written += compact_->outputs[i].file_size;
|
||||
@@ -555,33 +534,41 @@ void CompactionJob::Install(Status* status,
|
||||
VersionStorageInfo::LevelSummaryStorage tmp;
|
||||
auto vstorage = cfd->current()->storage_info();
|
||||
const auto& stats = compaction_stats_;
|
||||
LogToBuffer(log_buffer_,
|
||||
"[%s] compacted to: %s, MB/sec: %.1f rd, %.1f wr, level %d, "
|
||||
"files in(%d, %d) out(%d) "
|
||||
"MB in(%.1f, %.1f) out(%.1f), read-write-amplify(%.1f) "
|
||||
"write-amplify(%.1f) %s, records in: %d, records dropped: %d\n",
|
||||
cfd->GetName().c_str(), vstorage->LevelSummary(&tmp),
|
||||
(stats.bytes_readn + stats.bytes_readnp1) /
|
||||
static_cast<double>(stats.micros),
|
||||
stats.bytes_written / static_cast<double>(stats.micros),
|
||||
compact_->compaction->output_level(), stats.files_in_leveln,
|
||||
stats.files_in_levelnp1, stats.files_out_levelnp1,
|
||||
stats.bytes_readn / 1048576.0, stats.bytes_readnp1 / 1048576.0,
|
||||
stats.bytes_written / 1048576.0,
|
||||
(stats.bytes_written + stats.bytes_readnp1 + stats.bytes_readn) /
|
||||
static_cast<double>(stats.bytes_readn),
|
||||
stats.bytes_written / static_cast<double>(stats.bytes_readn),
|
||||
status->ToString().c_str(), stats.num_input_records,
|
||||
stats.num_dropped_records);
|
||||
LogToBuffer(
|
||||
log_buffer_,
|
||||
"[%s] compacted to: %s, MB/sec: %.1f rd, %.1f wr, level %d, "
|
||||
"files in(%d, %d) out(%d) "
|
||||
"MB in(%.1f, %.1f) out(%.1f), read-write-amplify(%.1f) "
|
||||
"write-amplify(%.1f) %s, records in: %d, records dropped: %d\n",
|
||||
cfd->GetName().c_str(), vstorage->LevelSummary(&tmp),
|
||||
(stats.bytes_read_non_output_levels + stats.bytes_read_output_level) /
|
||||
static_cast<double>(stats.micros),
|
||||
stats.bytes_written / static_cast<double>(stats.micros),
|
||||
compact_->compaction->output_level(),
|
||||
stats.num_input_files_in_non_output_levels,
|
||||
stats.num_input_files_in_output_level,
|
||||
stats.num_output_files,
|
||||
stats.bytes_read_non_output_levels / 1048576.0,
|
||||
stats.bytes_read_output_level / 1048576.0,
|
||||
stats.bytes_written / 1048576.0,
|
||||
(stats.bytes_written + stats.bytes_read_output_level +
|
||||
stats.bytes_read_non_output_levels) /
|
||||
static_cast<double>(stats.bytes_read_non_output_levels),
|
||||
stats.bytes_written /
|
||||
static_cast<double>(stats.bytes_read_non_output_levels),
|
||||
status->ToString().c_str(), stats.num_input_records,
|
||||
stats.num_dropped_records);
|
||||
|
||||
UpdateCompactionJobStats(stats);
|
||||
|
||||
auto stream = event_logger_->LogToBuffer(log_buffer_);
|
||||
stream << "job" << job_id_ << "event"
|
||||
<< "compaction_finished"
|
||||
<< "output_level" << compact_->compaction->output_level()
|
||||
<< "num_output_files" << compact_->outputs.size()
|
||||
<< "total_output_size" << compact_->total_bytes << "num_input_records"
|
||||
<< compact_->num_input_records << "num_output_records"
|
||||
<< compact_->num_output_records;
|
||||
<< "total_output_size" << compact_->total_bytes
|
||||
<< "num_input_records" << compact_->num_input_records
|
||||
<< "num_output_records" << compact_->num_output_records;
|
||||
stream << "lsm_state";
|
||||
stream.StartArray();
|
||||
for (int level = 0; level < vstorage->num_levels(); ++level) {
|
||||
@@ -615,10 +602,8 @@ Status CompactionJob::ProcessKeyValueCompaction(int64_t* imm_micros,
|
||||
auto compaction_filter = cfd->ioptions()->compaction_filter;
|
||||
std::unique_ptr<CompactionFilter> compaction_filter_from_factory = nullptr;
|
||||
if (!compaction_filter) {
|
||||
auto context = compact_->GetFilterContextV1();
|
||||
compaction_filter_from_factory =
|
||||
cfd->ioptions()->compaction_filter_factory->CreateCompactionFilter(
|
||||
context);
|
||||
compact_->compaction->CreateCompactionFilter();
|
||||
compaction_filter = compaction_filter_from_factory.get();
|
||||
}
|
||||
|
||||
@@ -635,19 +620,8 @@ Status CompactionJob::ProcessKeyValueCompaction(int64_t* imm_micros,
|
||||
!cfd->IsDropped() && status.ok()) {
|
||||
compact_->num_input_records++;
|
||||
if (++loop_cnt > 1000) {
|
||||
if (key_drop_user > 0) {
|
||||
RecordTick(stats_, COMPACTION_KEY_DROP_USER, key_drop_user);
|
||||
key_drop_user = 0;
|
||||
}
|
||||
if (key_drop_newer_entry > 0) {
|
||||
RecordTick(stats_, COMPACTION_KEY_DROP_NEWER_ENTRY,
|
||||
key_drop_newer_entry);
|
||||
key_drop_newer_entry = 0;
|
||||
}
|
||||
if (key_drop_obsolete > 0) {
|
||||
RecordTick(stats_, COMPACTION_KEY_DROP_OBSOLETE, key_drop_obsolete);
|
||||
key_drop_obsolete = 0;
|
||||
}
|
||||
RecordDroppedKeys(
|
||||
&key_drop_user, &key_drop_newer_entry, &key_drop_obsolete);
|
||||
RecordCompactionIOStats();
|
||||
loop_cnt = 0;
|
||||
}
|
||||
@@ -679,6 +653,13 @@ Status CompactionJob::ProcessKeyValueCompaction(int64_t* imm_micros,
|
||||
++combined_idx;
|
||||
}
|
||||
|
||||
if (compaction_job_stats_ != nullptr) {
|
||||
compaction_job_stats_->total_input_raw_key_bytes +=
|
||||
input->key().size();
|
||||
compaction_job_stats_->total_input_raw_value_bytes +=
|
||||
input->value().size();
|
||||
}
|
||||
|
||||
if (compact_->compaction->ShouldStopBefore(key) &&
|
||||
compact_->builder != nullptr) {
|
||||
status = FinishCompactionOutputFile(input);
|
||||
@@ -921,20 +902,33 @@ Status CompactionJob::ProcessKeyValueCompaction(int64_t* imm_micros,
|
||||
}
|
||||
}
|
||||
RecordTick(stats_, FILTER_OPERATION_TOTAL_TIME, total_filter_time);
|
||||
if (key_drop_user > 0) {
|
||||
RecordTick(stats_, COMPACTION_KEY_DROP_USER, key_drop_user);
|
||||
}
|
||||
if (key_drop_newer_entry > 0) {
|
||||
RecordTick(stats_, COMPACTION_KEY_DROP_NEWER_ENTRY, key_drop_newer_entry);
|
||||
}
|
||||
if (key_drop_obsolete > 0) {
|
||||
RecordTick(stats_, COMPACTION_KEY_DROP_OBSOLETE, key_drop_obsolete);
|
||||
}
|
||||
RecordDroppedKeys(&key_drop_user, &key_drop_newer_entry, &key_drop_obsolete);
|
||||
RecordCompactionIOStats();
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
void CompactionJob::RecordDroppedKeys(
|
||||
int64_t* key_drop_user,
|
||||
int64_t* key_drop_newer_entry,
|
||||
int64_t* key_drop_obsolete) {
|
||||
if (*key_drop_user > 0) {
|
||||
RecordTick(stats_, COMPACTION_KEY_DROP_USER, *key_drop_user);
|
||||
*key_drop_user = 0;
|
||||
}
|
||||
if (*key_drop_newer_entry > 0) {
|
||||
RecordTick(stats_, COMPACTION_KEY_DROP_NEWER_ENTRY, *key_drop_newer_entry);
|
||||
if (compaction_job_stats_) {
|
||||
compaction_job_stats_->num_records_replaced += *key_drop_newer_entry;
|
||||
}
|
||||
*key_drop_newer_entry = 0;
|
||||
}
|
||||
if (*key_drop_obsolete > 0) {
|
||||
RecordTick(stats_, COMPACTION_KEY_DROP_OBSOLETE, *key_drop_obsolete);
|
||||
*key_drop_obsolete = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void CompactionJob::CallCompactionFilterV2(
|
||||
CompactionFilterV2* compaction_filter_v2, uint64_t* time) {
|
||||
if (compact_ == nullptr || compaction_filter_v2 == nullptr) {
|
||||
@@ -1015,18 +1009,16 @@ Status CompactionJob::FinishCompactionOutputFile(Iterator* input) {
|
||||
// Check for iterator errors
|
||||
Status s = input->status();
|
||||
const uint64_t current_entries = compact_->builder->NumEntries();
|
||||
compact_->current_output()->need_compaction =
|
||||
compact_->builder->NeedCompact();
|
||||
if (s.ok()) {
|
||||
s = compact_->builder->Finish();
|
||||
} else {
|
||||
compact_->builder->Abandon();
|
||||
}
|
||||
if (s.ok()) {
|
||||
table_properties = compact_->builder->GetTableProperties();
|
||||
}
|
||||
const uint64_t current_bytes = compact_->builder->FileSize();
|
||||
compact_->current_output()->file_size = current_bytes;
|
||||
compact_->total_bytes += current_bytes;
|
||||
compact_->builder.reset();
|
||||
|
||||
// Finish and check for file errors
|
||||
if (s.ok() && !db_options_.disableDataSync) {
|
||||
@@ -1058,16 +1050,25 @@ Status CompactionJob::FinishCompactionOutputFile(Iterator* input) {
|
||||
|
||||
delete iter;
|
||||
if (s.ok()) {
|
||||
TableFileCreationInfo info(compact_->builder->GetTableProperties());
|
||||
info.db_name = dbname_;
|
||||
info.cf_name = cfd->GetName();
|
||||
info.file_path = TableFileName(cfd->ioptions()->db_paths,
|
||||
fd.GetNumber(), fd.GetPathId());
|
||||
info.file_size = fd.GetFileSize();
|
||||
info.job_id = job_id_;
|
||||
Log(InfoLogLevel::INFO_LEVEL, db_options_.info_log,
|
||||
"[%s] [JOB %d] Generated table #%" PRIu64 ": %" PRIu64
|
||||
" keys, %" PRIu64 " bytes",
|
||||
" keys, %" PRIu64 " bytes%s",
|
||||
cfd->GetName().c_str(), job_id_, output_number, current_entries,
|
||||
current_bytes);
|
||||
EventLoggerHelpers::LogTableFileCreation(event_logger_, job_id_,
|
||||
output_number, current_bytes,
|
||||
table_properties);
|
||||
current_bytes,
|
||||
compact_->current_output()->need_compaction ? " (need compaction)"
|
||||
: "");
|
||||
EventHelpers::LogAndNotifyTableFileCreation(
|
||||
event_logger_, cfd->ioptions()->listeners, fd, info);
|
||||
}
|
||||
}
|
||||
compact_->builder.reset();
|
||||
return s;
|
||||
}
|
||||
|
||||
@@ -1102,9 +1103,10 @@ Status CompactionJob::InstallCompactionResults(
|
||||
compaction->AddInputDeletions(compact_->compaction->edit());
|
||||
for (size_t i = 0; i < compact_->outputs.size(); i++) {
|
||||
const CompactionState::Output& out = compact_->outputs[i];
|
||||
compaction->edit()->AddFile(
|
||||
compaction->output_level(), out.number, out.path_id, out.file_size,
|
||||
out.smallest, out.largest, out.smallest_seqno, out.largest_seqno);
|
||||
compaction->edit()->AddFile(compaction->output_level(), out.number,
|
||||
out.path_id, out.file_size, out.smallest,
|
||||
out.largest, out.smallest_seqno,
|
||||
out.largest_seqno, out.need_compaction);
|
||||
}
|
||||
return versions_->LogAndApply(compaction->column_family_data(),
|
||||
mutable_cf_options, compaction->edit(),
|
||||
@@ -1223,4 +1225,90 @@ void CompactionJob::CleanupCompaction(const Status& status) {
|
||||
compact_ = nullptr;
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
namespace {
|
||||
void CopyPrefix(
|
||||
const Slice& src, size_t prefix_length, std::string* dst) {
|
||||
assert(prefix_length > 0);
|
||||
size_t length = src.size() > prefix_length ? prefix_length : src.size();
|
||||
dst->assign(src.data(), length);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
#endif // !ROCKSDB_LITE
|
||||
|
||||
void CompactionJob::UpdateCompactionInputStats() {
|
||||
Compaction* compaction = compact_->compaction;
|
||||
compaction_stats_.num_input_files_in_non_output_levels = 0;
|
||||
compaction_stats_.num_input_files_in_output_level = 0;
|
||||
for (int input_level = 0;
|
||||
input_level < static_cast<int>(compaction->num_input_levels());
|
||||
++input_level) {
|
||||
if (compaction->start_level() + input_level
|
||||
!= compaction->output_level()) {
|
||||
UpdateCompactionInputStatsHelper(
|
||||
&compaction_stats_.num_input_files_in_non_output_levels,
|
||||
&compaction_stats_.bytes_read_non_output_levels,
|
||||
input_level);
|
||||
} else {
|
||||
UpdateCompactionInputStatsHelper(
|
||||
&compaction_stats_.num_input_files_in_output_level,
|
||||
&compaction_stats_.bytes_read_output_level,
|
||||
input_level);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CompactionJob::UpdateCompactionInputStatsHelper(
|
||||
int* num_files, uint64_t* bytes_read, int input_level) {
|
||||
const Compaction* compaction = compact_->compaction;
|
||||
auto num_input_files = compaction->num_input_files(input_level);
|
||||
*num_files += static_cast<int>(num_input_files);
|
||||
|
||||
for (size_t i = 0; i < num_input_files; ++i) {
|
||||
const auto* file_meta = compaction->input(input_level, i);
|
||||
*bytes_read += file_meta->fd.GetFileSize();
|
||||
compaction_stats_.num_input_records +=
|
||||
static_cast<uint64_t>(file_meta->num_entries);
|
||||
}
|
||||
}
|
||||
|
||||
void CompactionJob::UpdateCompactionJobStats(
|
||||
const InternalStats::CompactionStats& stats) const {
|
||||
#ifndef ROCKSDB_LITE
|
||||
if (compaction_job_stats_) {
|
||||
compaction_job_stats_->elapsed_micros = stats.micros;
|
||||
|
||||
// input information
|
||||
compaction_job_stats_->total_input_bytes =
|
||||
stats.bytes_read_non_output_levels +
|
||||
stats.bytes_read_output_level;
|
||||
compaction_job_stats_->num_input_records =
|
||||
compact_->num_input_records;
|
||||
compaction_job_stats_->num_input_files =
|
||||
stats.num_input_files_in_non_output_levels +
|
||||
stats.num_input_files_in_output_level;
|
||||
compaction_job_stats_->num_input_files_at_output_level =
|
||||
stats.num_input_files_in_output_level;
|
||||
|
||||
// output information
|
||||
compaction_job_stats_->total_output_bytes = stats.bytes_written;
|
||||
compaction_job_stats_->num_output_records =
|
||||
compact_->num_output_records;
|
||||
compaction_job_stats_->num_output_files = stats.num_output_files;
|
||||
|
||||
if (compact_->outputs.size() > 0U) {
|
||||
CopyPrefix(
|
||||
compact_->outputs[0].smallest.user_key(),
|
||||
CompactionJobStats::kMaxPrefixLength,
|
||||
&compaction_job_stats_->smallest_output_key_prefix);
|
||||
CopyPrefix(
|
||||
compact_->current_output()->largest.user_key(),
|
||||
CompactionJobStats::kMaxPrefixLength,
|
||||
&compaction_job_stats_->largest_output_key_prefix);
|
||||
}
|
||||
}
|
||||
#endif // !ROCKSDB_LITE
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
+15
-1
@@ -27,6 +27,7 @@
|
||||
#include "rocksdb/env.h"
|
||||
#include "rocksdb/memtablerep.h"
|
||||
#include "rocksdb/compaction_filter.h"
|
||||
#include "rocksdb/compaction_job_stats.h"
|
||||
#include "rocksdb/transaction_log.h"
|
||||
#include "util/autovector.h"
|
||||
#include "util/event_logger.h"
|
||||
@@ -58,7 +59,9 @@ class CompactionJob {
|
||||
std::vector<SequenceNumber> existing_snapshots,
|
||||
std::shared_ptr<Cache> table_cache,
|
||||
std::function<uint64_t()> yield_callback,
|
||||
EventLogger* event_logger, bool paranoid_file_checks);
|
||||
EventLogger* event_logger, bool paranoid_file_checks,
|
||||
const std::string& dbname,
|
||||
CompactionJobStats* compaction_job_stats);
|
||||
|
||||
~CompactionJob();
|
||||
|
||||
@@ -96,12 +99,22 @@ class CompactionJob {
|
||||
void RecordCompactionIOStats();
|
||||
Status OpenCompactionOutputFile();
|
||||
void CleanupCompaction(const Status& status);
|
||||
void UpdateCompactionJobStats(
|
||||
const InternalStats::CompactionStats& stats) const;
|
||||
void RecordDroppedKeys(int64_t* key_drop_user,
|
||||
int64_t* key_drop_newer_entry,
|
||||
int64_t* key_drop_obsolete);
|
||||
|
||||
void UpdateCompactionInputStats();
|
||||
void UpdateCompactionInputStatsHelper(
|
||||
int* num_files, uint64_t* bytes_read, int input_level);
|
||||
|
||||
int job_id_;
|
||||
|
||||
// CompactionJob state
|
||||
struct CompactionState;
|
||||
CompactionState* compact_;
|
||||
CompactionJobStats* compaction_job_stats_;
|
||||
|
||||
bool bottommost_level_;
|
||||
SequenceNumber earliest_snapshot_;
|
||||
@@ -111,6 +124,7 @@ class CompactionJob {
|
||||
InternalStats::CompactionStats compaction_stats_;
|
||||
|
||||
// DBImpl state
|
||||
const std::string& dbname_;
|
||||
const DBOptions& db_options_;
|
||||
const EnvOptions& env_options_;
|
||||
Env* env_;
|
||||
|
||||
@@ -0,0 +1,777 @@
|
||||
// Copyright (c) 2013, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the root directory of this source tree. An additional grant
|
||||
// of patent rights can be found in the PATENTS file in the same directory.
|
||||
//
|
||||
// Copyright (c) 2011 The LevelDB Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
|
||||
#ifndef __STDC_FORMAT_MACROS
|
||||
#define __STDC_FORMAT_MACROS
|
||||
#endif
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
#include <mutex>
|
||||
#include <queue>
|
||||
#include <set>
|
||||
#include <thread>
|
||||
#include <unordered_set>
|
||||
#include <utility>
|
||||
|
||||
#include "db/db_impl.h"
|
||||
#include "db/dbformat.h"
|
||||
#include "db/filename.h"
|
||||
#include "db/job_context.h"
|
||||
#include "db/version_set.h"
|
||||
#include "db/write_batch_internal.h"
|
||||
#include "port/stack_trace.h"
|
||||
#include "rocksdb/cache.h"
|
||||
#include "rocksdb/compaction_filter.h"
|
||||
#include "rocksdb/db.h"
|
||||
#include "rocksdb/env.h"
|
||||
#include "rocksdb/experimental.h"
|
||||
#include "rocksdb/filter_policy.h"
|
||||
#include "rocksdb/options.h"
|
||||
#include "rocksdb/perf_context.h"
|
||||
#include "rocksdb/slice.h"
|
||||
#include "rocksdb/slice_transform.h"
|
||||
#include "rocksdb/table.h"
|
||||
#include "rocksdb/table_properties.h"
|
||||
#include "rocksdb/thread_status.h"
|
||||
#include "rocksdb/utilities/checkpoint.h"
|
||||
#include "rocksdb/utilities/convenience.h"
|
||||
#include "rocksdb/utilities/write_batch_with_index.h"
|
||||
#include "table/block_based_table_factory.h"
|
||||
#include "table/mock_table.h"
|
||||
#include "table/plain_table_factory.h"
|
||||
#include "util/compression.h"
|
||||
#include "util/hash.h"
|
||||
#include "util/hash_linklist_rep.h"
|
||||
#include "util/logging.h"
|
||||
#include "util/mock_env.h"
|
||||
#include "util/mutexlock.h"
|
||||
#include "util/rate_limiter.h"
|
||||
#include "util/scoped_arena_iterator.h"
|
||||
#include "util/statistics.h"
|
||||
#include "util/string_util.h"
|
||||
#include "util/sync_point.h"
|
||||
#include "util/testharness.h"
|
||||
#include "util/testutil.h"
|
||||
#include "util/thread_status_util.h"
|
||||
#include "util/xfunc.h"
|
||||
#include "utilities/merge_operators.h"
|
||||
|
||||
#if !defined(IOS_CROSS_COMPILE)
|
||||
#ifndef ROCKSDB_LITE
|
||||
namespace rocksdb {
|
||||
|
||||
static std::string RandomString(Random* rnd, int len, double ratio) {
|
||||
std::string r;
|
||||
test::CompressibleString(rnd, ratio, len, &r);
|
||||
return r;
|
||||
}
|
||||
|
||||
std::string Key(uint64_t key, int length) {
|
||||
const int kBufSize = 1000;
|
||||
char buf[kBufSize];
|
||||
if (length > kBufSize) {
|
||||
length = kBufSize;
|
||||
}
|
||||
snprintf(buf, kBufSize, "%0*" PRIu64, length, key);
|
||||
return std::string(buf);
|
||||
}
|
||||
|
||||
class CompactionJobStatsTest : public testing::Test {
|
||||
public:
|
||||
std::string dbname_;
|
||||
std::string alternative_wal_dir_;
|
||||
Env* env_;
|
||||
DB* db_;
|
||||
std::vector<ColumnFamilyHandle*> handles_;
|
||||
|
||||
Options last_options_;
|
||||
|
||||
CompactionJobStatsTest() : env_(Env::Default()) {
|
||||
env_->SetBackgroundThreads(1, Env::LOW);
|
||||
env_->SetBackgroundThreads(1, Env::HIGH);
|
||||
dbname_ = test::TmpDir(env_) + "/compaction_job_stats_test";
|
||||
alternative_wal_dir_ = dbname_ + "/wal";
|
||||
Options options;
|
||||
options.create_if_missing = true;
|
||||
auto delete_options = options;
|
||||
delete_options.wal_dir = alternative_wal_dir_;
|
||||
EXPECT_OK(DestroyDB(dbname_, delete_options));
|
||||
// Destroy it for not alternative WAL dir is used.
|
||||
EXPECT_OK(DestroyDB(dbname_, options));
|
||||
db_ = nullptr;
|
||||
Reopen(options);
|
||||
}
|
||||
|
||||
~CompactionJobStatsTest() {
|
||||
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
||||
rocksdb::SyncPoint::GetInstance()->LoadDependency({});
|
||||
rocksdb::SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
Close();
|
||||
Options options;
|
||||
options.db_paths.emplace_back(dbname_, 0);
|
||||
options.db_paths.emplace_back(dbname_ + "_2", 0);
|
||||
options.db_paths.emplace_back(dbname_ + "_3", 0);
|
||||
options.db_paths.emplace_back(dbname_ + "_4", 0);
|
||||
EXPECT_OK(DestroyDB(dbname_, options));
|
||||
}
|
||||
|
||||
DBImpl* dbfull() {
|
||||
return reinterpret_cast<DBImpl*>(db_);
|
||||
}
|
||||
|
||||
void CreateColumnFamilies(const std::vector<std::string>& cfs,
|
||||
const Options& options) {
|
||||
ColumnFamilyOptions cf_opts(options);
|
||||
size_t cfi = handles_.size();
|
||||
handles_.resize(cfi + cfs.size());
|
||||
for (auto cf : cfs) {
|
||||
ASSERT_OK(db_->CreateColumnFamily(cf_opts, cf, &handles_[cfi++]));
|
||||
}
|
||||
}
|
||||
|
||||
void CreateAndReopenWithCF(const std::vector<std::string>& cfs,
|
||||
const Options& options) {
|
||||
CreateColumnFamilies(cfs, options);
|
||||
std::vector<std::string> cfs_plus_default = cfs;
|
||||
cfs_plus_default.insert(cfs_plus_default.begin(), kDefaultColumnFamilyName);
|
||||
ReopenWithColumnFamilies(cfs_plus_default, options);
|
||||
}
|
||||
|
||||
void ReopenWithColumnFamilies(const std::vector<std::string>& cfs,
|
||||
const std::vector<Options>& options) {
|
||||
ASSERT_OK(TryReopenWithColumnFamilies(cfs, options));
|
||||
}
|
||||
|
||||
void ReopenWithColumnFamilies(const std::vector<std::string>& cfs,
|
||||
const Options& options) {
|
||||
ASSERT_OK(TryReopenWithColumnFamilies(cfs, options));
|
||||
}
|
||||
|
||||
Status TryReopenWithColumnFamilies(
|
||||
const std::vector<std::string>& cfs,
|
||||
const std::vector<Options>& options) {
|
||||
Close();
|
||||
EXPECT_EQ(cfs.size(), options.size());
|
||||
std::vector<ColumnFamilyDescriptor> column_families;
|
||||
for (size_t i = 0; i < cfs.size(); ++i) {
|
||||
column_families.push_back(ColumnFamilyDescriptor(cfs[i], options[i]));
|
||||
}
|
||||
DBOptions db_opts = DBOptions(options[0]);
|
||||
return DB::Open(db_opts, dbname_, column_families, &handles_, &db_);
|
||||
}
|
||||
|
||||
Status TryReopenWithColumnFamilies(const std::vector<std::string>& cfs,
|
||||
const Options& options) {
|
||||
Close();
|
||||
std::vector<Options> v_opts(cfs.size(), options);
|
||||
return TryReopenWithColumnFamilies(cfs, v_opts);
|
||||
}
|
||||
|
||||
void Reopen(const Options& options) {
|
||||
ASSERT_OK(TryReopen(options));
|
||||
}
|
||||
|
||||
void Close() {
|
||||
for (auto h : handles_) {
|
||||
delete h;
|
||||
}
|
||||
handles_.clear();
|
||||
delete db_;
|
||||
db_ = nullptr;
|
||||
}
|
||||
|
||||
void DestroyAndReopen(const Options& options) {
|
||||
// Destroy using last options
|
||||
Destroy(last_options_);
|
||||
ASSERT_OK(TryReopen(options));
|
||||
}
|
||||
|
||||
void Destroy(const Options& options) {
|
||||
Close();
|
||||
ASSERT_OK(DestroyDB(dbname_, options));
|
||||
}
|
||||
|
||||
Status ReadOnlyReopen(const Options& options) {
|
||||
return DB::OpenForReadOnly(options, dbname_, &db_);
|
||||
}
|
||||
|
||||
Status TryReopen(const Options& options) {
|
||||
Close();
|
||||
last_options_ = options;
|
||||
return DB::Open(options, dbname_, &db_);
|
||||
}
|
||||
|
||||
Status Flush(int cf = 0) {
|
||||
if (cf == 0) {
|
||||
return db_->Flush(FlushOptions());
|
||||
} else {
|
||||
return db_->Flush(FlushOptions(), handles_[cf]);
|
||||
}
|
||||
}
|
||||
|
||||
Status Put(const Slice& k, const Slice& v, WriteOptions wo = WriteOptions()) {
|
||||
return db_->Put(wo, k, v);
|
||||
}
|
||||
|
||||
Status Put(int cf, const Slice& k, const Slice& v,
|
||||
WriteOptions wo = WriteOptions()) {
|
||||
return db_->Put(wo, handles_[cf], k, v);
|
||||
}
|
||||
|
||||
Status Delete(const std::string& k) {
|
||||
return db_->Delete(WriteOptions(), k);
|
||||
}
|
||||
|
||||
Status Delete(int cf, const std::string& k) {
|
||||
return db_->Delete(WriteOptions(), handles_[cf], k);
|
||||
}
|
||||
|
||||
std::string Get(const std::string& k, const Snapshot* snapshot = nullptr) {
|
||||
ReadOptions options;
|
||||
options.verify_checksums = true;
|
||||
options.snapshot = snapshot;
|
||||
std::string result;
|
||||
Status s = db_->Get(options, k, &result);
|
||||
if (s.IsNotFound()) {
|
||||
result = "NOT_FOUND";
|
||||
} else if (!s.ok()) {
|
||||
result = s.ToString();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string Get(int cf, const std::string& k,
|
||||
const Snapshot* snapshot = nullptr) {
|
||||
ReadOptions options;
|
||||
options.verify_checksums = true;
|
||||
options.snapshot = snapshot;
|
||||
std::string result;
|
||||
Status s = db_->Get(options, handles_[cf], k, &result);
|
||||
if (s.IsNotFound()) {
|
||||
result = "NOT_FOUND";
|
||||
} else if (!s.ok()) {
|
||||
result = s.ToString();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
int NumTableFilesAtLevel(int level, int cf = 0) {
|
||||
std::string property;
|
||||
if (cf == 0) {
|
||||
// default cfd
|
||||
EXPECT_TRUE(db_->GetProperty(
|
||||
"rocksdb.num-files-at-level" + NumberToString(level), &property));
|
||||
} else {
|
||||
EXPECT_TRUE(db_->GetProperty(
|
||||
handles_[cf], "rocksdb.num-files-at-level" + NumberToString(level),
|
||||
&property));
|
||||
}
|
||||
return atoi(property.c_str());
|
||||
}
|
||||
|
||||
// Return spread of files per level
|
||||
std::string FilesPerLevel(int cf = 0) {
|
||||
int num_levels =
|
||||
(cf == 0) ? db_->NumberLevels() : db_->NumberLevels(handles_[1]);
|
||||
std::string result;
|
||||
size_t last_non_zero_offset = 0;
|
||||
for (int level = 0; level < num_levels; level++) {
|
||||
int f = NumTableFilesAtLevel(level, cf);
|
||||
char buf[100];
|
||||
snprintf(buf, sizeof(buf), "%s%d", (level ? "," : ""), f);
|
||||
result += buf;
|
||||
if (f > 0) {
|
||||
last_non_zero_offset = result.size();
|
||||
}
|
||||
}
|
||||
result.resize(last_non_zero_offset);
|
||||
return result;
|
||||
}
|
||||
|
||||
uint64_t Size(const Slice& start, const Slice& limit, int cf = 0) {
|
||||
Range r(start, limit);
|
||||
uint64_t size;
|
||||
if (cf == 0) {
|
||||
db_->GetApproximateSizes(&r, 1, &size);
|
||||
} else {
|
||||
db_->GetApproximateSizes(handles_[1], &r, 1, &size);
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
void Compact(int cf, const Slice& start, const Slice& limit,
|
||||
uint32_t target_path_id) {
|
||||
CompactRangeOptions compact_options;
|
||||
compact_options.target_path_id = target_path_id;
|
||||
ASSERT_OK(db_->CompactRange(compact_options, handles_[cf], &start, &limit));
|
||||
}
|
||||
|
||||
void Compact(int cf, const Slice& start, const Slice& limit) {
|
||||
ASSERT_OK(
|
||||
db_->CompactRange(CompactRangeOptions(), handles_[cf], &start, &limit));
|
||||
}
|
||||
|
||||
void Compact(const Slice& start, const Slice& limit) {
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), &start, &limit));
|
||||
}
|
||||
|
||||
void TEST_Compact(int level, int cf, const Slice& start, const Slice& limit) {
|
||||
ASSERT_OK(dbfull()->TEST_CompactRange(level, &start, &limit, handles_[cf],
|
||||
true /* disallow trivial move */));
|
||||
}
|
||||
|
||||
// Do n memtable compactions, each of which produces an sstable
|
||||
// covering the range [small,large].
|
||||
void MakeTables(int n, const std::string& small, const std::string& large,
|
||||
int cf = 0) {
|
||||
for (int i = 0; i < n; i++) {
|
||||
ASSERT_OK(Put(cf, small, "begin"));
|
||||
ASSERT_OK(Put(cf, large, "end"));
|
||||
ASSERT_OK(Flush(cf));
|
||||
}
|
||||
}
|
||||
|
||||
void MakeTableWithKeyValues(
|
||||
Random* rnd, uint64_t smallest, uint64_t largest,
|
||||
int key_size, int value_size, uint64_t interval,
|
||||
double ratio, int cf = 0) {
|
||||
for (auto key = smallest; key < largest; key += interval) {
|
||||
ASSERT_OK(Put(cf, Slice(Key(key, key_size)),
|
||||
Slice(RandomString(rnd, value_size, ratio))));
|
||||
}
|
||||
ASSERT_OK(Flush(cf));
|
||||
}
|
||||
};
|
||||
|
||||
// An EventListener which helps verify the compaction results in
|
||||
// test CompactionJobStatsTest.
|
||||
class CompactionJobStatsChecker : public EventListener {
|
||||
public:
|
||||
CompactionJobStatsChecker() : compression_enabled_(false) {}
|
||||
|
||||
size_t NumberOfUnverifiedStats() { return expected_stats_.size(); }
|
||||
|
||||
// Once a compaction completed, this functionw will verify the returned
|
||||
// CompactionJobInfo with the oldest CompactionJobInfo added earlier
|
||||
// in "expected_stats_" which has not yet being used for verification.
|
||||
virtual void OnCompactionCompleted(DB *db, const CompactionJobInfo& ci) {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
|
||||
if (expected_stats_.size()) {
|
||||
Verify(ci.stats, expected_stats_.front());
|
||||
expected_stats_.pop();
|
||||
}
|
||||
}
|
||||
|
||||
// A helper function which verifies whether two CompactionJobStats
|
||||
// match. The verification of all compaction stats are done by
|
||||
// ASSERT_EQ except for the total input / output bytes, which we
|
||||
// use ASSERT_GE and ASSERT_LE with a reasonable bias ---
|
||||
// 10% in uncompressed case and 20% when compression is used.
|
||||
void Verify(const CompactionJobStats& current_stats,
|
||||
const CompactionJobStats& stats) {
|
||||
// time
|
||||
ASSERT_GT(current_stats.elapsed_micros, 0U);
|
||||
|
||||
ASSERT_EQ(current_stats.num_input_records,
|
||||
stats.num_input_records);
|
||||
ASSERT_EQ(current_stats.num_input_files,
|
||||
stats.num_input_files);
|
||||
ASSERT_EQ(current_stats.num_input_files_at_output_level,
|
||||
stats.num_input_files_at_output_level);
|
||||
|
||||
ASSERT_EQ(current_stats.num_output_records,
|
||||
stats.num_output_records);
|
||||
ASSERT_EQ(current_stats.num_output_files,
|
||||
stats.num_output_files);
|
||||
|
||||
ASSERT_EQ(current_stats.is_manual_compaction,
|
||||
stats.is_manual_compaction);
|
||||
|
||||
// file size
|
||||
double kFileSizeBias = compression_enabled_ ? 0.20 : 0.10;
|
||||
ASSERT_GE(current_stats.total_input_bytes * (1.00 + kFileSizeBias),
|
||||
stats.total_input_bytes);
|
||||
ASSERT_LE(current_stats.total_input_bytes,
|
||||
stats.total_input_bytes * (1.00 + kFileSizeBias));
|
||||
ASSERT_GE(current_stats.total_output_bytes * (1.00 + kFileSizeBias),
|
||||
stats.total_output_bytes);
|
||||
ASSERT_LE(current_stats.total_output_bytes,
|
||||
stats.total_output_bytes * (1.00 + kFileSizeBias));
|
||||
ASSERT_EQ(current_stats.total_input_raw_key_bytes,
|
||||
stats.total_input_raw_key_bytes);
|
||||
ASSERT_EQ(current_stats.total_input_raw_value_bytes,
|
||||
stats.total_input_raw_value_bytes);
|
||||
|
||||
ASSERT_EQ(current_stats.num_records_replaced,
|
||||
stats.num_records_replaced);
|
||||
|
||||
ASSERT_EQ(
|
||||
std::string(current_stats.smallest_output_key_prefix),
|
||||
std::string(stats.smallest_output_key_prefix));
|
||||
ASSERT_EQ(
|
||||
std::string(current_stats.largest_output_key_prefix),
|
||||
std::string(stats.largest_output_key_prefix));
|
||||
}
|
||||
|
||||
// Add an expected compaction stats, which will be used to
|
||||
// verify the CompactionJobStats returned by the OnCompactionCompleted()
|
||||
// callback.
|
||||
void AddExpectedStats(const CompactionJobStats& stats) {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
expected_stats_.push(stats);
|
||||
}
|
||||
|
||||
void EnableCompression(bool flag) {
|
||||
compression_enabled_ = flag;
|
||||
}
|
||||
|
||||
private:
|
||||
std::mutex mutex_;
|
||||
std::queue<CompactionJobStats> expected_stats_;
|
||||
bool compression_enabled_;
|
||||
};
|
||||
|
||||
namespace {
|
||||
|
||||
uint64_t EstimatedFileSize(
|
||||
uint64_t num_records, size_t key_size, size_t value_size,
|
||||
double compression_ratio = 1.0,
|
||||
size_t block_size = 4096,
|
||||
int bloom_bits_per_key = 10) {
|
||||
const size_t kPerKeyOverhead = 8;
|
||||
const size_t kFooterSize = 512;
|
||||
|
||||
uint64_t data_size =
|
||||
num_records * (key_size + value_size * compression_ratio +
|
||||
kPerKeyOverhead);
|
||||
|
||||
return data_size + kFooterSize
|
||||
+ num_records * bloom_bits_per_key / 8 // filter block
|
||||
+ data_size * (key_size + 8) / block_size; // index block
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
void CopyPrefix(
|
||||
const Slice& src, size_t prefix_length, std::string* dst) {
|
||||
assert(prefix_length > 0);
|
||||
size_t length = src.size() > prefix_length ? prefix_length : src.size();
|
||||
dst->assign(src.data(), length);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
CompactionJobStats NewManualCompactionJobStats(
|
||||
const std::string& smallest_key, const std::string& largest_key,
|
||||
size_t num_input_files, size_t num_input_files_at_output_level,
|
||||
uint64_t num_input_records, size_t key_size, size_t value_size,
|
||||
size_t num_output_files, uint64_t num_output_records,
|
||||
double compression_ratio, uint64_t num_records_replaced,
|
||||
bool is_manual = true) {
|
||||
CompactionJobStats stats;
|
||||
stats.Reset();
|
||||
|
||||
stats.num_input_records = num_input_records;
|
||||
stats.num_input_files = num_input_files;
|
||||
stats.num_input_files_at_output_level = num_input_files_at_output_level;
|
||||
|
||||
stats.num_output_records = num_output_records;
|
||||
stats.num_output_files = num_output_files;
|
||||
|
||||
stats.total_input_bytes =
|
||||
EstimatedFileSize(
|
||||
num_input_records / num_input_files,
|
||||
key_size, value_size, compression_ratio) * num_input_files;
|
||||
stats.total_output_bytes =
|
||||
EstimatedFileSize(
|
||||
num_output_records / num_output_files,
|
||||
key_size, value_size, compression_ratio) * num_output_files;
|
||||
stats.total_input_raw_key_bytes =
|
||||
num_input_records * (key_size + 8);
|
||||
stats.total_input_raw_value_bytes =
|
||||
num_input_records * value_size;
|
||||
|
||||
stats.is_manual_compaction = is_manual;
|
||||
|
||||
stats.num_records_replaced = num_records_replaced;
|
||||
|
||||
CopyPrefix(smallest_key,
|
||||
CompactionJobStats::kMaxPrefixLength,
|
||||
&stats.smallest_output_key_prefix);
|
||||
CopyPrefix(largest_key,
|
||||
CompactionJobStats::kMaxPrefixLength,
|
||||
&stats.largest_output_key_prefix);
|
||||
|
||||
return stats;
|
||||
}
|
||||
|
||||
CompressionType GetAnyCompression() {
|
||||
if (Snappy_Supported()) {
|
||||
return kSnappyCompression;
|
||||
} else if (Zlib_Supported()) {
|
||||
return kZlibCompression;
|
||||
} else if (BZip2_Supported()) {
|
||||
return kBZip2Compression;
|
||||
} else if (LZ4_Supported()) {
|
||||
return kLZ4Compression;
|
||||
}
|
||||
return kNoCompression;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST_F(CompactionJobStatsTest, CompactionJobStatsTest) {
|
||||
Random rnd(301);
|
||||
const int kBufSize = 100;
|
||||
char buf[kBufSize];
|
||||
uint64_t key_base = 100000000l;
|
||||
// Note: key_base must be multiple of num_keys_per_L0_file
|
||||
int num_keys_per_L0_file = 100;
|
||||
const int kTestScale = 8;
|
||||
const int kKeySize = 10;
|
||||
const int kValueSize = 1000;
|
||||
const double kCompressionRatio = 0.5;
|
||||
double compression_ratio = 1.0;
|
||||
uint64_t key_interval = key_base / num_keys_per_L0_file;
|
||||
|
||||
// Whenever a compaction completes, this listener will try to
|
||||
// verify whether the returned CompactionJobStats matches
|
||||
// what we expect. The expected CompactionJobStats is added
|
||||
// via AddExpectedStats().
|
||||
auto* stats_checker = new CompactionJobStatsChecker();
|
||||
Options options;
|
||||
options.listeners.emplace_back(stats_checker);
|
||||
options.create_if_missing = true;
|
||||
options.max_background_flushes = 0;
|
||||
options.max_mem_compaction_level = 0;
|
||||
// just enough setting to hold off auto-compaction.
|
||||
options.level0_file_num_compaction_trigger = kTestScale + 1;
|
||||
options.num_levels = 3;
|
||||
options.compression = kNoCompression;
|
||||
|
||||
for (int test = 0; test < 2; ++test) {
|
||||
DestroyAndReopen(options);
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
|
||||
// 1st Phase: generate "num_L0_files" L0 files.
|
||||
int num_L0_files = 0;
|
||||
for (uint64_t start_key = key_base;
|
||||
start_key <= key_base * kTestScale;
|
||||
start_key += key_base) {
|
||||
MakeTableWithKeyValues(
|
||||
&rnd, start_key, start_key + key_base - 1,
|
||||
kKeySize, kValueSize, key_interval,
|
||||
compression_ratio, 1);
|
||||
snprintf(buf, kBufSize, "%d", ++num_L0_files);
|
||||
ASSERT_EQ(std::string(buf), FilesPerLevel(1));
|
||||
}
|
||||
ASSERT_EQ(ToString(num_L0_files), FilesPerLevel(1));
|
||||
|
||||
// 2nd Phase: perform L0 -> L1 compaction.
|
||||
int L0_compaction_count = 6;
|
||||
int count = 1;
|
||||
std::string smallest_key;
|
||||
std::string largest_key;
|
||||
for (uint64_t start_key = key_base;
|
||||
start_key <= key_base * L0_compaction_count;
|
||||
start_key += key_base, count++) {
|
||||
smallest_key = Key(start_key, 10);
|
||||
largest_key = Key(start_key + key_base - key_interval, 10);
|
||||
stats_checker->AddExpectedStats(
|
||||
NewManualCompactionJobStats(
|
||||
smallest_key, largest_key,
|
||||
1, 0, num_keys_per_L0_file,
|
||||
kKeySize, kValueSize,
|
||||
1, num_keys_per_L0_file,
|
||||
compression_ratio, 0));
|
||||
ASSERT_EQ(stats_checker->NumberOfUnverifiedStats(), 1U);
|
||||
TEST_Compact(0, 1, smallest_key, largest_key);
|
||||
snprintf(buf, kBufSize, "%d,%d", num_L0_files - count, count);
|
||||
ASSERT_EQ(std::string(buf), FilesPerLevel(1));
|
||||
}
|
||||
|
||||
// compact two files into one in the last L0 -> L1 compaction
|
||||
int num_remaining_L0 = num_L0_files - L0_compaction_count;
|
||||
smallest_key = Key(key_base * (L0_compaction_count + 1), 10);
|
||||
largest_key = Key(key_base * (kTestScale + 1) - key_interval, 10);
|
||||
stats_checker->AddExpectedStats(
|
||||
NewManualCompactionJobStats(
|
||||
smallest_key, largest_key,
|
||||
num_remaining_L0,
|
||||
0, num_keys_per_L0_file * num_remaining_L0,
|
||||
kKeySize, kValueSize,
|
||||
1, num_keys_per_L0_file * num_remaining_L0,
|
||||
compression_ratio, 0));
|
||||
ASSERT_EQ(stats_checker->NumberOfUnverifiedStats(), 1U);
|
||||
TEST_Compact(0, 1, smallest_key, largest_key);
|
||||
|
||||
int num_L1_files = num_L0_files - num_remaining_L0 + 1;
|
||||
num_L0_files = 0;
|
||||
snprintf(buf, kBufSize, "%d,%d", num_L0_files, num_L1_files);
|
||||
ASSERT_EQ(std::string(buf), FilesPerLevel(1));
|
||||
|
||||
// 3rd Phase: generate sparse L0 files (wider key-range, same num of keys)
|
||||
int sparseness = 2;
|
||||
for (uint64_t start_key = key_base;
|
||||
start_key <= key_base * kTestScale;
|
||||
start_key += key_base * sparseness) {
|
||||
MakeTableWithKeyValues(
|
||||
&rnd, start_key, start_key + key_base * sparseness - 1,
|
||||
kKeySize, kValueSize,
|
||||
key_base * sparseness / num_keys_per_L0_file,
|
||||
compression_ratio, 1);
|
||||
snprintf(buf, kBufSize, "%d,%d", ++num_L0_files, num_L1_files);
|
||||
ASSERT_EQ(std::string(buf), FilesPerLevel(1));
|
||||
}
|
||||
|
||||
// 4th Phase: perform L0 -> L1 compaction again, expect higher write amp
|
||||
for (uint64_t start_key = key_base;
|
||||
num_L0_files > 1;
|
||||
start_key += key_base * sparseness) {
|
||||
smallest_key = Key(start_key, 10);
|
||||
largest_key =
|
||||
Key(start_key + key_base * sparseness - key_interval, 10);
|
||||
stats_checker->AddExpectedStats(
|
||||
NewManualCompactionJobStats(
|
||||
smallest_key, largest_key,
|
||||
3, 2, num_keys_per_L0_file * 3,
|
||||
kKeySize, kValueSize,
|
||||
1, num_keys_per_L0_file * 2, // 1/3 of the data will be updated.
|
||||
compression_ratio,
|
||||
num_keys_per_L0_file));
|
||||
ASSERT_EQ(stats_checker->NumberOfUnverifiedStats(), 1U);
|
||||
Compact(1, smallest_key, largest_key);
|
||||
snprintf(buf, kBufSize, "%d,%d",
|
||||
--num_L0_files, --num_L1_files);
|
||||
ASSERT_EQ(std::string(buf), FilesPerLevel(1));
|
||||
}
|
||||
|
||||
// 5th Phase: Do a full compaction, which involves in two sub-compactions.
|
||||
// Here we expect to have 1 L0 files and 4 L1 files
|
||||
// In the first sub-compaction, we expect L0 compaction.
|
||||
smallest_key = Key(key_base, 10);
|
||||
largest_key = Key(key_base * (kTestScale + 1) - key_interval, 10);
|
||||
stats_checker->AddExpectedStats(
|
||||
NewManualCompactionJobStats(
|
||||
Key(key_base * (kTestScale + 1 - sparseness), 10), largest_key,
|
||||
2, 1, num_keys_per_L0_file * 3,
|
||||
kKeySize, kValueSize,
|
||||
1, num_keys_per_L0_file * 2,
|
||||
compression_ratio,
|
||||
num_keys_per_L0_file));
|
||||
ASSERT_EQ(stats_checker->NumberOfUnverifiedStats(), 1U);
|
||||
Compact(1, smallest_key, largest_key);
|
||||
ASSERT_EQ("0,4", FilesPerLevel(1));
|
||||
options.compression = GetAnyCompression();
|
||||
if (options.compression == kNoCompression) {
|
||||
break;
|
||||
}
|
||||
stats_checker->EnableCompression(true);
|
||||
compression_ratio = kCompressionRatio;
|
||||
}
|
||||
ASSERT_EQ(stats_checker->NumberOfUnverifiedStats(), 0U);
|
||||
}
|
||||
|
||||
namespace {
|
||||
int GetUniversalCompactionInputUnits(uint32_t num_flushes) {
|
||||
uint32_t compaction_input_units;
|
||||
for (compaction_input_units = 1;
|
||||
num_flushes >= compaction_input_units;
|
||||
compaction_input_units *= 2) {
|
||||
if ((num_flushes & compaction_input_units) != 0) {
|
||||
return compaction_input_units > 1 ? compaction_input_units : 0;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST_F(CompactionJobStatsTest, UniversalCompactionTest) {
|
||||
Random rnd(301);
|
||||
uint64_t key_base = 100000000l;
|
||||
// Note: key_base must be multiple of num_keys_per_L0_file
|
||||
int num_keys_per_table = 100;
|
||||
const uint32_t kTestScale = 8;
|
||||
const int kKeySize = 10;
|
||||
const int kValueSize = 900;
|
||||
double compression_ratio = 1.0;
|
||||
uint64_t key_interval = key_base / num_keys_per_table;
|
||||
|
||||
auto* stats_checker = new CompactionJobStatsChecker();
|
||||
Options options;
|
||||
options.listeners.emplace_back(stats_checker);
|
||||
options.create_if_missing = true;
|
||||
options.max_background_flushes = 0;
|
||||
options.max_mem_compaction_level = 0;
|
||||
options.num_levels = 3;
|
||||
options.compression = kNoCompression;
|
||||
options.level0_file_num_compaction_trigger = 2;
|
||||
options.target_file_size_base = num_keys_per_table * 1000;
|
||||
options.compaction_style = kCompactionStyleUniversal;
|
||||
options.compaction_options_universal.size_ratio = 1;
|
||||
options.compaction_options_universal.max_size_amplification_percent = 1000;
|
||||
DestroyAndReopen(options);
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
|
||||
// Generates the expected CompactionJobStats for each compaction
|
||||
for (uint32_t num_flushes = 2; num_flushes <= kTestScale; num_flushes++) {
|
||||
// Here we treat one newly flushed file as an unit.
|
||||
//
|
||||
// For example, if a newly flushed file is 100k, and a compaction has
|
||||
// 4 input units, then this compaction inputs 400k.
|
||||
uint32_t num_input_units = GetUniversalCompactionInputUnits(num_flushes);
|
||||
if (num_input_units == 0) {
|
||||
continue;
|
||||
}
|
||||
// The following statement determines the expected smallest key
|
||||
// based on whether it is a full compaction. A full compaction only
|
||||
// happens when the number of flushes equals to the number of compaction
|
||||
// input runs.
|
||||
uint64_t smallest_key =
|
||||
(num_flushes == num_input_units) ?
|
||||
key_base : key_base * (num_flushes - 1);
|
||||
|
||||
stats_checker->AddExpectedStats(
|
||||
NewManualCompactionJobStats(
|
||||
Key(smallest_key, 10),
|
||||
Key(smallest_key + key_base * num_input_units - key_interval, 10),
|
||||
num_input_units,
|
||||
num_input_units > 2 ? num_input_units / 2 : 0,
|
||||
num_keys_per_table * num_input_units,
|
||||
kKeySize, kValueSize,
|
||||
num_input_units,
|
||||
num_keys_per_table * num_input_units,
|
||||
1.0, 0, false));
|
||||
}
|
||||
ASSERT_EQ(stats_checker->NumberOfUnverifiedStats(), 4U);
|
||||
|
||||
for (uint64_t start_key = key_base;
|
||||
start_key <= key_base * kTestScale;
|
||||
start_key += key_base) {
|
||||
MakeTableWithKeyValues(
|
||||
&rnd, start_key, start_key + key_base - 1,
|
||||
kKeySize, kValueSize, key_interval,
|
||||
compression_ratio, 1);
|
||||
}
|
||||
ASSERT_EQ(stats_checker->NumberOfUnverifiedStats(), 0U);
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
rocksdb::port::InstallStackTraceHandler();
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
|
||||
#endif // !ROCKSDB_LITE
|
||||
#endif // !defined(IOS_CROSS_COMPILE)
|
||||
@@ -76,11 +76,10 @@ class CompactionJobTest : public testing::Test {
|
||||
largest = internal_key;
|
||||
largest_seqno = sequence_number;
|
||||
}
|
||||
std::pair<std::string, std::string> key_value(
|
||||
{bottommost_internal_key.Encode().ToString(), value});
|
||||
contents.insert(key_value);
|
||||
contents.insert({internal_key.Encode().ToString(), value});
|
||||
if (i == 1 || k < kKeysPerFile / 2) {
|
||||
expected_results.insert(key_value);
|
||||
expected_results.insert(
|
||||
{bottommost_internal_key.Encode().ToString(), value});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,14 +89,14 @@ class CompactionJobTest : public testing::Test {
|
||||
|
||||
VersionEdit edit;
|
||||
edit.AddFile(0, file_number, 0, 10, smallest, largest, smallest_seqno,
|
||||
largest_seqno);
|
||||
largest_seqno, false);
|
||||
|
||||
mutex_.Lock();
|
||||
versions_->LogAndApply(versions_->GetColumnFamilySet()->GetDefault(),
|
||||
mutable_cf_options_, &edit, &mutex_);
|
||||
mutex_.Unlock();
|
||||
}
|
||||
versions_->SetLastSequence(sequence_number);
|
||||
versions_->SetLastSequence(sequence_number + 1);
|
||||
return expected_results;
|
||||
}
|
||||
|
||||
@@ -138,6 +137,45 @@ class CompactionJobTest : public testing::Test {
|
||||
std::shared_ptr<mock::MockTableFactory> mock_table_factory_;
|
||||
};
|
||||
|
||||
namespace {
|
||||
void VerifyInitializationOfCompactionJobStats(
|
||||
const CompactionJobStats& compaction_job_stats) {
|
||||
#if !defined(IOS_CROSS_COMPILE)
|
||||
ASSERT_EQ(compaction_job_stats.elapsed_micros, 0U);
|
||||
|
||||
ASSERT_EQ(compaction_job_stats.num_input_records, 0U);
|
||||
ASSERT_EQ(compaction_job_stats.num_input_files, 0U);
|
||||
ASSERT_EQ(compaction_job_stats.num_input_files_at_output_level, 0U);
|
||||
|
||||
ASSERT_EQ(compaction_job_stats.num_output_records, 0U);
|
||||
ASSERT_EQ(compaction_job_stats.num_output_files, 0U);
|
||||
|
||||
ASSERT_EQ(compaction_job_stats.is_manual_compaction, 0U);
|
||||
|
||||
ASSERT_EQ(compaction_job_stats.total_input_bytes, 0U);
|
||||
ASSERT_EQ(compaction_job_stats.total_output_bytes, 0U);
|
||||
|
||||
ASSERT_EQ(compaction_job_stats.total_input_raw_key_bytes, 0U);
|
||||
ASSERT_EQ(compaction_job_stats.total_input_raw_value_bytes, 0U);
|
||||
|
||||
ASSERT_EQ(compaction_job_stats.smallest_output_key_prefix[0], 0);
|
||||
ASSERT_EQ(compaction_job_stats.largest_output_key_prefix[0], 0);
|
||||
|
||||
ASSERT_EQ(compaction_job_stats.num_records_replaced, 0U);
|
||||
#endif // !defined(IOS_CROSS_COMPILE)
|
||||
}
|
||||
|
||||
void VerifyCompactionJobStats(
|
||||
const CompactionJobStats& compaction_job_stats,
|
||||
const std::vector<FileMetaData*>& files,
|
||||
size_t num_output_files,
|
||||
uint64_t min_elapsed_time) {
|
||||
ASSERT_GE(compaction_job_stats.elapsed_micros, min_elapsed_time);
|
||||
ASSERT_EQ(compaction_job_stats.num_input_files, files.size());
|
||||
ASSERT_EQ(compaction_job_stats.num_output_files, num_output_files);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST_F(CompactionJobTest, Simple) {
|
||||
auto cfd = versions_->GetColumnFamilySet()->GetDefault();
|
||||
|
||||
@@ -163,10 +201,16 @@ TEST_F(CompactionJobTest, Simple) {
|
||||
LogBuffer log_buffer(InfoLogLevel::INFO_LEVEL, db_options_.info_log.get());
|
||||
mutex_.Lock();
|
||||
EventLogger event_logger(db_options_.info_log.get());
|
||||
std::string db_name = "dbname";
|
||||
CompactionJobStats compaction_job_stats;
|
||||
CompactionJob compaction_job(0, compaction.get(), db_options_, env_options_,
|
||||
versions_.get(), &shutting_down_, &log_buffer,
|
||||
nullptr, nullptr, nullptr, {}, table_cache_,
|
||||
std::move(yield_callback), &event_logger, false);
|
||||
std::move(yield_callback), &event_logger, false,
|
||||
db_name, &compaction_job_stats);
|
||||
|
||||
auto start_micros = Env::Default()->NowMicros();
|
||||
VerifyInitializationOfCompactionJobStats(compaction_job_stats);
|
||||
|
||||
compaction_job.Prepare();
|
||||
mutex_.Unlock();
|
||||
@@ -177,6 +221,10 @@ TEST_F(CompactionJobTest, Simple) {
|
||||
ASSERT_OK(s);
|
||||
mutex_.Unlock();
|
||||
|
||||
VerifyCompactionJobStats(
|
||||
compaction_job_stats,
|
||||
files, 1, (Env::Default()->NowMicros() - start_micros) / 2);
|
||||
|
||||
mock_table_factory_->AssertLatestFile(expected_results);
|
||||
ASSERT_EQ(yield_callback_called, 20000);
|
||||
}
|
||||
|
||||
@@ -763,6 +763,10 @@ void LevelCompactionPicker::PickFilesMarkedForCompactionExperimental(
|
||||
*level = level_file.first;
|
||||
*output_level = (*level == 0) ? vstorage->base_level() : *level + 1;
|
||||
|
||||
if (*level == 0 && !level0_compactions_in_progress_.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
inputs->files = {level_file.second};
|
||||
inputs->level = *level;
|
||||
return ExpandWhileOverlapping(cf_name, vstorage, inputs);
|
||||
|
||||
@@ -90,6 +90,7 @@ class CompactionPickerTest : public testing::Test {
|
||||
vstorage_->GenerateFileIndexer();
|
||||
vstorage_->GenerateLevelFilesBrief();
|
||||
vstorage_->ComputeCompactionScore(mutable_cf_options_, fifo_options_);
|
||||
vstorage_->GenerateLevel0NonOverlapping();
|
||||
vstorage_->SetFinalized();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -177,8 +177,13 @@ class DoubleComparator : public Comparator {
|
||||
virtual const char* Name() const override { return "DoubleComparator"; }
|
||||
|
||||
virtual int Compare(const Slice& a, const Slice& b) const override {
|
||||
#ifndef CYGWIN
|
||||
double da = std::stod(a.ToString());
|
||||
double db = std::stod(b.ToString());
|
||||
#else
|
||||
double da = std::strtod(a.ToString().c_str(), 0 /* endptr */);
|
||||
double db = std::strtod(a.ToString().c_str(), 0 /* endptr */);
|
||||
#endif
|
||||
if (da == db) {
|
||||
return a.compare(b);
|
||||
} else if (da > db) {
|
||||
|
||||
@@ -243,7 +243,8 @@ TEST_F(CuckooTableDBTest, CompactionIntoMultipleFiles) {
|
||||
dbfull()->TEST_WaitForFlushMemTable();
|
||||
ASSERT_EQ("1", FilesPerLevel());
|
||||
|
||||
dbfull()->TEST_CompactRange(0, nullptr, nullptr);
|
||||
dbfull()->TEST_CompactRange(0, nullptr, nullptr, nullptr,
|
||||
true /* disallow trivial move */);
|
||||
ASSERT_EQ("0,2", FilesPerLevel());
|
||||
for (int idx = 0; idx < 28; ++idx) {
|
||||
ASSERT_EQ(std::string(10000, 'a' + idx), Get(Key(idx)));
|
||||
|
||||
+438
-17
@@ -32,6 +32,12 @@ int main() {
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <gflags/gflags.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <condition_variable>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
|
||||
#include "db/db_impl.h"
|
||||
#include "db/version_set.h"
|
||||
#include "rocksdb/options.h"
|
||||
@@ -46,6 +52,8 @@ int main() {
|
||||
#include "rocksdb/slice_transform.h"
|
||||
#include "rocksdb/perf_context.h"
|
||||
#include "rocksdb/utilities/flashcache.h"
|
||||
#include "rocksdb/utilities/optimistic_transaction.h"
|
||||
#include "rocksdb/utilities/optimistic_transaction_db.h"
|
||||
#include "port/port.h"
|
||||
#include "port/stack_trace.h"
|
||||
#include "util/crc32c.h"
|
||||
@@ -94,7 +102,8 @@ DEFINE_string(benchmarks,
|
||||
"compress,"
|
||||
"uncompress,"
|
||||
"acquireload,"
|
||||
"fillseekseq,",
|
||||
"fillseekseq,"
|
||||
"randomtransaction",
|
||||
|
||||
"Comma-separated list of operations to run in the specified order"
|
||||
"Actual benchmarks:\n"
|
||||
@@ -145,6 +154,8 @@ DEFINE_string(benchmarks,
|
||||
"\tacquireload -- load N*1000 times\n"
|
||||
"\tfillseekseq -- write N values in sequential key, then read "
|
||||
"them by seeking to each key\n"
|
||||
"\trandomtransaction -- execute N random transactions and "
|
||||
"verify correctness\n"
|
||||
"Meta operations:\n"
|
||||
"\tcompact -- Compact the entire DB\n"
|
||||
"\tstats -- Print DB stats\n"
|
||||
@@ -251,6 +262,20 @@ DEFINE_int32(min_write_buffer_number_to_merge,
|
||||
" writing less data to storage if there are duplicate records "
|
||||
" in each of these individual write buffers.");
|
||||
|
||||
DEFINE_int32(max_write_buffer_number_to_maintain,
|
||||
rocksdb::Options().max_write_buffer_number_to_maintain,
|
||||
"The total maximum number of write buffers to maintain in memory "
|
||||
"including copies of buffers that have already been flushed. "
|
||||
"Unlike max_write_buffer_number, this parameter does not affect "
|
||||
"flushing. This controls the minimum amount of write history "
|
||||
"that will be available in memory for conflict checking when "
|
||||
"Transactions are used. If this value is too low, some "
|
||||
"transactions may fail at commit time due to not being able to "
|
||||
"determine whether there were any write conflicts. Setting this "
|
||||
"value to 0 will cause write buffers to be freed immediately "
|
||||
"after they are flushed. If this value is set to -1, "
|
||||
"'max_write_buffer_number' will be used.");
|
||||
|
||||
DEFINE_int32(max_background_compactions,
|
||||
rocksdb::Options().max_background_compactions,
|
||||
"The maximum number of concurrent background compactions"
|
||||
@@ -297,6 +322,10 @@ DEFINE_int32(block_restart_interval,
|
||||
DEFINE_int64(compressed_cache_size, -1,
|
||||
"Number of bytes to use as a cache of compressed data.");
|
||||
|
||||
DEFINE_int64(row_cache_size, 0,
|
||||
"Number of bytes to use as a cache of individual rows"
|
||||
" (0 = disabled).");
|
||||
|
||||
DEFINE_int32(open_files, rocksdb::Options().max_open_files,
|
||||
"Maximum number of files to keep open at the same time"
|
||||
" (use default if == 0)");
|
||||
@@ -367,13 +396,19 @@ static std::vector<int> FLAGS_max_bytes_for_level_multiplier_additional_v;
|
||||
DEFINE_string(max_bytes_for_level_multiplier_additional, "",
|
||||
"A vector that specifies additional fanout per level");
|
||||
|
||||
DEFINE_int32(level0_stop_writes_trigger, 12, "Number of files in level-0"
|
||||
DEFINE_int32(level0_stop_writes_trigger,
|
||||
rocksdb::Options().level0_stop_writes_trigger,
|
||||
"Number of files in level-0"
|
||||
" that will trigger put stop.");
|
||||
|
||||
DEFINE_int32(level0_slowdown_writes_trigger, 8, "Number of files in level-0"
|
||||
DEFINE_int32(level0_slowdown_writes_trigger,
|
||||
rocksdb::Options().level0_slowdown_writes_trigger,
|
||||
"Number of files in level-0"
|
||||
" that will slow down writes.");
|
||||
|
||||
DEFINE_int32(level0_file_num_compaction_trigger, 4, "Number of files in level-0"
|
||||
DEFINE_int32(level0_file_num_compaction_trigger,
|
||||
rocksdb::Options().level0_file_num_compaction_trigger,
|
||||
"Number of files in level-0"
|
||||
" when compactions start");
|
||||
|
||||
static bool ValidateInt32Percent(const char* flagname, int32_t value) {
|
||||
@@ -403,6 +438,18 @@ DEFINE_int32(deletepercent, 2, "Percentage of deletes out of reads/writes/"
|
||||
DEFINE_uint64(delete_obsolete_files_period_micros, 0,
|
||||
"Ignored. Left here for backward compatibility");
|
||||
|
||||
DEFINE_bool(transaction_db, false,
|
||||
"Open a OptimisticTransactionDB instance. "
|
||||
"Required for randomtransaction benchmark.");
|
||||
|
||||
DEFINE_uint64(transaction_sets, 2,
|
||||
"Number of keys each transaction will "
|
||||
"modify (use in RandomTransaction only). Max: 9999");
|
||||
|
||||
DEFINE_int32(transaction_sleep, 0,
|
||||
"Max microseconds to sleep in between "
|
||||
"reading and writing a value (used in RandomTransaction only). ");
|
||||
|
||||
namespace {
|
||||
enum rocksdb::CompressionType StringToCompressionType(const char* ctype) {
|
||||
assert(ctype);
|
||||
@@ -485,6 +532,14 @@ DEFINE_int64(stats_interval_seconds, 0, "Report stats every N seconds. This "
|
||||
DEFINE_int32(stats_per_interval, 0, "Reports additional stats per interval when"
|
||||
" this is greater than 0.");
|
||||
|
||||
DEFINE_int64(report_interval_seconds, 0,
|
||||
"If greater than zero, it will write simple stats in CVS format "
|
||||
"to --report_file every N seconds");
|
||||
|
||||
DEFINE_string(report_file, "report.csv",
|
||||
"Filename where some simple stats are reported to (if "
|
||||
"--report_interval_seconds is bigger than 0)");
|
||||
|
||||
DEFINE_int32(thread_status_per_interval, 0,
|
||||
"Takes and report a snapshot of the current status of each thread"
|
||||
" when this is greater than 0.");
|
||||
@@ -506,12 +561,20 @@ DEFINE_double(hard_rate_limit, 0.0, "When not equal to 0 this make threads "
|
||||
"sleep at each stats reporting interval until the compaction"
|
||||
" score for all levels is less than or equal to this value.");
|
||||
|
||||
DEFINE_uint64(delayed_write_rate, 2097152u,
|
||||
"Limited bytes allowed to DB when soft_rate_limit or "
|
||||
"level0_slowdown_writes_trigger triggers");
|
||||
|
||||
DEFINE_int32(rate_limit_delay_max_milliseconds, 1000,
|
||||
"When hard_rate_limit is set then this is the max time a put will"
|
||||
" be stalled.");
|
||||
|
||||
DEFINE_uint64(rate_limiter_bytes_per_sec, 0, "Set options.rate_limiter value.");
|
||||
|
||||
DEFINE_uint64(
|
||||
benchmark_write_rate_limit, 0,
|
||||
"If non-zero, db_bench will rate-limit the writes going into RocksDB");
|
||||
|
||||
DEFINE_int32(max_grandparent_overlap_factor, 10, "Control maximum bytes of "
|
||||
"overlaps in grandparent (i.e., level+2) before we stop building a"
|
||||
" single file in a level->level+1 compaction.");
|
||||
@@ -846,6 +909,7 @@ static void AppendWithSpace(std::string* str, Slice msg) {
|
||||
struct DBWithColumnFamilies {
|
||||
std::vector<ColumnFamilyHandle*> cfh;
|
||||
DB* db;
|
||||
OptimisticTransactionDB* txn_db;
|
||||
std::atomic<size_t> num_created; // Need to be updated after all the
|
||||
// new entries in cfh are set.
|
||||
size_t num_hot; // Number of column families to be queried at each moment.
|
||||
@@ -853,7 +917,7 @@ struct DBWithColumnFamilies {
|
||||
// Column families will be created and used to be queried.
|
||||
port::Mutex create_cf_mutex; // Only one thread can execute CreateNewCf()
|
||||
|
||||
DBWithColumnFamilies() : db(nullptr) {
|
||||
DBWithColumnFamilies() : db(nullptr), txn_db(nullptr) {
|
||||
cfh.clear();
|
||||
num_created = 0;
|
||||
num_hot = 0;
|
||||
@@ -862,9 +926,23 @@ struct DBWithColumnFamilies {
|
||||
DBWithColumnFamilies(const DBWithColumnFamilies& other)
|
||||
: cfh(other.cfh),
|
||||
db(other.db),
|
||||
txn_db(other.txn_db),
|
||||
num_created(other.num_created.load()),
|
||||
num_hot(other.num_hot) {}
|
||||
|
||||
void DeleteDBs() {
|
||||
std::for_each(cfh.begin(), cfh.end(),
|
||||
[](ColumnFamilyHandle* cfhi) { delete cfhi; });
|
||||
cfh.clear();
|
||||
if (txn_db) {
|
||||
delete txn_db;
|
||||
txn_db = nullptr;
|
||||
} else {
|
||||
delete db;
|
||||
}
|
||||
db = nullptr;
|
||||
}
|
||||
|
||||
ColumnFamilyHandle* GetCfh(int64_t rand_num) {
|
||||
assert(num_hot > 0);
|
||||
return cfh[num_created.load(std::memory_order_acquire) - num_hot +
|
||||
@@ -894,6 +972,96 @@ struct DBWithColumnFamilies {
|
||||
}
|
||||
};
|
||||
|
||||
// a class that reports stats to CSV file
|
||||
class ReporterAgent {
|
||||
public:
|
||||
ReporterAgent(Env* env, const std::string& fname,
|
||||
uint64_t report_interval_secs)
|
||||
: env_(env),
|
||||
total_ops_done_(0),
|
||||
last_report_(0),
|
||||
report_interval_secs_(report_interval_secs),
|
||||
stop_(false) {
|
||||
auto s = env_->NewWritableFile(fname, &report_file_, EnvOptions());
|
||||
if (s.ok()) {
|
||||
s = report_file_->Append(Header() + "\n");
|
||||
}
|
||||
if (s.ok()) {
|
||||
s = report_file_->Flush();
|
||||
}
|
||||
if (!s.ok()) {
|
||||
fprintf(stderr, "Can't open %s: %s\n", fname.c_str(),
|
||||
s.ToString().c_str());
|
||||
abort();
|
||||
}
|
||||
|
||||
reporting_thread_ = std::thread([&]() { SleepAndReport(); });
|
||||
}
|
||||
|
||||
~ReporterAgent() {
|
||||
{
|
||||
std::unique_lock<std::mutex> lk(mutex_);
|
||||
stop_ = true;
|
||||
stop_cv_.notify_all();
|
||||
}
|
||||
reporting_thread_.join();
|
||||
}
|
||||
|
||||
// thread safe
|
||||
void ReportFinishedOps(int64_t num_ops) {
|
||||
total_ops_done_.fetch_add(num_ops);
|
||||
}
|
||||
|
||||
private:
|
||||
std::string Header() const { return "secs_elapsed,interval_qps"; }
|
||||
void SleepAndReport() {
|
||||
uint64_t kMicrosInSecond = 1000 * 1000;
|
||||
auto time_started = env_->NowMicros();
|
||||
while (true) {
|
||||
{
|
||||
std::unique_lock<std::mutex> lk(mutex_);
|
||||
if (stop_ ||
|
||||
stop_cv_.wait_for(lk, std::chrono::seconds(report_interval_secs_),
|
||||
[&]() { return stop_; })) {
|
||||
// stopping
|
||||
break;
|
||||
}
|
||||
// else -> timeout, which means time for a report!
|
||||
}
|
||||
auto total_ops_done_snapshot = total_ops_done_.load();
|
||||
// round the seconds elapsed
|
||||
auto secs_elapsed =
|
||||
(env_->NowMicros() - time_started + kMicrosInSecond / 2) /
|
||||
kMicrosInSecond;
|
||||
std::string report = ToString(secs_elapsed) + "," +
|
||||
ToString(total_ops_done_snapshot - last_report_) +
|
||||
"\n";
|
||||
auto s = report_file_->Append(report);
|
||||
if (s.ok()) {
|
||||
s = report_file_->Flush();
|
||||
}
|
||||
if (!s.ok()) {
|
||||
fprintf(stderr,
|
||||
"Can't write to report file (%s), stopping the reporting\n",
|
||||
s.ToString().c_str());
|
||||
break;
|
||||
}
|
||||
last_report_ = total_ops_done_snapshot;
|
||||
}
|
||||
}
|
||||
|
||||
Env* env_;
|
||||
std::unique_ptr<WritableFile> report_file_;
|
||||
std::atomic<int64_t> total_ops_done_;
|
||||
int64_t last_report_;
|
||||
const uint64_t report_interval_secs_;
|
||||
std::thread reporting_thread_;
|
||||
std::mutex mutex_;
|
||||
// will notify on stop
|
||||
std::condition_variable stop_cv_;
|
||||
bool stop_;
|
||||
};
|
||||
|
||||
class Stats {
|
||||
private:
|
||||
int id_;
|
||||
@@ -909,10 +1077,15 @@ class Stats {
|
||||
HistogramImpl hist_;
|
||||
std::string message_;
|
||||
bool exclude_from_merge_;
|
||||
ReporterAgent* reporter_agent_; // does not own
|
||||
|
||||
public:
|
||||
Stats() { Start(-1); }
|
||||
|
||||
void SetReporterAgent(ReporterAgent* reporter_agent) {
|
||||
reporter_agent_ = reporter_agent;
|
||||
}
|
||||
|
||||
void Start(int id) {
|
||||
id_ = id;
|
||||
next_report_ = FLAGS_stats_interval ? FLAGS_stats_interval : 100;
|
||||
@@ -988,6 +1161,9 @@ class Stats {
|
||||
}
|
||||
|
||||
void FinishedOps(DBWithColumnFamilies* db_with_cfh, DB* db, int64_t num_ops) {
|
||||
if (reporter_agent_) {
|
||||
reporter_agent_->ReportFinishedOps(num_ops);
|
||||
}
|
||||
if (FLAGS_histogram) {
|
||||
double now = FLAGS_env->NowMicros();
|
||||
double micros = now - last_op_finish_;
|
||||
@@ -1120,6 +1296,7 @@ struct SharedState {
|
||||
port::CondVar cv;
|
||||
int total;
|
||||
int perf_level;
|
||||
std::shared_ptr<RateLimiter> write_rate_limiter;
|
||||
|
||||
// Each thread goes through the following states:
|
||||
// (1) initializing
|
||||
@@ -1232,7 +1409,7 @@ class Benchmark {
|
||||
(((FLAGS_key_size + FLAGS_value_size * FLAGS_compression_ratio)
|
||||
* num_)
|
||||
/ 1048576.0));
|
||||
fprintf(stdout, "Write rate limit: %d\n", FLAGS_writes_per_second);
|
||||
fprintf(stdout, "Writes per second: %d\n", FLAGS_writes_per_second);
|
||||
if (FLAGS_enable_numa) {
|
||||
fprintf(stderr, "Running in NUMA enabled mode.\n");
|
||||
#ifndef NUMA
|
||||
@@ -1467,9 +1644,7 @@ class Benchmark {
|
||||
}
|
||||
|
||||
~Benchmark() {
|
||||
std::for_each(db_.cfh.begin(), db_.cfh.end(),
|
||||
[](ColumnFamilyHandle* cfh) { delete cfh; });
|
||||
delete db_.db;
|
||||
db_.DeleteDBs();
|
||||
delete prefix_extractor_;
|
||||
if (cache_.get() != nullptr) {
|
||||
// this will leak, but we're shutting down so nobody cares
|
||||
@@ -1573,6 +1748,8 @@ class Benchmark {
|
||||
write_options_.disableWAL = FLAGS_disable_wal;
|
||||
|
||||
void (Benchmark::*method)(ThreadState*) = nullptr;
|
||||
void (Benchmark::*post_process_method)() = nullptr;
|
||||
|
||||
bool fresh_db = false;
|
||||
int num_threads = FLAGS_threads;
|
||||
|
||||
@@ -1688,6 +1865,9 @@ class Benchmark {
|
||||
method = &Benchmark::Compress;
|
||||
} else if (name == Slice("uncompress")) {
|
||||
method = &Benchmark::Uncompress;
|
||||
} else if (name == Slice("randomtransaction")) {
|
||||
method = &Benchmark::RandomTransaction;
|
||||
post_process_method = &Benchmark::RandomTransactionVerify;
|
||||
} else if (name == Slice("stats")) {
|
||||
PrintStats("rocksdb.stats");
|
||||
} else if (name == Slice("levelstats")) {
|
||||
@@ -1708,11 +1888,7 @@ class Benchmark {
|
||||
method = nullptr;
|
||||
} else {
|
||||
if (db_.db != nullptr) {
|
||||
std::for_each(db_.cfh.begin(), db_.cfh.end(),
|
||||
[](ColumnFamilyHandle* cfh) { delete cfh; });
|
||||
delete db_.db;
|
||||
db_.db = nullptr;
|
||||
db_.cfh.clear();
|
||||
db_.DeleteDBs();
|
||||
DestroyDB(FLAGS_db, open_options_);
|
||||
}
|
||||
for (size_t i = 0; i < multi_dbs_.size(); i++) {
|
||||
@@ -1728,6 +1904,9 @@ class Benchmark {
|
||||
fprintf(stdout, "DB path: [%s]\n", FLAGS_db.c_str());
|
||||
RunBenchmark(num_threads, name, method);
|
||||
}
|
||||
if (post_process_method != nullptr) {
|
||||
(this->*post_process_method)();
|
||||
}
|
||||
}
|
||||
if (FLAGS_statistics) {
|
||||
fprintf(stdout, "STATISTICS:\n%s\n", dbstats->ToString().c_str());
|
||||
@@ -1780,6 +1959,16 @@ class Benchmark {
|
||||
shared.num_initialized = 0;
|
||||
shared.num_done = 0;
|
||||
shared.start = false;
|
||||
if (FLAGS_benchmark_write_rate_limit > 0) {
|
||||
shared.write_rate_limiter.reset(
|
||||
NewGenericRateLimiter(FLAGS_benchmark_write_rate_limit));
|
||||
}
|
||||
|
||||
std::unique_ptr<ReporterAgent> reporter_agent;
|
||||
if (FLAGS_report_interval_seconds > 0) {
|
||||
reporter_agent.reset(new ReporterAgent(FLAGS_env, FLAGS_report_file,
|
||||
FLAGS_report_interval_seconds));
|
||||
}
|
||||
|
||||
ThreadArg* arg = new ThreadArg[n];
|
||||
|
||||
@@ -1805,6 +1994,7 @@ class Benchmark {
|
||||
arg[i].method = method;
|
||||
arg[i].shared = &shared;
|
||||
arg[i].thread = new ThreadState(i);
|
||||
arg[i].thread->stats.SetReporterAgent(reporter_agent.get());
|
||||
arg[i].thread->shared = &shared;
|
||||
FLAGS_env->StartThread(ThreadBody, &arg[i]);
|
||||
}
|
||||
@@ -2027,6 +2217,8 @@ class Benchmark {
|
||||
options.max_write_buffer_number = FLAGS_max_write_buffer_number;
|
||||
options.min_write_buffer_number_to_merge =
|
||||
FLAGS_min_write_buffer_number_to_merge;
|
||||
options.max_write_buffer_number_to_maintain =
|
||||
FLAGS_max_write_buffer_number_to_maintain;
|
||||
options.max_background_compactions = FLAGS_max_background_compactions;
|
||||
options.max_background_flushes = FLAGS_max_background_flushes;
|
||||
options.compaction_style = FLAGS_compaction_style_e;
|
||||
@@ -2080,6 +2272,14 @@ class Benchmark {
|
||||
options.max_bytes_for_level_multiplier =
|
||||
FLAGS_max_bytes_for_level_multiplier;
|
||||
options.filter_deletes = FLAGS_filter_deletes;
|
||||
if (FLAGS_row_cache_size) {
|
||||
if (FLAGS_cache_numshardbits >= 1) {
|
||||
options.row_cache =
|
||||
NewLRUCache(FLAGS_row_cache_size, FLAGS_cache_numshardbits);
|
||||
} else {
|
||||
options.row_cache = NewLRUCache(FLAGS_row_cache_size);
|
||||
}
|
||||
}
|
||||
if ((FLAGS_prefix_size == 0) && (FLAGS_rep_factory == kPrefixHash ||
|
||||
FLAGS_rep_factory == kHashLinkedList)) {
|
||||
fprintf(stderr, "prefix_size should be non-zero if PrefixHash or "
|
||||
@@ -2214,6 +2414,7 @@ class Benchmark {
|
||||
}
|
||||
options.soft_rate_limit = FLAGS_soft_rate_limit;
|
||||
options.hard_rate_limit = FLAGS_hard_rate_limit;
|
||||
options.delayed_write_rate = FLAGS_delayed_write_rate;
|
||||
options.rate_limit_delay_max_milliseconds =
|
||||
FLAGS_rate_limit_delay_max_milliseconds;
|
||||
options.table_cache_numshardbits = FLAGS_table_cache_numshardbits;
|
||||
@@ -2271,6 +2472,11 @@ class Benchmark {
|
||||
NewGenericRateLimiter(FLAGS_rate_limiter_bytes_per_sec));
|
||||
}
|
||||
|
||||
if (FLAGS_readonly && FLAGS_transaction_db) {
|
||||
fprintf(stderr, "Cannot use readonly flag with transaction_db\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (FLAGS_num_multi_db <= 1) {
|
||||
OpenDb(options, FLAGS_db, &db_);
|
||||
} else {
|
||||
@@ -2305,15 +2511,25 @@ class Benchmark {
|
||||
if (FLAGS_readonly) {
|
||||
s = DB::OpenForReadOnly(options, db_name, column_families,
|
||||
&db->cfh, &db->db);
|
||||
} else if (FLAGS_transaction_db) {
|
||||
s = OptimisticTransactionDB::Open(options, db_name, column_families,
|
||||
&db->cfh, &db->txn_db);
|
||||
if (s.ok()) {
|
||||
db->db = db->txn_db->GetBaseDB();
|
||||
}
|
||||
} else {
|
||||
s = DB::Open(options, db_name, column_families, &db->cfh, &db->db);
|
||||
}
|
||||
db->cfh.resize(FLAGS_num_column_families);
|
||||
db->num_created = num_hot;
|
||||
db->num_hot = num_hot;
|
||||
|
||||
} else if (FLAGS_readonly) {
|
||||
s = DB::OpenForReadOnly(options, db_name, &db->db);
|
||||
} else if (FLAGS_transaction_db) {
|
||||
s = OptimisticTransactionDB::Open(options, db_name, &db->txn_db);
|
||||
if (s.ok()) {
|
||||
db->db = db->txn_db->GetBaseDB();
|
||||
}
|
||||
} else {
|
||||
s = DB::Open(options, db_name, &db->db);
|
||||
}
|
||||
@@ -2451,6 +2667,10 @@ class Benchmark {
|
||||
DBWithColumnFamilies* db_with_cfh = SelectDBWithCfh(id);
|
||||
batch.Clear();
|
||||
for (int64_t j = 0; j < entries_per_batch_; j++) {
|
||||
if (thread->shared->write_rate_limiter.get() != nullptr) {
|
||||
thread->shared->write_rate_limiter->Request(value_size_ + key_size_,
|
||||
Env::IO_HIGH);
|
||||
}
|
||||
int64_t rand_num = key_gens[id]->Next();
|
||||
GenerateKeyFromInt(rand_num, FLAGS_num, &key);
|
||||
if (FLAGS_num_column_families <= 1) {
|
||||
@@ -3354,9 +3574,206 @@ class Benchmark {
|
||||
}
|
||||
}
|
||||
|
||||
// This benchmark stress tests Transactions. For a given --duration (or
|
||||
// total number of --writes, a Transaction will perform a read-modify-write
|
||||
// to increment the value of a key in each of N(--transaction-sets) sets of
|
||||
// keys (where each set has --num keys). If --threads is set, this will be
|
||||
// done in parallel.
|
||||
//
|
||||
// To test transactions, use --transaction_db=true. Not setting this
|
||||
// parameter
|
||||
// will run the same benchmark without transactions.
|
||||
//
|
||||
// RandomTransactionVerify() will then validate the correctness of the results
|
||||
// by checking if the sum of all keys in each set is the same.
|
||||
void RandomTransaction(ThreadState* thread) {
|
||||
ReadOptions options(FLAGS_verify_checksum, true);
|
||||
Duration duration(FLAGS_duration, readwrites_);
|
||||
ReadOptions read_options(FLAGS_verify_checksum, true);
|
||||
std::string value;
|
||||
DB* db = db_.db;
|
||||
uint64_t transactions_done = 0;
|
||||
uint64_t transactions_aborted = 0;
|
||||
Status s;
|
||||
uint64_t num_prefix_ranges = FLAGS_transaction_sets;
|
||||
bool use_txn = FLAGS_transaction_db;
|
||||
|
||||
if (num_prefix_ranges == 0 || num_prefix_ranges > 9999) {
|
||||
fprintf(stderr, "invalid value for transaction_sets\n");
|
||||
abort();
|
||||
}
|
||||
|
||||
if (FLAGS_num_multi_db > 1) {
|
||||
fprintf(stderr,
|
||||
"Cannot run RandomTransaction benchmark with "
|
||||
"FLAGS_multi_db > 1.");
|
||||
abort();
|
||||
}
|
||||
|
||||
while (!duration.Done(1)) {
|
||||
OptimisticTransaction* txn = nullptr;
|
||||
WriteBatch* batch = nullptr;
|
||||
|
||||
if (use_txn) {
|
||||
txn = db_.txn_db->BeginTransaction(write_options_);
|
||||
assert(txn);
|
||||
} else {
|
||||
batch = new WriteBatch();
|
||||
}
|
||||
|
||||
// pick a random number to use to increment a key in each set
|
||||
uint64_t incr = (thread->rand.Next() % 100) + 1;
|
||||
|
||||
// For each set, pick a key at random and increment it
|
||||
for (uint8_t i = 0; i < num_prefix_ranges; i++) {
|
||||
uint64_t int_value;
|
||||
char prefix_buf[5];
|
||||
|
||||
// key format: [SET#][random#]
|
||||
std::string rand_key = ToString(thread->rand.Next() % FLAGS_num);
|
||||
Slice base_key(rand_key);
|
||||
|
||||
// Pad prefix appropriately so we can iterate over each set
|
||||
snprintf(prefix_buf, sizeof(prefix_buf), "%04d", i + 1);
|
||||
std::string full_key = std::string(prefix_buf) + base_key.ToString();
|
||||
Slice key(full_key);
|
||||
|
||||
if (use_txn) {
|
||||
s = txn->Get(read_options, key, &value);
|
||||
} else {
|
||||
s = db->Get(read_options, key, &value);
|
||||
}
|
||||
|
||||
if (s.ok()) {
|
||||
int_value = std::stoull(value);
|
||||
|
||||
if (int_value == 0 || int_value == ULONG_MAX) {
|
||||
fprintf(stderr, "Get returned unexpected value: %s\n",
|
||||
value.c_str());
|
||||
abort();
|
||||
}
|
||||
} else if (s.IsNotFound()) {
|
||||
int_value = 0;
|
||||
} else {
|
||||
fprintf(stderr, "Get returned an error: %s\n", s.ToString().c_str());
|
||||
abort();
|
||||
}
|
||||
|
||||
if (FLAGS_transaction_sleep > 0) {
|
||||
FLAGS_env->SleepForMicroseconds(thread->rand.Next() %
|
||||
FLAGS_transaction_sleep);
|
||||
}
|
||||
|
||||
std::string sum = ToString(int_value + incr);
|
||||
if (use_txn) {
|
||||
txn->Put(key, sum);
|
||||
} else {
|
||||
batch->Put(key, sum);
|
||||
}
|
||||
}
|
||||
|
||||
if (use_txn) {
|
||||
s = txn->Commit();
|
||||
} else {
|
||||
s = db->Write(write_options_, batch);
|
||||
}
|
||||
|
||||
if (!s.ok()) {
|
||||
// Ideally, we'd want to run this stress test with enough concurrency
|
||||
// on a small enough set of keys that we get some failed transactions
|
||||
// due to conflicts.
|
||||
if (use_txn && s.IsBusy()) {
|
||||
transactions_aborted++;
|
||||
} else {
|
||||
fprintf(stderr, "Unexpected write error: %s\n", s.ToString().c_str());
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
if (txn) {
|
||||
delete txn;
|
||||
}
|
||||
if (batch) {
|
||||
delete batch;
|
||||
}
|
||||
|
||||
transactions_done++;
|
||||
}
|
||||
|
||||
char msg[100];
|
||||
if (use_txn) {
|
||||
snprintf(msg, sizeof(msg),
|
||||
"( transactions:%" PRIu64 " aborts:%" PRIu64 ")",
|
||||
transactions_done, transactions_aborted);
|
||||
} else {
|
||||
snprintf(msg, sizeof(msg), "( batches:%" PRIu64 " )", transactions_done);
|
||||
}
|
||||
thread->stats.AddMessage(msg);
|
||||
|
||||
if (FLAGS_perf_level > 0) {
|
||||
thread->stats.AddMessage(perf_context.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
// Verifies consistency of data after RandomTransaction() has been run.
|
||||
// Since each iteration of RandomTransaction() incremented a key in each set
|
||||
// by the same value, the sum of the keys in each set should be the same.
|
||||
void RandomTransactionVerify() {
|
||||
if (!FLAGS_transaction_db) {
|
||||
// transactions not used, nothing to verify.
|
||||
return;
|
||||
}
|
||||
|
||||
uint64_t prev_total = 0;
|
||||
|
||||
// For each set of keys with the same prefix, sum all the values
|
||||
for (uint32_t i = 0; i < FLAGS_transaction_sets; i++) {
|
||||
char prefix_buf[5];
|
||||
snprintf(prefix_buf, sizeof(prefix_buf), "%04u", i + 1);
|
||||
uint64_t total = 0;
|
||||
|
||||
Iterator* iter = db_.db->NewIterator(ReadOptions());
|
||||
|
||||
for (iter->Seek(Slice(prefix_buf, 4)); iter->Valid(); iter->Next()) {
|
||||
Slice key = iter->key();
|
||||
|
||||
// stop when we reach a different prefix
|
||||
if (key.ToString().compare(0, 4, prefix_buf) != 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
Slice value = iter->value();
|
||||
uint64_t int_value = std::stoull(value.ToString());
|
||||
if (int_value == 0 || int_value == ULONG_MAX) {
|
||||
fprintf(stderr, "Iter returned unexpected value: %s\n",
|
||||
value.ToString().c_str());
|
||||
abort();
|
||||
}
|
||||
|
||||
total += int_value;
|
||||
}
|
||||
delete iter;
|
||||
|
||||
if (i > 0) {
|
||||
if (total != prev_total) {
|
||||
fprintf(stderr,
|
||||
"RandomTransactionVerify found inconsistent totals. "
|
||||
"Set[%" PRIu32 "]: %" PRIu64 ", Set[%" PRIu32 "]: %" PRIu64
|
||||
" \n",
|
||||
i - 1, prev_total, i, total);
|
||||
abort();
|
||||
}
|
||||
}
|
||||
prev_total = total;
|
||||
}
|
||||
|
||||
fprintf(stdout, "RandomTransactionVerify Success! Total:%" PRIu64 "\n",
|
||||
prev_total);
|
||||
}
|
||||
|
||||
void Compact(ThreadState* thread) {
|
||||
DB* db = SelectDB(thread);
|
||||
db->CompactRange(nullptr, nullptr);
|
||||
db->CompactRange(CompactRangeOptions(), nullptr, nullptr);
|
||||
}
|
||||
|
||||
void PrintStats(const char* key) {
|
||||
@@ -3397,7 +3814,11 @@ int main(int argc, char** argv) {
|
||||
FLAGS_max_bytes_for_level_multiplier_additional, ',');
|
||||
for (unsigned int j= 0; j < fanout.size(); j++) {
|
||||
FLAGS_max_bytes_for_level_multiplier_additional_v.push_back(
|
||||
std::stoi(fanout[j]));
|
||||
#ifndef CYGWIN
|
||||
std::stoi(fanout[j]));
|
||||
#else
|
||||
stoi(fanout[j]));
|
||||
#endif
|
||||
}
|
||||
|
||||
FLAGS_compression_type_e =
|
||||
|
||||
@@ -98,6 +98,8 @@ Status DBImpl::GetLiveFiles(std::vector<std::string>& ret,
|
||||
cfd->Ref();
|
||||
mutex_.Unlock();
|
||||
status = FlushMemTable(cfd, FlushOptions());
|
||||
TEST_SYNC_POINT("DBImpl::GetLiveFiles:1");
|
||||
TEST_SYNC_POINT("DBImpl::GetLiveFiles:2");
|
||||
mutex_.Lock();
|
||||
cfd->Unref();
|
||||
if (!status.ok()) {
|
||||
|
||||
+642
-248
File diff suppressed because it is too large.
Load diff
+101
-44
@@ -22,6 +22,8 @@
|
||||
#include "db/log_writer.h"
|
||||
#include "db/snapshot.h"
|
||||
#include "db/column_family.h"
|
||||
#include "db/compaction_job.h"
|
||||
#include "db/flush_job.h"
|
||||
#include "db/version_edit.h"
|
||||
#include "db/wal_manager.h"
|
||||
#include "db/writebuffer.h"
|
||||
@@ -53,6 +55,7 @@ class VersionEdit;
|
||||
class VersionSet;
|
||||
class CompactionFilterV2;
|
||||
class Arena;
|
||||
class WriteCallback;
|
||||
struct JobContext;
|
||||
|
||||
class DBImpl : public DB {
|
||||
@@ -76,6 +79,7 @@ class DBImpl : public DB {
|
||||
using DB::Write;
|
||||
virtual Status Write(const WriteOptions& options,
|
||||
WriteBatch* updates) override;
|
||||
|
||||
using DB::Get;
|
||||
virtual Status Get(const ReadOptions& options,
|
||||
ColumnFamilyHandle* column_family, const Slice& key,
|
||||
@@ -118,13 +122,12 @@ class DBImpl : public DB {
|
||||
const Slice& property, uint64_t* value) override;
|
||||
using DB::GetApproximateSizes;
|
||||
virtual void GetApproximateSizes(ColumnFamilyHandle* column_family,
|
||||
const Range* range, int n,
|
||||
uint64_t* sizes) override;
|
||||
const Range* range, int n, uint64_t* sizes,
|
||||
bool include_memtable = false) override;
|
||||
using DB::CompactRange;
|
||||
virtual Status CompactRange(ColumnFamilyHandle* column_family,
|
||||
const Slice* begin, const Slice* end,
|
||||
bool reduce_level = false, int target_level = -1,
|
||||
uint32_t target_path_id = 0) override;
|
||||
virtual Status CompactRange(const CompactRangeOptions& options,
|
||||
ColumnFamilyHandle* column_family,
|
||||
const Slice* begin, const Slice* end) override;
|
||||
|
||||
using DB::CompactFiles;
|
||||
virtual Status CompactFiles(const CompactionOptions& compact_options,
|
||||
@@ -191,17 +194,46 @@ class DBImpl : public DB {
|
||||
|
||||
Status PromoteL0(ColumnFamilyHandle* column_family, int target_level);
|
||||
|
||||
// Similar to Write() but will call the callback once on the single write
|
||||
// thread to determine whether it is safe to perform the write.
|
||||
virtual Status WriteWithCallback(const WriteOptions& write_options,
|
||||
WriteBatch* my_batch,
|
||||
WriteCallback* callback);
|
||||
|
||||
// Returns the sequence number that is guaranteed to be smaller than or equal
|
||||
// to the sequence number of any key that could be inserted into the current
|
||||
// memtables. It can then be assumed that any write with a larger(or equal)
|
||||
// sequence number will be present in this memtable or a later memtable.
|
||||
//
|
||||
// If the earliest sequence number could not be determined,
|
||||
// kMaxSequenceNumber will be returned.
|
||||
//
|
||||
// If include_history=true, will also search Memtables in MemTableList
|
||||
// History.
|
||||
SequenceNumber GetEarliestMemTableSequenceNumber(SuperVersion* sv,
|
||||
bool include_history);
|
||||
|
||||
// For a given key, check to see if there are any records for this key
|
||||
// in the memtables, including memtable history.
|
||||
|
||||
// On success, *seq will contain the sequence number for the
|
||||
// latest such change or kMaxSequenceNumber if no records were present.
|
||||
// Returns OK on success, other status on error reading memtables.
|
||||
Status GetLatestSequenceForKeyFromMemtable(SuperVersion* sv, const Slice& key,
|
||||
SequenceNumber* seq);
|
||||
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
// checks if all live files exist on file system and that their file sizes
|
||||
// match to our in-memory records
|
||||
virtual Status CheckConsistency();
|
||||
|
||||
virtual Status GetDbIdentity(std::string& identity) override;
|
||||
virtual Status GetDbIdentity(std::string& identity) const override;
|
||||
|
||||
Status RunManualCompaction(ColumnFamilyData* cfd, int input_level,
|
||||
int output_level, uint32_t output_path_id,
|
||||
const Slice* begin, const Slice* end);
|
||||
const Slice* begin, const Slice* end,
|
||||
bool disallow_trivial_move = false);
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
// Extra methods (for testing) that are not in the public DB interface
|
||||
@@ -209,7 +241,8 @@ class DBImpl : public DB {
|
||||
|
||||
// Compact any files in the named level that overlap [*begin, *end]
|
||||
Status TEST_CompactRange(int level, const Slice* begin, const Slice* end,
|
||||
ColumnFamilyHandle* column_family = nullptr);
|
||||
ColumnFamilyHandle* column_family = nullptr,
|
||||
bool disallow_trivial_move = false);
|
||||
|
||||
// Force current memtable contents to be flushed.
|
||||
Status TEST_FlushMemTable(bool wait = true);
|
||||
@@ -257,6 +290,8 @@ class DBImpl : public DB {
|
||||
|
||||
size_t TEST_LogsToFreeSize();
|
||||
|
||||
uint64_t TEST_LogfileNumber();
|
||||
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
// Returns the list of live files in 'live' and the list
|
||||
@@ -279,6 +314,32 @@ class DBImpl : public DB {
|
||||
|
||||
void CancelAllBackgroundWork(bool wait);
|
||||
|
||||
// Find Super version and reference it. Based on options, it might return
|
||||
// the thread local cached one.
|
||||
// Call ReturnAndCleanupSuperVersion() when it is no longer needed.
|
||||
SuperVersion* GetAndRefSuperVersion(ColumnFamilyData* cfd);
|
||||
|
||||
// Similar to the previous function but looks up based on a column family id.
|
||||
// nullptr will be returned if this column family no longer exists.
|
||||
// REQUIRED: this function should only be called on the write thread or if the
|
||||
// mutex is held.
|
||||
SuperVersion* GetAndRefSuperVersion(uint32_t column_family_id);
|
||||
|
||||
// Un-reference the super version and return it to thread local cache if
|
||||
// needed. If it is the last reference of the super version. Clean it up
|
||||
// after un-referencing it.
|
||||
void ReturnAndCleanupSuperVersion(ColumnFamilyData* cfd, SuperVersion* sv);
|
||||
|
||||
// Similar to the previous function but looks up based on a column family id.
|
||||
// nullptr will be returned if this column family no longer exists.
|
||||
// REQUIRED: this function should only be called on the write thread.
|
||||
void ReturnAndCleanupSuperVersion(uint32_t colun_family_id, SuperVersion* sv);
|
||||
|
||||
// REQUIRED: this function should only be called on the write thread or if the
|
||||
// mutex is held. Return value only valid until next call to this function or
|
||||
// mutex is released.
|
||||
ColumnFamilyHandle* GetColumnFamilyHandle(uint32_t column_family_id);
|
||||
|
||||
protected:
|
||||
Env* const env_;
|
||||
const std::string dbname_;
|
||||
@@ -289,11 +350,14 @@ class DBImpl : public DB {
|
||||
Iterator* NewInternalIterator(const ReadOptions&, ColumnFamilyData* cfd,
|
||||
SuperVersion* super_version, Arena* arena);
|
||||
|
||||
void NotifyOnFlushCompleted(ColumnFamilyData* cfd, uint64_t file_number,
|
||||
const MutableCFOptions& mutable_cf_options);
|
||||
void NotifyOnFlushCompleted(ColumnFamilyData* cfd, FileMetaData* file_meta,
|
||||
const MutableCFOptions& mutable_cf_options,
|
||||
int job_id);
|
||||
|
||||
void NotifyOnCompactionCompleted(ColumnFamilyData* cfd,
|
||||
Compaction *c, const Status &st);
|
||||
Compaction *c, const Status &st,
|
||||
const CompactionJobStats& job_stats,
|
||||
int job_id);
|
||||
|
||||
void NewThreadStatusCfInfo(ColumnFamilyData* cfd) const;
|
||||
|
||||
@@ -301,6 +365,9 @@ class DBImpl : public DB {
|
||||
|
||||
void EraseThreadStatusDbInfo() const;
|
||||
|
||||
Status WriteImpl(const WriteOptions& options, WriteBatch* updates,
|
||||
WriteCallback* callback);
|
||||
|
||||
private:
|
||||
friend class DB;
|
||||
friend class InternalStats;
|
||||
@@ -309,6 +376,9 @@ class DBImpl : public DB {
|
||||
#endif
|
||||
friend struct SuperVersion;
|
||||
friend class CompactedDBImpl;
|
||||
#ifndef NDEBUG
|
||||
friend class XFTransactionWriteHandler;
|
||||
#endif
|
||||
struct CompactionState;
|
||||
|
||||
struct WriteContext;
|
||||
@@ -364,12 +434,14 @@ class DBImpl : public DB {
|
||||
// concurrent flush memtables to storage.
|
||||
Status WriteLevel0TableForRecovery(int job_id, ColumnFamilyData* cfd,
|
||||
MemTable* mem, VersionEdit* edit);
|
||||
Status DelayWrite(uint64_t expiration_time);
|
||||
|
||||
// num_bytes: for slowdown case, delay time is calculated based on
|
||||
// `num_bytes` going through.
|
||||
Status DelayWrite(uint64_t num_bytes, uint64_t expiration_time);
|
||||
|
||||
Status ScheduleFlushes(WriteContext* context);
|
||||
|
||||
Status SetNewMemtableAndNewLogFile(ColumnFamilyData* cfd,
|
||||
WriteContext* context);
|
||||
Status SwitchMemtable(ColumnFamilyData* cfd, WriteContext* context);
|
||||
|
||||
// Force current memtable contents to be flushed.
|
||||
Status FlushMemTable(ColumnFamilyData* cfd, const FlushOptions& options);
|
||||
@@ -513,6 +585,11 @@ class DBImpl : public DB {
|
||||
WriteBatch tmp_batch_;
|
||||
|
||||
WriteController write_controller_;
|
||||
|
||||
// Size of the last batch group. In slowdown mode, next write needs to
|
||||
// sleep if it uses up the quota.
|
||||
uint64_t last_batch_group_size_;
|
||||
|
||||
FlushScheduler flush_scheduler_;
|
||||
|
||||
SnapshotList snapshots_;
|
||||
@@ -574,10 +651,11 @@ class DBImpl : public DB {
|
||||
uint32_t output_path_id;
|
||||
bool done;
|
||||
Status status;
|
||||
bool in_progress; // compaction request being processed?
|
||||
const InternalKey* begin; // nullptr means beginning of key range
|
||||
const InternalKey* end; // nullptr means end of key range
|
||||
InternalKey tmp_storage; // Used to keep track of compaction progress
|
||||
bool in_progress; // compaction request being processed?
|
||||
const InternalKey* begin; // nullptr means beginning of key range
|
||||
const InternalKey* end; // nullptr means end of key range
|
||||
InternalKey tmp_storage; // Used to keep track of compaction progress
|
||||
bool disallow_trivial_move; // Force actual compaction to run
|
||||
};
|
||||
ManualCompaction* manual_compaction_;
|
||||
|
||||
@@ -626,12 +704,6 @@ class DBImpl : public DB {
|
||||
// Indicate DB was opened successfully
|
||||
bool opened_successfully_;
|
||||
|
||||
// The list of registered event listeners.
|
||||
std::list<EventListener*> listeners_;
|
||||
|
||||
// count how many events are currently being notified.
|
||||
int notifying_events_;
|
||||
|
||||
// No copying allowed
|
||||
DBImpl(const DBImpl&);
|
||||
void operator=(const DBImpl&);
|
||||
@@ -647,31 +719,16 @@ class DBImpl : public DB {
|
||||
// the InstallSuperVersion() function. Background threads carry
|
||||
// job_context which can have new_superversion already
|
||||
// allocated.
|
||||
void InstallSuperVersionBackground(
|
||||
void InstallSuperVersionAndScheduleWorkWrapper(
|
||||
ColumnFamilyData* cfd, JobContext* job_context,
|
||||
const MutableCFOptions& mutable_cf_options);
|
||||
|
||||
// All ColumnFamily state changes go through this function. Here we analyze
|
||||
// the new state and we schedule background work if we detect that the new
|
||||
// state needs flush or compaction.
|
||||
// If dont_schedule_bg_work == true, then caller asks us to not schedule flush
|
||||
// or compaction here, but it also promises to schedule needed background
|
||||
// work. We use this to scheduling background compactions when we are in the
|
||||
// write thread, which is very performance critical. Caller schedules
|
||||
// background work as soon as it exits the write thread
|
||||
SuperVersion* InstallSuperVersion(ColumnFamilyData* cfd, SuperVersion* new_sv,
|
||||
const MutableCFOptions& mutable_cf_options,
|
||||
bool dont_schedule_bg_work = false);
|
||||
|
||||
// Find Super version and reference it. Based on options, it might return
|
||||
// the thread local cached one.
|
||||
inline SuperVersion* GetAndRefSuperVersion(ColumnFamilyData* cfd);
|
||||
|
||||
// Un-reference the super version and return it to thread local cache if
|
||||
// needed. If it is the last reference of the super version. Clean it up
|
||||
// after un-referencing it.
|
||||
inline void ReturnAndCleanupSuperVersion(ColumnFamilyData* cfd,
|
||||
SuperVersion* sv);
|
||||
SuperVersion* InstallSuperVersionAndScheduleWork(
|
||||
ColumnFamilyData* cfd, SuperVersion* new_sv,
|
||||
const MutableCFOptions& mutable_cf_options);
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
using DB::GetPropertiesOfAllTables;
|
||||
|
||||
+9
-2
@@ -73,7 +73,8 @@ uint64_t DBImpl::TEST_Current_Manifest_FileNo() {
|
||||
|
||||
Status DBImpl::TEST_CompactRange(int level, const Slice* begin,
|
||||
const Slice* end,
|
||||
ColumnFamilyHandle* column_family) {
|
||||
ColumnFamilyHandle* column_family,
|
||||
bool disallow_trivial_move) {
|
||||
ColumnFamilyData* cfd;
|
||||
if (column_family == nullptr) {
|
||||
cfd = default_cf_handle_->cfd();
|
||||
@@ -86,7 +87,8 @@ Status DBImpl::TEST_CompactRange(int level, const Slice* begin,
|
||||
cfd->ioptions()->compaction_style == kCompactionStyleFIFO)
|
||||
? level
|
||||
: level + 1;
|
||||
return RunManualCompaction(cfd, level, output_level, 0, begin, end);
|
||||
return RunManualCompaction(cfd, level, output_level, 0, begin, end,
|
||||
disallow_trivial_move);
|
||||
}
|
||||
|
||||
Status DBImpl::TEST_FlushMemTable(bool wait) {
|
||||
@@ -146,5 +148,10 @@ size_t DBImpl::TEST_LogsToFreeSize() {
|
||||
return logs_to_free_.size();
|
||||
}
|
||||
|
||||
uint64_t DBImpl::TEST_LogfileNumber() {
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
return logfile_number_;
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
#endif // ROCKSDB_LITE
|
||||
@@ -130,14 +130,15 @@ Status DBImpl::PromoteL0(ColumnFamilyHandle* column_family, int target_level) {
|
||||
edit.DeleteFile(0, f->fd.GetNumber());
|
||||
edit.AddFile(target_level, f->fd.GetNumber(), f->fd.GetPathId(),
|
||||
f->fd.GetFileSize(), f->smallest, f->largest,
|
||||
f->smallest_seqno, f->largest_seqno);
|
||||
f->smallest_seqno, f->largest_seqno,
|
||||
f->marked_for_compaction);
|
||||
}
|
||||
|
||||
status = versions_->LogAndApply(cfd, *cfd->GetLatestMutableCFOptions(),
|
||||
&edit, &mutex_, directories_.GetDbDir());
|
||||
if (status.ok()) {
|
||||
InstallSuperVersionBackground(cfd, &job_context,
|
||||
*cfd->GetLatestMutableCFOptions());
|
||||
InstallSuperVersionAndScheduleWorkWrapper(
|
||||
cfd, &job_context, *cfd->GetLatestMutableCFOptions());
|
||||
}
|
||||
} // lock released here
|
||||
LogFlush(db_options_.info_log);
|
||||
|
||||
@@ -58,10 +58,9 @@ class DBImplReadOnly : public DBImpl {
|
||||
return Status::NotSupported("Not supported operation in read only mode.");
|
||||
}
|
||||
using DBImpl::CompactRange;
|
||||
virtual Status CompactRange(ColumnFamilyHandle* column_family,
|
||||
const Slice* begin, const Slice* end,
|
||||
bool reduce_level = false, int target_level = -1,
|
||||
uint32_t target_path_id = 0) override {
|
||||
virtual Status CompactRange(const CompactRangeOptions& options,
|
||||
ColumnFamilyHandle* column_family,
|
||||
const Slice* begin, const Slice* end) override {
|
||||
return Status::NotSupported("Not supported operation in read only mode.");
|
||||
}
|
||||
|
||||
|
||||
@@ -664,7 +664,28 @@ void DBIter::SeekToLast() {
|
||||
PERF_TIMER_GUARD(seek_internal_seek_time);
|
||||
iter_->SeekToLast();
|
||||
}
|
||||
// When the iterate_upper_bound is set to a value,
|
||||
// it will seek to the last key before the
|
||||
// ReadOptions.iterate_upper_bound
|
||||
if (iter_->Valid() && iterate_upper_bound_ != nullptr) {
|
||||
saved_key_.SetKey(*iterate_upper_bound_);
|
||||
std::string last_key;
|
||||
AppendInternalKey(&last_key,
|
||||
ParsedInternalKey(saved_key_.GetKey(), kMaxSequenceNumber,
|
||||
kValueTypeForSeek));
|
||||
|
||||
iter_->Seek(last_key);
|
||||
|
||||
if (!iter_->Valid()) {
|
||||
iter_->SeekToLast();
|
||||
} else {
|
||||
iter_->Prev();
|
||||
if (!iter_->Valid()) {
|
||||
valid_ = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
PrevInternal();
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include "db/dbformat.h"
|
||||
#include "rocksdb/comparator.h"
|
||||
#include "rocksdb/options.h"
|
||||
#include "rocksdb/perf_context.h"
|
||||
#include "rocksdb/slice.h"
|
||||
#include "rocksdb/statistics.h"
|
||||
#include "db/db_iter.h"
|
||||
@@ -184,6 +185,272 @@ TEST_F(DBIteratorTest, DBIteratorPrevNext) {
|
||||
db_iter->Next();
|
||||
ASSERT_TRUE(!db_iter->Valid());
|
||||
}
|
||||
// Test to check the SeekToLast() with iterate_upper_bound not set
|
||||
{
|
||||
TestIterator* internal_iter = new TestIterator(BytewiseComparator());
|
||||
internal_iter->AddPut("a", "val_a");
|
||||
internal_iter->AddPut("b", "val_b");
|
||||
internal_iter->AddPut("b", "val_b");
|
||||
internal_iter->AddPut("c", "val_c");
|
||||
internal_iter->Finish();
|
||||
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
env_, ImmutableCFOptions(options), BytewiseComparator(), internal_iter,
|
||||
10, options.max_sequential_skip_in_iterations));
|
||||
|
||||
db_iter->SeekToLast();
|
||||
ASSERT_TRUE(db_iter->Valid());
|
||||
ASSERT_EQ(db_iter->key().ToString(), "c");
|
||||
}
|
||||
|
||||
// Test to check the SeekToLast() with iterate_upper_bound set
|
||||
{
|
||||
TestIterator* internal_iter = new TestIterator(BytewiseComparator());
|
||||
|
||||
internal_iter->AddPut("a", "val_a");
|
||||
internal_iter->AddPut("b", "val_b");
|
||||
internal_iter->AddPut("c", "val_c");
|
||||
internal_iter->AddPut("d", "val_d");
|
||||
internal_iter->AddPut("e", "val_e");
|
||||
internal_iter->AddPut("f", "val_f");
|
||||
internal_iter->Finish();
|
||||
|
||||
Slice prefix("d");
|
||||
|
||||
ReadOptions ro;
|
||||
ro.iterate_upper_bound = &prefix;
|
||||
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
env_, ImmutableCFOptions(options), BytewiseComparator(), internal_iter,
|
||||
10, options.max_sequential_skip_in_iterations, ro.iterate_upper_bound));
|
||||
|
||||
db_iter->SeekToLast();
|
||||
ASSERT_TRUE(db_iter->Valid());
|
||||
ASSERT_EQ(db_iter->key().ToString(), "c");
|
||||
|
||||
db_iter->Next();
|
||||
ASSERT_TRUE(!db_iter->Valid());
|
||||
|
||||
db_iter->SeekToLast();
|
||||
ASSERT_TRUE(db_iter->Valid());
|
||||
ASSERT_EQ(db_iter->key().ToString(), "c");
|
||||
}
|
||||
// Test to check the SeekToLast() iterate_upper_bound set to a key that
|
||||
// is not Put yet
|
||||
{
|
||||
TestIterator* internal_iter = new TestIterator(BytewiseComparator());
|
||||
|
||||
internal_iter->AddPut("a", "val_a");
|
||||
internal_iter->AddPut("a", "val_a");
|
||||
internal_iter->AddPut("b", "val_b");
|
||||
internal_iter->AddPut("c", "val_c");
|
||||
internal_iter->AddPut("d", "val_d");
|
||||
internal_iter->Finish();
|
||||
|
||||
Slice prefix("z");
|
||||
|
||||
ReadOptions ro;
|
||||
ro.iterate_upper_bound = &prefix;
|
||||
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
env_, ImmutableCFOptions(options), BytewiseComparator(), internal_iter,
|
||||
10, options.max_sequential_skip_in_iterations, ro.iterate_upper_bound));
|
||||
|
||||
db_iter->SeekToLast();
|
||||
ASSERT_TRUE(db_iter->Valid());
|
||||
ASSERT_EQ(db_iter->key().ToString(), "d");
|
||||
|
||||
db_iter->Next();
|
||||
ASSERT_TRUE(!db_iter->Valid());
|
||||
|
||||
db_iter->SeekToLast();
|
||||
ASSERT_TRUE(db_iter->Valid());
|
||||
ASSERT_EQ(db_iter->key().ToString(), "d");
|
||||
|
||||
db_iter->Prev();
|
||||
ASSERT_TRUE(db_iter->Valid());
|
||||
ASSERT_EQ(db_iter->key().ToString(), "c");
|
||||
}
|
||||
// Test to check the SeekToLast() with iterate_upper_bound set to the
|
||||
// first key
|
||||
{
|
||||
TestIterator* internal_iter = new TestIterator(BytewiseComparator());
|
||||
internal_iter->AddPut("a", "val_a");
|
||||
internal_iter->AddPut("a", "val_a");
|
||||
internal_iter->AddPut("a", "val_a");
|
||||
internal_iter->AddPut("b", "val_b");
|
||||
internal_iter->AddPut("b", "val_b");
|
||||
internal_iter->Finish();
|
||||
|
||||
Slice prefix("a");
|
||||
|
||||
ReadOptions ro;
|
||||
ro.iterate_upper_bound = &prefix;
|
||||
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
env_, ImmutableCFOptions(options), BytewiseComparator(), internal_iter,
|
||||
10, options.max_sequential_skip_in_iterations, ro.iterate_upper_bound));
|
||||
|
||||
db_iter->SeekToLast();
|
||||
ASSERT_TRUE(!db_iter->Valid());
|
||||
}
|
||||
// Test case to check SeekToLast with iterate_upper_bound set
|
||||
// (same key put may times - SeekToLast should start with the
|
||||
// maximum sequence id of the upper bound)
|
||||
|
||||
{
|
||||
TestIterator* internal_iter = new TestIterator(BytewiseComparator());
|
||||
internal_iter->AddPut("a", "val_a");
|
||||
internal_iter->AddPut("b", "val_b");
|
||||
internal_iter->AddPut("c", "val_c");
|
||||
internal_iter->AddPut("c", "val_c");
|
||||
internal_iter->AddPut("c", "val_c");
|
||||
internal_iter->AddPut("c", "val_c");
|
||||
internal_iter->AddPut("c", "val_c");
|
||||
internal_iter->AddPut("c", "val_c");
|
||||
internal_iter->AddPut("c", "val_c");
|
||||
internal_iter->Finish();
|
||||
|
||||
Slice prefix("c");
|
||||
|
||||
ReadOptions ro;
|
||||
ro.iterate_upper_bound = &prefix;
|
||||
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
env_, ImmutableCFOptions(options), BytewiseComparator(), internal_iter,
|
||||
7, options.max_sequential_skip_in_iterations, ro.iterate_upper_bound));
|
||||
|
||||
SetPerfLevel(kEnableCount);
|
||||
ASSERT_TRUE(GetPerfLevel() == kEnableCount);
|
||||
|
||||
perf_context.Reset();
|
||||
db_iter->SeekToLast();
|
||||
|
||||
ASSERT_TRUE(db_iter->Valid());
|
||||
ASSERT_EQ(static_cast<int>(perf_context.internal_key_skipped_count), 1);
|
||||
ASSERT_EQ(db_iter->key().ToString(), "b");
|
||||
|
||||
SetPerfLevel(kDisable);
|
||||
}
|
||||
// Test to check the SeekToLast() with the iterate_upper_bound set
|
||||
// (Checking the value of the key which has sequence ids greater than
|
||||
// and less that the iterator's sequence id)
|
||||
{
|
||||
TestIterator* internal_iter = new TestIterator(BytewiseComparator());
|
||||
|
||||
internal_iter->AddPut("a", "val_a1");
|
||||
internal_iter->AddPut("a", "val_a2");
|
||||
internal_iter->AddPut("b", "val_b1");
|
||||
internal_iter->AddPut("c", "val_c1");
|
||||
internal_iter->AddPut("c", "val_c2");
|
||||
internal_iter->AddPut("c", "val_c3");
|
||||
internal_iter->AddPut("b", "val_b2");
|
||||
internal_iter->AddPut("d", "val_d1");
|
||||
internal_iter->Finish();
|
||||
|
||||
Slice prefix("c");
|
||||
|
||||
ReadOptions ro;
|
||||
ro.iterate_upper_bound = &prefix;
|
||||
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
env_, ImmutableCFOptions(options), BytewiseComparator(), internal_iter,
|
||||
4, options.max_sequential_skip_in_iterations, ro.iterate_upper_bound));
|
||||
|
||||
db_iter->SeekToLast();
|
||||
ASSERT_TRUE(db_iter->Valid());
|
||||
ASSERT_EQ(db_iter->key().ToString(), "b");
|
||||
ASSERT_EQ(db_iter->value().ToString(), "val_b1");
|
||||
}
|
||||
|
||||
// Test to check the SeekToLast() with the iterate_upper_bound set to the
|
||||
// key that is deleted
|
||||
{
|
||||
TestIterator* internal_iter = new TestIterator(BytewiseComparator());
|
||||
internal_iter->AddPut("a", "val_a");
|
||||
internal_iter->AddDeletion("a");
|
||||
internal_iter->AddPut("b", "val_b");
|
||||
internal_iter->AddPut("c", "val_c");
|
||||
internal_iter->Finish();
|
||||
|
||||
Slice prefix("a");
|
||||
|
||||
ReadOptions ro;
|
||||
ro.iterate_upper_bound = &prefix;
|
||||
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
env_, ImmutableCFOptions(options), BytewiseComparator(), internal_iter,
|
||||
10, options.max_sequential_skip_in_iterations, ro.iterate_upper_bound));
|
||||
|
||||
db_iter->SeekToLast();
|
||||
ASSERT_TRUE(!db_iter->Valid());
|
||||
}
|
||||
// Test to check the SeekToLast() with the iterate_upper_bound set
|
||||
// (Deletion cases)
|
||||
{
|
||||
TestIterator* internal_iter = new TestIterator(BytewiseComparator());
|
||||
internal_iter->AddPut("a", "val_a");
|
||||
internal_iter->AddPut("b", "val_b");
|
||||
internal_iter->AddDeletion("b");
|
||||
internal_iter->AddPut("c", "val_c");
|
||||
internal_iter->Finish();
|
||||
|
||||
Slice prefix("c");
|
||||
|
||||
ReadOptions ro;
|
||||
ro.iterate_upper_bound = &prefix;
|
||||
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
env_, ImmutableCFOptions(options), BytewiseComparator(), internal_iter,
|
||||
10, options.max_sequential_skip_in_iterations, ro.iterate_upper_bound));
|
||||
|
||||
db_iter->SeekToLast();
|
||||
ASSERT_TRUE(db_iter->Valid());
|
||||
ASSERT_EQ(db_iter->key().ToString(), "a");
|
||||
|
||||
db_iter->Next();
|
||||
ASSERT_TRUE(!db_iter->Valid());
|
||||
|
||||
db_iter->SeekToLast();
|
||||
ASSERT_TRUE(db_iter->Valid());
|
||||
ASSERT_EQ(db_iter->key().ToString(), "a");
|
||||
}
|
||||
// Test to check the SeekToLast() with iterate_upper_bound set
|
||||
// (Deletion cases - Lot of internal keys after the upper_bound
|
||||
// is deleted)
|
||||
{
|
||||
TestIterator* internal_iter = new TestIterator(BytewiseComparator());
|
||||
internal_iter->AddPut("a", "val_a");
|
||||
internal_iter->AddPut("b", "val_b");
|
||||
internal_iter->AddDeletion("c");
|
||||
internal_iter->AddDeletion("d");
|
||||
internal_iter->AddDeletion("e");
|
||||
internal_iter->AddDeletion("f");
|
||||
internal_iter->AddDeletion("g");
|
||||
internal_iter->AddDeletion("h");
|
||||
internal_iter->Finish();
|
||||
|
||||
Slice prefix("c");
|
||||
|
||||
ReadOptions ro;
|
||||
ro.iterate_upper_bound = &prefix;
|
||||
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
env_, ImmutableCFOptions(options), BytewiseComparator(), internal_iter,
|
||||
7, options.max_sequential_skip_in_iterations, ro.iterate_upper_bound));
|
||||
|
||||
SetPerfLevel(kEnableCount);
|
||||
ASSERT_TRUE(GetPerfLevel() == kEnableCount);
|
||||
|
||||
perf_context.Reset();
|
||||
db_iter->SeekToLast();
|
||||
|
||||
ASSERT_TRUE(db_iter->Valid());
|
||||
ASSERT_EQ(static_cast<int>(perf_context.internal_delete_skipped_count), 0);
|
||||
ASSERT_EQ(db_iter->key().ToString(), "b");
|
||||
|
||||
SetPerfLevel(kDisable);
|
||||
}
|
||||
|
||||
{
|
||||
TestIterator* internal_iter = new TestIterator(BytewiseComparator());
|
||||
@@ -1401,6 +1668,85 @@ TEST_F(DBIteratorTest, DBIterator8) {
|
||||
ASSERT_EQ(db_iter->value().ToString(), "0");
|
||||
}
|
||||
|
||||
// TODO(3.13): fix the issue of Seek() then Prev() which might not necessary
|
||||
// return the biggest element smaller than the seek key.
|
||||
TEST_F(DBIteratorTest, DISABLED_DBIterator9) {
|
||||
Options options;
|
||||
options.merge_operator = MergeOperators::CreateFromStringId("stringappend");
|
||||
{
|
||||
TestIterator* internal_iter = new TestIterator(BytewiseComparator());
|
||||
internal_iter->AddMerge("a", "merge_1");
|
||||
internal_iter->AddMerge("a", "merge_2");
|
||||
internal_iter->AddMerge("b", "merge_3");
|
||||
internal_iter->AddMerge("b", "merge_4");
|
||||
internal_iter->AddMerge("d", "merge_5");
|
||||
internal_iter->AddMerge("d", "merge_6");
|
||||
internal_iter->Finish();
|
||||
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
env_, ImmutableCFOptions(options), BytewiseComparator(), internal_iter,
|
||||
10, options.max_sequential_skip_in_iterations));
|
||||
|
||||
db_iter->SeekToLast();
|
||||
ASSERT_TRUE(db_iter->Valid());
|
||||
db_iter->Prev();
|
||||
ASSERT_TRUE(db_iter->Valid());
|
||||
ASSERT_EQ(db_iter->key().ToString(), "b");
|
||||
ASSERT_EQ(db_iter->value().ToString(), "merge_3,merge_4");
|
||||
db_iter->Next();
|
||||
ASSERT_TRUE(db_iter->Valid());
|
||||
ASSERT_EQ(db_iter->key().ToString(), "d");
|
||||
ASSERT_EQ(db_iter->value().ToString(), "merge_5,merge_6");
|
||||
|
||||
db_iter->Seek("b");
|
||||
ASSERT_TRUE(db_iter->Valid());
|
||||
ASSERT_EQ(db_iter->key().ToString(), "b");
|
||||
ASSERT_EQ(db_iter->value().ToString(), "merge_3,merge_4");
|
||||
db_iter->Prev();
|
||||
ASSERT_TRUE(db_iter->Valid());
|
||||
ASSERT_EQ(db_iter->key().ToString(), "a");
|
||||
ASSERT_EQ(db_iter->value().ToString(), "merge_1,merge_2");
|
||||
|
||||
db_iter->Seek("c");
|
||||
ASSERT_TRUE(db_iter->Valid());
|
||||
ASSERT_EQ(db_iter->key().ToString(), "d");
|
||||
ASSERT_EQ(db_iter->value().ToString(), "merge_5,merge_6");
|
||||
db_iter->Prev();
|
||||
ASSERT_TRUE(db_iter->Valid());
|
||||
ASSERT_EQ(db_iter->key().ToString(), "b");
|
||||
ASSERT_EQ(db_iter->value().ToString(), "merge_3,merge_4");
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(3.13): fix the issue of Seek() then Prev() which might not necessary
|
||||
// return the biggest element smaller than the seek key.
|
||||
TEST_F(DBIteratorTest, DISABLED_DBIterator10) {
|
||||
Options options;
|
||||
|
||||
TestIterator* internal_iter = new TestIterator(BytewiseComparator());
|
||||
internal_iter->AddPut("a", "1");
|
||||
internal_iter->AddPut("b", "2");
|
||||
internal_iter->AddPut("c", "3");
|
||||
internal_iter->AddPut("d", "4");
|
||||
internal_iter->Finish();
|
||||
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
env_, ImmutableCFOptions(options), BytewiseComparator(), internal_iter,
|
||||
10, options.max_sequential_skip_in_iterations));
|
||||
|
||||
db_iter->Seek("c");
|
||||
ASSERT_TRUE(db_iter->Valid());
|
||||
db_iter->Prev();
|
||||
ASSERT_TRUE(db_iter->Valid());
|
||||
ASSERT_EQ(db_iter->key().ToString(), "b");
|
||||
ASSERT_EQ(db_iter->value().ToString(), "2");
|
||||
|
||||
db_iter->Next();
|
||||
ASSERT_TRUE(db_iter->Valid());
|
||||
ASSERT_EQ(db_iter->key().ToString(), "c");
|
||||
ASSERT_EQ(db_iter->value().ToString(), "3");
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
+1452
-226
File diff suppressed because it is too large.
Load diff
+15
-3
@@ -8,6 +8,11 @@
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
#include "db/dbformat.h"
|
||||
|
||||
#ifndef __STDC_FORMAT_MACROS
|
||||
#define __STDC_FORMAT_MACROS
|
||||
#endif
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <stdio.h>
|
||||
#include "port/port.h"
|
||||
#include "util/coding.h"
|
||||
@@ -21,6 +26,14 @@ uint64_t PackSequenceAndType(uint64_t seq, ValueType t) {
|
||||
return (seq << 8) | t;
|
||||
}
|
||||
|
||||
void UnPackSequenceAndType(uint64_t packed, uint64_t* seq, ValueType* t) {
|
||||
*seq = packed >> 8;
|
||||
*t = static_cast<ValueType>(packed & 0xff);
|
||||
|
||||
assert(*seq <= kMaxSequenceNumber);
|
||||
assert(*t <= kValueTypeForSeek);
|
||||
}
|
||||
|
||||
void AppendInternalKey(std::string* result, const ParsedInternalKey& key) {
|
||||
result->append(key.user_key.data(), key.user_key.size());
|
||||
PutFixed64(result, PackSequenceAndType(key.sequence, key.type));
|
||||
@@ -28,9 +41,8 @@ void AppendInternalKey(std::string* result, const ParsedInternalKey& key) {
|
||||
|
||||
std::string ParsedInternalKey::DebugString(bool hex) const {
|
||||
char buf[50];
|
||||
snprintf(buf, sizeof(buf), "' @ %llu : %d",
|
||||
(unsigned long long) sequence,
|
||||
int(type));
|
||||
snprintf(buf, sizeof(buf), "' @ %" PRIu64 ": %d", sequence,
|
||||
static_cast<int>(type));
|
||||
std::string result = "'";
|
||||
result += user_key.ToString(hex);
|
||||
result += buf;
|
||||
|
||||
@@ -71,8 +71,13 @@ inline size_t InternalKeyEncodingLength(const ParsedInternalKey& key) {
|
||||
return key.user_key.size() + 8;
|
||||
}
|
||||
|
||||
// Pack a sequence number and a ValueType into a uint64_t
|
||||
extern uint64_t PackSequenceAndType(uint64_t seq, ValueType t);
|
||||
|
||||
// Given the result of PackSequenceAndType, store the sequence number in *seq
|
||||
// and the ValueType in *t.
|
||||
extern void UnPackSequenceAndType(uint64_t packed, uint64_t* seq, ValueType* t);
|
||||
|
||||
// Append the serialization of "key" to *result.
|
||||
extern void AppendInternalKey(std::string* result,
|
||||
const ParsedInternalKey& key);
|
||||
|
||||
@@ -201,8 +201,11 @@ TEST_F(DeleteFileTest, PurgeObsoleteFilesTest) {
|
||||
// 2 ssts, 1 manifest
|
||||
CheckFileTypeCounts(dbname_, 0, 2, 1);
|
||||
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);
|
||||
db_->CompactRange(&first_slice, &last_slice, true, 2);
|
||||
db_->CompactRange(compact_options, &first_slice, &last_slice);
|
||||
// 1 sst after compaction
|
||||
CheckFileTypeCounts(dbname_, 0, 1, 1);
|
||||
|
||||
@@ -211,7 +214,7 @@ TEST_F(DeleteFileTest, PurgeObsoleteFilesTest) {
|
||||
Iterator *itr = 0;
|
||||
CreateTwoLevels();
|
||||
itr = db_->NewIterator(ReadOptions());
|
||||
db_->CompactRange(&first_slice, &last_slice, true, 2);
|
||||
db_->CompactRange(compact_options, &first_slice, &last_slice);
|
||||
// 3 sst after compaction with live iterator
|
||||
CheckFileTypeCounts(dbname_, 0, 3, 1);
|
||||
delete itr;
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
// Copyright (c) 2013, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the root directory of this source tree. An additional grant
|
||||
// of patent rights can be found in the PATENTS file in the same directory.
|
||||
|
||||
#include "db/event_helpers.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
namespace {
|
||||
inline double SafeDivide(double a, double b) { return b == 0.0 ? 0 : a / b; }
|
||||
} // namespace
|
||||
|
||||
void EventHelpers::AppendCurrentTime(JSONWriter* jwriter) {
|
||||
*jwriter << "time_micros"
|
||||
<< std::chrono::duration_cast<std::chrono::microseconds>(
|
||||
std::chrono::system_clock::now().time_since_epoch()).count();
|
||||
}
|
||||
|
||||
void EventHelpers::LogAndNotifyTableFileCreation(
|
||||
EventLogger* event_logger,
|
||||
const std::vector<std::shared_ptr<EventListener>>& listeners,
|
||||
const FileDescriptor& fd, const TableFileCreationInfo& info) {
|
||||
assert(event_logger);
|
||||
JSONWriter jwriter;
|
||||
AppendCurrentTime(&jwriter);
|
||||
jwriter << "cf_name" << info.cf_name
|
||||
<< "job" << info.job_id
|
||||
<< "event" << "table_file_creation"
|
||||
<< "file_number" << fd.GetNumber()
|
||||
<< "file_size" << fd.GetFileSize();
|
||||
|
||||
// table_properties
|
||||
{
|
||||
jwriter << "table_properties";
|
||||
jwriter.StartObject();
|
||||
|
||||
// basic properties:
|
||||
jwriter << "data_size" << info.table_properties.data_size
|
||||
<< "index_size" << info.table_properties.index_size
|
||||
<< "filter_size" << info.table_properties.filter_size
|
||||
<< "raw_key_size" << info.table_properties.raw_key_size
|
||||
<< "raw_average_key_size" << SafeDivide(
|
||||
info.table_properties.raw_key_size,
|
||||
info.table_properties.num_entries)
|
||||
<< "raw_value_size" << info.table_properties.raw_value_size
|
||||
<< "raw_average_value_size" << SafeDivide(
|
||||
info.table_properties.raw_value_size,
|
||||
info.table_properties.num_entries)
|
||||
<< "num_data_blocks" << info.table_properties.num_data_blocks
|
||||
<< "num_entries" << info.table_properties.num_entries
|
||||
<< "filter_policy_name" <<
|
||||
info.table_properties.filter_policy_name;
|
||||
|
||||
// user collected properties
|
||||
for (const auto& prop : info.table_properties.user_collected_properties) {
|
||||
jwriter << prop.first << prop.second;
|
||||
}
|
||||
jwriter.EndObject();
|
||||
}
|
||||
jwriter.EndObject();
|
||||
|
||||
event_logger->Log(jwriter);
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
if (listeners.size() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto listener : listeners) {
|
||||
listener->OnTableFileCreated(info);
|
||||
}
|
||||
#endif // !ROCKSDB_LITE
|
||||
}
|
||||
|
||||
void EventHelpers::LogAndNotifyTableFileDeletion(
|
||||
EventLogger* event_logger, int job_id,
|
||||
uint64_t file_number, const std::string& file_path,
|
||||
const Status& status, const std::string& dbname,
|
||||
const std::vector<std::shared_ptr<EventListener>>& listeners) {
|
||||
|
||||
JSONWriter jwriter;
|
||||
AppendCurrentTime(&jwriter);
|
||||
|
||||
jwriter << "job" << job_id
|
||||
<< "event" << "table_file_deletion"
|
||||
<< "file_number" << file_number;
|
||||
if (!status.ok()) {
|
||||
jwriter << "status" << status.ToString();
|
||||
}
|
||||
|
||||
jwriter.EndObject();
|
||||
|
||||
event_logger->Log(jwriter);
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
TableFileDeletionInfo info;
|
||||
info.db_name = dbname;
|
||||
info.job_id = job_id;
|
||||
info.file_path = file_path;
|
||||
info.status = status;
|
||||
for (auto listener : listeners) {
|
||||
listener->OnTableFileDeleted(info);
|
||||
}
|
||||
#endif // !ROCKSDB_LITE
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
@@ -0,0 +1,33 @@
|
||||
// Copyright (c) 2013, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the root directory of this source tree. An additional grant
|
||||
// of patent rights can be found in the PATENTS file in the same directory.
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "db/column_family.h"
|
||||
#include "db/version_edit.h"
|
||||
#include "rocksdb/listener.h"
|
||||
#include "rocksdb/table_properties.h"
|
||||
#include "util/event_logger.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
class EventHelpers {
|
||||
public:
|
||||
static void AppendCurrentTime(JSONWriter* json_writer);
|
||||
static void LogAndNotifyTableFileCreation(
|
||||
EventLogger* event_logger,
|
||||
const std::vector<std::shared_ptr<EventListener>>& listeners,
|
||||
const FileDescriptor& fd, const TableFileCreationInfo& info);
|
||||
static void LogAndNotifyTableFileDeletion(
|
||||
EventLogger* event_logger, int job_id,
|
||||
uint64_t file_number, const std::string& file_path,
|
||||
const Status& status, const std::string& db_name,
|
||||
const std::vector<std::shared_ptr<EventListener>>& listeners);
|
||||
};
|
||||
|
||||
} // namespace rocksdb
|
||||
@@ -1,46 +0,0 @@
|
||||
// Copyright (c) 2013, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the root directory of this source tree. An additional grant
|
||||
// of patent rights can be found in the PATENTS file in the same directory.
|
||||
|
||||
#include "db/event_logger_helpers.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
namespace {
|
||||
inline double SafeDivide(double a, double b) { return b == 0.0 ? 0 : a / b; }
|
||||
} // namespace
|
||||
|
||||
void EventLoggerHelpers::LogTableFileCreation(
|
||||
EventLogger* event_logger, int job_id, uint64_t file_number,
|
||||
uint64_t file_size, const TableProperties& table_properties) {
|
||||
auto stream = event_logger->Log();
|
||||
stream << "job" << job_id << "event"
|
||||
<< "table_file_creation"
|
||||
<< "file_number" << file_number << "file_size" << file_size
|
||||
<< "table_properties";
|
||||
stream.StartObject();
|
||||
|
||||
// basic properties:
|
||||
stream << "data_size" << table_properties.data_size
|
||||
<< "index_size" << table_properties.index_size
|
||||
<< "filter_size" << table_properties.filter_size
|
||||
<< "raw_key_size" << table_properties.raw_key_size
|
||||
<< "raw_average_key_size" << SafeDivide(table_properties.raw_key_size,
|
||||
table_properties.num_entries)
|
||||
<< "raw_value_size" << table_properties.raw_value_size
|
||||
<< "raw_average_value_size" << SafeDivide(
|
||||
table_properties.raw_value_size, table_properties.num_entries)
|
||||
<< "num_data_blocks" << table_properties.num_data_blocks
|
||||
<< "num_entries" << table_properties.num_entries
|
||||
<< "filter_policy_name" << table_properties.filter_policy_name;
|
||||
|
||||
// user collected properties
|
||||
for (const auto& prop : table_properties.user_collected_properties) {
|
||||
stream << prop.first << prop.second;
|
||||
}
|
||||
|
||||
stream.EndObject();
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
@@ -659,7 +659,7 @@ class FaultInjectionTest : public testing::Test {
|
||||
|
||||
Build(write_options, 0, num_pre_sync);
|
||||
if (sync_use_compact_) {
|
||||
db_->CompactRange(nullptr, nullptr);
|
||||
db_->CompactRange(CompactRangeOptions(), nullptr, nullptr);
|
||||
}
|
||||
write_options.sync = false;
|
||||
Build(write_options, num_pre_sync, num_post_sync);
|
||||
|
||||
+42
-32
@@ -20,7 +20,7 @@
|
||||
#include "db/builder.h"
|
||||
#include "db/db_iter.h"
|
||||
#include "db/dbformat.h"
|
||||
#include "db/event_logger_helpers.h"
|
||||
#include "db/event_helpers.h"
|
||||
#include "db/filename.h"
|
||||
#include "db/log_reader.h"
|
||||
#include "db/log_writer.h"
|
||||
@@ -116,11 +116,11 @@ void FlushJob::RecordFlushIOStats() {
|
||||
IOSTATS_RESET(bytes_written);
|
||||
}
|
||||
|
||||
Status FlushJob::Run(uint64_t* file_number) {
|
||||
Status FlushJob::Run(FileMetaData* file_meta) {
|
||||
AutoThreadOperationStageUpdater stage_run(
|
||||
ThreadStatus::STAGE_FLUSH_RUN);
|
||||
// Save the contents of the earliest memtable as a new Table
|
||||
uint64_t fn;
|
||||
FileMetaData meta;
|
||||
autovector<MemTable*> mems;
|
||||
cfd_->imm()->PickMemtablesToFlush(&mems);
|
||||
if (mems.empty()) {
|
||||
@@ -143,7 +143,7 @@ Status FlushJob::Run(uint64_t* file_number) {
|
||||
edit->SetColumnFamily(cfd_->GetID());
|
||||
|
||||
// This will release and re-acquire the mutex.
|
||||
Status s = WriteLevel0Table(mems, edit, &fn);
|
||||
Status s = WriteLevel0Table(mems, edit, &meta);
|
||||
|
||||
if (s.ok() &&
|
||||
(shutting_down_->load(std::memory_order_acquire) || cfd_->IsDropped())) {
|
||||
@@ -152,16 +152,17 @@ Status FlushJob::Run(uint64_t* file_number) {
|
||||
}
|
||||
|
||||
if (!s.ok()) {
|
||||
cfd_->imm()->RollbackMemtableFlush(mems, fn);
|
||||
cfd_->imm()->RollbackMemtableFlush(mems, meta.fd.GetNumber());
|
||||
} else {
|
||||
// Replace immutable memtable with the generated Table
|
||||
s = cfd_->imm()->InstallMemtableFlushResults(
|
||||
cfd_, mutable_cf_options_, mems, versions_, db_mutex_, fn,
|
||||
&job_context_->memtables_to_free, db_directory_, log_buffer_);
|
||||
cfd_, mutable_cf_options_, mems, versions_, db_mutex_,
|
||||
meta.fd.GetNumber(), &job_context_->memtables_to_free, db_directory_,
|
||||
log_buffer_);
|
||||
}
|
||||
|
||||
if (s.ok() && file_number != nullptr) {
|
||||
*file_number = fn;
|
||||
if (s.ok() && file_meta != nullptr) {
|
||||
*file_meta = meta;
|
||||
}
|
||||
RecordFlushIOStats();
|
||||
|
||||
@@ -180,15 +181,13 @@ Status FlushJob::Run(uint64_t* file_number) {
|
||||
}
|
||||
|
||||
Status FlushJob::WriteLevel0Table(const autovector<MemTable*>& mems,
|
||||
VersionEdit* edit, uint64_t* filenumber) {
|
||||
VersionEdit* edit, FileMetaData* meta) {
|
||||
AutoThreadOperationStageUpdater stage_updater(
|
||||
ThreadStatus::STAGE_FLUSH_WRITE_L0);
|
||||
db_mutex_->AssertHeld();
|
||||
const uint64_t start_micros = db_options_.env->NowMicros();
|
||||
FileMetaData meta;
|
||||
// path 0 for level 0 file.
|
||||
meta.fd = FileDescriptor(versions_->NewFileNumber(), 0, 0);
|
||||
*filenumber = meta.fd.GetNumber();
|
||||
meta->fd = FileDescriptor(versions_->NewFileNumber(), 0, 0);
|
||||
|
||||
const SequenceNumber earliest_seqno_in_memtable =
|
||||
mems[0]->GetFirstSequenceNumber();
|
||||
@@ -222,37 +221,47 @@ Status FlushJob::WriteLevel0Table(const autovector<MemTable*>& mems,
|
||||
<< total_num_entries << "num_deletes"
|
||||
<< total_num_deletes << "memory_usage"
|
||||
<< total_memory_usage;
|
||||
TableProperties table_properties;
|
||||
TableFileCreationInfo info;
|
||||
{
|
||||
ScopedArenaIterator iter(
|
||||
NewMergingIterator(&cfd_->internal_comparator(), &memtables[0],
|
||||
static_cast<int>(memtables.size()), &arena));
|
||||
Log(InfoLogLevel::INFO_LEVEL, db_options_.info_log,
|
||||
"[%s] [JOB %d] Level-0 flush table #%" PRIu64 ": started",
|
||||
cfd_->GetName().c_str(), job_context_->job_id, meta.fd.GetNumber());
|
||||
cfd_->GetName().c_str(), job_context_->job_id, meta->fd.GetNumber());
|
||||
|
||||
TEST_SYNC_POINT_CALLBACK("FlushJob::WriteLevel0Table:output_compression",
|
||||
&output_compression_);
|
||||
s = BuildTable(dbname_, db_options_.env, *cfd_->ioptions(), env_options_,
|
||||
cfd_->table_cache(), iter.get(), &meta,
|
||||
cfd_->table_cache(), iter.get(), meta,
|
||||
cfd_->internal_comparator(),
|
||||
cfd_->int_tbl_prop_collector_factories(), newest_snapshot_,
|
||||
earliest_seqno_in_memtable, output_compression_,
|
||||
cfd_->ioptions()->compression_opts,
|
||||
mutable_cf_options_.paranoid_file_checks, Env::IO_HIGH,
|
||||
&table_properties);
|
||||
&info.table_properties);
|
||||
LogFlush(db_options_.info_log);
|
||||
}
|
||||
Log(InfoLogLevel::INFO_LEVEL, db_options_.info_log,
|
||||
"[%s] [JOB %d] Level-0 flush table #%" PRIu64 ": %" PRIu64 " bytes %s",
|
||||
cfd_->GetName().c_str(), job_context_->job_id, meta.fd.GetNumber(),
|
||||
meta.fd.GetFileSize(), s.ToString().c_str());
|
||||
"[%s] [JOB %d] Level-0 flush table #%" PRIu64 ": %" PRIu64
|
||||
" bytes %s"
|
||||
"%s",
|
||||
cfd_->GetName().c_str(), job_context_->job_id, meta->fd.GetNumber(),
|
||||
meta->fd.GetFileSize(), s.ToString().c_str(),
|
||||
meta->marked_for_compaction ? " (needs compaction)" : "");
|
||||
|
||||
// output to event logger
|
||||
if (s.ok()) {
|
||||
EventLoggerHelpers::LogTableFileCreation(
|
||||
event_logger_, job_context_->job_id, meta.fd.GetNumber(),
|
||||
meta.fd.GetFileSize(), table_properties);
|
||||
info.db_name = dbname_;
|
||||
info.cf_name = cfd_->GetName();
|
||||
info.file_path = TableFileName(db_options_.db_paths,
|
||||
meta->fd.GetNumber(),
|
||||
meta->fd.GetPathId());
|
||||
info.file_size = meta->fd.GetFileSize();
|
||||
info.job_id = job_context_->job_id;
|
||||
EventHelpers::LogAndNotifyTableFileCreation(
|
||||
event_logger_, db_options_.listeners,
|
||||
meta->fd, info);
|
||||
}
|
||||
|
||||
if (!db_options_.disableDataSync && output_file_directory_ != nullptr) {
|
||||
@@ -268,9 +277,9 @@ Status FlushJob::WriteLevel0Table(const autovector<MemTable*>& mems,
|
||||
// Note that if file_size is zero, the file has been deleted and
|
||||
// should not be added to the manifest.
|
||||
int level = 0;
|
||||
if (s.ok() && meta.fd.GetFileSize() > 0) {
|
||||
const Slice min_user_key = meta.smallest.user_key();
|
||||
const Slice max_user_key = meta.largest.user_key();
|
||||
if (s.ok() && meta->fd.GetFileSize() > 0) {
|
||||
const Slice min_user_key = meta->smallest.user_key();
|
||||
const Slice max_user_key = meta->largest.user_key();
|
||||
// if we have more than 1 background thread, then we cannot
|
||||
// insert files directly into higher levels because some other
|
||||
// threads could be concurrently producing compacted files for
|
||||
@@ -287,18 +296,19 @@ Status FlushJob::WriteLevel0Table(const autovector<MemTable*>& mems,
|
||||
level = 0;
|
||||
}
|
||||
}
|
||||
edit->AddFile(level, meta.fd.GetNumber(), meta.fd.GetPathId(),
|
||||
meta.fd.GetFileSize(), meta.smallest, meta.largest,
|
||||
meta.smallest_seqno, meta.largest_seqno);
|
||||
edit->AddFile(level, meta->fd.GetNumber(), meta->fd.GetPathId(),
|
||||
meta->fd.GetFileSize(), meta->smallest, meta->largest,
|
||||
meta->smallest_seqno, meta->largest_seqno,
|
||||
meta->marked_for_compaction);
|
||||
}
|
||||
|
||||
InternalStats::CompactionStats stats(1);
|
||||
stats.micros = db_options_.env->NowMicros() - start_micros;
|
||||
stats.bytes_written = meta.fd.GetFileSize();
|
||||
stats.bytes_written = meta->fd.GetFileSize();
|
||||
cfd_->internal_stats()->AddCompactionStats(level, stats);
|
||||
cfd_->internal_stats()->AddCFStats(InternalStats::BYTES_FLUSHED,
|
||||
meta.fd.GetFileSize());
|
||||
RecordTick(stats_, COMPACT_WRITE_BYTES, meta.fd.GetFileSize());
|
||||
meta->fd.GetFileSize());
|
||||
RecordTick(stats_, COMPACT_WRITE_BYTES, meta->fd.GetFileSize());
|
||||
return s;
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -64,14 +64,14 @@ class FlushJob {
|
||||
|
||||
~FlushJob();
|
||||
|
||||
Status Run(uint64_t* file_number = nullptr);
|
||||
Status Run(FileMetaData* file_meta = nullptr);
|
||||
|
||||
private:
|
||||
void ReportStartedFlush();
|
||||
void ReportFlushInputSize(const autovector<MemTable*>& mems);
|
||||
void RecordFlushIOStats();
|
||||
Status WriteLevel0Table(const autovector<MemTable*>& mems, VersionEdit* edit,
|
||||
uint64_t* filenumber);
|
||||
FileMetaData* meta);
|
||||
const std::string& dbname_;
|
||||
ColumnFamilyData* cfd_;
|
||||
const DBOptions& db_options_;
|
||||
|
||||
@@ -97,7 +97,8 @@ TEST_F(FlushJobTest, Empty) {
|
||||
TEST_F(FlushJobTest, NonEmpty) {
|
||||
JobContext job_context(0);
|
||||
auto cfd = versions_->GetColumnFamilySet()->GetDefault();
|
||||
auto new_mem = cfd->ConstructNewMemtable(*cfd->GetLatestMutableCFOptions());
|
||||
auto new_mem = cfd->ConstructNewMemtable(*cfd->GetLatestMutableCFOptions(),
|
||||
kMaxSequenceNumber);
|
||||
new_mem->Ref();
|
||||
std::map<std::string, std::string> inserted_keys;
|
||||
for (int i = 1; i < 10000; ++i) {
|
||||
@@ -107,7 +108,12 @@ TEST_F(FlushJobTest, NonEmpty) {
|
||||
InternalKey internal_key(key, SequenceNumber(i), kTypeValue);
|
||||
inserted_keys.insert({internal_key.Encode().ToString(), value});
|
||||
}
|
||||
cfd->imm()->Add(new_mem);
|
||||
|
||||
autovector<MemTable*> to_delete;
|
||||
cfd->imm()->Add(new_mem, &to_delete);
|
||||
for (auto& m : to_delete) {
|
||||
delete m;
|
||||
}
|
||||
|
||||
EventLogger event_logger(db_options_.info_log.get());
|
||||
FlushJob flush_job(dbname_, versions_->GetColumnFamilySet()->GetDefault(),
|
||||
|
||||
@@ -169,6 +169,7 @@ void ForwardIterator::Cleanup(bool release_sv) {
|
||||
if (job_context.HaveSomethingToDelete()) {
|
||||
db_->PurgeObsoleteFiles(job_context);
|
||||
}
|
||||
job_context.Clean();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+22
-13
@@ -45,8 +45,10 @@ void PrintLevelStats(char* buf, size_t len, const std::string& name,
|
||||
int num_files, int being_compacted, double total_file_size, double score,
|
||||
double w_amp, uint64_t stalls,
|
||||
const InternalStats::CompactionStats& stats) {
|
||||
uint64_t bytes_read = stats.bytes_readn + stats.bytes_readnp1;
|
||||
int64_t bytes_new = stats.bytes_written - stats.bytes_readnp1;
|
||||
uint64_t bytes_read =
|
||||
stats.bytes_read_non_output_levels + stats.bytes_read_output_level;
|
||||
int64_t bytes_new =
|
||||
stats.bytes_written - stats.bytes_read_output_level;
|
||||
double elapsed = (stats.micros + 1) / 1000000.0;
|
||||
std::string num_input_records = NumberToHumanString(stats.num_input_records);
|
||||
std::string num_dropped_records =
|
||||
@@ -71,8 +73,8 @@ void PrintLevelStats(char* buf, size_t len, const std::string& name,
|
||||
"%7s " /* KeyIn */
|
||||
"%6s\n", /* KeyDrop */
|
||||
name.c_str(), num_files, being_compacted, total_file_size / kMB,
|
||||
score, bytes_read / kGB, stats.bytes_readn / kGB,
|
||||
stats.bytes_readnp1 / kGB, stats.bytes_written / kGB,
|
||||
score, bytes_read / kGB, stats.bytes_read_non_output_levels / kGB,
|
||||
stats.bytes_read_output_level / kGB, stats.bytes_written / kGB,
|
||||
bytes_new / kGB, stats.bytes_moved / kGB,
|
||||
w_amp, bytes_read / kMB / elapsed,
|
||||
stats.bytes_written / kMB / elapsed, stats.micros / 1000000.0,
|
||||
@@ -92,6 +94,8 @@ static const std::string cfstats = "cfstats";
|
||||
static const std::string dbstats = "dbstats";
|
||||
static const std::string levelstats = "levelstats";
|
||||
static const std::string num_immutable_mem_table = "num-immutable-mem-table";
|
||||
static const std::string num_immutable_mem_table_flushed =
|
||||
"num-immutable-mem-table-flushed";
|
||||
static const std::string mem_table_flush_pending = "mem-table-flush-pending";
|
||||
static const std::string compaction_pending = "compaction-pending";
|
||||
static const std::string background_errors = "background-errors";
|
||||
@@ -185,6 +189,8 @@ DBPropertyType GetPropertyType(const Slice& property, bool* is_int_property,
|
||||
*is_int_property = true;
|
||||
if (in == num_immutable_mem_table) {
|
||||
return kNumImmutableMemTable;
|
||||
} else if (in == num_immutable_mem_table_flushed) {
|
||||
return kNumImmutableMemTableFlushed;
|
||||
} else if (in == mem_table_flush_pending) {
|
||||
return kMemtableFlushPending;
|
||||
} else if (in == compaction_pending) {
|
||||
@@ -307,7 +313,10 @@ bool InternalStats::GetIntProperty(DBPropertyType property_type,
|
||||
|
||||
switch (property_type) {
|
||||
case kNumImmutableMemTable:
|
||||
*value = cfd_->imm()->size();
|
||||
*value = cfd_->imm()->NumNotFlushed();
|
||||
return true;
|
||||
case kNumImmutableMemTableFlushed:
|
||||
*value = cfd_->imm()->NumFlushed();
|
||||
return true;
|
||||
case kMemtableFlushPending:
|
||||
// Return number of mem tables that are ready to flush (made immutable)
|
||||
@@ -433,8 +442,8 @@ void InternalStats::DumpDBStats(std::string* value) {
|
||||
value->append(buf);
|
||||
// Compact
|
||||
for (int level = 0; level < number_levels_; level++) {
|
||||
compact_bytes_read += comp_stats_[level].bytes_readnp1 +
|
||||
comp_stats_[level].bytes_readn;
|
||||
compact_bytes_read += comp_stats_[level].bytes_read_output_level +
|
||||
comp_stats_[level].bytes_read_non_output_levels;
|
||||
compact_bytes_write += comp_stats_[level].bytes_written;
|
||||
compact_micros += comp_stats_[level].micros;
|
||||
}
|
||||
@@ -541,8 +550,7 @@ void InternalStats::DumpCFStats(std::string* value) {
|
||||
const VersionStorageInfo* vstorage = cfd_->current()->storage_info();
|
||||
|
||||
int num_levels_to_check =
|
||||
(cfd_->ioptions()->compaction_style != kCompactionStyleUniversal &&
|
||||
cfd_->ioptions()->compaction_style != kCompactionStyleFIFO)
|
||||
(cfd_->ioptions()->compaction_style != kCompactionStyleFIFO)
|
||||
? vstorage->num_levels() - 1
|
||||
: 1;
|
||||
|
||||
@@ -555,7 +563,7 @@ void InternalStats::DumpCFStats(std::string* value) {
|
||||
}
|
||||
// Count # of files being compacted for each level
|
||||
std::vector<int> files_being_compacted(number_levels_, 0);
|
||||
for (int level = 0; level < num_levels_to_check; ++level) {
|
||||
for (int level = 0; level < number_levels_; ++level) {
|
||||
for (auto* f : vstorage->LevelFiles(level)) {
|
||||
if (f->being_compacted) {
|
||||
++files_being_compacted[level];
|
||||
@@ -592,9 +600,10 @@ void InternalStats::DumpCFStats(std::string* value) {
|
||||
total_stall_count += stalls;
|
||||
total_slowdown_count_soft += stall_leveln_slowdown_count_soft_[level];
|
||||
total_slowdown_count_hard += stall_leveln_slowdown_count_hard_[level];
|
||||
double w_amp = (comp_stats_[level].bytes_readn == 0) ? 0.0
|
||||
: comp_stats_[level].bytes_written /
|
||||
static_cast<double>(comp_stats_[level].bytes_readn);
|
||||
double w_amp =
|
||||
(comp_stats_[level].bytes_read_non_output_levels == 0) ? 0.0
|
||||
: static_cast<double>(comp_stats_[level].bytes_written) /
|
||||
comp_stats_[level].bytes_read_non_output_levels;
|
||||
PrintLevelStats(buf, sizeof(buf), "L" + ToString(level), files,
|
||||
files_being_compacted[level],
|
||||
vstorage->NumLevelBytes(level), compaction_score[level],
|
||||
|
||||
+49
-40
@@ -32,9 +32,12 @@ enum DBPropertyType : uint32_t {
|
||||
kStats, // Return general statitistics of both DB and CF
|
||||
kSsTables, // Return a human readable string of current SST files
|
||||
kStartIntTypes, // ---- Dummy value to indicate the start of integer values
|
||||
kNumImmutableMemTable, // Return number of immutable mem tables
|
||||
kMemtableFlushPending, // Return 1 if mem table flushing is pending,
|
||||
// otherwise 0.
|
||||
kNumImmutableMemTable, // Return number of immutable mem tables that
|
||||
// have not been flushed.
|
||||
kNumImmutableMemTableFlushed, // Return number of immutable mem tables
|
||||
// in memory that have already been flushed
|
||||
kMemtableFlushPending, // Return 1 if mem table flushing is pending,
|
||||
// otherwise 0.
|
||||
kCompactionPending, // Return 1 if a compaction is pending. Otherwise 0.
|
||||
kBackgroundErrors, // Return accumulated background errors encountered.
|
||||
kCurSizeActiveMemTable, // Return current size of the active memtable
|
||||
@@ -117,26 +120,26 @@ class InternalStats {
|
||||
struct CompactionStats {
|
||||
uint64_t micros;
|
||||
|
||||
// Bytes read from level N during compaction between levels N and N+1
|
||||
uint64_t bytes_readn;
|
||||
// The number of bytes read from all non-output levels
|
||||
uint64_t bytes_read_non_output_levels;
|
||||
|
||||
// Bytes read from level N+1 during compaction between levels N and N+1
|
||||
uint64_t bytes_readnp1;
|
||||
// The number of bytes read from the compaction output level.
|
||||
uint64_t bytes_read_output_level;
|
||||
|
||||
// Total bytes written during compaction between levels N and N+1
|
||||
// Total number of bytes written during compaction
|
||||
uint64_t bytes_written;
|
||||
|
||||
// Total bytes moved to this level
|
||||
// Total number of bytes moved to the output level
|
||||
uint64_t bytes_moved;
|
||||
|
||||
// Files read from level N during compaction between levels N and N+1
|
||||
int files_in_leveln;
|
||||
// The number of compaction input files in all non-output levels.
|
||||
int num_input_files_in_non_output_levels;
|
||||
|
||||
// Files read from level N+1 during compaction between levels N and N+1
|
||||
int files_in_levelnp1;
|
||||
// The number of compaction input files in the output level.
|
||||
int num_input_files_in_output_level;
|
||||
|
||||
// Files written during compaction between levels N and N+1
|
||||
int files_out_levelnp1;
|
||||
// The number of compaction output files.
|
||||
int num_output_files;
|
||||
|
||||
// Total incoming entries during compaction between levels N and N+1
|
||||
uint64_t num_input_records;
|
||||
@@ -150,39 +153,43 @@ class InternalStats {
|
||||
|
||||
explicit CompactionStats(int _count = 0)
|
||||
: micros(0),
|
||||
bytes_readn(0),
|
||||
bytes_readnp1(0),
|
||||
bytes_read_non_output_levels(0),
|
||||
bytes_read_output_level(0),
|
||||
bytes_written(0),
|
||||
bytes_moved(0),
|
||||
files_in_leveln(0),
|
||||
files_in_levelnp1(0),
|
||||
files_out_levelnp1(0),
|
||||
num_input_files_in_non_output_levels(0),
|
||||
num_input_files_in_output_level(0),
|
||||
num_output_files(0),
|
||||
num_input_records(0),
|
||||
num_dropped_records(0),
|
||||
count(_count) {}
|
||||
|
||||
explicit CompactionStats(const CompactionStats& c)
|
||||
: micros(c.micros),
|
||||
bytes_readn(c.bytes_readn),
|
||||
bytes_readnp1(c.bytes_readnp1),
|
||||
bytes_read_non_output_levels(c.bytes_read_non_output_levels),
|
||||
bytes_read_output_level(c.bytes_read_output_level),
|
||||
bytes_written(c.bytes_written),
|
||||
bytes_moved(c.bytes_moved),
|
||||
files_in_leveln(c.files_in_leveln),
|
||||
files_in_levelnp1(c.files_in_levelnp1),
|
||||
files_out_levelnp1(c.files_out_levelnp1),
|
||||
num_input_files_in_non_output_levels(
|
||||
c.num_input_files_in_non_output_levels),
|
||||
num_input_files_in_output_level(
|
||||
c.num_input_files_in_output_level),
|
||||
num_output_files(c.num_output_files),
|
||||
num_input_records(c.num_input_records),
|
||||
num_dropped_records(c.num_dropped_records),
|
||||
count(c.count) {}
|
||||
|
||||
void Add(const CompactionStats& c) {
|
||||
this->micros += c.micros;
|
||||
this->bytes_readn += c.bytes_readn;
|
||||
this->bytes_readnp1 += c.bytes_readnp1;
|
||||
this->bytes_read_non_output_levels += c.bytes_read_non_output_levels;
|
||||
this->bytes_read_output_level += c.bytes_read_output_level;
|
||||
this->bytes_written += c.bytes_written;
|
||||
this->bytes_moved += c.bytes_moved;
|
||||
this->files_in_leveln += c.files_in_leveln;
|
||||
this->files_in_levelnp1 += c.files_in_levelnp1;
|
||||
this->files_out_levelnp1 += c.files_out_levelnp1;
|
||||
this->num_input_files_in_non_output_levels +=
|
||||
c.num_input_files_in_non_output_levels;
|
||||
this->num_input_files_in_output_level +=
|
||||
c.num_input_files_in_output_level;
|
||||
this->num_output_files += c.num_output_files;
|
||||
this->num_input_records += c.num_input_records;
|
||||
this->num_dropped_records += c.num_dropped_records;
|
||||
this->count += c.count;
|
||||
@@ -190,13 +197,15 @@ class InternalStats {
|
||||
|
||||
void Subtract(const CompactionStats& c) {
|
||||
this->micros -= c.micros;
|
||||
this->bytes_readn -= c.bytes_readn;
|
||||
this->bytes_readnp1 -= c.bytes_readnp1;
|
||||
this->bytes_read_non_output_levels -= c.bytes_read_non_output_levels;
|
||||
this->bytes_read_output_level -= c.bytes_read_output_level;
|
||||
this->bytes_written -= c.bytes_written;
|
||||
this->bytes_moved -= c.bytes_moved;
|
||||
this->files_in_leveln -= c.files_in_leveln;
|
||||
this->files_in_levelnp1 -= c.files_in_levelnp1;
|
||||
this->files_out_levelnp1 -= c.files_out_levelnp1;
|
||||
this->num_input_files_in_non_output_levels -=
|
||||
c.num_input_files_in_non_output_levels;
|
||||
this->num_input_files_in_output_level -=
|
||||
c.num_input_files_in_output_level;
|
||||
this->num_output_files -= c.num_output_files;
|
||||
this->num_input_records -= c.num_input_records;
|
||||
this->num_dropped_records -= c.num_dropped_records;
|
||||
this->count -= c.count;
|
||||
@@ -349,13 +358,13 @@ class InternalStats {
|
||||
|
||||
struct CompactionStats {
|
||||
uint64_t micros;
|
||||
uint64_t bytes_readn;
|
||||
uint64_t bytes_readnp1;
|
||||
uint64_t bytes_read_non_output_levels;
|
||||
uint64_t bytes_read_output_level;
|
||||
uint64_t bytes_written;
|
||||
uint64_t bytes_moved;
|
||||
int files_in_leveln;
|
||||
int files_in_levelnp1;
|
||||
int files_out_levelnp1;
|
||||
int num_input_files_in_non_output_levels;
|
||||
int num_input_files_in_output_level;
|
||||
int num_output_files;
|
||||
uint64_t num_input_records;
|
||||
uint64_t num_dropped_records;
|
||||
int count;
|
||||
|
||||
@@ -83,6 +83,10 @@ struct JobContext {
|
||||
new_superversion = create_superversion ? new SuperVersion() : nullptr;
|
||||
}
|
||||
|
||||
// For non-empty JobContext Clean() has to be called at least once before
|
||||
// before destruction (see asserts in ~JobContext()). Should be called with
|
||||
// unlocked DB mutex. Destructor doesn't call Clean() to avoid accidentally
|
||||
// doing potentially slow Clean() with locked DB mutex.
|
||||
void Clean() {
|
||||
// free pending memtables
|
||||
for (auto m : memtables_to_free) {
|
||||
@@ -109,6 +113,7 @@ struct JobContext {
|
||||
assert(memtables_to_free.size() == 0);
|
||||
assert(superversions_to_free.size() == 0);
|
||||
assert(new_superversion == nullptr);
|
||||
assert(logs_to_free.size() == 0);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
+82
-13
@@ -156,6 +156,8 @@ class TestCompactionListener : public EventListener {
|
||||
compacted_dbs_.push_back(db);
|
||||
ASSERT_GT(ci.input_files.size(), 0U);
|
||||
ASSERT_GT(ci.output_files.size(), 0U);
|
||||
ASSERT_EQ(db->GetEnv()->GetThreadID(), ci.thread_id);
|
||||
ASSERT_GT(ci.thread_id, 0U);
|
||||
}
|
||||
|
||||
std::vector<DB*> compacted_dbs_;
|
||||
@@ -177,7 +179,9 @@ TEST_F(EventListenerTest, OnSingleDBCompactionTest) {
|
||||
options.max_bytes_for_level_base = options.target_file_size_base * 2;
|
||||
options.max_bytes_for_level_multiplier = 2;
|
||||
options.compression = kNoCompression;
|
||||
#if ROCKSDB_USING_THREAD_STATUS
|
||||
options.enable_thread_tracking = true;
|
||||
#endif // ROCKSDB_USING_THREAD_STATUS
|
||||
options.level0_file_num_compaction_trigger = kNumL0Files;
|
||||
|
||||
TestCompactionListener* listener = new TestCompactionListener();
|
||||
@@ -197,7 +201,8 @@ TEST_F(EventListenerTest, OnSingleDBCompactionTest) {
|
||||
ASSERT_OK(Flush(static_cast<int>(i)));
|
||||
const Slice kStart = "a";
|
||||
const Slice kEnd = "z";
|
||||
ASSERT_OK(dbfull()->CompactRange(handles_[i], &kStart, &kEnd));
|
||||
ASSERT_OK(dbfull()->CompactRange(CompactRangeOptions(), handles_[i],
|
||||
&kStart, &kEnd));
|
||||
dbfull()->TEST_WaitForFlushMemTable();
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
}
|
||||
@@ -208,33 +213,86 @@ TEST_F(EventListenerTest, OnSingleDBCompactionTest) {
|
||||
}
|
||||
}
|
||||
|
||||
// This simple Listener can only handle one flush at a time.
|
||||
class TestFlushListener : public EventListener {
|
||||
public:
|
||||
explicit TestFlushListener(Env* env) :
|
||||
slowdown_count(0),
|
||||
stop_count(0),
|
||||
db_closed(false),
|
||||
env_(env) {}
|
||||
void OnTableFileCreated(
|
||||
const TableFileCreationInfo& info) override {
|
||||
// remember the info for later checking the FlushJobInfo.
|
||||
prev_fc_info_ = info;
|
||||
ASSERT_GT(info.db_name.size(), 0U);
|
||||
ASSERT_GT(info.cf_name.size(), 0U);
|
||||
ASSERT_GT(info.file_path.size(), 0U);
|
||||
ASSERT_GT(info.job_id, 0);
|
||||
ASSERT_GT(info.table_properties.data_size, 0U);
|
||||
ASSERT_GT(info.table_properties.raw_key_size, 0U);
|
||||
ASSERT_GT(info.table_properties.raw_value_size, 0U);
|
||||
ASSERT_GT(info.table_properties.num_data_blocks, 0U);
|
||||
ASSERT_GT(info.table_properties.num_entries, 0U);
|
||||
|
||||
#if ROCKSDB_USING_THREAD_STATUS
|
||||
// Verify the id of the current thread that created this table
|
||||
// file matches the id of any active flush or compaction thread.
|
||||
uint64_t thread_id = env_->GetThreadID();
|
||||
std::vector<ThreadStatus> thread_list;
|
||||
ASSERT_OK(env_->GetThreadList(&thread_list));
|
||||
bool found_match = false;
|
||||
for (auto thread_status : thread_list) {
|
||||
if (thread_status.operation_type == ThreadStatus::OP_FLUSH ||
|
||||
thread_status.operation_type == ThreadStatus::OP_COMPACTION) {
|
||||
if (thread_id == thread_status.thread_id) {
|
||||
found_match = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
ASSERT_TRUE(found_match);
|
||||
#endif // ROCKSDB_USING_THREAD_STATUS
|
||||
}
|
||||
|
||||
void OnFlushCompleted(
|
||||
DB* db, const std::string& name,
|
||||
const std::string& file_path,
|
||||
bool triggered_writes_slowdown,
|
||||
bool triggered_writes_stop) override {
|
||||
DB* db, const FlushJobInfo& info) override {
|
||||
flushed_dbs_.push_back(db);
|
||||
flushed_column_family_names_.push_back(name);
|
||||
if (triggered_writes_slowdown) {
|
||||
flushed_column_family_names_.push_back(info.cf_name);
|
||||
if (info.triggered_writes_slowdown) {
|
||||
slowdown_count++;
|
||||
}
|
||||
if (triggered_writes_stop) {
|
||||
if (info.triggered_writes_stop) {
|
||||
stop_count++;
|
||||
}
|
||||
// verify whether the previously created file matches the flushed file.
|
||||
ASSERT_EQ(prev_fc_info_.db_name, db->GetName());
|
||||
ASSERT_EQ(prev_fc_info_.cf_name, info.cf_name);
|
||||
ASSERT_EQ(prev_fc_info_.job_id, info.job_id);
|
||||
ASSERT_EQ(prev_fc_info_.file_path, info.file_path);
|
||||
ASSERT_EQ(db->GetEnv()->GetThreadID(), info.thread_id);
|
||||
ASSERT_GT(info.thread_id, 0U);
|
||||
}
|
||||
|
||||
std::vector<std::string> flushed_column_family_names_;
|
||||
std::vector<DB*> flushed_dbs_;
|
||||
int slowdown_count;
|
||||
int stop_count;
|
||||
bool db_closing;
|
||||
std::atomic_bool db_closed;
|
||||
TableFileCreationInfo prev_fc_info_;
|
||||
|
||||
protected:
|
||||
Env* env_;
|
||||
};
|
||||
|
||||
TEST_F(EventListenerTest, OnSingleDBFlushTest) {
|
||||
Options options;
|
||||
options.write_buffer_size = 100000;
|
||||
TestFlushListener* listener = new TestFlushListener();
|
||||
#if ROCKSDB_USING_THREAD_STATUS
|
||||
options.enable_thread_tracking = true;
|
||||
#endif // ROCKSDB_USING_THREAD_STATUS
|
||||
TestFlushListener* listener = new TestFlushListener(options.env);
|
||||
options.listeners.emplace_back(listener);
|
||||
std::vector<std::string> cf_names = {
|
||||
"pikachu", "ilya", "muromec", "dobrynia",
|
||||
@@ -265,7 +323,10 @@ TEST_F(EventListenerTest, OnSingleDBFlushTest) {
|
||||
TEST_F(EventListenerTest, MultiCF) {
|
||||
Options options;
|
||||
options.write_buffer_size = 100000;
|
||||
TestFlushListener* listener = new TestFlushListener();
|
||||
#if ROCKSDB_USING_THREAD_STATUS
|
||||
options.enable_thread_tracking = true;
|
||||
#endif // ROCKSDB_USING_THREAD_STATUS
|
||||
TestFlushListener* listener = new TestFlushListener(options.env);
|
||||
options.listeners.emplace_back(listener);
|
||||
std::vector<std::string> cf_names = {
|
||||
"pikachu", "ilya", "muromec", "dobrynia",
|
||||
@@ -293,18 +354,21 @@ TEST_F(EventListenerTest, MultiCF) {
|
||||
}
|
||||
|
||||
TEST_F(EventListenerTest, MultiDBMultiListeners) {
|
||||
Options options;
|
||||
#if ROCKSDB_USING_THREAD_STATUS
|
||||
options.enable_thread_tracking = true;
|
||||
#endif // ROCKSDB_USING_THREAD_STATUS
|
||||
std::vector<TestFlushListener*> listeners;
|
||||
const int kNumDBs = 5;
|
||||
const int kNumListeners = 10;
|
||||
for (int i = 0; i < kNumListeners; ++i) {
|
||||
listeners.emplace_back(new TestFlushListener());
|
||||
listeners.emplace_back(new TestFlushListener(options.env));
|
||||
}
|
||||
|
||||
std::vector<std::string> cf_names = {
|
||||
"pikachu", "ilya", "muromec", "dobrynia",
|
||||
"nikitich", "alyosha", "popovich"};
|
||||
|
||||
Options options;
|
||||
options.create_if_missing = true;
|
||||
for (int i = 0; i < kNumListeners; ++i) {
|
||||
options.listeners.emplace_back(listeners[i]);
|
||||
@@ -355,6 +419,7 @@ TEST_F(EventListenerTest, MultiDBMultiListeners) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
for (auto handles : vec_handles) {
|
||||
for (auto h : handles) {
|
||||
delete h;
|
||||
@@ -370,7 +435,10 @@ TEST_F(EventListenerTest, MultiDBMultiListeners) {
|
||||
|
||||
TEST_F(EventListenerTest, DisableBGCompaction) {
|
||||
Options options;
|
||||
TestFlushListener* listener = new TestFlushListener();
|
||||
#if ROCKSDB_USING_THREAD_STATUS
|
||||
options.enable_thread_tracking = true;
|
||||
#endif // ROCKSDB_USING_THREAD_STATUS
|
||||
TestFlushListener* listener = new TestFlushListener(options.env);
|
||||
const int kSlowdownTrigger = 5;
|
||||
const int kStopTrigger = 10;
|
||||
options.level0_slowdown_writes_trigger = kSlowdownTrigger;
|
||||
@@ -390,6 +458,7 @@ TEST_F(EventListenerTest, DisableBGCompaction) {
|
||||
// keep writing until writes are forced to stop.
|
||||
for (int i = 0; static_cast<int>(cf_meta.file_count) < kStopTrigger; ++i) {
|
||||
Put(1, ToString(i), std::string(100000, 'x'), wopts);
|
||||
db_->Flush(FlushOptions());
|
||||
db_->GetColumnFamilyMetaData(handles_[1], &cf_meta);
|
||||
}
|
||||
ASSERT_GE(listener->slowdown_count, kStopTrigger - kSlowdownTrigger);
|
||||
|
||||
+18
-6
@@ -61,7 +61,8 @@ bool Reader::SkipToInitialBlock() {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Reader::ReadRecord(Slice* record, std::string* scratch) {
|
||||
bool Reader::ReadRecord(Slice* record, std::string* scratch,
|
||||
const bool report_eof_inconsistency) {
|
||||
if (last_record_offset_ < initial_offset_) {
|
||||
if (!SkipToInitialBlock()) {
|
||||
return false;
|
||||
@@ -78,7 +79,8 @@ bool Reader::ReadRecord(Slice* record, std::string* scratch) {
|
||||
Slice fragment;
|
||||
while (true) {
|
||||
uint64_t physical_record_offset = end_of_buffer_offset_ - buffer_.size();
|
||||
const unsigned int record_type = ReadPhysicalRecord(&fragment);
|
||||
const unsigned int record_type =
|
||||
ReadPhysicalRecord(&fragment, report_eof_inconsistency);
|
||||
switch (record_type) {
|
||||
case kFullType:
|
||||
if (in_fragmented_record && !scratch->empty()) {
|
||||
@@ -130,6 +132,9 @@ bool Reader::ReadRecord(Slice* record, std::string* scratch) {
|
||||
|
||||
case kEof:
|
||||
if (in_fragmented_record) {
|
||||
if (report_eof_inconsistency) {
|
||||
ReportCorruption(scratch->size(), "error reading trailing data");
|
||||
}
|
||||
// This can be caused by the writer dying immediately after
|
||||
// writing a physical record but before completing the next; don't
|
||||
// treat it as a corruption, just ignore the entire logical record.
|
||||
@@ -238,7 +243,8 @@ void Reader::ReportDrop(size_t bytes, const Status& reason) {
|
||||
}
|
||||
}
|
||||
|
||||
unsigned int Reader::ReadPhysicalRecord(Slice* result) {
|
||||
unsigned int Reader::ReadPhysicalRecord(Slice* result,
|
||||
const bool report_eof_inconsistency) {
|
||||
while (true) {
|
||||
if (buffer_.size() < (size_t)kHeaderSize) {
|
||||
if (!eof_ && !read_error_) {
|
||||
@@ -259,8 +265,11 @@ unsigned int Reader::ReadPhysicalRecord(Slice* result) {
|
||||
} else {
|
||||
// Note that if buffer_ is non-empty, we have a truncated header at the
|
||||
// end of the file, which can be caused by the writer crashing in the
|
||||
// middle of writing the header. Instead of considering this an error,
|
||||
// just report EOF.
|
||||
// middle of writing the header. Unless explicitly requested we don't
|
||||
// considering this an error, just report EOF.
|
||||
if (buffer_.size() && report_eof_inconsistency) {
|
||||
ReportCorruption(buffer_.size(), "truncated header");
|
||||
}
|
||||
buffer_.clear();
|
||||
return kEof;
|
||||
}
|
||||
@@ -281,7 +290,10 @@ unsigned int Reader::ReadPhysicalRecord(Slice* result) {
|
||||
}
|
||||
// If the end of the file has been reached without reading |length| bytes
|
||||
// of payload, assume the writer died in the middle of writing the record.
|
||||
// Don't report a corruption.
|
||||
// Don't report a corruption unless requested.
|
||||
if (drop_size && report_eof_inconsistency) {
|
||||
ReportCorruption(drop_size, "truncated header");
|
||||
}
|
||||
return kEof;
|
||||
}
|
||||
|
||||
|
||||
+4
-2
@@ -61,7 +61,8 @@ class Reader {
|
||||
// "*scratch" as temporary storage. The contents filled in *record
|
||||
// will only be valid until the next mutating operation on this
|
||||
// reader or the next mutation to *scratch.
|
||||
bool ReadRecord(Slice* record, std::string* scratch);
|
||||
bool ReadRecord(Slice* record, std::string* scratch,
|
||||
bool report_eof_inconsistency = false);
|
||||
|
||||
// Returns the physical offset of the last record returned by ReadRecord.
|
||||
//
|
||||
@@ -120,7 +121,8 @@ class Reader {
|
||||
bool SkipToInitialBlock();
|
||||
|
||||
// Return type, or one of the preceding special values
|
||||
unsigned int ReadPhysicalRecord(Slice* result);
|
||||
unsigned int ReadPhysicalRecord(Slice* result,
|
||||
bool report_eof_inconsistency = false);
|
||||
|
||||
// Reports dropped bytes to the reporter.
|
||||
// buffer_ must be updated to remove the dropped bytes prior to invocation.
|
||||
|
||||
+39
-2
@@ -208,10 +208,10 @@ class LogTest : public testing::Test {
|
||||
return dest_contents().size();
|
||||
}
|
||||
|
||||
std::string Read() {
|
||||
std::string Read(const bool report_eof_inconsistency = false) {
|
||||
std::string scratch;
|
||||
Slice record;
|
||||
if (reader_.ReadRecord(&record, &scratch)) {
|
||||
if (reader_.ReadRecord(&record, &scratch, report_eof_inconsistency)) {
|
||||
return record.ToString();
|
||||
} else {
|
||||
return "EOF";
|
||||
@@ -452,6 +452,15 @@ TEST_F(LogTest, TruncatedTrailingRecordIsIgnored) {
|
||||
ASSERT_EQ("", ReportMessage());
|
||||
}
|
||||
|
||||
TEST_F(LogTest, TruncatedTrailingRecordIsNotIgnored) {
|
||||
Write("foo");
|
||||
ShrinkSize(4); // Drop all payload as well as a header byte
|
||||
ASSERT_EQ("EOF", Read(/*report_eof_inconsistency*/ true));
|
||||
// Truncated last record is ignored, not treated as an error
|
||||
ASSERT_GT(DroppedBytes(), 0U);
|
||||
ASSERT_EQ("OK", MatchError("Corruption: truncated header"));
|
||||
}
|
||||
|
||||
TEST_F(LogTest, BadLength) {
|
||||
const int kPayloadSize = kBlockSize - kHeaderSize;
|
||||
Write(BigString("bar", kPayloadSize));
|
||||
@@ -471,6 +480,14 @@ TEST_F(LogTest, BadLengthAtEndIsIgnored) {
|
||||
ASSERT_EQ("", ReportMessage());
|
||||
}
|
||||
|
||||
TEST_F(LogTest, BadLengthAtEndIsNotIgnored) {
|
||||
Write("foo");
|
||||
ShrinkSize(1);
|
||||
ASSERT_EQ("EOF", Read(/*report_eof_inconsistency=*/true));
|
||||
ASSERT_GT(DroppedBytes(), 0U);
|
||||
ASSERT_EQ("OK", MatchError("Corruption: truncated header"));
|
||||
}
|
||||
|
||||
TEST_F(LogTest, ChecksumMismatch) {
|
||||
Write("foo");
|
||||
IncrementByte(0, 10);
|
||||
@@ -528,6 +545,15 @@ TEST_F(LogTest, MissingLastIsIgnored) {
|
||||
ASSERT_EQ(0U, DroppedBytes());
|
||||
}
|
||||
|
||||
TEST_F(LogTest, MissingLastIsNotIgnored) {
|
||||
Write(BigString("bar", kBlockSize));
|
||||
// Remove the LAST block, including header.
|
||||
ShrinkSize(14);
|
||||
ASSERT_EQ("EOF", Read(/*report_eof_inconsistency=*/true));
|
||||
ASSERT_GT(DroppedBytes(), 0U);
|
||||
ASSERT_EQ("OK", MatchError("Corruption: error reading trailing data"));
|
||||
}
|
||||
|
||||
TEST_F(LogTest, PartialLastIsIgnored) {
|
||||
Write(BigString("bar", kBlockSize));
|
||||
// Cause a bad record length in the LAST block.
|
||||
@@ -537,6 +563,17 @@ TEST_F(LogTest, PartialLastIsIgnored) {
|
||||
ASSERT_EQ(0U, DroppedBytes());
|
||||
}
|
||||
|
||||
TEST_F(LogTest, PartialLastIsNotIgnored) {
|
||||
Write(BigString("bar", kBlockSize));
|
||||
// Cause a bad record length in the LAST block.
|
||||
ShrinkSize(1);
|
||||
ASSERT_EQ("EOF", Read(/*report_eof_inconsistency=*/true));
|
||||
ASSERT_GT(DroppedBytes(), 0U);
|
||||
ASSERT_EQ("OK", MatchError(
|
||||
"Corruption: truncated headerCorruption: "
|
||||
"error reading trailing data"));
|
||||
}
|
||||
|
||||
TEST_F(LogTest, ErrorJoinsRecords) {
|
||||
// Consider two fragmented records:
|
||||
// first(R1) last(R1) first(R2) last(R2)
|
||||
|
||||
+56
-9
@@ -54,7 +54,7 @@ MemTableOptions::MemTableOptions(
|
||||
MemTable::MemTable(const InternalKeyComparator& cmp,
|
||||
const ImmutableCFOptions& ioptions,
|
||||
const MutableCFOptions& mutable_cf_options,
|
||||
WriteBuffer* write_buffer)
|
||||
WriteBuffer* write_buffer, SequenceNumber earliest_seq)
|
||||
: comparator_(cmp),
|
||||
moptions_(ioptions, mutable_cf_options),
|
||||
refs_(0),
|
||||
@@ -64,12 +64,14 @@ MemTable::MemTable(const InternalKeyComparator& cmp,
|
||||
table_(ioptions.memtable_factory->CreateMemTableRep(
|
||||
comparator_, &allocator_, ioptions.prefix_extractor,
|
||||
ioptions.info_log)),
|
||||
data_size_(0),
|
||||
num_entries_(0),
|
||||
num_deletes_(0),
|
||||
flush_in_progress_(false),
|
||||
flush_completed_(false),
|
||||
file_number_(0),
|
||||
first_seqno_(0),
|
||||
earliest_seqno_(earliest_seq),
|
||||
mem_next_logfile_number_(0),
|
||||
locks_(moptions_.inplace_update_support
|
||||
? moptions_.inplace_update_num_locks
|
||||
@@ -289,6 +291,26 @@ port::RWMutex* MemTable::GetLock(const Slice& key) {
|
||||
return &locks_[hash(key) % locks_.size()];
|
||||
}
|
||||
|
||||
uint64_t MemTable::ApproximateSize(const Slice& start_ikey,
|
||||
const Slice& end_ikey) {
|
||||
uint64_t entry_count = table_->ApproximateNumEntries(start_ikey, end_ikey);
|
||||
if (entry_count == 0) {
|
||||
return 0;
|
||||
}
|
||||
uint64_t n = num_entries_.load(std::memory_order_relaxed);
|
||||
if (n == 0) {
|
||||
return 0;
|
||||
}
|
||||
if (entry_count > n) {
|
||||
// table_->ApproximateNumEntries() is just an estimate so it can be larger
|
||||
// than actual entries we have. Cap it to entries we have to limit the
|
||||
// inaccuracy.
|
||||
entry_count = n;
|
||||
}
|
||||
uint64_t data_size = data_size_.load(std::memory_order_relaxed);
|
||||
return entry_count * (data_size / n);
|
||||
}
|
||||
|
||||
void MemTable::Add(SequenceNumber s, ValueType type,
|
||||
const Slice& key, /* user key */
|
||||
const Slice& value) {
|
||||
@@ -309,13 +331,17 @@ void MemTable::Add(SequenceNumber s, ValueType type,
|
||||
char* p = EncodeVarint32(buf, internal_key_size);
|
||||
memcpy(p, key.data(), key_size);
|
||||
p += key_size;
|
||||
EncodeFixed64(p, (s << 8) | type);
|
||||
uint64_t packed = PackSequenceAndType(s, type);
|
||||
EncodeFixed64(p, packed);
|
||||
p += 8;
|
||||
p = EncodeVarint32(p, val_size);
|
||||
memcpy(p, value.data(), val_size);
|
||||
assert((unsigned)(p + val_size - buf) == (unsigned)encoded_len);
|
||||
table_->Insert(handle);
|
||||
num_entries_++;
|
||||
num_entries_.store(num_entries_.load(std::memory_order_relaxed) + 1,
|
||||
std::memory_order_relaxed);
|
||||
data_size_.store(data_size_.load(std::memory_order_relaxed) + encoded_len,
|
||||
std::memory_order_relaxed);
|
||||
if (type == kTypeDeletion) {
|
||||
num_deletes_++;
|
||||
}
|
||||
@@ -329,6 +355,11 @@ void MemTable::Add(SequenceNumber s, ValueType type,
|
||||
assert(first_seqno_ == 0 || s > first_seqno_);
|
||||
if (first_seqno_ == 0) {
|
||||
first_seqno_ = s;
|
||||
|
||||
if (earliest_seqno_ == kMaxSequenceNumber) {
|
||||
earliest_seqno_ = first_seqno_;
|
||||
}
|
||||
assert(first_seqno_ >= earliest_seqno_);
|
||||
}
|
||||
|
||||
should_flush_ = ShouldFlushNow();
|
||||
@@ -343,6 +374,7 @@ struct Saver {
|
||||
bool* found_final_value; // Is value set correctly? Used by KeyMayExist
|
||||
bool* merge_in_progress;
|
||||
std::string* value;
|
||||
SequenceNumber seq;
|
||||
const MergeOperator* merge_operator;
|
||||
// the merge operations encountered;
|
||||
MergeContext* merge_context;
|
||||
@@ -376,7 +408,10 @@ static bool SaveValue(void* arg, const char* entry) {
|
||||
Slice(key_ptr, key_length - 8), s->key->user_key()) == 0) {
|
||||
// Correct user key
|
||||
const uint64_t tag = DecodeFixed64(key_ptr + key_length - 8);
|
||||
switch (static_cast<ValueType>(tag & 0xff)) {
|
||||
ValueType type;
|
||||
UnPackSequenceAndType(tag, &s->seq, &type);
|
||||
|
||||
switch (type) {
|
||||
case kTypeValue: {
|
||||
if (s->inplace_update_support) {
|
||||
s->mem->GetLock(s->key->user_key())->ReadLock();
|
||||
@@ -461,7 +496,7 @@ static bool SaveValue(void* arg, const char* entry) {
|
||||
}
|
||||
|
||||
bool MemTable::Get(const LookupKey& key, std::string* value, Status* s,
|
||||
MergeContext* merge_context) {
|
||||
MergeContext* merge_context, SequenceNumber* seq) {
|
||||
// The sequence number is updated synchronously in version_set.h
|
||||
if (IsEmpty()) {
|
||||
// Avoiding recording stats for speed.
|
||||
@@ -476,6 +511,7 @@ bool MemTable::Get(const LookupKey& key, std::string* value, Status* s,
|
||||
if (prefix_bloom_ &&
|
||||
!prefix_bloom_->MayContain(prefix_extractor_->Transform(user_key))) {
|
||||
// iter is null if prefix bloom says the key does not exist
|
||||
*seq = kMaxSequenceNumber;
|
||||
} else {
|
||||
Saver saver;
|
||||
saver.status = s;
|
||||
@@ -483,7 +519,7 @@ bool MemTable::Get(const LookupKey& key, std::string* value, Status* s,
|
||||
saver.merge_in_progress = &merge_in_progress;
|
||||
saver.key = &key;
|
||||
saver.value = value;
|
||||
saver.status = s;
|
||||
saver.seq = kMaxSequenceNumber;
|
||||
saver.mem = this;
|
||||
saver.merge_context = merge_context;
|
||||
saver.merge_operator = moptions_.merge_operator;
|
||||
@@ -492,6 +528,8 @@ bool MemTable::Get(const LookupKey& key, std::string* value, Status* s,
|
||||
saver.statistics = moptions_.statistics;
|
||||
saver.env_ = env_;
|
||||
table_->Get(key, &saver, SaveValue);
|
||||
|
||||
*seq = saver.seq;
|
||||
}
|
||||
|
||||
// No change to value, since we have not yet found a Put/Delete
|
||||
@@ -529,7 +567,10 @@ void MemTable::Update(SequenceNumber seq,
|
||||
Slice(key_ptr, key_length - 8), lkey.user_key()) == 0) {
|
||||
// Correct user key
|
||||
const uint64_t tag = DecodeFixed64(key_ptr + key_length - 8);
|
||||
switch (static_cast<ValueType>(tag & 0xff)) {
|
||||
ValueType type;
|
||||
SequenceNumber unused;
|
||||
UnPackSequenceAndType(tag, &unused, &type);
|
||||
switch (type) {
|
||||
case kTypeValue: {
|
||||
Slice prev_value = GetLengthPrefixedSlice(key_ptr + key_length);
|
||||
uint32_t prev_size = static_cast<uint32_t>(prev_value.size());
|
||||
@@ -587,7 +628,10 @@ bool MemTable::UpdateCallback(SequenceNumber seq,
|
||||
Slice(key_ptr, key_length - 8), lkey.user_key()) == 0) {
|
||||
// Correct user key
|
||||
const uint64_t tag = DecodeFixed64(key_ptr + key_length - 8);
|
||||
switch (static_cast<ValueType>(tag & 0xff)) {
|
||||
ValueType type;
|
||||
uint64_t unused;
|
||||
UnPackSequenceAndType(tag, &unused, &type);
|
||||
switch (type) {
|
||||
case kTypeValue: {
|
||||
Slice prev_value = GetLengthPrefixedSlice(key_ptr + key_length);
|
||||
uint32_t prev_size = static_cast<uint32_t>(prev_value.size());
|
||||
@@ -657,7 +701,10 @@ size_t MemTable::CountSuccessiveMergeEntries(const LookupKey& key) {
|
||||
}
|
||||
|
||||
const uint64_t tag = DecodeFixed64(iter_key_ptr + key_length - 8);
|
||||
if (static_cast<ValueType>(tag & 0xff) != kTypeMerge) {
|
||||
ValueType type;
|
||||
uint64_t unused;
|
||||
UnPackSequenceAndType(tag, &unused, &type);
|
||||
if (type != kTypeMerge) {
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
+41
-4
@@ -80,10 +80,17 @@ class MemTable {
|
||||
|
||||
// MemTables are reference counted. The initial reference count
|
||||
// is zero and the caller must call Ref() at least once.
|
||||
//
|
||||
// earliest_seq should be the current SequenceNumber in the db such that any
|
||||
// key inserted into this memtable will have an equal or larger seq number.
|
||||
// (When a db is first created, the earliest sequence number will be 0).
|
||||
// If the earliest sequence number is not known, kMaxSequenceNumber may be
|
||||
// used, but this may prevent some transactions from succeeding until the
|
||||
// first key is inserted into the memtable.
|
||||
explicit MemTable(const InternalKeyComparator& comparator,
|
||||
const ImmutableCFOptions& ioptions,
|
||||
const MutableCFOptions& mutable_cf_options,
|
||||
WriteBuffer* write_buffer);
|
||||
WriteBuffer* write_buffer, SequenceNumber earliest_seq);
|
||||
|
||||
// Do not delete this MemTable unless Unref() indicates it not in use.
|
||||
~MemTable();
|
||||
@@ -153,8 +160,19 @@ class MemTable {
|
||||
// prepend the current merge operand to *operands.
|
||||
// store MergeInProgress in s, and return false.
|
||||
// Else, return false.
|
||||
// If any operation was found, its most recent sequence number
|
||||
// will be stored in *seq on success (regardless of whether true/false is
|
||||
// returned). Otherwise, *seq will be set to kMaxSequenceNumber.
|
||||
// On success, *s may be set to OK, NotFound, or MergeInProgress. Any other
|
||||
// status returned indicates a corruption or other unexpected error.
|
||||
bool Get(const LookupKey& key, std::string* value, Status* s,
|
||||
MergeContext* merge_context);
|
||||
MergeContext* merge_context, SequenceNumber* seq);
|
||||
|
||||
bool Get(const LookupKey& key, std::string* value, Status* s,
|
||||
MergeContext* merge_context) {
|
||||
SequenceNumber seq;
|
||||
return Get(key, value, s, merge_context, &seq);
|
||||
}
|
||||
|
||||
// Attempts to update the new_value inplace, else does normal Add
|
||||
// Pseudocode
|
||||
@@ -194,7 +212,9 @@ class MemTable {
|
||||
// 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).
|
||||
uint64_t num_entries() const { return num_entries_; }
|
||||
uint64_t num_entries() const {
|
||||
return num_entries_.load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
// Get total number of deletes in the mem table.
|
||||
// REQUIRES: external synchronization to prevent simultaneous
|
||||
@@ -215,6 +235,15 @@ class MemTable {
|
||||
// operations on the same MemTable (unless this Memtable is immutable).
|
||||
SequenceNumber GetFirstSequenceNumber() { return first_seqno_; }
|
||||
|
||||
// Returns the sequence number that is guaranteed to be smaller than or equal
|
||||
// to the sequence number of any key that could be inserted into this
|
||||
// memtable. It can then be assumed that any write with a larger(or equal)
|
||||
// sequence number will be present in this memtable or a later memtable.
|
||||
//
|
||||
// If the earliest sequence number could not be determined,
|
||||
// kMaxSequenceNumber will be returned.
|
||||
SequenceNumber GetEarliestSequenceNumber() { return earliest_seqno_; }
|
||||
|
||||
// Returns the next active logfile number when this memtable is about to
|
||||
// be flushed to storage
|
||||
// REQUIRES: external synchronization to prevent simultaneous
|
||||
@@ -248,6 +277,8 @@ class MemTable {
|
||||
return table_->IsSnapshotSupported() && !moptions_.inplace_update_support;
|
||||
}
|
||||
|
||||
uint64_t ApproximateSize(const Slice& start_ikey, const Slice& end_ikey);
|
||||
|
||||
// Get the lock associated for the key
|
||||
port::RWMutex* GetLock(const Slice& key);
|
||||
|
||||
@@ -273,7 +304,9 @@ class MemTable {
|
||||
MemTableAllocator allocator_;
|
||||
unique_ptr<MemTableRep> table_;
|
||||
|
||||
uint64_t num_entries_;
|
||||
// Total data size of all data inserted
|
||||
std::atomic<uint64_t> data_size_;
|
||||
std::atomic<uint64_t> num_entries_;
|
||||
uint64_t num_deletes_;
|
||||
|
||||
// These are used to manage memtable flushes to storage
|
||||
@@ -288,6 +321,10 @@ class MemTable {
|
||||
// The sequence number of the kv that was inserted first
|
||||
SequenceNumber first_seqno_;
|
||||
|
||||
// The db sequence number at the time of creation or kMaxSequenceNumber
|
||||
// if not set.
|
||||
SequenceNumber earliest_seqno_;
|
||||
|
||||
// The log files earlier than this number can be deleted.
|
||||
uint64_t mem_next_logfile_number_;
|
||||
|
||||
|
||||
+114
-26
@@ -27,16 +27,27 @@ class InternalKeyComparator;
|
||||
class Mutex;
|
||||
class VersionSet;
|
||||
|
||||
MemTableListVersion::MemTableListVersion(MemTableListVersion* old) {
|
||||
MemTableListVersion::MemTableListVersion(MemTableListVersion* old)
|
||||
: max_write_buffer_number_to_maintain_(
|
||||
old->max_write_buffer_number_to_maintain_) {
|
||||
if (old != nullptr) {
|
||||
memlist_ = old->memlist_;
|
||||
size_ = old->size_;
|
||||
for (auto& m : memlist_) {
|
||||
m->Ref();
|
||||
}
|
||||
|
||||
memlist_history_ = old->memlist_history_;
|
||||
for (auto& m : memlist_history_) {
|
||||
m->Ref();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MemTableListVersion::MemTableListVersion(
|
||||
int max_write_buffer_number_to_maintain)
|
||||
: max_write_buffer_number_to_maintain_(
|
||||
max_write_buffer_number_to_maintain) {}
|
||||
|
||||
void MemTableListVersion::Ref() { ++refs_; }
|
||||
|
||||
void MemTableListVersion::Unref(autovector<MemTable*>* to_delete) {
|
||||
@@ -52,25 +63,62 @@ void MemTableListVersion::Unref(autovector<MemTable*>* to_delete) {
|
||||
to_delete->push_back(x);
|
||||
}
|
||||
}
|
||||
for (const auto& m : memlist_history_) {
|
||||
MemTable* x = m->Unref();
|
||||
if (x != nullptr) {
|
||||
to_delete->push_back(x);
|
||||
}
|
||||
}
|
||||
delete this;
|
||||
}
|
||||
}
|
||||
|
||||
int MemTableListVersion::size() const { return size_; }
|
||||
int MemTableList::NumNotFlushed() const {
|
||||
int size = static_cast<int>(current_->memlist_.size());
|
||||
assert(num_flush_not_started_ <= size);
|
||||
return size;
|
||||
}
|
||||
|
||||
// Returns the total number of memtables in the list
|
||||
int MemTableList::size() const {
|
||||
assert(num_flush_not_started_ <= current_->size_);
|
||||
return current_->size_;
|
||||
int MemTableList::NumFlushed() const {
|
||||
return static_cast<int>(current_->memlist_history_.size());
|
||||
}
|
||||
|
||||
// Search all the memtables starting from the most recent one.
|
||||
// Return the most recent value found, if any.
|
||||
// Operands stores the list of merge operations to apply, so far.
|
||||
bool MemTableListVersion::Get(const LookupKey& key, std::string* value,
|
||||
Status* s, MergeContext* merge_context) {
|
||||
for (auto& memtable : memlist_) {
|
||||
if (memtable->Get(key, value, s, merge_context)) {
|
||||
Status* s, MergeContext* merge_context,
|
||||
SequenceNumber* seq) {
|
||||
return GetFromList(&memlist_, key, value, s, merge_context, seq);
|
||||
}
|
||||
|
||||
bool MemTableListVersion::GetFromHistory(const LookupKey& key,
|
||||
std::string* value, Status* s,
|
||||
MergeContext* merge_context,
|
||||
SequenceNumber* seq) {
|
||||
return GetFromList(&memlist_history_, key, value, s, merge_context, seq);
|
||||
}
|
||||
|
||||
bool MemTableListVersion::GetFromList(std::list<MemTable*>* list,
|
||||
const LookupKey& key, std::string* value,
|
||||
Status* s, MergeContext* merge_context,
|
||||
SequenceNumber* seq) {
|
||||
*seq = kMaxSequenceNumber;
|
||||
|
||||
for (auto& memtable : *list) {
|
||||
SequenceNumber current_seq = kMaxSequenceNumber;
|
||||
|
||||
bool done = memtable->Get(key, value, s, merge_context, ¤t_seq);
|
||||
if (*seq == kMaxSequenceNumber) {
|
||||
// Store the most recent sequence number of any operation on this key.
|
||||
// Since we only care about the most recent change, we only need to
|
||||
// return the first operation found when searching memtables in
|
||||
// reverse-chronological order.
|
||||
*seq = current_seq;
|
||||
}
|
||||
|
||||
if (done) {
|
||||
assert(*seq != kMaxSequenceNumber);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -101,6 +149,15 @@ uint64_t MemTableListVersion::GetTotalNumEntries() const {
|
||||
return total_num;
|
||||
}
|
||||
|
||||
uint64_t MemTableListVersion::ApproximateSize(const Slice& start_ikey,
|
||||
const Slice& end_ikey) {
|
||||
uint64_t total_size = 0;
|
||||
for (auto& m : memlist_) {
|
||||
total_size += m->ApproximateSize(start_ikey, end_ikey);
|
||||
}
|
||||
return total_size;
|
||||
}
|
||||
|
||||
uint64_t MemTableListVersion::GetTotalNumDeletes() const {
|
||||
uint64_t total_num = 0;
|
||||
for (auto& m : memlist_) {
|
||||
@@ -109,18 +166,53 @@ uint64_t MemTableListVersion::GetTotalNumDeletes() const {
|
||||
return total_num;
|
||||
}
|
||||
|
||||
// caller is responsible for referencing m
|
||||
void MemTableListVersion::Add(MemTable* m) {
|
||||
assert(refs_ == 1); // only when refs_ == 1 is MemTableListVersion mutable
|
||||
memlist_.push_front(m);
|
||||
++size_;
|
||||
SequenceNumber MemTableListVersion::GetEarliestSequenceNumber(
|
||||
bool include_history) const {
|
||||
if (include_history && !memlist_history_.empty()) {
|
||||
return memlist_history_.back()->GetEarliestSequenceNumber();
|
||||
} else if (!memlist_.empty()) {
|
||||
return memlist_.back()->GetEarliestSequenceNumber();
|
||||
} else {
|
||||
return kMaxSequenceNumber;
|
||||
}
|
||||
}
|
||||
|
||||
// caller is responsible for unreferencing m
|
||||
void MemTableListVersion::Remove(MemTable* m) {
|
||||
// caller is responsible for referencing m
|
||||
void MemTableListVersion::Add(MemTable* m, autovector<MemTable*>* to_delete) {
|
||||
assert(refs_ == 1); // only when refs_ == 1 is MemTableListVersion mutable
|
||||
memlist_.push_front(m);
|
||||
|
||||
TrimHistory(to_delete);
|
||||
}
|
||||
|
||||
// Removes m from list of memtables not flushed. Caller should NOT Unref m.
|
||||
void MemTableListVersion::Remove(MemTable* m,
|
||||
autovector<MemTable*>* to_delete) {
|
||||
assert(refs_ == 1); // only when refs_ == 1 is MemTableListVersion mutable
|
||||
memlist_.remove(m);
|
||||
--size_;
|
||||
|
||||
if (max_write_buffer_number_to_maintain_ > 0) {
|
||||
memlist_history_.push_front(m);
|
||||
TrimHistory(to_delete);
|
||||
} else {
|
||||
if (m->Unref()) {
|
||||
to_delete->push_back(m);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure we don't use up too much space in history
|
||||
void MemTableListVersion::TrimHistory(autovector<MemTable*>* to_delete) {
|
||||
while (memlist_.size() + memlist_history_.size() >
|
||||
static_cast<size_t>(max_write_buffer_number_to_maintain_) &&
|
||||
!memlist_history_.empty()) {
|
||||
MemTable* x = memlist_history_.back();
|
||||
memlist_history_.pop_back();
|
||||
|
||||
if (x->Unref()) {
|
||||
to_delete->push_back(x);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Returns true if there is at least one memtable on which flush has
|
||||
@@ -229,12 +321,8 @@ Status MemTableList::InstallMemtableFlushResults(
|
||||
LogToBuffer(log_buffer, "[%s] Level-0 commit table #%" PRIu64
|
||||
": memtable #%" PRIu64 " done",
|
||||
cfd->GetName().c_str(), m->file_number_, mem_id);
|
||||
current_->Remove(m);
|
||||
assert(m->file_number_ > 0);
|
||||
|
||||
if (m->Unref() != nullptr) {
|
||||
to_delete->push_back(m);
|
||||
}
|
||||
current_->Remove(m, to_delete);
|
||||
} else {
|
||||
//commit failed. setup state so that we can flush again.
|
||||
LogToBuffer(log_buffer, "Level-0 commit table #%" PRIu64
|
||||
@@ -256,15 +344,15 @@ Status MemTableList::InstallMemtableFlushResults(
|
||||
}
|
||||
|
||||
// New memtables are inserted at the front of the list.
|
||||
void MemTableList::Add(MemTable* m) {
|
||||
assert(current_->size_ >= num_flush_not_started_);
|
||||
void MemTableList::Add(MemTable* m, autovector<MemTable*>* to_delete) {
|
||||
assert(static_cast<int>(current_->memlist_.size()) >= num_flush_not_started_);
|
||||
InstallNewVersion();
|
||||
// this method is used to move mutable memtable into an immutable list.
|
||||
// since mutable memtable is already refcounted by the DBImpl,
|
||||
// and when moving to the imutable list we don't unref it,
|
||||
// we don't have to ref the memtable here. we just take over the
|
||||
// reference from the DBImpl.
|
||||
current_->Add(m);
|
||||
current_->Add(m, to_delete);
|
||||
m->MarkImmutable();
|
||||
num_flush_not_started_++;
|
||||
if (num_flush_not_started_ == 1) {
|
||||
|
||||
+66
-14
@@ -21,6 +21,7 @@
|
||||
#include "rocksdb/db.h"
|
||||
#include "rocksdb/iterator.h"
|
||||
#include "rocksdb/options.h"
|
||||
#include "rocksdb/types.h"
|
||||
#include "util/autovector.h"
|
||||
#include "util/instrumented_mutex.h"
|
||||
#include "util/log_buffer.h"
|
||||
@@ -41,16 +42,37 @@ class MergeIteratorBuilder;
|
||||
class MemTableListVersion {
|
||||
public:
|
||||
explicit MemTableListVersion(MemTableListVersion* old = nullptr);
|
||||
explicit MemTableListVersion(int max_write_buffer_number_to_maintain);
|
||||
|
||||
void Ref();
|
||||
void Unref(autovector<MemTable*>* to_delete = nullptr);
|
||||
|
||||
int size() const;
|
||||
|
||||
// Search all the memtables starting from the most recent one.
|
||||
// Return the most recent value found, if any.
|
||||
//
|
||||
// If any operation was found for this key, its most recent sequence number
|
||||
// will be stored in *seq on success (regardless of whether true/false is
|
||||
// returned). Otherwise, *seq will be set to kMaxSequenceNumber.
|
||||
bool Get(const LookupKey& key, std::string* value, Status* s,
|
||||
MergeContext* merge_context);
|
||||
MergeContext* merge_context, SequenceNumber* seq);
|
||||
|
||||
bool Get(const LookupKey& key, std::string* value, Status* s,
|
||||
MergeContext* merge_context) {
|
||||
SequenceNumber seq;
|
||||
return Get(key, value, s, merge_context, &seq);
|
||||
}
|
||||
|
||||
// Similar to Get(), but searches the Memtable history of memtables that
|
||||
// have already been flushed. Should only be used from in-memory only
|
||||
// queries (such as Transaction validation) as the history may contain
|
||||
// writes that are also present in the SST files.
|
||||
bool GetFromHistory(const LookupKey& key, std::string* value, Status* s,
|
||||
MergeContext* merge_context, SequenceNumber* seq);
|
||||
bool GetFromHistory(const LookupKey& key, std::string* value, Status* s,
|
||||
MergeContext* merge_context) {
|
||||
SequenceNumber seq;
|
||||
return GetFromHistory(key, value, s, merge_context, &seq);
|
||||
}
|
||||
|
||||
void AddIterators(const ReadOptions& options,
|
||||
std::vector<Iterator*>* iterator_list, Arena* arena);
|
||||
@@ -62,15 +84,39 @@ class MemTableListVersion {
|
||||
|
||||
uint64_t GetTotalNumDeletes() const;
|
||||
|
||||
uint64_t ApproximateSize(const Slice& start_ikey, const Slice& end_ikey);
|
||||
|
||||
// Returns the value of MemTable::GetEarliestSequenceNumber() on the most
|
||||
// recent MemTable in this list or kMaxSequenceNumber if the list is empty.
|
||||
// If include_history=true, will also search Memtables in MemTableList
|
||||
// History.
|
||||
SequenceNumber GetEarliestSequenceNumber(bool include_history = false) const;
|
||||
|
||||
private:
|
||||
// REQUIRE: m is mutable memtable
|
||||
void Add(MemTable* m);
|
||||
// REQUIRE: m is mutable memtable
|
||||
void Remove(MemTable* m);
|
||||
// REQUIRE: m is an immutable memtable
|
||||
void Add(MemTable* m, autovector<MemTable*>* to_delete);
|
||||
// REQUIRE: m is an immutable memtable
|
||||
void Remove(MemTable* m, autovector<MemTable*>* to_delete);
|
||||
|
||||
void TrimHistory(autovector<MemTable*>* to_delete);
|
||||
|
||||
bool GetFromList(std::list<MemTable*>* list, const LookupKey& key,
|
||||
std::string* value, Status* s, MergeContext* merge_context,
|
||||
SequenceNumber* seq);
|
||||
|
||||
friend class MemTableList;
|
||||
|
||||
// Immutable MemTables that have not yet been flushed.
|
||||
std::list<MemTable*> memlist_;
|
||||
int size_ = 0;
|
||||
|
||||
// MemTables that have already been flushed
|
||||
// (used during Transaction validation)
|
||||
std::list<MemTable*> memlist_history_;
|
||||
|
||||
// Maximum number of MemTables to keep in memory (including both flushed
|
||||
// and not-yet-flushed tables).
|
||||
const int max_write_buffer_number_to_maintain_;
|
||||
|
||||
int refs_ = 0;
|
||||
};
|
||||
|
||||
@@ -88,10 +134,11 @@ class MemTableListVersion {
|
||||
class MemTableList {
|
||||
public:
|
||||
// A list of memtables.
|
||||
explicit MemTableList(int min_write_buffer_number_to_merge)
|
||||
explicit MemTableList(int min_write_buffer_number_to_merge,
|
||||
int max_write_buffer_number_to_maintain)
|
||||
: imm_flush_needed(false),
|
||||
min_write_buffer_number_to_merge_(min_write_buffer_number_to_merge),
|
||||
current_(new MemTableListVersion()),
|
||||
current_(new MemTableListVersion(max_write_buffer_number_to_maintain)),
|
||||
num_flush_not_started_(0),
|
||||
commit_in_progress_(false),
|
||||
flush_requested_(false) {
|
||||
@@ -108,8 +155,13 @@ class MemTableList {
|
||||
// determine whether there is anything more to start flushing.
|
||||
std::atomic<bool> imm_flush_needed;
|
||||
|
||||
// Returns the total number of memtables in the list
|
||||
int size() const;
|
||||
// Returns the total number of memtables in the list that haven't yet
|
||||
// been flushed and logged.
|
||||
int NumNotFlushed() const;
|
||||
|
||||
// Returns total number of memtables in the list that have been
|
||||
// completely flushed and logged.
|
||||
int NumFlushed() const;
|
||||
|
||||
// Returns true if there is at least one memtable on which flush has
|
||||
// not yet started.
|
||||
@@ -133,7 +185,7 @@ class MemTableList {
|
||||
|
||||
// New memtables are inserted at the front of the list.
|
||||
// Takes ownership of the referenced held on *m by the caller of Add().
|
||||
void Add(MemTable* m);
|
||||
void Add(MemTable* m, autovector<MemTable*>* to_delete);
|
||||
|
||||
// Returns an estimate of the number of bytes of data in use.
|
||||
size_t ApproximateMemoryUsage();
|
||||
@@ -153,7 +205,7 @@ class MemTableList {
|
||||
// DB mutex held
|
||||
void InstallNewVersion();
|
||||
|
||||
int min_write_buffer_number_to_merge_;
|
||||
const int min_write_buffer_number_to_merge_;
|
||||
|
||||
MemTableListVersion* current_;
|
||||
|
||||
|
||||
+240
-47
@@ -3,6 +3,7 @@
|
||||
// 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 <algorithm>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include "db/memtable_list.h"
|
||||
@@ -12,6 +13,7 @@
|
||||
#include "db/writebuffer.h"
|
||||
#include "rocksdb/db.h"
|
||||
#include "rocksdb/status.h"
|
||||
#include "util/string_util.h"
|
||||
#include "util/testharness.h"
|
||||
|
||||
namespace rocksdb {
|
||||
@@ -64,7 +66,7 @@ class MemTableListTest : public testing::Test {
|
||||
EnvOptions env_options;
|
||||
shared_ptr<Cache> table_cache(NewLRUCache(50000, 16));
|
||||
WriteBuffer write_buffer(db_options.db_write_buffer_size);
|
||||
WriteController write_controller;
|
||||
WriteController write_controller(10000000u);
|
||||
|
||||
CreateDB();
|
||||
VersionSet versions(dbname, &db_options, env_options, table_cache.get(),
|
||||
@@ -92,9 +94,9 @@ class MemTableListTest : public testing::Test {
|
||||
|
||||
TEST_F(MemTableListTest, Empty) {
|
||||
// Create an empty MemTableList and validate basic functions.
|
||||
MemTableList list(1);
|
||||
MemTableList list(1, 0);
|
||||
|
||||
ASSERT_EQ(0, list.size());
|
||||
ASSERT_EQ(0, list.NumNotFlushed());
|
||||
ASSERT_FALSE(list.imm_flush_needed.load(std::memory_order_acquire));
|
||||
ASSERT_FALSE(list.IsFlushPending());
|
||||
|
||||
@@ -110,12 +112,15 @@ TEST_F(MemTableListTest, Empty) {
|
||||
TEST_F(MemTableListTest, GetTest) {
|
||||
// Create MemTableList
|
||||
int min_write_buffer_number_to_merge = 2;
|
||||
MemTableList list(min_write_buffer_number_to_merge);
|
||||
int max_write_buffer_number_to_maintain = 0;
|
||||
MemTableList list(min_write_buffer_number_to_merge,
|
||||
max_write_buffer_number_to_maintain);
|
||||
|
||||
SequenceNumber seq = 1;
|
||||
std::string value;
|
||||
Status s;
|
||||
MergeContext merge_context;
|
||||
autovector<MemTable*> to_delete;
|
||||
|
||||
LookupKey lkey("key1", seq);
|
||||
bool found = list.current()->Get(lkey, &value, &s, &merge_context);
|
||||
@@ -129,7 +134,8 @@ TEST_F(MemTableListTest, GetTest) {
|
||||
|
||||
WriteBuffer wb(options.db_write_buffer_size);
|
||||
MemTable* mem =
|
||||
new MemTable(cmp, ioptions, MutableCFOptions(options, ioptions), &wb);
|
||||
new MemTable(cmp, ioptions, MutableCFOptions(options, ioptions), &wb,
|
||||
kMaxSequenceNumber);
|
||||
mem->Ref();
|
||||
|
||||
// Write some keys to this memtable.
|
||||
@@ -158,21 +164,22 @@ TEST_F(MemTableListTest, GetTest) {
|
||||
ASSERT_EQ(1, mem->num_deletes());
|
||||
|
||||
// Add memtable to list
|
||||
list.Add(mem);
|
||||
list.Add(mem, &to_delete);
|
||||
|
||||
SequenceNumber saved_seq = seq;
|
||||
|
||||
// Create another memtable and write some keys to it
|
||||
WriteBuffer wb2(options.db_write_buffer_size);
|
||||
MemTable* mem2 =
|
||||
new MemTable(cmp, ioptions, MutableCFOptions(options, ioptions), &wb2);
|
||||
new MemTable(cmp, ioptions, MutableCFOptions(options, ioptions), &wb2,
|
||||
kMaxSequenceNumber);
|
||||
mem2->Ref();
|
||||
|
||||
mem2->Add(++seq, kTypeDeletion, "key1", "");
|
||||
mem2->Add(++seq, kTypeValue, "key2", "value2.3");
|
||||
|
||||
// Add second memtable to list
|
||||
list.Add(mem2);
|
||||
list.Add(mem2, &to_delete);
|
||||
|
||||
// Fetch keys via MemTableList
|
||||
merge_context.Clear();
|
||||
@@ -196,15 +203,190 @@ TEST_F(MemTableListTest, GetTest) {
|
||||
found = list.current()->Get(LookupKey("key2", 1), &value, &s, &merge_context);
|
||||
ASSERT_FALSE(found);
|
||||
|
||||
ASSERT_EQ(2, list.size());
|
||||
ASSERT_EQ(2, list.NumNotFlushed());
|
||||
|
||||
autovector<MemTable*> to_delete;
|
||||
list.current()->Unref(&to_delete);
|
||||
for (MemTable* m : to_delete) {
|
||||
delete m;
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(MemTableListTest, GetFromHistoryTest) {
|
||||
// Create MemTableList
|
||||
int min_write_buffer_number_to_merge = 2;
|
||||
int max_write_buffer_number_to_maintain = 2;
|
||||
MemTableList list(min_write_buffer_number_to_merge,
|
||||
max_write_buffer_number_to_maintain);
|
||||
|
||||
SequenceNumber seq = 1;
|
||||
std::string value;
|
||||
Status s;
|
||||
MergeContext merge_context;
|
||||
autovector<MemTable*> to_delete;
|
||||
|
||||
LookupKey lkey("key1", seq);
|
||||
bool found = list.current()->Get(lkey, &value, &s, &merge_context);
|
||||
ASSERT_FALSE(found);
|
||||
|
||||
// Create a MemTable
|
||||
InternalKeyComparator cmp(BytewiseComparator());
|
||||
auto factory = std::make_shared<SkipListFactory>();
|
||||
options.memtable_factory = factory;
|
||||
ImmutableCFOptions ioptions(options);
|
||||
|
||||
WriteBuffer wb(options.db_write_buffer_size);
|
||||
MemTable* mem =
|
||||
new MemTable(cmp, ioptions, MutableCFOptions(options, ioptions), &wb,
|
||||
kMaxSequenceNumber);
|
||||
mem->Ref();
|
||||
|
||||
// Write some keys to this memtable.
|
||||
mem->Add(++seq, kTypeDeletion, "key1", "");
|
||||
mem->Add(++seq, kTypeValue, "key2", "value2");
|
||||
mem->Add(++seq, kTypeValue, "key2", "value2.2");
|
||||
|
||||
// Fetch the newly written keys
|
||||
merge_context.Clear();
|
||||
found = mem->Get(LookupKey("key1", seq), &value, &s, &merge_context);
|
||||
// MemTable found out that this key is *not* found (at this sequence#)
|
||||
ASSERT_TRUE(found && s.IsNotFound());
|
||||
|
||||
merge_context.Clear();
|
||||
found = mem->Get(LookupKey("key2", seq), &value, &s, &merge_context);
|
||||
ASSERT_TRUE(s.ok() && found);
|
||||
ASSERT_EQ(value, "value2.2");
|
||||
|
||||
// Add memtable to list
|
||||
list.Add(mem, &to_delete);
|
||||
ASSERT_EQ(0, to_delete.size());
|
||||
|
||||
// Fetch keys via MemTableList
|
||||
merge_context.Clear();
|
||||
found =
|
||||
list.current()->Get(LookupKey("key1", seq), &value, &s, &merge_context);
|
||||
ASSERT_TRUE(found && s.IsNotFound());
|
||||
|
||||
merge_context.Clear();
|
||||
found =
|
||||
list.current()->Get(LookupKey("key2", seq), &value, &s, &merge_context);
|
||||
ASSERT_TRUE(s.ok() && found);
|
||||
ASSERT_EQ("value2.2", value);
|
||||
|
||||
// Flush this memtable from the list.
|
||||
// (It will then be a part of the memtable history).
|
||||
autovector<MemTable*> to_flush;
|
||||
list.PickMemtablesToFlush(&to_flush);
|
||||
ASSERT_EQ(1, to_flush.size());
|
||||
|
||||
s = Mock_InstallMemtableFlushResults(
|
||||
&list, MutableCFOptions(options, ioptions), to_flush, &to_delete);
|
||||
ASSERT_OK(s);
|
||||
ASSERT_EQ(0, list.NumNotFlushed());
|
||||
ASSERT_EQ(1, list.NumFlushed());
|
||||
ASSERT_EQ(0, to_delete.size());
|
||||
|
||||
// Verify keys are no longer in MemTableList
|
||||
merge_context.Clear();
|
||||
found =
|
||||
list.current()->Get(LookupKey("key1", seq), &value, &s, &merge_context);
|
||||
ASSERT_FALSE(found);
|
||||
|
||||
merge_context.Clear();
|
||||
found =
|
||||
list.current()->Get(LookupKey("key2", seq), &value, &s, &merge_context);
|
||||
ASSERT_FALSE(found);
|
||||
|
||||
// Verify keys are present in history
|
||||
merge_context.Clear();
|
||||
found = list.current()->GetFromHistory(LookupKey("key1", seq), &value, &s,
|
||||
&merge_context);
|
||||
ASSERT_TRUE(found && s.IsNotFound());
|
||||
|
||||
merge_context.Clear();
|
||||
found = list.current()->GetFromHistory(LookupKey("key2", seq), &value, &s,
|
||||
&merge_context);
|
||||
ASSERT_TRUE(found);
|
||||
ASSERT_EQ("value2.2", value);
|
||||
|
||||
// Create another memtable and write some keys to it
|
||||
WriteBuffer wb2(options.db_write_buffer_size);
|
||||
MemTable* mem2 =
|
||||
new MemTable(cmp, ioptions, MutableCFOptions(options, ioptions), &wb2,
|
||||
kMaxSequenceNumber);
|
||||
mem2->Ref();
|
||||
|
||||
mem2->Add(++seq, kTypeDeletion, "key1", "");
|
||||
mem2->Add(++seq, kTypeValue, "key3", "value3");
|
||||
|
||||
// Add second memtable to list
|
||||
list.Add(mem2, &to_delete);
|
||||
ASSERT_EQ(0, to_delete.size());
|
||||
|
||||
to_flush.clear();
|
||||
list.PickMemtablesToFlush(&to_flush);
|
||||
ASSERT_EQ(1, to_flush.size());
|
||||
|
||||
// Flush second memtable
|
||||
s = Mock_InstallMemtableFlushResults(
|
||||
&list, MutableCFOptions(options, ioptions), to_flush, &to_delete);
|
||||
ASSERT_OK(s);
|
||||
ASSERT_EQ(0, list.NumNotFlushed());
|
||||
ASSERT_EQ(2, list.NumFlushed());
|
||||
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);
|
||||
MemTable* mem3 =
|
||||
new MemTable(cmp, ioptions, MutableCFOptions(options, ioptions), &wb3,
|
||||
kMaxSequenceNumber);
|
||||
mem3->Ref();
|
||||
list.Add(mem3, &to_delete);
|
||||
ASSERT_EQ(1, list.NumNotFlushed());
|
||||
ASSERT_EQ(1, list.NumFlushed());
|
||||
ASSERT_EQ(1, to_delete.size());
|
||||
|
||||
// Verify keys are no longer in MemTableList
|
||||
merge_context.Clear();
|
||||
found =
|
||||
list.current()->Get(LookupKey("key1", seq), &value, &s, &merge_context);
|
||||
ASSERT_FALSE(found);
|
||||
|
||||
merge_context.Clear();
|
||||
found =
|
||||
list.current()->Get(LookupKey("key2", seq), &value, &s, &merge_context);
|
||||
ASSERT_FALSE(found);
|
||||
|
||||
merge_context.Clear();
|
||||
found =
|
||||
list.current()->Get(LookupKey("key3", seq), &value, &s, &merge_context);
|
||||
ASSERT_FALSE(found);
|
||||
|
||||
// Verify that the second memtable's keys are in the history
|
||||
merge_context.Clear();
|
||||
found = list.current()->GetFromHistory(LookupKey("key1", seq), &value, &s,
|
||||
&merge_context);
|
||||
ASSERT_TRUE(found && s.IsNotFound());
|
||||
|
||||
merge_context.Clear();
|
||||
found = list.current()->GetFromHistory(LookupKey("key3", seq), &value, &s,
|
||||
&merge_context);
|
||||
ASSERT_TRUE(found);
|
||||
ASSERT_EQ("value3", value);
|
||||
|
||||
// Verify that key2 from the first memtable is no longer in the history
|
||||
merge_context.Clear();
|
||||
found =
|
||||
list.current()->Get(LookupKey("key2", seq), &value, &s, &merge_context);
|
||||
ASSERT_FALSE(found);
|
||||
|
||||
// Cleanup
|
||||
list.current()->Unref(&to_delete);
|
||||
ASSERT_EQ(3, to_delete.size());
|
||||
for (MemTable* m : to_delete) {
|
||||
delete m;
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(MemTableListTest, FlushPendingTest) {
|
||||
const int num_tables = 5;
|
||||
SequenceNumber seq = 1;
|
||||
@@ -215,26 +397,30 @@ TEST_F(MemTableListTest, FlushPendingTest) {
|
||||
ImmutableCFOptions ioptions(options);
|
||||
InternalKeyComparator cmp(BytewiseComparator());
|
||||
WriteBuffer wb(options.db_write_buffer_size);
|
||||
autovector<MemTable*> to_delete;
|
||||
|
||||
// Create MemTableList
|
||||
int min_write_buffer_number_to_merge = 3;
|
||||
MemTableList list(min_write_buffer_number_to_merge);
|
||||
int max_write_buffer_number_to_maintain = 7;
|
||||
MemTableList list(min_write_buffer_number_to_merge,
|
||||
max_write_buffer_number_to_maintain);
|
||||
|
||||
// Create some MemTables
|
||||
std::vector<MemTable*> tables;
|
||||
MutableCFOptions mutable_cf_options(options, ioptions);
|
||||
for (int i = 0; i < num_tables; i++) {
|
||||
MemTable* mem = new MemTable(cmp, ioptions, mutable_cf_options, &wb);
|
||||
MemTable* mem = new MemTable(cmp, ioptions, mutable_cf_options, &wb,
|
||||
kMaxSequenceNumber);
|
||||
mem->Ref();
|
||||
|
||||
std::string value;
|
||||
MergeContext merge_context;
|
||||
|
||||
mem->Add(++seq, kTypeValue, "key1", std::to_string(i));
|
||||
mem->Add(++seq, kTypeValue, "keyN" + std::to_string(i), "valueN");
|
||||
mem->Add(++seq, kTypeValue, "keyX" + std::to_string(i), "value");
|
||||
mem->Add(++seq, kTypeValue, "keyM" + std::to_string(i), "valueM");
|
||||
mem->Add(++seq, kTypeDeletion, "keyX" + std::to_string(i), "");
|
||||
mem->Add(++seq, kTypeValue, "key1", ToString(i));
|
||||
mem->Add(++seq, kTypeValue, "keyN" + ToString(i), "valueN");
|
||||
mem->Add(++seq, kTypeValue, "keyX" + ToString(i), "value");
|
||||
mem->Add(++seq, kTypeValue, "keyM" + ToString(i), "valueM");
|
||||
mem->Add(++seq, kTypeDeletion, "keyX" + ToString(i), "");
|
||||
|
||||
tables.push_back(mem);
|
||||
}
|
||||
@@ -264,9 +450,10 @@ TEST_F(MemTableListTest, FlushPendingTest) {
|
||||
ASSERT_FALSE(list.imm_flush_needed.load(std::memory_order_acquire));
|
||||
|
||||
// Add 2 tables
|
||||
list.Add(tables[0]);
|
||||
list.Add(tables[1]);
|
||||
ASSERT_EQ(2, list.size());
|
||||
list.Add(tables[0], &to_delete);
|
||||
list.Add(tables[1], &to_delete);
|
||||
ASSERT_EQ(2, list.NumNotFlushed());
|
||||
ASSERT_EQ(0, to_delete.size());
|
||||
|
||||
// Even though we have less than the minimum to flush, a flush is
|
||||
// pending since we had previously requested a flush and never called
|
||||
@@ -277,7 +464,7 @@ TEST_F(MemTableListTest, FlushPendingTest) {
|
||||
// Pick tables to flush
|
||||
list.PickMemtablesToFlush(&to_flush);
|
||||
ASSERT_EQ(2, to_flush.size());
|
||||
ASSERT_EQ(2, list.size());
|
||||
ASSERT_EQ(2, list.NumNotFlushed());
|
||||
ASSERT_FALSE(list.IsFlushPending());
|
||||
ASSERT_FALSE(list.imm_flush_needed.load(std::memory_order_acquire));
|
||||
|
||||
@@ -288,16 +475,17 @@ TEST_F(MemTableListTest, FlushPendingTest) {
|
||||
to_flush.clear();
|
||||
|
||||
// Add another table
|
||||
list.Add(tables[2]);
|
||||
list.Add(tables[2], &to_delete);
|
||||
// We now have the minimum to flush regardles of whether FlushRequested()
|
||||
// was called.
|
||||
ASSERT_TRUE(list.IsFlushPending());
|
||||
ASSERT_TRUE(list.imm_flush_needed.load(std::memory_order_acquire));
|
||||
ASSERT_EQ(0, to_delete.size());
|
||||
|
||||
// Pick tables to flush
|
||||
list.PickMemtablesToFlush(&to_flush);
|
||||
ASSERT_EQ(3, to_flush.size());
|
||||
ASSERT_EQ(3, list.size());
|
||||
ASSERT_EQ(3, list.NumNotFlushed());
|
||||
ASSERT_FALSE(list.IsFlushPending());
|
||||
ASSERT_FALSE(list.imm_flush_needed.load(std::memory_order_acquire));
|
||||
|
||||
@@ -305,14 +493,15 @@ TEST_F(MemTableListTest, FlushPendingTest) {
|
||||
autovector<MemTable*> to_flush2;
|
||||
list.PickMemtablesToFlush(&to_flush2);
|
||||
ASSERT_EQ(0, to_flush2.size());
|
||||
ASSERT_EQ(3, list.size());
|
||||
ASSERT_EQ(3, list.NumNotFlushed());
|
||||
ASSERT_FALSE(list.IsFlushPending());
|
||||
ASSERT_FALSE(list.imm_flush_needed.load(std::memory_order_acquire));
|
||||
|
||||
// Add another table
|
||||
list.Add(tables[3]);
|
||||
list.Add(tables[3], &to_delete);
|
||||
ASSERT_FALSE(list.IsFlushPending());
|
||||
ASSERT_TRUE(list.imm_flush_needed.load(std::memory_order_acquire));
|
||||
ASSERT_EQ(0, to_delete.size());
|
||||
|
||||
// Request a flush again
|
||||
list.FlushRequested();
|
||||
@@ -322,7 +511,7 @@ TEST_F(MemTableListTest, FlushPendingTest) {
|
||||
// Pick tables to flush again
|
||||
list.PickMemtablesToFlush(&to_flush2);
|
||||
ASSERT_EQ(1, to_flush2.size());
|
||||
ASSERT_EQ(4, list.size());
|
||||
ASSERT_EQ(4, list.NumNotFlushed());
|
||||
ASSERT_FALSE(list.IsFlushPending());
|
||||
ASSERT_FALSE(list.imm_flush_needed.load(std::memory_order_acquire));
|
||||
|
||||
@@ -333,29 +522,28 @@ TEST_F(MemTableListTest, FlushPendingTest) {
|
||||
to_flush.clear();
|
||||
|
||||
// Add another tables
|
||||
list.Add(tables[4]);
|
||||
ASSERT_EQ(5, list.size());
|
||||
list.Add(tables[4], &to_delete);
|
||||
ASSERT_EQ(5, list.NumNotFlushed());
|
||||
// We now have the minimum to flush regardles of whether FlushRequested()
|
||||
ASSERT_TRUE(list.IsFlushPending());
|
||||
ASSERT_TRUE(list.imm_flush_needed.load(std::memory_order_acquire));
|
||||
ASSERT_EQ(0, to_delete.size());
|
||||
|
||||
// Pick tables to flush
|
||||
list.PickMemtablesToFlush(&to_flush);
|
||||
// Should pick 4 of 5 since 1 table has been picked in to_flush2
|
||||
ASSERT_EQ(4, to_flush.size());
|
||||
ASSERT_EQ(5, list.size());
|
||||
ASSERT_EQ(5, list.NumNotFlushed());
|
||||
ASSERT_FALSE(list.IsFlushPending());
|
||||
ASSERT_FALSE(list.imm_flush_needed.load(std::memory_order_acquire));
|
||||
|
||||
// Pick tables to flush again
|
||||
autovector<MemTable*> to_flush3;
|
||||
ASSERT_EQ(0, to_flush3.size()); // nothing not in progress of being flushed
|
||||
ASSERT_EQ(5, list.size());
|
||||
ASSERT_EQ(5, list.NumNotFlushed());
|
||||
ASSERT_FALSE(list.IsFlushPending());
|
||||
ASSERT_FALSE(list.imm_flush_needed.load(std::memory_order_acquire));
|
||||
|
||||
autovector<MemTable*> to_delete;
|
||||
|
||||
// Flush the 4 memtables that were picked in to_flush
|
||||
s = Mock_InstallMemtableFlushResults(
|
||||
&list, MutableCFOptions(options, ioptions), to_flush, &to_delete);
|
||||
@@ -366,17 +554,10 @@ TEST_F(MemTableListTest, FlushPendingTest) {
|
||||
// Current implementation will only commit memtables in the order they were
|
||||
// created. So InstallMemtableFlushResults will install the first 3 tables
|
||||
// in to_flush and stop when it encounters a table not yet flushed.
|
||||
ASSERT_EQ(3, to_delete.size());
|
||||
ASSERT_EQ(2, list.size());
|
||||
|
||||
for (const auto& m : to_delete) {
|
||||
// Refcount should be 0 after calling InstallMemtableFlushResults.
|
||||
// Verify this, by Ref'ing then UnRef'ing:
|
||||
m->Ref();
|
||||
ASSERT_EQ(m, m->Unref());
|
||||
delete m;
|
||||
}
|
||||
to_delete.clear();
|
||||
ASSERT_EQ(2, list.NumNotFlushed());
|
||||
int num_in_history = std::min(3, max_write_buffer_number_to_maintain);
|
||||
ASSERT_EQ(num_in_history, list.NumFlushed());
|
||||
ASSERT_EQ(5 - list.NumNotFlushed() - num_in_history, to_delete.size());
|
||||
|
||||
// Request a flush again. Should be nothing to flush
|
||||
list.FlushRequested();
|
||||
@@ -388,10 +569,12 @@ TEST_F(MemTableListTest, FlushPendingTest) {
|
||||
&list, MutableCFOptions(options, ioptions), to_flush2, &to_delete);
|
||||
ASSERT_OK(s);
|
||||
|
||||
// This will actually intall 2 tables. The 1 we told it to flush, and also
|
||||
// This will actually install 2 tables. The 1 we told it to flush, and also
|
||||
// tables[4] which has been waiting for tables[3] to commit.
|
||||
ASSERT_EQ(2, to_delete.size());
|
||||
ASSERT_EQ(0, list.size());
|
||||
ASSERT_EQ(0, list.NumNotFlushed());
|
||||
num_in_history = std::min(5, max_write_buffer_number_to_maintain);
|
||||
ASSERT_EQ(num_in_history, list.NumFlushed());
|
||||
ASSERT_EQ(5 - list.NumNotFlushed() - num_in_history, to_delete.size());
|
||||
|
||||
for (const auto& m : to_delete) {
|
||||
// Refcount should be 0 after calling InstallMemtableFlushResults.
|
||||
@@ -403,7 +586,17 @@ TEST_F(MemTableListTest, FlushPendingTest) {
|
||||
to_delete.clear();
|
||||
|
||||
list.current()->Unref(&to_delete);
|
||||
ASSERT_EQ(0, to_delete.size());
|
||||
int to_delete_size = std::min(5, max_write_buffer_number_to_maintain);
|
||||
ASSERT_EQ(to_delete_size, to_delete.size());
|
||||
|
||||
for (const auto& m : to_delete) {
|
||||
// Refcount should be 0 after calling InstallMemtableFlushResults.
|
||||
// Verify this, by Ref'ing then UnRef'ing:
|
||||
m->Ref();
|
||||
ASSERT_EQ(m, m->Unref());
|
||||
delete m;
|
||||
}
|
||||
to_delete.clear();
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
+12
-8
@@ -3,14 +3,16 @@
|
||||
// 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 <stdio.h>
|
||||
#include <string>
|
||||
|
||||
#include "merge_helper.h"
|
||||
#include "db/dbformat.h"
|
||||
#include "rocksdb/comparator.h"
|
||||
#include "rocksdb/db.h"
|
||||
#include "rocksdb/merge_operator.h"
|
||||
#include "util/statistics.h"
|
||||
#include <string>
|
||||
#include <stdio.h>
|
||||
#include "util/perf_context_imp.h"
|
||||
#include "util/stop_watch.h"
|
||||
|
||||
@@ -79,7 +81,6 @@ void MergeHelper::MergeUntil(Iterator* iter, SequenceNumber stop_before,
|
||||
ParseInternalKey(keys_.back(), &orig_ikey);
|
||||
|
||||
bool hit_the_next_user_key = false;
|
||||
std::string merge_result; // Temporary value for merge results
|
||||
if (steps) {
|
||||
++(*steps);
|
||||
}
|
||||
@@ -116,6 +117,7 @@ void MergeHelper::MergeUntil(Iterator* iter, SequenceNumber stop_before,
|
||||
// => change the entry type to kTypeValue for keys_.back()
|
||||
// We are done! Return a success if the merge passes.
|
||||
|
||||
std::string merge_result;
|
||||
Status s = TimedFullMerge(ikey.user_key, nullptr, operands_,
|
||||
user_merge_operator_, stats, env_, logger_,
|
||||
&merge_result);
|
||||
@@ -128,7 +130,7 @@ void MergeHelper::MergeUntil(Iterator* iter, SequenceNumber stop_before,
|
||||
orig_ikey.type = kTypeValue;
|
||||
UpdateInternalKey(&original_key[0], original_key.size(),
|
||||
orig_ikey.sequence, orig_ikey.type);
|
||||
swap(operands_.back(), merge_result);
|
||||
operands_.back() = std::move(merge_result);
|
||||
}
|
||||
|
||||
// move iter to the next entry (before doing anything else)
|
||||
@@ -146,6 +148,7 @@ void MergeHelper::MergeUntil(Iterator* iter, SequenceNumber stop_before,
|
||||
// => change the entry type to kTypeValue for keys_.back()
|
||||
// We are done! Success!
|
||||
const Slice val = iter->value();
|
||||
std::string merge_result;
|
||||
Status s =
|
||||
TimedFullMerge(ikey.user_key, &val, operands_, user_merge_operator_,
|
||||
stats, env_, logger_, &merge_result);
|
||||
@@ -158,7 +161,7 @@ void MergeHelper::MergeUntil(Iterator* iter, SequenceNumber stop_before,
|
||||
orig_ikey.type = kTypeValue;
|
||||
UpdateInternalKey(&original_key[0], original_key.size(),
|
||||
orig_ikey.sequence, orig_ikey.type);
|
||||
swap(operands_.back(), merge_result);
|
||||
operands_.back() = std::move(merge_result);
|
||||
}
|
||||
|
||||
// move iter to the next entry
|
||||
@@ -208,6 +211,7 @@ void MergeHelper::MergeUntil(Iterator* iter, SequenceNumber stop_before,
|
||||
assert(kTypeMerge == orig_ikey.type);
|
||||
assert(operands_.size() >= 1);
|
||||
assert(operands_.size() == keys_.size());
|
||||
std::string merge_result;
|
||||
{
|
||||
StopWatchNano timer(env_, stats != nullptr);
|
||||
PERF_TIMER_GUARD(merge_operator_time_nanos);
|
||||
@@ -222,8 +226,7 @@ void MergeHelper::MergeUntil(Iterator* iter, SequenceNumber stop_before,
|
||||
UpdateInternalKey(&original_key[0], original_key.size(),
|
||||
orig_ikey.sequence, orig_ikey.type);
|
||||
|
||||
// The final value() is always stored in operands_.back()
|
||||
swap(operands_.back(),merge_result);
|
||||
operands_.back() = std::move(merge_result);
|
||||
} else {
|
||||
RecordTick(stats, NUMBER_MERGE_FAILURES);
|
||||
// Do nothing if not success_. Leave keys() and operands() as they are.
|
||||
@@ -236,6 +239,7 @@ void MergeHelper::MergeUntil(Iterator* iter, SequenceNumber stop_before,
|
||||
if (operands_.size() >= 2 &&
|
||||
operands_.size() >= min_partial_merge_operands_) {
|
||||
bool merge_success = false;
|
||||
std::string merge_result;
|
||||
{
|
||||
StopWatchNano timer(env_, stats != nullptr);
|
||||
PERF_TIMER_GUARD(merge_operator_time_nanos);
|
||||
@@ -250,7 +254,7 @@ void MergeHelper::MergeUntil(Iterator* iter, SequenceNumber stop_before,
|
||||
// Merging of operands (associative merge) was successful.
|
||||
// Replace operands with the merge result
|
||||
operands_.clear();
|
||||
operands_.push_front(std::move(merge_result));
|
||||
operands_.emplace_front(std::move(merge_result));
|
||||
keys_.erase(keys_.begin(), keys_.end() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,11 +20,11 @@ bool MergeOperator::PartialMergeMulti(const Slice& key,
|
||||
Logger* logger) const {
|
||||
assert(operand_list.size() >= 2);
|
||||
// Simply loop through the operands
|
||||
std::string temp_value;
|
||||
Slice temp_slice(operand_list[0]);
|
||||
|
||||
for (size_t i = 1; i < operand_list.size(); ++i) {
|
||||
auto& operand = operand_list[i];
|
||||
std::string temp_value;
|
||||
if (!PartialMerge(key, temp_slice, operand, &temp_value, logger)) {
|
||||
return false;
|
||||
}
|
||||
@@ -48,9 +48,9 @@ bool AssociativeMergeOperator::FullMerge(
|
||||
|
||||
// Simply loop through the operands
|
||||
Slice temp_existing;
|
||||
std::string temp_value;
|
||||
for (const auto& operand : operand_list) {
|
||||
Slice value(operand);
|
||||
std::string temp_value;
|
||||
if (!Merge(key, existing_value, value, &temp_value, logger)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
+8
-4
@@ -7,6 +7,7 @@
|
||||
#include <memory>
|
||||
#include <iostream>
|
||||
|
||||
#include "port/stack_trace.h"
|
||||
#include "rocksdb/cache.h"
|
||||
#include "rocksdb/comparator.h"
|
||||
#include "rocksdb/db.h"
|
||||
@@ -41,6 +42,7 @@ class CountMergeOperator : public AssociativeMergeOperator {
|
||||
const Slice& value,
|
||||
std::string* new_value,
|
||||
Logger* logger) const override {
|
||||
assert(new_value->empty());
|
||||
++num_merge_operator_calls;
|
||||
if (existing_value == nullptr) {
|
||||
new_value->assign(value.data(), value.size());
|
||||
@@ -59,6 +61,7 @@ class CountMergeOperator : public AssociativeMergeOperator {
|
||||
const std::deque<Slice>& operand_list,
|
||||
std::string* new_value,
|
||||
Logger* logger) const override {
|
||||
assert(new_value->empty());
|
||||
++num_partial_merge_calls;
|
||||
return mergeOperator_->PartialMergeMulti(key, operand_list, new_value,
|
||||
logger);
|
||||
@@ -294,7 +297,7 @@ void testCounters(Counters& counters, DB* db, bool test_compaction) {
|
||||
db->Flush(o);
|
||||
|
||||
cout << "Compaction started ...\n";
|
||||
db->CompactRange(nullptr, nullptr);
|
||||
db->CompactRange(CompactRangeOptions(), nullptr, nullptr);
|
||||
cout << "Compaction ended\n";
|
||||
|
||||
dumpDb(db);
|
||||
@@ -341,7 +344,7 @@ void testPartialMerge(Counters* counters, DB* db, size_t max_merge,
|
||||
tmp_sum += i;
|
||||
}
|
||||
db->Flush(o);
|
||||
db->CompactRange(nullptr, nullptr);
|
||||
db->CompactRange(CompactRangeOptions(), nullptr, nullptr);
|
||||
ASSERT_EQ(tmp_sum, counters->assert_get("b"));
|
||||
if (count > max_merge) {
|
||||
// in this case, FullMerge should be called instead.
|
||||
@@ -360,7 +363,7 @@ void testPartialMerge(Counters* counters, DB* db, size_t max_merge,
|
||||
tmp_sum += i;
|
||||
}
|
||||
db->Flush(o);
|
||||
db->CompactRange(nullptr, nullptr);
|
||||
db->CompactRange(CompactRangeOptions(), nullptr, nullptr);
|
||||
ASSERT_EQ(tmp_sum, counters->assert_get("c"));
|
||||
ASSERT_EQ(num_partial_merge_calls, 0U);
|
||||
}
|
||||
@@ -467,7 +470,7 @@ void runTest(int argc, const string& dbname, const bool use_ttl = false) {
|
||||
counters.add("test-key", 1);
|
||||
counters.add("test-key", 1);
|
||||
counters.add("test-key", 1);
|
||||
db->CompactRange(nullptr, nullptr);
|
||||
db->CompactRange(CompactRangeOptions(), nullptr, nullptr);
|
||||
}
|
||||
|
||||
DB* reopen_db;
|
||||
@@ -498,6 +501,7 @@ void runTest(int argc, const string& dbname, const bool use_ttl = false) {
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
//TODO: Make this test like a general rocksdb unit-test
|
||||
rocksdb::port::InstallStackTraceHandler();
|
||||
runTest(argc, test::TmpDir() + "/merge_testdb");
|
||||
runTest(argc, test::TmpDir() + "/merge_testdbttl", true); // Run test on TTL database
|
||||
printf("Passed all tests!\n");
|
||||
|
||||
+5
-3
@@ -254,8 +254,9 @@ class Repairer {
|
||||
Slice record;
|
||||
WriteBatch batch;
|
||||
WriteBuffer wb(options_.db_write_buffer_size);
|
||||
MemTable* mem = new MemTable(icmp_, ioptions_,
|
||||
MutableCFOptions(options_, ioptions_), &wb);
|
||||
MemTable* mem =
|
||||
new MemTable(icmp_, ioptions_, MutableCFOptions(options_, ioptions_),
|
||||
&wb, kMaxSequenceNumber);
|
||||
auto cf_mems_default = new ColumnFamilyMemTablesDefault(mem);
|
||||
mem->Ref();
|
||||
int counter = 0;
|
||||
@@ -400,7 +401,8 @@ class Repairer {
|
||||
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.min_sequence, t.max_sequence,
|
||||
t.meta.marked_for_compaction);
|
||||
}
|
||||
|
||||
//fprintf(stderr, "NewDescriptor:\n%s\n", edit_.DebugString().c_str());
|
||||
|
||||
+30
-3
@@ -59,6 +59,9 @@ class SkipList {
|
||||
// Returns true iff an entry that compares equal to key is in the list.
|
||||
bool Contains(const Key& key) const;
|
||||
|
||||
// Return estimated number of entries smaller than `key`.
|
||||
uint64_t EstimateCount(const Key& key) const;
|
||||
|
||||
// Iteration over the contents of a skip list
|
||||
class Iterator {
|
||||
public:
|
||||
@@ -354,10 +357,34 @@ typename SkipList<Key, Comparator>::Node* SkipList<Key, Comparator>::FindLast()
|
||||
}
|
||||
}
|
||||
|
||||
template<typename Key, class Comparator>
|
||||
template <typename Key, class Comparator>
|
||||
uint64_t SkipList<Key, Comparator>::EstimateCount(const Key& key) const {
|
||||
uint64_t count = 0;
|
||||
|
||||
Node* x = head_;
|
||||
int level = GetMaxHeight() - 1;
|
||||
while (true) {
|
||||
assert(x == head_ || compare_(x->key, key) < 0);
|
||||
Node* next = x->Next(level);
|
||||
if (next == nullptr || compare_(next->key, key) >= 0) {
|
||||
if (level == 0) {
|
||||
return count;
|
||||
} else {
|
||||
// Switch to next list
|
||||
count *= kBranching_;
|
||||
level--;
|
||||
}
|
||||
} else {
|
||||
x = next;
|
||||
count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Key, class Comparator>
|
||||
SkipList<Key, Comparator>::SkipList(const Comparator cmp, Allocator* allocator,
|
||||
int32_t max_height,
|
||||
int32_t branching_factor)
|
||||
int32_t max_height,
|
||||
int32_t branching_factor)
|
||||
: kMaxHeight_(max_height),
|
||||
kBranching_(branching_factor),
|
||||
compare_(cmp),
|
||||
|
||||
+3
-3
@@ -49,8 +49,8 @@ class SnapshotList {
|
||||
SnapshotImpl* oldest() const { assert(!empty()); return list_.next_; }
|
||||
SnapshotImpl* newest() const { assert(!empty()); return list_.prev_; }
|
||||
|
||||
const SnapshotImpl* New(SequenceNumber seq, uint64_t unix_time) {
|
||||
SnapshotImpl* s = new SnapshotImpl;
|
||||
const SnapshotImpl* New(SnapshotImpl* s, SequenceNumber seq,
|
||||
uint64_t unix_time) {
|
||||
s->number_ = seq;
|
||||
s->unix_time_ = unix_time;
|
||||
s->list_ = this;
|
||||
@@ -62,12 +62,12 @@ class SnapshotList {
|
||||
return s;
|
||||
}
|
||||
|
||||
// Do not responsible to free the object.
|
||||
void Delete(const SnapshotImpl* s) {
|
||||
assert(s->list_ == this);
|
||||
s->prev_->next_ = s->next_;
|
||||
s->next_->prev_ = s->prev_;
|
||||
count_--;
|
||||
delete s;
|
||||
}
|
||||
|
||||
// retrieve all snapshot numbers. They are sorted in ascending order.
|
||||
|
||||
+83
-7
@@ -9,6 +9,7 @@
|
||||
|
||||
#include "db/table_cache.h"
|
||||
|
||||
#include "db/dbformat.h"
|
||||
#include "db/filename.h"
|
||||
#include "db/version_edit.h"
|
||||
|
||||
@@ -21,9 +22,12 @@
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
namespace {
|
||||
|
||||
template <class T>
|
||||
static void DeleteEntry(const Slice& key, void* value) {
|
||||
TableReader* table_reader = reinterpret_cast<TableReader*>(value);
|
||||
delete table_reader;
|
||||
T* typed_value = reinterpret_cast<T*>(value);
|
||||
delete typed_value;
|
||||
}
|
||||
|
||||
static void UnrefEntry(void* arg1, void* arg2) {
|
||||
@@ -37,11 +41,27 @@ static Slice GetSliceForFileNumber(const uint64_t* file_number) {
|
||||
sizeof(*file_number));
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
|
||||
void AppendVarint64(IterKey* key, uint64_t v) {
|
||||
char buf[10];
|
||||
auto ptr = EncodeVarint64(buf, v);
|
||||
key->TrimAppend(key->Size(), buf, ptr - buf);
|
||||
}
|
||||
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
} // namespace
|
||||
|
||||
TableCache::TableCache(const ImmutableCFOptions& ioptions,
|
||||
const EnvOptions& env_options, Cache* const cache)
|
||||
: ioptions_(ioptions),
|
||||
env_options_(env_options),
|
||||
cache_(cache) {}
|
||||
: ioptions_(ioptions), env_options_(env_options), cache_(cache) {
|
||||
if (ioptions_.row_cache) {
|
||||
// If the same cache is shared by multiple instances, we need to
|
||||
// disambiguate its entries.
|
||||
PutVarint64(&row_cache_id_, ioptions_.row_cache->NewId());
|
||||
}
|
||||
}
|
||||
|
||||
TableCache::~TableCache() {
|
||||
}
|
||||
@@ -88,7 +108,8 @@ Status TableCache::FindTable(const EnvOptions& env_options,
|
||||
// We do not cache error results so that if the error is transient,
|
||||
// or somebody repairs the file, we recover automatically.
|
||||
} else {
|
||||
*handle = cache_->Insert(key, table_reader.release(), 1, &DeleteEntry);
|
||||
*handle = cache_->Insert(key, table_reader.release(), 1,
|
||||
&DeleteEntry<TableReader>);
|
||||
}
|
||||
}
|
||||
return s;
|
||||
@@ -137,6 +158,46 @@ Status TableCache::Get(const ReadOptions& options,
|
||||
TableReader* t = fd.table_reader;
|
||||
Status s;
|
||||
Cache::Handle* handle = nullptr;
|
||||
std::string* row_cache_entry = nullptr;
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
IterKey row_cache_key;
|
||||
std::string row_cache_entry_buffer;
|
||||
|
||||
if (ioptions_.row_cache) {
|
||||
uint64_t fd_number = fd.GetNumber();
|
||||
auto user_key = ExtractUserKey(k);
|
||||
// We use the user key as cache key instead of the internal key,
|
||||
// otherwise the whole cache would be invalidated every time the
|
||||
// sequence key increases. However, to support caching snapshot
|
||||
// reads, we append the sequence number (incremented by 1 to
|
||||
// distinguish from 0) only in this case.
|
||||
uint64_t seq_no =
|
||||
options.snapshot == nullptr ? 0 : 1 + GetInternalKeySeqno(k);
|
||||
|
||||
// Compute row cache key.
|
||||
row_cache_key.TrimAppend(row_cache_key.Size(), row_cache_id_.data(),
|
||||
row_cache_id_.size());
|
||||
AppendVarint64(&row_cache_key, fd_number);
|
||||
AppendVarint64(&row_cache_key, seq_no);
|
||||
row_cache_key.TrimAppend(row_cache_key.Size(), user_key.data(),
|
||||
user_key.size());
|
||||
|
||||
if (auto row_handle = ioptions_.row_cache->Lookup(row_cache_key.GetKey())) {
|
||||
auto found_row_cache_entry = static_cast<const std::string*>(
|
||||
ioptions_.row_cache->Value(row_handle));
|
||||
replayGetContextLog(*found_row_cache_entry, user_key, get_context);
|
||||
ioptions_.row_cache->Release(row_handle);
|
||||
RecordTick(ioptions_.statistics, ROW_CACHE_HIT);
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
// Not found, setting up the replay log.
|
||||
RecordTick(ioptions_.statistics, ROW_CACHE_MISS);
|
||||
row_cache_entry = &row_cache_entry_buffer;
|
||||
}
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
if (!t) {
|
||||
s = FindTable(env_options_, internal_comparator, fd, &handle,
|
||||
options.read_tier == kBlockCacheTier);
|
||||
@@ -145,15 +206,30 @@ Status TableCache::Get(const ReadOptions& options,
|
||||
}
|
||||
}
|
||||
if (s.ok()) {
|
||||
get_context->SetReplayLog(row_cache_entry); // nullptr if no cache.
|
||||
s = t->Get(options, k, get_context);
|
||||
get_context->SetReplayLog(nullptr);
|
||||
if (handle != nullptr) {
|
||||
ReleaseHandle(handle);
|
||||
}
|
||||
} else if (options.read_tier && s.IsIncomplete()) {
|
||||
// Couldnt find Table in cache but treat as kFound if no_io set
|
||||
// Couldn't find Table in cache but treat as kFound if no_io set
|
||||
get_context->MarkKeyMayExist();
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
// Put the replay log in row cache only if something was found.
|
||||
if (s.ok() && row_cache_entry && !row_cache_entry->empty()) {
|
||||
size_t charge =
|
||||
row_cache_key.Size() + row_cache_entry->size() + sizeof(std::string);
|
||||
void* row_ptr = new std::string(std::move(*row_cache_entry));
|
||||
auto row_handle = ioptions_.row_cache->Insert(
|
||||
row_cache_key.GetKey(), row_ptr, charge, &DeleteEntry<std::string>);
|
||||
ioptions_.row_cache->Release(row_handle);
|
||||
}
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
|
||||
@@ -94,6 +94,7 @@ class TableCache {
|
||||
const ImmutableCFOptions& ioptions_;
|
||||
const EnvOptions& env_options_;
|
||||
Cache* const cache_;
|
||||
std::string row_cache_id_;
|
||||
};
|
||||
|
||||
} // namespace rocksdb
|
||||
@@ -32,6 +32,8 @@ class IntTblPropCollector {
|
||||
uint64_t file_size) = 0;
|
||||
|
||||
virtual UserCollectedProperties GetReadableProperties() const = 0;
|
||||
|
||||
virtual bool NeedCompact() const { return false; }
|
||||
};
|
||||
|
||||
// Facrtory for internal table properties collector.
|
||||
@@ -98,6 +100,10 @@ class UserKeyTablePropertiesCollector : public IntTblPropCollector {
|
||||
|
||||
UserCollectedProperties GetReadableProperties() const override;
|
||||
|
||||
virtual bool NeedCompact() const override {
|
||||
return collector_->NeedCompact();
|
||||
}
|
||||
|
||||
protected:
|
||||
std::unique_ptr<TablePropertiesCollector> collector_;
|
||||
};
|
||||
|
||||
@@ -383,6 +383,7 @@ TEST_P(TablePropertiesTest, CustomizedTablePropertiesCollector) {
|
||||
kBlockBasedTableMagicNumber,
|
||||
encode_as_internal, options, ikc);
|
||||
|
||||
#ifndef ROCKSDB_LITE // PlainTable is not supported in Lite
|
||||
// test plain table
|
||||
PlainTableOptions plain_table_options;
|
||||
plain_table_options.user_key_len = 8;
|
||||
@@ -394,6 +395,7 @@ TEST_P(TablePropertiesTest, CustomizedTablePropertiesCollector) {
|
||||
TestCustomizedTablePropertiesCollector(backward_mode_,
|
||||
kPlainTableMagicNumber,
|
||||
encode_as_internal, options, ikc);
|
||||
#endif // !ROCKSDB_LITE
|
||||
}
|
||||
}
|
||||
|
||||
@@ -495,6 +497,7 @@ TEST_P(TablePropertiesTest, InternalKeyPropertiesCollector) {
|
||||
std::make_shared<BlockBasedTableFactory>());
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE // PlainTable is not supported in Lite
|
||||
PlainTableOptions plain_table_options;
|
||||
plain_table_options.user_key_len = 8;
|
||||
plain_table_options.bloom_bits_per_key = 8;
|
||||
@@ -503,6 +506,7 @@ TEST_P(TablePropertiesTest, InternalKeyPropertiesCollector) {
|
||||
TestInternalKeyPropertiesCollector(
|
||||
backward_mode_, kPlainTableMagicNumber, false /* not sanitize */,
|
||||
std::make_shared<PlainTableFactory>(plain_table_options));
|
||||
#endif // !ROCKSDB_LITE
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(InternalKeyPropertiesCollector, TablePropertiesTest,
|
||||
|
||||
+16
-15
@@ -82,6 +82,7 @@ class VersionBuilderTest : public testing::Test {
|
||||
vstorage_.GenerateFileIndexer();
|
||||
vstorage_.GenerateLevelFilesBrief();
|
||||
vstorage_.CalculateBaseBytes(ioptions_, mutable_cf_options_);
|
||||
vstorage_.GenerateLevel0NonOverlapping();
|
||||
vstorage_.SetFinalized();
|
||||
}
|
||||
};
|
||||
@@ -114,7 +115,7 @@ TEST_F(VersionBuilderTest, ApplyAndSaveTo) {
|
||||
|
||||
VersionEdit version_edit;
|
||||
version_edit.AddFile(2, 666, 0, 100U, GetInternalKey("301"),
|
||||
GetInternalKey("350"), 200, 200);
|
||||
GetInternalKey("350"), 200, 200, false);
|
||||
version_edit.DeleteFile(3, 27U);
|
||||
|
||||
EnvOptions env_options;
|
||||
@@ -148,7 +149,7 @@ TEST_F(VersionBuilderTest, ApplyAndSaveToDynamic) {
|
||||
|
||||
VersionEdit version_edit;
|
||||
version_edit.AddFile(3, 666, 0, 100U, GetInternalKey("301"),
|
||||
GetInternalKey("350"), 200, 200);
|
||||
GetInternalKey("350"), 200, 200, false);
|
||||
version_edit.DeleteFile(0, 1U);
|
||||
version_edit.DeleteFile(0, 88U);
|
||||
|
||||
@@ -185,7 +186,7 @@ TEST_F(VersionBuilderTest, ApplyAndSaveToDynamic2) {
|
||||
|
||||
VersionEdit version_edit;
|
||||
version_edit.AddFile(4, 666, 0, 100U, GetInternalKey("301"),
|
||||
GetInternalKey("350"), 200, 200);
|
||||
GetInternalKey("350"), 200, 200, false);
|
||||
version_edit.DeleteFile(0, 1U);
|
||||
version_edit.DeleteFile(0, 88U);
|
||||
version_edit.DeleteFile(4, 6U);
|
||||
@@ -213,15 +214,15 @@ TEST_F(VersionBuilderTest, ApplyMultipleAndSaveTo) {
|
||||
|
||||
VersionEdit version_edit;
|
||||
version_edit.AddFile(2, 666, 0, 100U, GetInternalKey("301"),
|
||||
GetInternalKey("350"), 200, 200);
|
||||
GetInternalKey("350"), 200, 200, false);
|
||||
version_edit.AddFile(2, 676, 0, 100U, GetInternalKey("401"),
|
||||
GetInternalKey("450"), 200, 200);
|
||||
GetInternalKey("450"), 200, 200, false);
|
||||
version_edit.AddFile(2, 636, 0, 100U, GetInternalKey("601"),
|
||||
GetInternalKey("650"), 200, 200);
|
||||
GetInternalKey("650"), 200, 200, false);
|
||||
version_edit.AddFile(2, 616, 0, 100U, GetInternalKey("501"),
|
||||
GetInternalKey("550"), 200, 200);
|
||||
GetInternalKey("550"), 200, 200, false);
|
||||
version_edit.AddFile(2, 606, 0, 100U, GetInternalKey("701"),
|
||||
GetInternalKey("750"), 200, 200);
|
||||
GetInternalKey("750"), 200, 200, false);
|
||||
|
||||
EnvOptions env_options;
|
||||
|
||||
@@ -247,24 +248,24 @@ TEST_F(VersionBuilderTest, ApplyDeleteAndSaveTo) {
|
||||
|
||||
VersionEdit version_edit;
|
||||
version_edit.AddFile(2, 666, 0, 100U, GetInternalKey("301"),
|
||||
GetInternalKey("350"), 200, 200);
|
||||
GetInternalKey("350"), 200, 200, false);
|
||||
version_edit.AddFile(2, 676, 0, 100U, GetInternalKey("401"),
|
||||
GetInternalKey("450"), 200, 200);
|
||||
GetInternalKey("450"), 200, 200, false);
|
||||
version_edit.AddFile(2, 636, 0, 100U, GetInternalKey("601"),
|
||||
GetInternalKey("650"), 200, 200);
|
||||
GetInternalKey("650"), 200, 200, false);
|
||||
version_edit.AddFile(2, 616, 0, 100U, GetInternalKey("501"),
|
||||
GetInternalKey("550"), 200, 200);
|
||||
GetInternalKey("550"), 200, 200, false);
|
||||
version_edit.AddFile(2, 606, 0, 100U, GetInternalKey("701"),
|
||||
GetInternalKey("750"), 200, 200);
|
||||
GetInternalKey("750"), 200, 200, false);
|
||||
version_builder.Apply(&version_edit);
|
||||
|
||||
VersionEdit version_edit2;
|
||||
version_edit.AddFile(2, 808, 0, 100U, GetInternalKey("901"),
|
||||
GetInternalKey("950"), 200, 200);
|
||||
GetInternalKey("950"), 200, 200, false);
|
||||
version_edit2.DeleteFile(2, 616);
|
||||
version_edit2.DeleteFile(2, 636);
|
||||
version_edit.AddFile(2, 806, 0, 100U, GetInternalKey("801"),
|
||||
GetInternalKey("850"), 200, 200);
|
||||
GetInternalKey("850"), 200, 200, false);
|
||||
version_builder.Apply(&version_edit2);
|
||||
|
||||
version_builder.SaveTo(&new_vstorage);
|
||||
|
||||
+3
-1
@@ -169,7 +169,8 @@ class VersionEdit {
|
||||
void AddFile(int level, uint64_t file, uint32_t file_path_id,
|
||||
uint64_t file_size, const InternalKey& smallest,
|
||||
const InternalKey& largest, const SequenceNumber& smallest_seqno,
|
||||
const SequenceNumber& largest_seqno) {
|
||||
const SequenceNumber& largest_seqno,
|
||||
bool marked_for_compaction) {
|
||||
assert(smallest_seqno <= largest_seqno);
|
||||
FileMetaData f;
|
||||
f.fd = FileDescriptor(file, file_path_id, file_size);
|
||||
@@ -177,6 +178,7 @@ class VersionEdit {
|
||||
f.largest = largest;
|
||||
f.smallest_seqno = smallest_seqno;
|
||||
f.largest_seqno = largest_seqno;
|
||||
f.marked_for_compaction = marked_for_compaction;
|
||||
new_files_.push_back(std::make_pair(level, f));
|
||||
}
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ TEST_F(VersionEditTest, EncodeDecode) {
|
||||
edit.AddFile(3, kBig + 300 + i, kBig32Bit + 400 + i, 0,
|
||||
InternalKey("foo", kBig + 500 + i, kTypeValue),
|
||||
InternalKey("zoo", kBig + 600 + i, kTypeDeletion),
|
||||
kBig + 500 + i, kBig + 600 + i);
|
||||
kBig + 500 + i, kBig + 600 + i, false);
|
||||
edit.DeleteFile(4, kBig + 700 + i);
|
||||
}
|
||||
|
||||
@@ -47,10 +47,7 @@ TEST_F(VersionEditTest, EncodeDecode) {
|
||||
|
||||
TEST_F(VersionEditTest, EncodeEmptyFile) {
|
||||
VersionEdit edit;
|
||||
edit.AddFile(0, 0, 0, 0,
|
||||
InternalKey(),
|
||||
InternalKey(),
|
||||
0, 0);
|
||||
edit.AddFile(0, 0, 0, 0, InternalKey(), InternalKey(), 0, 0, false);
|
||||
std::string buffer;
|
||||
ASSERT_TRUE(!edit.EncodeTo(&buffer));
|
||||
}
|
||||
|
||||
+86
-25
@@ -44,6 +44,7 @@
|
||||
#include "util/coding.h"
|
||||
#include "util/logging.h"
|
||||
#include "util/stop_watch.h"
|
||||
#include "util/sync_point.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
@@ -694,12 +695,14 @@ void Version::AddIterators(const ReadOptions& read_options,
|
||||
return;
|
||||
}
|
||||
|
||||
auto* arena = merge_iter_builder->GetArena();
|
||||
|
||||
// Merge all level zero files together since they may overlap
|
||||
for (size_t i = 0; i < storage_info_.LevelFilesBrief(0).num_files; i++) {
|
||||
const auto& file = storage_info_.LevelFilesBrief(0).files[i];
|
||||
merge_iter_builder->AddIterator(cfd_->table_cache()->NewIterator(
|
||||
read_options, soptions, cfd_->internal_comparator(), file.fd, nullptr,
|
||||
false, merge_iter_builder->GetArena()));
|
||||
false, arena));
|
||||
}
|
||||
|
||||
// For levels > 0, we can use a concatenating iterator that sequentially
|
||||
@@ -707,14 +710,16 @@ void Version::AddIterators(const ReadOptions& read_options,
|
||||
// lazily.
|
||||
for (int level = 1; level < storage_info_.num_non_empty_levels(); level++) {
|
||||
if (storage_info_.LevelFilesBrief(level).num_files != 0) {
|
||||
merge_iter_builder->AddIterator(NewTwoLevelIterator(
|
||||
new LevelFileIteratorState(
|
||||
cfd_->table_cache(), read_options, soptions,
|
||||
cfd_->internal_comparator(), false /* for_compaction */,
|
||||
cfd_->ioptions()->prefix_extractor != nullptr),
|
||||
new LevelFileNumIterator(cfd_->internal_comparator(),
|
||||
&storage_info_.LevelFilesBrief(level)),
|
||||
merge_iter_builder->GetArena()));
|
||||
auto* mem = arena->AllocateAligned(sizeof(LevelFileIteratorState));
|
||||
auto* state = new (mem) LevelFileIteratorState(
|
||||
cfd_->table_cache(), read_options, soptions,
|
||||
cfd_->internal_comparator(), false /* for_compaction */,
|
||||
cfd_->ioptions()->prefix_extractor != nullptr);
|
||||
mem = arena->AllocateAligned(sizeof(LevelFileNumIterator));
|
||||
auto* first_level_iter = new (mem) LevelFileNumIterator(
|
||||
cfd_->internal_comparator(), &storage_info_.LevelFilesBrief(level));
|
||||
merge_iter_builder->AddIterator(
|
||||
NewTwoLevelIterator(state, first_level_iter, arena, false));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -733,6 +738,7 @@ VersionStorageInfo::VersionStorageInfo(
|
||||
files_(new std::vector<FileMetaData*>[num_levels_]),
|
||||
base_level_(num_levels_ == 1 ? -1 : 1),
|
||||
files_by_size_(num_levels_),
|
||||
level0_non_overlapping_(false),
|
||||
next_file_to_compact_by_size_(num_levels_),
|
||||
compaction_score_(num_levels_),
|
||||
compaction_level_(num_levels_),
|
||||
@@ -871,6 +877,7 @@ void Version::PrepareApply(const MutableCFOptions& mutable_cf_options) {
|
||||
storage_info_.UpdateFilesBySize();
|
||||
storage_info_.GenerateFileIndexer();
|
||||
storage_info_.GenerateLevelFilesBrief();
|
||||
storage_info_.GenerateLevel0NonOverlapping();
|
||||
}
|
||||
|
||||
bool Version::MaybeInitializeFileMetaData(FileMetaData* file_meta) {
|
||||
@@ -962,9 +969,20 @@ void VersionStorageInfo::ComputeCompensatedSizes() {
|
||||
// for files that have been created right now and no other thread has
|
||||
// access to them. That's why we can safely mutate compensated_file_size.
|
||||
if (file_meta->compensated_file_size == 0) {
|
||||
file_meta->compensated_file_size = file_meta->fd.GetFileSize() +
|
||||
file_meta->num_deletions * average_value_size *
|
||||
kDeletionWeightOnCompaction;
|
||||
file_meta->compensated_file_size = file_meta->fd.GetFileSize();
|
||||
// Here we only boost the size of deletion entries of a file only
|
||||
// when the number of deletion entries is greater than the number of
|
||||
// non-deletion entries in the file. The motivation here is that in
|
||||
// a stable workload, the number of deletion entries should be roughly
|
||||
// equal to the number of non-deletion entries. If we compensate the
|
||||
// size of deletion entries in a stable workload, the deletion
|
||||
// compensation logic might introduce unwanted effet which changes the
|
||||
// shape of LSM tree.
|
||||
if (file_meta->num_deletions * 2 >= file_meta->num_entries) {
|
||||
file_meta->compensated_file_size +=
|
||||
(file_meta->num_deletions * 2 - file_meta->num_entries) *
|
||||
average_value_size * kDeletionWeightOnCompaction;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1019,13 +1037,6 @@ void VersionStorageInfo::ComputeCompactionScore(
|
||||
if (compaction_style_ == kCompactionStyleFIFO) {
|
||||
score = static_cast<double>(total_size) /
|
||||
compaction_options_fifo.max_table_files_size;
|
||||
} else if (num_sorted_runs >=
|
||||
mutable_cf_options.level0_stop_writes_trigger) {
|
||||
// If we are slowing down writes, then we better compact that first
|
||||
score = 1000000;
|
||||
} else if (num_sorted_runs >=
|
||||
mutable_cf_options.level0_slowdown_writes_trigger) {
|
||||
score = 10000;
|
||||
} else {
|
||||
score = static_cast<double>(num_sorted_runs) /
|
||||
mutable_cf_options.level0_file_num_compaction_trigger;
|
||||
@@ -1072,7 +1083,19 @@ void VersionStorageInfo::ComputeCompactionScore(
|
||||
|
||||
void VersionStorageInfo::ComputeFilesMarkedForCompaction() {
|
||||
files_marked_for_compaction_.clear();
|
||||
for (int level = 0; level <= MaxInputLevel(); level++) {
|
||||
int last_qualify_level = 0;
|
||||
|
||||
// Do not include files from the last level with data
|
||||
// If table properties collector suggests a file on the last level,
|
||||
// we should not move it to a new level.
|
||||
for (int level = num_levels() - 1; level >= 1; level--) {
|
||||
if (!files_[level].empty()) {
|
||||
last_qualify_level = level - 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (int level = 0; level <= last_qualify_level; level++) {
|
||||
for (auto* f : files_[level]) {
|
||||
if (!f->being_compacted && f->marked_for_compaction) {
|
||||
files_marked_for_compaction_.emplace_back(level, f);
|
||||
@@ -1117,6 +1140,7 @@ void VersionStorageInfo::AddFile(int level, FileMetaData* f) {
|
||||
// 3. UpdateFilesBySize();
|
||||
// 4. GenerateFileIndexer();
|
||||
// 5. GenerateLevelFilesBrief();
|
||||
// 6. GenerateLevel0NonOverlapping();
|
||||
void VersionStorageInfo::SetFinalized() {
|
||||
finalized_ = true;
|
||||
#ifndef NDEBUG
|
||||
@@ -1202,6 +1226,33 @@ void VersionStorageInfo::UpdateFilesBySize() {
|
||||
}
|
||||
}
|
||||
|
||||
void VersionStorageInfo::GenerateLevel0NonOverlapping() {
|
||||
assert(!finalized_);
|
||||
level0_non_overlapping_ = true;
|
||||
if (level_files_brief_.size() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// A copy of L0 files sorted by smallest key
|
||||
std::vector<FdWithKeyRange> level0_sorted_file(
|
||||
level_files_brief_[0].files,
|
||||
level_files_brief_[0].files + level_files_brief_[0].num_files);
|
||||
sort(level0_sorted_file.begin(), level0_sorted_file.end(),
|
||||
[this](const FdWithKeyRange & f1, const FdWithKeyRange & f2)->bool {
|
||||
return (internal_comparator_->Compare(f1.smallest_key, f2.smallest_key) <
|
||||
0);
|
||||
});
|
||||
|
||||
for (size_t i = 1; i < level0_sorted_file.size(); ++i) {
|
||||
FdWithKeyRange& f = level0_sorted_file[i];
|
||||
FdWithKeyRange& prev = level0_sorted_file[i - 1];
|
||||
if (internal_comparator_->Compare(prev.largest_key, f.smallest_key) >= 0) {
|
||||
level0_non_overlapping_ = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Version::Ref() {
|
||||
++refs_;
|
||||
}
|
||||
@@ -1505,8 +1556,15 @@ const char* VersionStorageInfo::LevelSummary(
|
||||
// overwrite the last space
|
||||
--len;
|
||||
}
|
||||
snprintf(scratch->buffer + len, sizeof(scratch->buffer) - len,
|
||||
"] max score %.2f", compaction_score_[0]);
|
||||
len += snprintf(scratch->buffer + len, sizeof(scratch->buffer) - len,
|
||||
"] max score %.2f", compaction_score_[0]);
|
||||
|
||||
if (!files_marked_for_compaction_.empty()) {
|
||||
snprintf(scratch->buffer + len, sizeof(scratch->buffer) - len,
|
||||
" (%zu files need compaction)",
|
||||
files_marked_for_compaction_.size());
|
||||
}
|
||||
|
||||
return scratch->buffer;
|
||||
}
|
||||
|
||||
@@ -1871,6 +1929,7 @@ Status VersionSet::LogAndApply(ColumnFamilyData* column_family_data,
|
||||
|
||||
mu->Unlock();
|
||||
|
||||
TEST_SYNC_POINT("VersionSet::LogAndApply:WriteManifest");
|
||||
if (!edit->IsColumnFamilyManipulation() &&
|
||||
db_options_->max_open_files == -1) {
|
||||
// unlimited table cache. Pre-load table handle now.
|
||||
@@ -2441,7 +2500,7 @@ Status VersionSet::ReduceNumberOfLevels(const std::string& dbname,
|
||||
ColumnFamilyOptions cf_options(*options);
|
||||
std::shared_ptr<Cache> tc(NewLRUCache(options->max_open_files - 10,
|
||||
options->table_cache_numshardbits));
|
||||
WriteController wc;
|
||||
WriteController wc(options->delayed_write_rate);
|
||||
WriteBuffer wb(options->db_write_buffer_size);
|
||||
VersionSet versions(dbname, options, env_options, tc.get(), &wb, &wc);
|
||||
Status status;
|
||||
@@ -2751,7 +2810,8 @@ Status VersionSet::WriteSnapshot(log::Writer* log) {
|
||||
cfd->current()->storage_info()->LevelFiles(level)) {
|
||||
edit.AddFile(level, f->fd.GetNumber(), f->fd.GetPathId(),
|
||||
f->fd.GetFileSize(), f->smallest, f->largest,
|
||||
f->smallest_seqno, f->largest_seqno);
|
||||
f->smallest_seqno, f->largest_seqno,
|
||||
f->marked_for_compaction);
|
||||
}
|
||||
}
|
||||
edit.SetLogNumber(cfd->GetLogNumber());
|
||||
@@ -3111,7 +3171,8 @@ ColumnFamilyData* VersionSet::CreateColumnFamily(
|
||||
AppendVersion(new_cfd, v);
|
||||
// GetLatestMutableCFOptions() is safe here without mutex since the
|
||||
// cfd is not available to client
|
||||
new_cfd->CreateNewMemtable(*new_cfd->GetLatestMutableCFOptions());
|
||||
new_cfd->CreateNewMemtable(*new_cfd->GetLatestMutableCFOptions(),
|
||||
LastSequence());
|
||||
new_cfd->SetLogNumber(edit->log_number_);
|
||||
return new_cfd;
|
||||
}
|
||||
|
||||
+11
-1
@@ -131,6 +131,11 @@ class VersionStorageInfo {
|
||||
// record results in files_by_size_. The largest files are listed first.
|
||||
void UpdateFilesBySize();
|
||||
|
||||
void GenerateLevel0NonOverlapping();
|
||||
bool level0_non_overlapping() const {
|
||||
return level0_non_overlapping_;
|
||||
}
|
||||
|
||||
int MaxInputLevel() const;
|
||||
|
||||
// Returns the maxmimum compaction score for levels 1 to max
|
||||
@@ -343,6 +348,9 @@ class VersionStorageInfo {
|
||||
// This vector stores the index of the file from files_.
|
||||
std::vector<std::vector<int>> files_by_size_;
|
||||
|
||||
// If true, means that files in L0 have keys with non overlapping ranges
|
||||
bool level0_non_overlapping_;
|
||||
|
||||
// An index into files_by_size_ that specifies the first
|
||||
// file that is not yet compacted
|
||||
std::vector<int> next_file_to_compact_by_size_;
|
||||
@@ -604,7 +612,9 @@ class VersionSet {
|
||||
uint64_t MinLogNumber() const {
|
||||
uint64_t min_log_num = std::numeric_limits<uint64_t>::max();
|
||||
for (auto cfd : *column_family_set_) {
|
||||
if (min_log_num > cfd->GetLogNumber()) {
|
||||
// It's safe to ignore dropped column families here:
|
||||
// cfd->IsDropped() becomes true after the drop is persisted in MANIFEST.
|
||||
if (min_log_num > cfd->GetLogNumber() && !cfd->IsDropped()) {
|
||||
min_log_num = cfd->GetLogNumber();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -275,6 +275,27 @@ void WriteBatch::Merge(ColumnFamilyHandle* column_family, const Slice& key,
|
||||
WriteBatchInternal::Merge(this, GetColumnFamilyID(column_family), key, value);
|
||||
}
|
||||
|
||||
void WriteBatchInternal::Merge(WriteBatch* b, uint32_t column_family_id,
|
||||
const SliceParts& key,
|
||||
const SliceParts& value) {
|
||||
WriteBatchInternal::SetCount(b, WriteBatchInternal::Count(b) + 1);
|
||||
if (column_family_id == 0) {
|
||||
b->rep_.push_back(static_cast<char>(kTypeMerge));
|
||||
} else {
|
||||
b->rep_.push_back(static_cast<char>(kTypeColumnFamilyMerge));
|
||||
PutVarint32(&b->rep_, column_family_id);
|
||||
}
|
||||
PutLengthPrefixedSliceParts(&b->rep_, key);
|
||||
PutLengthPrefixedSliceParts(&b->rep_, value);
|
||||
}
|
||||
|
||||
void WriteBatch::Merge(ColumnFamilyHandle* column_family,
|
||||
const SliceParts& key,
|
||||
const SliceParts& value) {
|
||||
WriteBatchInternal::Merge(this, GetColumnFamilyID(column_family),
|
||||
key, value);
|
||||
}
|
||||
|
||||
void WriteBatch::PutLogData(const Slice& blob) {
|
||||
rep_.push_back(static_cast<char>(kTypeLogData));
|
||||
PutLengthPrefixedSlice(&rep_, blob);
|
||||
|
||||
@@ -43,4 +43,21 @@ void WriteBatchBase::Delete(const SliceParts& key) {
|
||||
Delete(key_slice);
|
||||
}
|
||||
|
||||
void WriteBatchBase::Merge(ColumnFamilyHandle* column_family,
|
||||
const SliceParts& key, const SliceParts& value) {
|
||||
std::string key_buf, value_buf;
|
||||
Slice key_slice(key, &key_buf);
|
||||
Slice value_slice(value, &value_buf);
|
||||
|
||||
Merge(column_family, key_slice, value_slice);
|
||||
}
|
||||
|
||||
void WriteBatchBase::Merge(const SliceParts& key, const SliceParts& value) {
|
||||
std::string key_buf, value_buf;
|
||||
Slice key_slice(key, &key_buf);
|
||||
Slice value_slice(value, &value_buf);
|
||||
|
||||
Merge(key_slice, value_slice);
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
@@ -76,6 +76,9 @@ class WriteBatchInternal {
|
||||
static void Merge(WriteBatch* batch, uint32_t column_family_id,
|
||||
const Slice& key, const Slice& value);
|
||||
|
||||
static void Merge(WriteBatch* batch, uint32_t column_family_id,
|
||||
const SliceParts& key, const SliceParts& value);
|
||||
|
||||
// Return the number of entries in the batch.
|
||||
static int Count(const WriteBatch* batch);
|
||||
|
||||
|
||||
@@ -31,8 +31,9 @@ static std::string PrintContents(WriteBatch* b) {
|
||||
options.memtable_factory = factory;
|
||||
ImmutableCFOptions ioptions(options);
|
||||
WriteBuffer wb(options.db_write_buffer_size);
|
||||
MemTable* mem = new MemTable(cmp, ioptions,
|
||||
MutableCFOptions(options, ioptions), &wb);
|
||||
MemTable* mem =
|
||||
new MemTable(cmp, ioptions, MutableCFOptions(options, ioptions), &wb,
|
||||
kMaxSequenceNumber);
|
||||
mem->Ref();
|
||||
std::string state;
|
||||
ColumnFamilyMemTablesDefault cf_mems_default(mem);
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
// Copyright (c) 2015, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the root directory of this source tree. An additional grant
|
||||
// of patent rights can be found in the PATENTS file in the same directory.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "rocksdb/status.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
class DB;
|
||||
|
||||
class WriteCallback {
|
||||
public:
|
||||
virtual ~WriteCallback() {}
|
||||
|
||||
// Will be called while on the write thread before the write executes. If
|
||||
// this function returns a non-OK status, the write will be aborted and this
|
||||
// status will be returned to the caller of DB::Write().
|
||||
virtual Status Callback(DB* db) = 0;
|
||||
};
|
||||
|
||||
} // namespace rocksdb
|
||||
@@ -0,0 +1,120 @@
|
||||
// Copyright (c) 2015, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the root directory of this source tree. An additional grant
|
||||
// of patent rights can be found in the PATENTS file in the same directory.
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "db/db_impl.h"
|
||||
#include "db/write_callback.h"
|
||||
#include "rocksdb/db.h"
|
||||
#include "rocksdb/write_batch.h"
|
||||
#include "util/logging.h"
|
||||
#include "util/testharness.h"
|
||||
|
||||
using std::string;
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
class WriteCallbackTest : public testing::Test {
|
||||
public:
|
||||
string dbname;
|
||||
|
||||
WriteCallbackTest() {
|
||||
dbname = test::TmpDir() + "/write_callback_testdb";
|
||||
}
|
||||
};
|
||||
|
||||
class WriteCallbackTestWriteCallback1 : public WriteCallback {
|
||||
public:
|
||||
bool was_called = false;
|
||||
|
||||
Status Callback(DB *db) override {
|
||||
was_called = true;
|
||||
|
||||
// Make sure db is a DBImpl
|
||||
DBImpl* db_impl = dynamic_cast<DBImpl*> (db);
|
||||
if (db_impl == nullptr) {
|
||||
return Status::InvalidArgument("");
|
||||
}
|
||||
|
||||
return Status::OK();
|
||||
}
|
||||
};
|
||||
|
||||
class WriteCallbackTestWriteCallback2 : public WriteCallback {
|
||||
public:
|
||||
Status Callback(DB *db) override {
|
||||
return Status::Busy();
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(WriteCallbackTest, WriteCallBackTest) {
|
||||
Options options;
|
||||
WriteOptions write_options;
|
||||
ReadOptions read_options;
|
||||
string value;
|
||||
DB* db;
|
||||
DBImpl* db_impl;
|
||||
|
||||
options.create_if_missing = true;
|
||||
Status s = DB::Open(options, dbname, &db);
|
||||
ASSERT_OK(s);
|
||||
|
||||
db_impl = dynamic_cast<DBImpl*> (db);
|
||||
ASSERT_TRUE(db_impl);
|
||||
|
||||
WriteBatch wb;
|
||||
|
||||
wb.Put("a", "value.a");
|
||||
wb.Delete("x");
|
||||
|
||||
// Test a simple Write
|
||||
s = db->Write(write_options, &wb);
|
||||
ASSERT_OK(s);
|
||||
|
||||
s = db->Get(read_options, "a", &value);
|
||||
ASSERT_OK(s);
|
||||
ASSERT_EQ("value.a", value);
|
||||
|
||||
// Test WriteWithCallback
|
||||
WriteCallbackTestWriteCallback1 callback1;
|
||||
WriteBatch wb2;
|
||||
|
||||
wb2.Put("a", "value.a2");
|
||||
|
||||
s = db_impl->WriteWithCallback(write_options, &wb2, &callback1);
|
||||
ASSERT_OK(s);
|
||||
ASSERT_TRUE(callback1.was_called);
|
||||
|
||||
s = db->Get(read_options, "a", &value);
|
||||
ASSERT_OK(s);
|
||||
ASSERT_EQ("value.a2", value);
|
||||
|
||||
// Test WriteWithCallback for a callback that fails
|
||||
WriteCallbackTestWriteCallback2 callback2;
|
||||
WriteBatch wb3;
|
||||
|
||||
wb3.Put("a", "value.a3");
|
||||
|
||||
s = db_impl->WriteWithCallback(write_options, &wb3, &callback2);
|
||||
ASSERT_NOK(s);
|
||||
|
||||
s = db->Get(read_options, "a", &value);
|
||||
ASSERT_OK(s);
|
||||
ASSERT_EQ("value.a2", value);
|
||||
|
||||
delete db;
|
||||
DestroyDB(dbname, options);
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
|
||||
#endif // ROCKSDB_LITE
|
||||
+78
-8
@@ -5,7 +5,9 @@
|
||||
|
||||
#include "db/write_controller.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <cassert>
|
||||
#include "rocksdb/env.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
@@ -14,15 +16,83 @@ std::unique_ptr<WriteControllerToken> WriteController::GetStopToken() {
|
||||
return std::unique_ptr<WriteControllerToken>(new StopWriteToken(this));
|
||||
}
|
||||
|
||||
std::unique_ptr<WriteControllerToken> WriteController::GetDelayToken(
|
||||
uint64_t delay_us) {
|
||||
total_delay_us_ += delay_us;
|
||||
return std::unique_ptr<WriteControllerToken>(
|
||||
new DelayWriteToken(this, delay_us));
|
||||
std::unique_ptr<WriteControllerToken> WriteController::GetDelayToken() {
|
||||
if (total_delayed_++ == 0) {
|
||||
last_refill_time_ = 0;
|
||||
bytes_left_ = 0;
|
||||
}
|
||||
return std::unique_ptr<WriteControllerToken>(new DelayWriteToken(this));
|
||||
}
|
||||
|
||||
bool WriteController::IsStopped() const { return total_stopped_ > 0; }
|
||||
uint64_t WriteController::GetDelay() const { return total_delay_us_; }
|
||||
// Tihs is inside DB mutex, so we can't sleep and need to minimize
|
||||
// frequency to get time.
|
||||
// If it turns out to be a performance issue, we can redesign the thread
|
||||
// synchronization model here.
|
||||
// The function trust caller will sleep micros returned.
|
||||
uint64_t WriteController::GetDelay(Env* env, uint64_t num_bytes) {
|
||||
if (total_stopped_ > 0) {
|
||||
return 0;
|
||||
}
|
||||
if (total_delayed_ == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const uint64_t kMicrosPerSecond = 1000000;
|
||||
const uint64_t kRefillInterval = 1024U;
|
||||
|
||||
if (bytes_left_ >= num_bytes) {
|
||||
bytes_left_ -= num_bytes;
|
||||
return 0;
|
||||
}
|
||||
// The frequency to get time inside DB mutex is less than one per refill
|
||||
// interval.
|
||||
auto time_now = env->NowMicros();
|
||||
|
||||
uint64_t sleep_debt = 0;
|
||||
uint64_t time_since_last_refill = 0;
|
||||
if (last_refill_time_ != 0) {
|
||||
if (last_refill_time_ > time_now) {
|
||||
sleep_debt = last_refill_time_ - time_now;
|
||||
} else {
|
||||
time_since_last_refill = time_now - last_refill_time_;
|
||||
bytes_left_ +=
|
||||
static_cast<uint64_t>(static_cast<double>(time_since_last_refill) /
|
||||
kMicrosPerSecond * delayed_write_rate_);
|
||||
if (time_since_last_refill >= kRefillInterval &&
|
||||
bytes_left_ > num_bytes) {
|
||||
// If refill interval already passed and we have enough bytes
|
||||
// return without extra sleeping.
|
||||
last_refill_time_ = time_now;
|
||||
bytes_left_ -= num_bytes;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t single_refill_amount =
|
||||
delayed_write_rate_ * kRefillInterval / kMicrosPerSecond;
|
||||
if (bytes_left_ + single_refill_amount >= num_bytes) {
|
||||
// Wait until a refill interval
|
||||
// Never trigger expire for less than one refill interval to avoid to get
|
||||
// time.
|
||||
bytes_left_ = bytes_left_ + single_refill_amount - num_bytes;
|
||||
last_refill_time_ = time_now + kRefillInterval;
|
||||
return kRefillInterval + sleep_debt;
|
||||
}
|
||||
|
||||
// Need to refill more than one interval. Need to sleep longer. Check
|
||||
// whether expiration will hit
|
||||
|
||||
// Sleep just until `num_bytes` is allowed.
|
||||
uint64_t sleep_amount =
|
||||
static_cast<uint64_t>(num_bytes /
|
||||
static_cast<long double>(delayed_write_rate_) *
|
||||
kMicrosPerSecond) +
|
||||
sleep_debt;
|
||||
last_refill_time_ = time_now + sleep_amount;
|
||||
return sleep_amount;
|
||||
}
|
||||
|
||||
StopWriteToken::~StopWriteToken() {
|
||||
assert(controller_->total_stopped_ >= 1);
|
||||
@@ -30,8 +100,8 @@ StopWriteToken::~StopWriteToken() {
|
||||
}
|
||||
|
||||
DelayWriteToken::~DelayWriteToken() {
|
||||
assert(controller_->total_delay_us_ >= delay_us_);
|
||||
controller_->total_delay_us_ -= delay_us_;
|
||||
controller_->total_delayed_--;
|
||||
assert(controller_->total_delayed_ >= 0);
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
+30
-11
@@ -11,6 +11,7 @@
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
class Env;
|
||||
class WriteControllerToken;
|
||||
|
||||
// WriteController is controlling write stalls in our write code-path. Write
|
||||
@@ -19,20 +20,38 @@ class WriteControllerToken;
|
||||
// to be called while holding DB mutex
|
||||
class WriteController {
|
||||
public:
|
||||
WriteController() : total_stopped_(0), total_delay_us_(0) {}
|
||||
explicit WriteController(uint64_t delayed_write_rate = 1024u * 1024u * 32u)
|
||||
: total_stopped_(0),
|
||||
total_delayed_(0),
|
||||
bytes_left_(0),
|
||||
last_refill_time_(0) {
|
||||
set_delayed_write_rate(delayed_write_rate);
|
||||
}
|
||||
~WriteController() = default;
|
||||
|
||||
// When an actor (column family) requests a stop token, all writes will be
|
||||
// stopped until the stop token is released (deleted)
|
||||
std::unique_ptr<WriteControllerToken> GetStopToken();
|
||||
// When an actor (column family) requests a delay token, total delay for all
|
||||
// writes will be increased by delay_us. The delay will last until delay token
|
||||
// is released
|
||||
std::unique_ptr<WriteControllerToken> GetDelayToken(uint64_t delay_us);
|
||||
// writes to the DB will be controlled under the delayed write rate. Every
|
||||
// write needs to call GetDelay() with number of bytes writing to the DB,
|
||||
// which returns number of microseconds to sleep.
|
||||
std::unique_ptr<WriteControllerToken> GetDelayToken();
|
||||
|
||||
// these two metods are querying the state of the WriteController
|
||||
bool IsStopped() const;
|
||||
uint64_t GetDelay() const;
|
||||
bool NeedsDelay() const { return total_delayed_ > 0; }
|
||||
// return how many microseconds the caller needs to sleep after the call
|
||||
// num_bytes: how many number of bytes to put into the DB.
|
||||
// Prerequisite: DB mutex held.
|
||||
uint64_t GetDelay(Env* env, uint64_t num_bytes);
|
||||
void set_delayed_write_rate(uint64_t delayed_write_rate) {
|
||||
delayed_write_rate_ = delayed_write_rate;
|
||||
if (delayed_write_rate_ == 0) {
|
||||
// avoid divide 0
|
||||
delayed_write_rate_ = 1U;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
friend class WriteControllerToken;
|
||||
@@ -40,7 +59,10 @@ class WriteController {
|
||||
friend class DelayWriteToken;
|
||||
|
||||
int total_stopped_;
|
||||
uint64_t total_delay_us_;
|
||||
int total_delayed_;
|
||||
uint64_t bytes_left_;
|
||||
uint64_t last_refill_time_;
|
||||
uint64_t delayed_write_rate_;
|
||||
};
|
||||
|
||||
class WriteControllerToken {
|
||||
@@ -67,12 +89,9 @@ class StopWriteToken : public WriteControllerToken {
|
||||
|
||||
class DelayWriteToken : public WriteControllerToken {
|
||||
public:
|
||||
DelayWriteToken(WriteController* controller, uint64_t delay_us)
|
||||
: WriteControllerToken(controller), delay_us_(delay_us) {}
|
||||
explicit DelayWriteToken(WriteController* controller)
|
||||
: WriteControllerToken(controller) {}
|
||||
virtual ~DelayWriteToken();
|
||||
|
||||
private:
|
||||
uint64_t delay_us_;
|
||||
};
|
||||
|
||||
} // namespace rocksdb
|
||||
@@ -5,14 +5,22 @@
|
||||
//
|
||||
#include "db/write_controller.h"
|
||||
|
||||
#include "rocksdb/env.h"
|
||||
#include "util/testharness.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
class WriteControllerTest : public testing::Test {};
|
||||
|
||||
class TimeSetEnv : public EnvWrapper {
|
||||
public:
|
||||
explicit TimeSetEnv() : EnvWrapper(nullptr) {}
|
||||
uint64_t now_micros_ = 6666;
|
||||
virtual uint64_t NowMicros() override { return now_micros_; }
|
||||
};
|
||||
|
||||
TEST_F(WriteControllerTest, SanityTest) {
|
||||
WriteController controller;
|
||||
WriteController controller(10000000u);
|
||||
auto stop_token_1 = controller.GetStopToken();
|
||||
auto stop_token_2 = controller.GetStopToken();
|
||||
|
||||
@@ -22,15 +30,66 @@ TEST_F(WriteControllerTest, SanityTest) {
|
||||
stop_token_2.reset();
|
||||
ASSERT_FALSE(controller.IsStopped());
|
||||
|
||||
auto delay_token_1 = controller.GetDelayToken(5);
|
||||
ASSERT_EQ(static_cast<uint64_t>(5), controller.GetDelay());
|
||||
auto delay_token_2 = controller.GetDelayToken(8);
|
||||
ASSERT_EQ(static_cast<uint64_t>(13), controller.GetDelay());
|
||||
TimeSetEnv env;
|
||||
|
||||
auto delay_token_1 = controller.GetDelayToken();
|
||||
ASSERT_EQ(static_cast<uint64_t>(2000000),
|
||||
controller.GetDelay(&env, 20000000u));
|
||||
|
||||
env.now_micros_ += 1999900u; // sleep debt 1000
|
||||
auto delay_token_2 = controller.GetDelayToken();
|
||||
// One refill: 10240 bytes allowed, 1000 used, 9240 left
|
||||
ASSERT_EQ(static_cast<uint64_t>(1124), controller.GetDelay(&env, 1000u));
|
||||
env.now_micros_ += 1124u; // sleep debt 0
|
||||
|
||||
delay_token_2.reset();
|
||||
ASSERT_EQ(static_cast<uint64_t>(5), controller.GetDelay());
|
||||
// 1000 used, 8240 left
|
||||
ASSERT_EQ(static_cast<uint64_t>(0), controller.GetDelay(&env, 1000u));
|
||||
|
||||
env.now_micros_ += 100u; // sleep credit 100
|
||||
// 1000 used, 7240 left
|
||||
ASSERT_EQ(static_cast<uint64_t>(0), controller.GetDelay(&env, 1000u));
|
||||
|
||||
env.now_micros_ += 100u; // sleep credit 200
|
||||
// One refill: 10240 fileed, sleep credit generates 2000. 8000 used
|
||||
// 7240 + 10240 + 2000 - 8000 = 11480 left
|
||||
ASSERT_EQ(static_cast<uint64_t>(1024u), controller.GetDelay(&env, 8000u));
|
||||
|
||||
env.now_micros_ += 200u; // sleep debt 824
|
||||
// 1000 used, 10480 left.
|
||||
ASSERT_EQ(static_cast<uint64_t>(0), controller.GetDelay(&env, 1000u));
|
||||
|
||||
env.now_micros_ += 200u; // sleep debt 624
|
||||
// Out of bound sleep, still 10480 left
|
||||
ASSERT_EQ(static_cast<uint64_t>(3000624u),
|
||||
controller.GetDelay(&env, 30000000u));
|
||||
|
||||
env.now_micros_ += 3000724u; // sleep credit 100
|
||||
// 6000 used, 4480 left.
|
||||
ASSERT_EQ(static_cast<uint64_t>(0), controller.GetDelay(&env, 6000u));
|
||||
|
||||
env.now_micros_ += 200u; // sleep credit 300
|
||||
// One refill, credit 4480 balance + 3000 credit + 10240 refill
|
||||
// Use 8000, 9720 left
|
||||
ASSERT_EQ(static_cast<uint64_t>(1024u), controller.GetDelay(&env, 8000u));
|
||||
|
||||
env.now_micros_ += 3024u; // sleep credit 2000
|
||||
|
||||
// 1720 left
|
||||
ASSERT_EQ(static_cast<uint64_t>(0u), controller.GetDelay(&env, 8000u));
|
||||
|
||||
// 1720 balance + 20000 credit = 20170 left
|
||||
// Use 8000, 12170 left
|
||||
ASSERT_EQ(static_cast<uint64_t>(0u), controller.GetDelay(&env, 8000u));
|
||||
|
||||
// 4170 left
|
||||
ASSERT_EQ(static_cast<uint64_t>(0u), controller.GetDelay(&env, 8000u));
|
||||
|
||||
// Need a refill
|
||||
ASSERT_EQ(static_cast<uint64_t>(1024u), controller.GetDelay(&env, 9000u));
|
||||
|
||||
delay_token_1.reset();
|
||||
ASSERT_EQ(static_cast<uint64_t>(0), controller.GetDelay());
|
||||
ASSERT_EQ(static_cast<uint64_t>(0), controller.GetDelay(&env, 30000000u));
|
||||
delay_token_1.reset();
|
||||
ASSERT_FALSE(controller.IsStopped());
|
||||
}
|
||||
|
||||
+17
-2
@@ -87,8 +87,9 @@ void WriteThread::ExitWriteThread(WriteThread::Writer* w,
|
||||
//
|
||||
// REQUIRES: Writer list must be non-empty
|
||||
// REQUIRES: First writer must have a non-nullptr batch
|
||||
void WriteThread::BuildBatchGroup(WriteThread::Writer** last_writer,
|
||||
autovector<WriteBatch*>* write_batch_group) {
|
||||
size_t WriteThread::BuildBatchGroup(
|
||||
WriteThread::Writer** last_writer,
|
||||
autovector<WriteBatch*>* write_batch_group) {
|
||||
assert(!writers_.empty());
|
||||
Writer* first = writers_.front();
|
||||
assert(first->batch != nullptr);
|
||||
@@ -105,6 +106,13 @@ void WriteThread::BuildBatchGroup(WriteThread::Writer** last_writer,
|
||||
}
|
||||
|
||||
*last_writer = first;
|
||||
|
||||
if (first->has_callback) {
|
||||
// TODO(agiardullo:) Batching not currently supported as this write may
|
||||
// fail if the callback function decides to abort this write.
|
||||
return size;
|
||||
}
|
||||
|
||||
std::deque<Writer*>::iterator iter = writers_.begin();
|
||||
++iter; // Advance past "first"
|
||||
for (; iter != writers_.end(); ++iter) {
|
||||
@@ -126,6 +134,12 @@ void WriteThread::BuildBatchGroup(WriteThread::Writer** last_writer,
|
||||
break;
|
||||
}
|
||||
|
||||
if (w->has_callback) {
|
||||
// Do not include writes which may be aborted if the callback does not
|
||||
// succeed.
|
||||
break;
|
||||
}
|
||||
|
||||
if (w->batch == nullptr) {
|
||||
// Do not include those writes with nullptr batch. Those are not writes,
|
||||
// those are something else. They want to be alone
|
||||
@@ -142,6 +156,7 @@ void WriteThread::BuildBatchGroup(WriteThread::Writer** last_writer,
|
||||
w->in_batch_group = true;
|
||||
*last_writer = w;
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
+5
-2
@@ -27,6 +27,7 @@ class WriteThread {
|
||||
bool disableWAL;
|
||||
bool in_batch_group;
|
||||
bool done;
|
||||
bool has_callback;
|
||||
uint64_t timeout_hint_us;
|
||||
InstrumentedCondVar cv;
|
||||
|
||||
@@ -36,6 +37,7 @@ class WriteThread {
|
||||
disableWAL(false),
|
||||
in_batch_group(false),
|
||||
done(false),
|
||||
has_callback(false),
|
||||
timeout_hint_us(kNoTimeOut),
|
||||
cv(mu) {}
|
||||
};
|
||||
@@ -70,8 +72,9 @@ class WriteThread {
|
||||
// REQUIRES: db mutex held
|
||||
void ExitWriteThread(Writer* w, Writer* last_writer, Status status);
|
||||
|
||||
void BuildBatchGroup(Writer** last_writer,
|
||||
autovector<WriteBatch*>* write_batch_group);
|
||||
// return total batch group size
|
||||
size_t BuildBatchGroup(Writer** last_writer,
|
||||
autovector<WriteBatch*>* write_batch_group);
|
||||
|
||||
private:
|
||||
// Queue of writers.
|
||||
|
||||
@@ -2,3 +2,4 @@ column_families_example
|
||||
simple_example
|
||||
c_simple_example
|
||||
compact_files_example
|
||||
transaction_example
|
||||
+5
-2
@@ -2,7 +2,7 @@ include ../make_config.mk
|
||||
|
||||
.PHONY: clean
|
||||
|
||||
all: simple_example column_families_example compact_files_example c_simple_example
|
||||
all: simple_example column_families_example compact_files_example c_simple_example transaction_example
|
||||
|
||||
simple_example: simple_example.cc
|
||||
$(CXX) $(CXXFLAGS) $@.cc -o$@ ../librocksdb.a -I../include -O2 -std=c++11 $(PLATFORM_LDFLAGS) $(PLATFORM_CXXFLAGS) $(EXEC_LDFLAGS)
|
||||
@@ -19,5 +19,8 @@ compact_files_example: compact_files_example.cc
|
||||
c_simple_example: c_simple_example.o
|
||||
$(CXX) $@.o -o$@ ../librocksdb.a $(PLATFORM_LDFLAGS) $(EXEC_LDFLAGS)
|
||||
|
||||
transaction_example: transaction_example.cc
|
||||
$(CXX) $(CXXFLAGS) $@.cc -o$@ ../librocksdb.a -I../include -O2 -std=c++11 $(PLATFORM_LDFLAGS) $(PLATFORM_CXXFLAGS) $(EXEC_LDFLAGS)
|
||||
|
||||
clean:
|
||||
rm -rf ./simple_example ./column_families_example ./compact_files_example ./c_simple_example c_simple_example.o
|
||||
rm -rf ./simple_example ./column_families_example ./compact_files_example ./c_simple_example c_simple_example.o ./transaction_example
|
||||
@@ -27,7 +27,7 @@ int main(int argc, char **argv) {
|
||||
db = rocksdb_open(options, DBPath, &err);
|
||||
assert(!err);
|
||||
|
||||
// open Backup Engine that we will use for backing up or database
|
||||
// open Backup Engine that we will use for backing up our database
|
||||
be = rocksdb_backup_engine_open(options, DBBackupPath, &err);
|
||||
assert(!err);
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
using namespace rocksdb;
|
||||
std::string kDBPath = "/tmp/rocksdb_compact_files_example";
|
||||
class CompactionTask;
|
||||
struct CompactionTask;
|
||||
|
||||
// This is an example interface of external-compaction algorithm.
|
||||
// Compaction algorithm can be implemented outside the core-RocksDB
|
||||
@@ -35,19 +35,19 @@ class Compactor : public EventListener {
|
||||
// Example structure that describes a compaction task.
|
||||
struct CompactionTask {
|
||||
CompactionTask(
|
||||
DB* db, Compactor* compactor,
|
||||
const std::string& column_family_name,
|
||||
const std::vector<std::string>& input_file_names,
|
||||
const int output_level,
|
||||
const CompactionOptions& compact_options,
|
||||
bool retry_on_fail)
|
||||
: db(db),
|
||||
compactor(compactor),
|
||||
column_family_name(column_family_name),
|
||||
input_file_names(input_file_names),
|
||||
output_level(output_level),
|
||||
compact_options(compact_options),
|
||||
retry_on_fail(false) {}
|
||||
DB* _db, Compactor* _compactor,
|
||||
const std::string& _column_family_name,
|
||||
const std::vector<std::string>& _input_file_names,
|
||||
const int _output_level,
|
||||
const CompactionOptions& _compact_options,
|
||||
bool _retry_on_fail)
|
||||
: db(_db),
|
||||
compactor(_compactor),
|
||||
column_family_name(_column_family_name),
|
||||
input_file_names(_input_file_names),
|
||||
output_level(_output_level),
|
||||
compact_options(_compact_options),
|
||||
retry_on_fail(_retry_on_fail) {}
|
||||
DB* db;
|
||||
Compactor* compactor;
|
||||
const std::string& column_family_name;
|
||||
@@ -71,13 +71,10 @@ class FullCompactor : public Compactor {
|
||||
// If triggered_writes_stop is true, it will also set the retry
|
||||
// flag of compaction-task to true.
|
||||
void OnFlushCompleted(
|
||||
DB* db, const std::string& cf_name,
|
||||
const std::string& file_path,
|
||||
bool triggered_writes_slowdown,
|
||||
bool triggered_writes_stop) override {
|
||||
CompactionTask* task = PickCompaction(db, cf_name);
|
||||
DB* db, const FlushJobInfo& info) override {
|
||||
CompactionTask* task = PickCompaction(db, info.cf_name);
|
||||
if (task != nullptr) {
|
||||
if (triggered_writes_stop) {
|
||||
if (info.triggered_writes_stop) {
|
||||
task->retry_on_fail = true;
|
||||
}
|
||||
// Schedule compaction in a different thread.
|
||||
|
||||
@@ -0,0 +1,142 @@
|
||||
// Copyright (c) 2015, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the root directory of this source tree. An additional grant
|
||||
// of patent rights can be found in the PATENTS file in the same directory.
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
|
||||
#include "rocksdb/db.h"
|
||||
#include "rocksdb/options.h"
|
||||
#include "rocksdb/slice.h"
|
||||
#include "rocksdb/utilities/optimistic_transaction.h"
|
||||
#include "rocksdb/utilities/optimistic_transaction_db.h"
|
||||
|
||||
using namespace rocksdb;
|
||||
|
||||
std::string kDBPath = "/tmp/rocksdb_transaction_example";
|
||||
|
||||
int main() {
|
||||
// open DB
|
||||
Options options;
|
||||
options.create_if_missing = true;
|
||||
DB* db;
|
||||
OptimisticTransactionDB* txn_db;
|
||||
|
||||
Status s = OptimisticTransactionDB::Open(options, kDBPath, &txn_db);
|
||||
assert(s.ok());
|
||||
db = txn_db->GetBaseDB();
|
||||
|
||||
WriteOptions write_options;
|
||||
ReadOptions read_options;
|
||||
OptimisticTransactionOptions txn_options;
|
||||
std::string value;
|
||||
|
||||
////////////////////////////////////////////////////////
|
||||
//
|
||||
// Simple OptimisticTransaction Example ("Read Committed")
|
||||
//
|
||||
////////////////////////////////////////////////////////
|
||||
|
||||
// Start a transaction
|
||||
OptimisticTransaction* txn = txn_db->BeginTransaction(write_options);
|
||||
assert(txn);
|
||||
|
||||
// Read a key in this transaction
|
||||
s = txn->Get(read_options, "abc", &value);
|
||||
assert(s.IsNotFound());
|
||||
|
||||
// Write a key in this transaction
|
||||
txn->Put("abc", "def");
|
||||
|
||||
// Read a key OUTSIDE this transaction. Does not affect txn.
|
||||
s = db->Get(read_options, "abc", &value);
|
||||
|
||||
// Write a key OUTSIDE of this transaction.
|
||||
// Does not affect txn since this is an unrelated key. If we wrote key 'abc'
|
||||
// here, the transaction would fail to commit.
|
||||
s = db->Put(write_options, "xyz", "zzz");
|
||||
|
||||
// Commit transaction
|
||||
s = txn->Commit();
|
||||
assert(s.ok());
|
||||
delete txn;
|
||||
|
||||
////////////////////////////////////////////////////////
|
||||
//
|
||||
// "Repeatable Read" (Snapshot Isolation) Example
|
||||
// -- Using a single Snapshot
|
||||
//
|
||||
////////////////////////////////////////////////////////
|
||||
|
||||
// Set a snapshot at start of transaction by setting set_snapshot=true
|
||||
txn_options.set_snapshot = true;
|
||||
txn = txn_db->BeginTransaction(write_options, txn_options);
|
||||
|
||||
const Snapshot* snapshot = txn->GetSnapshot();
|
||||
|
||||
// Write a key OUTSIDE of transaction
|
||||
db->Put(write_options, "abc", "xyz");
|
||||
|
||||
// Read a key using the snapshot
|
||||
read_options.snapshot = snapshot;
|
||||
s = txn->GetForUpdate(read_options, "abc", &value);
|
||||
assert(value == "def");
|
||||
|
||||
// Attempt to commit transaction
|
||||
s = txn->Commit();
|
||||
|
||||
// Transaction could not commit since the write outside of the txn conflicted
|
||||
// with the read!
|
||||
assert(s.IsBusy());
|
||||
|
||||
delete txn;
|
||||
// Clear snapshot from read options since it is no longer valid
|
||||
read_options.snapshot = nullptr;
|
||||
snapshot = nullptr;
|
||||
|
||||
////////////////////////////////////////////////////////
|
||||
//
|
||||
// "Read Committed" (Monotonic Atomic Views) Example
|
||||
// --Using multiple Snapshots
|
||||
//
|
||||
////////////////////////////////////////////////////////
|
||||
|
||||
// In this example, we set the snapshot multiple times. This is probably
|
||||
// only necessary if you have very strict isolation requirements to
|
||||
// implement.
|
||||
|
||||
// Set a snapshot at start of transaction
|
||||
txn_options.set_snapshot = true;
|
||||
txn = txn_db->BeginTransaction(write_options, txn_options);
|
||||
|
||||
// Do some reads and writes to key "x"
|
||||
read_options.snapshot = db->GetSnapshot();
|
||||
s = txn->Get(read_options, "x", &value);
|
||||
txn->Put("x", "x");
|
||||
|
||||
// Do a write outside of the transaction to key "y"
|
||||
s = db->Put(write_options, "y", "y");
|
||||
|
||||
// Set a new snapshot in the transaction
|
||||
txn->SetSnapshot();
|
||||
read_options.snapshot = db->GetSnapshot();
|
||||
|
||||
// Do some reads and writes to key "y"
|
||||
s = txn->GetForUpdate(read_options, "y", &value);
|
||||
txn->Put("y", "y");
|
||||
|
||||
// Commit. Since the snapshot was advanced, the write done outside of the
|
||||
// transaction does not prevent this transaction from Committing.
|
||||
s = txn->Commit();
|
||||
assert(s.ok());
|
||||
delete txn;
|
||||
// Clear snapshot from read options since it is no longer valid
|
||||
read_options.snapshot = nullptr;
|
||||
|
||||
// Cleanup
|
||||
delete txn_db;
|
||||
DestroyDB(kDBPath, options);
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif // ROCKSDB_LITE
|
||||
@@ -164,6 +164,10 @@ class HdfsEnv : public Env {
|
||||
return (uint64_t)pthread_self();
|
||||
}
|
||||
|
||||
virtual uint64_t GetThreadID() const override {
|
||||
return HdfsEnv::gettid();
|
||||
}
|
||||
|
||||
private:
|
||||
std::string fsname_; // string of the form "hdfs://hostname:port/"
|
||||
hdfsFS fileSys_; // a single FileSystem object for all files
|
||||
@@ -360,6 +364,10 @@ class HdfsEnv : public Env {
|
||||
virtual void IncBackgroundThreadsIfNeeded(int number, Priority pri) override {
|
||||
}
|
||||
virtual std::string TimeToString(uint64_t number) override { return ""; }
|
||||
|
||||
virtual uint64_t GetThreadID() const override {
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -264,6 +264,34 @@ extern char* rocksdb_get_cf(
|
||||
size_t* vallen,
|
||||
char** errptr);
|
||||
|
||||
// if values_list[i] == NULL and errs[i] == NULL,
|
||||
// then we got status.IsNotFound(), which we will not return.
|
||||
// all errors except status status.ok() and status.IsNotFound() are returned.
|
||||
//
|
||||
// errs, values_list and values_list_sizes must be num_keys in length,
|
||||
// allocated by the caller.
|
||||
// errs is a list of strings as opposed to the conventional one error,
|
||||
// where errs[i] is the status for retrieval of keys_list[i].
|
||||
// each non-NULL errs entry is a malloc()ed, null terminated string.
|
||||
// each non-NULL values_list entry is a malloc()ed array, with
|
||||
// the length for each stored in values_list_sizes[i].
|
||||
extern void rocksdb_multi_get(
|
||||
rocksdb_t* db,
|
||||
const rocksdb_readoptions_t* options,
|
||||
size_t num_keys, const char* const* keys_list,
|
||||
const size_t* keys_list_sizes,
|
||||
char** values_list, size_t* values_list_sizes,
|
||||
char** errs);
|
||||
|
||||
extern void rocksdb_multi_get_cf(
|
||||
rocksdb_t* db,
|
||||
const rocksdb_readoptions_t* options,
|
||||
const rocksdb_column_family_handle_t* const* column_families,
|
||||
size_t num_keys, const char* const* keys_list,
|
||||
const size_t* keys_list_sizes,
|
||||
char** values_list, size_t* values_list_sizes,
|
||||
char** errs);
|
||||
|
||||
extern rocksdb_iterator_t* rocksdb_create_iterator(
|
||||
rocksdb_t* db,
|
||||
const rocksdb_readoptions_t* options);
|
||||
@@ -380,6 +408,19 @@ extern void rocksdb_writebatch_put_cf(
|
||||
rocksdb_column_family_handle_t* column_family,
|
||||
const char* key, size_t klen,
|
||||
const char* val, size_t vlen);
|
||||
extern void rocksdb_writebatch_putv(
|
||||
rocksdb_writebatch_t* b,
|
||||
int num_keys, const char* const* keys_list,
|
||||
const size_t* keys_list_sizes,
|
||||
int num_values, const char* const* values_list,
|
||||
const size_t* values_list_sizes);
|
||||
extern void rocksdb_writebatch_putv_cf(
|
||||
rocksdb_writebatch_t* b,
|
||||
rocksdb_column_family_handle_t* column_family,
|
||||
int num_keys, const char* const* keys_list,
|
||||
const size_t* keys_list_sizes,
|
||||
int num_values, const char* const* values_list,
|
||||
const size_t* values_list_sizes);
|
||||
extern void rocksdb_writebatch_merge(
|
||||
rocksdb_writebatch_t*,
|
||||
const char* key, size_t klen,
|
||||
@@ -389,6 +430,19 @@ extern void rocksdb_writebatch_merge_cf(
|
||||
rocksdb_column_family_handle_t* column_family,
|
||||
const char* key, size_t klen,
|
||||
const char* val, size_t vlen);
|
||||
extern void rocksdb_writebatch_mergev(
|
||||
rocksdb_writebatch_t* b,
|
||||
int num_keys, const char* const* keys_list,
|
||||
const size_t* keys_list_sizes,
|
||||
int num_values, const char* const* values_list,
|
||||
const size_t* values_list_sizes);
|
||||
extern void rocksdb_writebatch_mergev_cf(
|
||||
rocksdb_writebatch_t* b,
|
||||
rocksdb_column_family_handle_t* column_family,
|
||||
int num_keys, const char* const* keys_list,
|
||||
const size_t* keys_list_sizes,
|
||||
int num_values, const char* const* values_list,
|
||||
const size_t* values_list_sizes);
|
||||
extern void rocksdb_writebatch_delete(
|
||||
rocksdb_writebatch_t*,
|
||||
const char* key, size_t klen);
|
||||
@@ -396,6 +450,18 @@ extern void rocksdb_writebatch_delete_cf(
|
||||
rocksdb_writebatch_t*,
|
||||
rocksdb_column_family_handle_t* column_family,
|
||||
const char* key, size_t klen);
|
||||
void rocksdb_writebatch_deletev(
|
||||
rocksdb_writebatch_t* b,
|
||||
int num_keys, const char* const* keys_list,
|
||||
const size_t* keys_list_sizes);
|
||||
void rocksdb_writebatch_deletev_cf(
|
||||
rocksdb_writebatch_t* b,
|
||||
rocksdb_column_family_handle_t* column_family,
|
||||
int num_keys, const char* const* keys_list,
|
||||
const size_t* keys_list_sizes);
|
||||
extern void rocksdb_writebatch_put_log_data(
|
||||
rocksdb_writebatch_t*,
|
||||
const char* blob, size_t len);
|
||||
extern void rocksdb_writebatch_iterate(
|
||||
rocksdb_writebatch_t*,
|
||||
void* state,
|
||||
@@ -428,6 +494,18 @@ extern void rocksdb_block_based_options_set_block_cache_compressed(
|
||||
rocksdb_cache_t* block_cache_compressed);
|
||||
extern void rocksdb_block_based_options_set_whole_key_filtering(
|
||||
rocksdb_block_based_table_options_t*, unsigned char);
|
||||
extern void rocksdb_block_based_options_set_format_version(
|
||||
rocksdb_block_based_table_options_t*, int);
|
||||
enum {
|
||||
rocksdb_block_based_table_index_type_binary_search = 0,
|
||||
rocksdb_block_based_table_index_type_hash_search = 1,
|
||||
};
|
||||
extern void rocksdb_block_based_options_set_index_type(
|
||||
rocksdb_block_based_table_options_t*, int); // uses one of the above enums
|
||||
extern void rocksdb_block_based_options_set_hash_index_allow_collision(
|
||||
rocksdb_block_based_table_options_t*, unsigned char);
|
||||
extern void rocksdb_block_based_options_set_cache_index_and_filter_blocks(
|
||||
rocksdb_block_based_table_options_t*, unsigned char);
|
||||
extern void rocksdb_options_set_block_based_table_factory(
|
||||
rocksdb_options_t *opt, rocksdb_block_based_table_options_t* table_options);
|
||||
|
||||
@@ -476,6 +554,7 @@ extern void rocksdb_options_set_comparator(
|
||||
extern void rocksdb_options_set_merge_operator(
|
||||
rocksdb_options_t*,
|
||||
rocksdb_mergeoperator_t*);
|
||||
extern void rocksdb_options_set_uint64add_merge_operator(rocksdb_options_t*);
|
||||
extern void rocksdb_options_set_compression_per_level(
|
||||
rocksdb_options_t* opt,
|
||||
int* level_values,
|
||||
@@ -523,8 +602,13 @@ extern void rocksdb_options_set_max_bytes_for_level_multiplier_additional(
|
||||
rocksdb_options_t*, int* level_values, size_t num_levels);
|
||||
extern void rocksdb_options_enable_statistics(rocksdb_options_t*);
|
||||
|
||||
/* returns a pointer to a malloc()-ed, null terminated string */
|
||||
extern char *rocksdb_options_statistics_get_string(rocksdb_options_t *opt);
|
||||
|
||||
extern void rocksdb_options_set_max_write_buffer_number(rocksdb_options_t*, int);
|
||||
extern void rocksdb_options_set_min_write_buffer_number_to_merge(rocksdb_options_t*, int);
|
||||
extern void rocksdb_options_set_max_write_buffer_number_to_maintain(
|
||||
rocksdb_options_t*, int);
|
||||
extern void rocksdb_options_set_max_background_compactions(rocksdb_options_t*, int);
|
||||
extern void rocksdb_options_set_max_background_flushes(rocksdb_options_t*, int);
|
||||
extern void rocksdb_options_set_max_log_file_size(rocksdb_options_t*, size_t);
|
||||
@@ -821,6 +905,7 @@ extern rocksdb_slicetransform_t* rocksdb_slicetransform_create(
|
||||
const char* key, size_t length),
|
||||
const char* (*name)(void*));
|
||||
extern rocksdb_slicetransform_t* rocksdb_slicetransform_create_fixed_prefix(size_t);
|
||||
extern rocksdb_slicetransform_t* rocksdb_slicetransform_create_noop();
|
||||
extern void rocksdb_slicetransform_destroy(rocksdb_slicetransform_t*);
|
||||
|
||||
/* Universal Compaction options */
|
||||
|
||||
@@ -104,6 +104,9 @@ class Cache {
|
||||
// returns the memory size for the entries residing in the cache.
|
||||
virtual size_t GetUsage() const = 0;
|
||||
|
||||
// returns the memory size for the entries in use by the system
|
||||
virtual size_t GetPinnedUsage() const = 0;
|
||||
|
||||
// Call this on shutdown if you want to speed it up. Cache will disown
|
||||
// any underlying data and will not free it on delete. This call will leak
|
||||
// memory - call this only if you're shutting down the process.
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
// Copyright (c) 2013, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the root directory of this source tree. An additional grant
|
||||
// of patent rights can be found in the PATENTS file in the same directory.
|
||||
|
||||
#pragma once
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <string>
|
||||
|
||||
namespace rocksdb {
|
||||
struct CompactionJobStats {
|
||||
CompactionJobStats() { Reset(); }
|
||||
void Reset();
|
||||
|
||||
// the elapsed time in micro of this compaction.
|
||||
uint64_t elapsed_micros;
|
||||
|
||||
// the number of compaction input records.
|
||||
uint64_t num_input_records;
|
||||
// the number of compaction input files.
|
||||
size_t num_input_files;
|
||||
// the number of compaction input files at the output level.
|
||||
size_t num_input_files_at_output_level;
|
||||
|
||||
// the number of compaction output records.
|
||||
uint64_t num_output_records;
|
||||
// the number of compaction output files.
|
||||
size_t num_output_files;
|
||||
|
||||
// true if the compaction is a manual compaction
|
||||
bool is_manual_compaction;
|
||||
|
||||
// the size of the compaction input in bytes.
|
||||
uint64_t total_input_bytes;
|
||||
// the size of the compaction output in bytes.
|
||||
uint64_t total_output_bytes;
|
||||
|
||||
// number of records being replaced by newer record associated with same key
|
||||
uint64_t num_records_replaced;
|
||||
|
||||
// the sum of the uncompressed input keys in bytes.
|
||||
uint64_t total_input_raw_key_bytes;
|
||||
// the sum of the uncompressed input values in bytes.
|
||||
uint64_t total_input_raw_value_bytes;
|
||||
|
||||
// 0-terminated strings storing the first 8 bytes of the smallest and
|
||||
// largest key in the output.
|
||||
static const size_t kMaxPrefixLength = 8;
|
||||
|
||||
std::string smallest_output_key_prefix;
|
||||
std::string largest_output_key_prefix;
|
||||
};
|
||||
} // namespace rocksdb
|
||||
+45
-22
@@ -33,6 +33,7 @@ struct ReadOptions;
|
||||
struct WriteOptions;
|
||||
struct FlushOptions;
|
||||
struct CompactionOptions;
|
||||
struct CompactRangeOptions;
|
||||
struct TableProperties;
|
||||
class WriteBatch;
|
||||
class Env;
|
||||
@@ -397,10 +398,12 @@ class DB {
|
||||
//
|
||||
// The results may not include the sizes of recently written data.
|
||||
virtual void GetApproximateSizes(ColumnFamilyHandle* column_family,
|
||||
const Range* range, int n,
|
||||
uint64_t* sizes) = 0;
|
||||
virtual void GetApproximateSizes(const Range* range, int n, uint64_t* sizes) {
|
||||
GetApproximateSizes(DefaultColumnFamily(), range, n, sizes);
|
||||
const Range* range, int n, uint64_t* sizes,
|
||||
bool include_memtable = false) = 0;
|
||||
virtual void GetApproximateSizes(const Range* range, int n, uint64_t* sizes,
|
||||
bool include_memtable = false) {
|
||||
GetApproximateSizes(DefaultColumnFamily(), range, n, sizes,
|
||||
include_memtable);
|
||||
}
|
||||
|
||||
// Compact the underlying storage for the key range [*begin,*end].
|
||||
@@ -413,25 +416,42 @@ class DB {
|
||||
// begin==nullptr is treated as a key before all keys in the database.
|
||||
// end==nullptr is treated as a key after all keys in the database.
|
||||
// Therefore the following call will compact the entire database:
|
||||
// db->CompactRange(nullptr, nullptr);
|
||||
// db->CompactRange(options, nullptr, nullptr);
|
||||
// Note that after the entire database is compacted, all data are pushed
|
||||
// down to the last level containing any data. If the total data size
|
||||
// after compaction is reduced, that level might not be appropriate for
|
||||
// hosting all the files. In this case, client could set reduce_level
|
||||
// to true, to move the files back to the minimum level capable of holding
|
||||
// the data set or a given level (specified by non-negative target_level).
|
||||
// Compaction outputs should be placed in options.db_paths[target_path_id].
|
||||
// Behavior is undefined if target_path_id is out of range.
|
||||
virtual Status CompactRange(ColumnFamilyHandle* column_family,
|
||||
const Slice* begin, const Slice* end,
|
||||
bool reduce_level = false, int target_level = -1,
|
||||
uint32_t target_path_id = 0) = 0;
|
||||
virtual Status CompactRange(const Slice* begin, const Slice* end,
|
||||
bool reduce_level = false, int target_level = -1,
|
||||
uint32_t target_path_id = 0) {
|
||||
return CompactRange(DefaultColumnFamily(), begin, end, reduce_level,
|
||||
target_level, target_path_id);
|
||||
// down to the last level containing any data. If the total data size after
|
||||
// compaction is reduced, that level might not be appropriate for hosting all
|
||||
// the files. In this case, client could set options.change_level to true, to
|
||||
// move the files back to the minimum level capable of holding the data set
|
||||
// or a given level (specified by non-negative options.target_level).
|
||||
virtual Status CompactRange(const CompactRangeOptions& options,
|
||||
ColumnFamilyHandle* column_family,
|
||||
const Slice* begin, const Slice* end) = 0;
|
||||
virtual Status CompactRange(const CompactRangeOptions& options,
|
||||
const Slice* begin, const Slice* end) {
|
||||
return CompactRange(options, DefaultColumnFamily(), begin, end);
|
||||
}
|
||||
|
||||
__attribute__((deprecated)) 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;
|
||||
options.target_path_id = target_path_id;
|
||||
return CompactRange(options, column_family, begin, end);
|
||||
}
|
||||
__attribute__((deprecated)) 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;
|
||||
options.target_path_id = target_path_id;
|
||||
return CompactRange(options, DefaultColumnFamily(), begin, end);
|
||||
}
|
||||
|
||||
virtual Status SetOptions(ColumnFamilyHandle* column_family,
|
||||
const std::unordered_map<std::string, std::string>& new_options) {
|
||||
return Status::NotSupported("Not implemented");
|
||||
@@ -590,7 +610,7 @@ class DB {
|
||||
// Sets the globally unique ID created at database creation time by invoking
|
||||
// Env::GenerateUniqueId(), in identity. Returns Status::OK if identity could
|
||||
// be set properly
|
||||
virtual Status GetDbIdentity(std::string& identity) = 0;
|
||||
virtual Status GetDbIdentity(std::string& identity) const = 0;
|
||||
|
||||
// Returns default column family handle
|
||||
virtual ColumnFamilyHandle* DefaultColumnFamily() const = 0;
|
||||
@@ -603,6 +623,9 @@ class DB {
|
||||
}
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
// Needed for StackableDB
|
||||
virtual DB* GetRootDB() { return this; }
|
||||
|
||||
private:
|
||||
// No copying allowed
|
||||
DB(const DB&);
|
||||
|
||||
+55
-5
@@ -17,12 +17,12 @@
|
||||
#ifndef STORAGE_ROCKSDB_INCLUDE_ENV_H_
|
||||
#define STORAGE_ROCKSDB_INCLUDE_ENV_H_
|
||||
|
||||
#include <cstdarg>
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <limits>
|
||||
#include <vector>
|
||||
#include <stdint.h>
|
||||
#include <cstdarg>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include "rocksdb/status.h"
|
||||
#include "rocksdb/thread_status.h"
|
||||
|
||||
@@ -320,6 +320,9 @@ class Env {
|
||||
return thread_status_updater_;
|
||||
}
|
||||
|
||||
// Returns the ID of the current thread.
|
||||
virtual uint64_t GetThreadID() const;
|
||||
|
||||
protected:
|
||||
// The pointer to an internal structure that will update the
|
||||
// status of each thread.
|
||||
@@ -543,6 +546,8 @@ class WritableFile {
|
||||
void operator=(const WritableFile&);
|
||||
|
||||
protected:
|
||||
friend class WritableFileWrapper;
|
||||
|
||||
Env::IOPriority io_priority_;
|
||||
};
|
||||
|
||||
@@ -874,10 +879,55 @@ class EnvWrapper : public Env {
|
||||
return target_->GetThreadStatusUpdater();
|
||||
}
|
||||
|
||||
uint64_t GetThreadID() const override {
|
||||
return target_->GetThreadID();
|
||||
}
|
||||
|
||||
private:
|
||||
Env* target_;
|
||||
};
|
||||
|
||||
// An implementation of WritableFile that forwards all calls to another
|
||||
// WritableFile. May be useful to clients who wish to override just part of the
|
||||
// functionality of another WritableFile.
|
||||
// It's declared as friend of WritableFile to allow forwarding calls to
|
||||
// protected virtual methods.
|
||||
class WritableFileWrapper : public WritableFile {
|
||||
public:
|
||||
explicit WritableFileWrapper(WritableFile* t) : target_(t) { }
|
||||
|
||||
Status Append(const Slice& data) override { return target_->Append(data); }
|
||||
Status Close() override { return target_->Close(); }
|
||||
Status Flush() override { return target_->Flush(); }
|
||||
Status Sync() override { return target_->Sync(); }
|
||||
Status Fsync() override { return target_->Fsync(); }
|
||||
void SetIOPriority(Env::IOPriority pri) override {
|
||||
target_->SetIOPriority(pri);
|
||||
}
|
||||
uint64_t GetFileSize() override { return target_->GetFileSize(); }
|
||||
void GetPreallocationStatus(size_t* block_size,
|
||||
size_t* last_allocated_block) override {
|
||||
target_->GetPreallocationStatus(block_size, last_allocated_block);
|
||||
}
|
||||
size_t GetUniqueId(char* id, size_t max_size) const override {
|
||||
return target_->GetUniqueId(id, max_size);
|
||||
}
|
||||
Status InvalidateCache(size_t offset, size_t length) override {
|
||||
return target_->InvalidateCache(offset, length);
|
||||
}
|
||||
|
||||
protected:
|
||||
Status Allocate(off_t offset, off_t len) override {
|
||||
return target_->Allocate(offset, len);
|
||||
}
|
||||
Status RangeSync(off_t offset, off_t nbytes) override {
|
||||
return target_->RangeSync(offset, nbytes);
|
||||
}
|
||||
|
||||
private:
|
||||
WritableFile* target_;
|
||||
};
|
||||
|
||||
// Returns a new environment that stores its data in memory and delegates
|
||||
// all non-file-storage tasks to base_env. The caller must delete the result
|
||||
// when it is no longer needed.
|
||||
|
||||
@@ -95,11 +95,11 @@ struct ImmutableCFOptions {
|
||||
|
||||
bool optimize_filters_for_hits;
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
// A vector of EventListeners which call-back functions will be called
|
||||
// when specific RocksDB event happens.
|
||||
std::vector<std::shared_ptr<EventListener>> listeners;
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
std::shared_ptr<Cache> row_cache;
|
||||
};
|
||||
|
||||
} // namespace rocksdb
|
||||
Loaded 100 of 198 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user