mirror of
https://github.com/facebook/rocksdb.git
synced 2026-11-06 02:52:35 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2b90ba02c7 | ||
|
|
acb6d82a6e | ||
|
|
ab4f72fea9 | ||
|
|
57db79499f | ||
|
|
15d9f3a4f4 | ||
|
|
61cac5aca7 | ||
|
|
354d6920f1 | ||
|
|
e44957c21d | ||
|
|
3e0700e1ea | ||
|
|
0ee2ee8b27 | ||
|
|
d66a90fc87 | ||
|
|
7e10e1e914 | ||
|
|
61621708c6 | ||
|
|
f5561ed779 | ||
|
|
35b43eeea1 | ||
|
|
297241e056 | ||
|
|
254c4fb88f | ||
|
|
40f893f4a9 | ||
|
|
6a4aaadcd7 | ||
|
|
d7314ba759 | ||
|
|
48e6e9aa82 | ||
|
|
c7742452eb | ||
|
|
29b028b0ed | ||
|
|
fca88f8e16 | ||
|
|
7d364d0d94 | ||
|
|
960d936e83 | ||
|
|
7ccd1c80a7 | ||
|
|
3ae386eafe | ||
|
|
8ecb51a7e9 | ||
|
|
9aec75fbb9 | ||
|
|
bd2fc5f5fb | ||
|
|
8e01bd1144 | ||
|
|
c465071029 | ||
|
|
e06cf1a098 | ||
|
|
5dc3e6881a | ||
|
|
c45a57b41e | ||
|
|
102ac118b2 | ||
|
|
3a1d4e6c9f | ||
|
|
f5d072ae6c | ||
|
|
f39cbcb0a5 | ||
|
|
f0b5bcc7b5 | ||
|
|
18ba58a943 | ||
|
|
ce21afd205 | ||
|
|
b0d12a135f | ||
|
|
241bb2aef3 | ||
|
|
3424eeb1ed | ||
|
|
cf3e05304f | ||
|
|
24daff6d7a | ||
|
|
14d0bfa429 | ||
|
|
e2a3bfe74b | ||
|
|
2e73bd4ff2 | ||
|
|
1205bdbcee | ||
|
|
d5c0a6da6c | ||
|
|
2d41403f45 | ||
|
|
92f7039eec | ||
|
|
be8621ffaf | ||
|
|
26894303c1 | ||
|
|
20b244fcca | ||
|
|
87df6295dd | ||
|
|
40c64434d4 | ||
|
|
47316c2d08 | ||
|
|
bd852bf118 | ||
|
|
544be638ab | ||
|
|
193dc977e7 | ||
|
|
05d4265a28 | ||
|
|
4be6d44167 | ||
|
|
8161bdb5a0 | ||
|
|
7bfae3a723 | ||
|
|
d06c82e477 | ||
|
|
e95c59cd2f | ||
|
|
221a94a5fd | ||
|
|
1bdfcef7bf | ||
|
|
6a82fba75f | ||
|
|
f73c801432 | ||
|
|
14f4137602 | ||
|
|
8279d41972 | ||
|
|
eb8e3b4c7a | ||
|
|
555ca3e7b7 | ||
|
|
82f148ef97 | ||
|
|
6002801e09 | ||
|
|
4922af6f8d | ||
|
|
3bf9f9a832 | ||
|
|
fe09a6dae3 | ||
|
|
06aebca592 | ||
|
|
85ac65536b | ||
|
|
f09d45690b | ||
|
|
31b35c902e | ||
|
|
66a3cbc549 | ||
|
|
ac5e441ad8 | ||
|
|
02b635fa38 | ||
|
|
3dbf4ba220 | ||
|
|
7219088cda | ||
|
|
0adecd9f4f | ||
|
|
064294081b | ||
|
|
9f1de95187 | ||
|
|
6867fb19c7 | ||
|
|
443c6646bc | ||
|
|
7462286d33 | ||
|
|
03467bdd4d | ||
|
|
331954ab83 | ||
|
|
a75f23eb87 | ||
|
|
ee80432ff8 | ||
|
|
58b4209e05 | ||
|
|
59eca2cc99 | ||
|
|
aa8ac6445b | ||
|
|
ce9712d340 | ||
|
|
c06d1d8393 | ||
|
|
144d2910db | ||
|
|
0d1d9aeebe | ||
|
|
4853e228ef | ||
|
|
f0fe9126f2 | ||
|
|
cf6a7bebc8 | ||
|
|
20922c4a5a | ||
|
|
6e9fbeb27c | ||
|
|
5ec829bc4f | ||
|
|
26ca893198 | ||
|
|
35ca59364c | ||
|
|
79373c372d | ||
|
|
74c755c552 | ||
|
|
a96fcd09b7 | ||
|
|
1d20fa9d0f | ||
|
|
aede5cd8e6 | ||
|
|
d730c36777 | ||
|
|
ac2b9367fe | ||
|
|
415c473239 | ||
|
|
d1a457181d | ||
|
|
6c0c8dee7b | ||
|
|
e4af3bfb27 | ||
|
|
91bf1b80ef | ||
|
|
3d00271e40 | ||
|
|
62dec0e2b7 | ||
|
|
c5bca53198 | ||
|
|
487bba4348 | ||
|
|
247690fe38 | ||
|
|
84c3577af9 | ||
|
|
43e9825625 | ||
|
|
d8263d9584 | ||
|
|
12c5528a89 | ||
|
|
81d072623c | ||
|
|
beb19ad0dd | ||
|
|
c613960690 | ||
|
|
2c8de0ecae | ||
|
|
6b2d44b2ff | ||
|
|
e1c99e10c1 | ||
|
|
9a6a0bd8c9 | ||
|
|
e94c510c3f | ||
|
|
ddad40e930 | ||
|
|
1bc8eb8777 | ||
|
|
801df912af | ||
|
|
3ca6b2541e | ||
|
|
ce829c77e3 | ||
|
|
c3f98bb89b | ||
|
|
0936362a70 | ||
|
|
05e194158c | ||
|
|
fc2b71d9cc | ||
|
|
8a9fca2619 | ||
|
|
18d5e1bf88 | ||
|
|
9d22a97379 | ||
|
|
49640bd82f | ||
|
|
a9c5109515 | ||
|
|
5aea98ddd8 | ||
|
|
ae29495e4b | ||
|
|
ab137af4ba | ||
|
|
1879d9370c | ||
|
|
b10cf4e2e0 | ||
|
|
e290f5d3ce | ||
|
|
04d201fa0b | ||
|
|
49f42ad032 | ||
|
|
625467a08a | ||
|
|
e8e8c90499 | ||
|
|
8bca83e5dd | ||
|
|
f9728640f3 | ||
|
|
76d3cd3286 | ||
|
|
5fd11853cb | ||
|
|
a6e38fd170 | ||
|
|
e41cbd9c2f | ||
|
|
4cab5ebecd | ||
|
|
296de4ae6d | ||
|
|
041b6f95a2 | ||
|
|
d08ba9f0c4 | ||
|
|
805fe84ba9 | ||
|
|
5555cc500f | ||
|
|
c903ccc4c2 | ||
|
|
54d124a388 | ||
|
|
7189e90c22 | ||
|
|
1f4d565709 | ||
|
|
5c79132335 | ||
|
|
d8586ab22b | ||
|
|
4bed00a44b | ||
|
|
e2e3d84b2c | ||
|
|
5219226d3d | ||
|
|
ef4b87f1b2 | ||
|
|
95f4c2bcb5 | ||
|
|
4f56632b16 | ||
|
|
411c8e3d19 | ||
|
|
b7a2369fb2 | ||
|
|
c0b23dd5b0 | ||
|
|
d8e3e766f9 | ||
|
|
57d216ea65 | ||
|
|
59b50dcef9 | ||
|
|
4ce5be4255 | ||
|
|
685582a0b4 | ||
|
|
e12b403991 | ||
|
|
58d7ab3c68 | ||
|
|
155ce60daf | ||
|
|
b6655a679d | ||
|
|
e25ee32e3d | ||
|
|
d2f0912bd3 | ||
|
|
35cd75c379 | ||
|
|
acee2b08a2 | ||
|
|
218487d8dc | ||
|
|
feb99c31a4 | ||
|
|
e70115e71b | ||
|
|
9dbde7277c | ||
|
|
326da912de | ||
|
|
ca2fe2c1b6 | ||
|
|
19e13a595d | ||
|
|
18285c1e2f | ||
|
|
dda74111ae | ||
|
|
d62b6ed838 |
No files matched your search
@@ -21,6 +21,13 @@ make_config.mk
|
||||
*.o-*
|
||||
*.swp
|
||||
*~
|
||||
*.vcxproj
|
||||
*.vcxproj.filters
|
||||
*.sln
|
||||
*.cmake
|
||||
CMakeCache.txt
|
||||
CMakeFiles/
|
||||
build/
|
||||
|
||||
ldb
|
||||
manifest_dump
|
||||
|
||||
+1
-1
@@ -14,6 +14,6 @@ before_install:
|
||||
- sudo dpkg -i libgflags-dev_2.0-1_amd64.deb
|
||||
# Lousy hack to disable use and testing of fallocate, which doesn't behave quite
|
||||
# as EnvPosixTest::AllocateTest expects within the Travis OpenVZ environment.
|
||||
script: OPT=-DTRAVIS V=1 make unity && make clean && OPT=-DTRAVIS V=1 make db_test && ./db_test
|
||||
script: OPT=-DTRAVIS V=1 make unity && make clean && OPT=-DTRAVIS V=1 make db_test && ./db_test && OPT=-DTRAVIS V=1 make clean jclean rocksdbjava jtest
|
||||
notifications:
|
||||
email: false
|
||||
+347
@@ -0,0 +1,347 @@
|
||||
# This cmake build is for Windows 64-bit only.
|
||||
#
|
||||
# Prerequisites:
|
||||
# You must have Visual Studio 2013 Update 4 installed. Start the Developer Command Prompt window that is a part of Visual Studio installation.
|
||||
# Run the build commands from within the Developer Command Prompt window to have paths to the compiler and runtime libraries set.
|
||||
# You must have git.exe in your %PATH% environment variable.
|
||||
#
|
||||
# To build Rocksdb for Windows is as easy as 1-2-3-4-5:
|
||||
#
|
||||
# 1. Update paths to third-party libraries in thirdparty.inc file
|
||||
# 2. Create a new directory for build artifacts
|
||||
# mkdir build
|
||||
# cd build
|
||||
# 3. Run cmake to generate project files for Windows, add more options to enable required third-party libraries.
|
||||
# See thirdparty.inc for more information.
|
||||
# cmake -G "Visual Studio 12 Win64" .. <more options>
|
||||
# 4. Then build the project in debug mode (you may want to add /m:<N> flag to run msbuild in <N> parallel threads)
|
||||
# msbuild ALL_BUILD.vcxproj
|
||||
# 5. And release mode (/m:<N> is also supported)
|
||||
# msbuild ALL_BUILD.vcxproj /p:Configuration=Release
|
||||
#
|
||||
|
||||
cmake_minimum_required(VERSION 2.6)
|
||||
project(rocksdb)
|
||||
|
||||
include(${CMAKE_CURRENT_SOURCE_DIR}/thirdparty.inc)
|
||||
|
||||
execute_process(COMMAND $ENV{COMSPEC} " /C date /T" OUTPUT_VARIABLE DATE)
|
||||
execute_process(COMMAND $ENV{COMSPEC} " /C time /T" OUTPUT_VARIABLE TIME)
|
||||
string(REGEX REPLACE "(..)/(..)/..(..).*" "\\1/\\2/\\3" DATE ${DATE})
|
||||
string(REGEX REPLACE "(..):(.....).*" " \\1:\\2" TIME ${TIME})
|
||||
string(CONCAT GIT_DATE_TIME ${DATE} ${TIME})
|
||||
|
||||
execute_process(COMMAND $ENV{COMSPEC} " /C git rev-parse HEAD 2>nil" OUTPUT_VARIABLE GIT_SHA)
|
||||
string(REGEX REPLACE "[^0-9a-f]+" "" GIT_SHA ${GIT_SHA})
|
||||
|
||||
set(BUILD_VERSION_CC ${CMAKE_CURRENT_SOURCE_DIR}/util/build_version.cc)
|
||||
|
||||
add_custom_command(OUTPUT ${BUILD_VERSION_CC}
|
||||
COMMAND echo "#include \"build_version.h\"" > ${BUILD_VERSION_CC}
|
||||
COMMAND echo "const char* rocksdb_build_git_sha = \"rocksdb_build_git_sha:${GIT_SHA}\";" >> ${BUILD_VERSION_CC}
|
||||
COMMAND echo "const char* rocksdb_build_git_datetime = \"rocksdb_build_git_datetime:${GIT_DATE_TIME}\";" >> ${BUILD_VERSION_CC}
|
||||
COMMAND echo const char* rocksdb_build_compile_date = __DATE__\; >> ${BUILD_VERSION_CC}
|
||||
)
|
||||
|
||||
add_custom_target(GenerateBuildVersion DEPENDS ${BUILD_VERSION_CC})
|
||||
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /Zi /nologo /W3 /WX /EHsc /GS /fp:precise /Zc:wchar_t /Zc:forScope /Gd /TP /errorReport:queue")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /FC /d2Zi+ /wd4018 /wd4100 /wd4101 /wd4127 /wd4189 /wd4200 /wd4244 /wd4267 /wd4296 /wd4305 /wd4307 /wd4309 /wd4512 /wd4701 /wd4702 /wd4800 /wd4804 /wd4996")
|
||||
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /Od /RTC1 /Gm /MDd")
|
||||
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /O2 /Oi /Gm- /Gy /MD")
|
||||
|
||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /DEBUG")
|
||||
|
||||
add_definitions(-DWIN32 -DOS_WIN -D_MBCS -DWIN64)
|
||||
|
||||
include_directories(${PROJECT_SOURCE_DIR})
|
||||
include_directories(${PROJECT_SOURCE_DIR}/include)
|
||||
include_directories(${PROJECT_SOURCE_DIR}/port)
|
||||
include_directories(${PROJECT_SOURCE_DIR}/third-party/gtest-1.7.0/fused-src)
|
||||
|
||||
set(ROCKSDB_LIBS rocksdblib${ARTIFACT_SUFFIX})
|
||||
set(THIRDPARTY_LIBS ${THIRDPARTY_LIBS} gtest)
|
||||
set(SYSTEM_LIBS Shlwapi.lib Rpcrt4.lib)
|
||||
|
||||
set(LIBS ${ROCKSDB_LIBS} ${THIRDPARTY_LIBS} ${SYSTEM_LIBS})
|
||||
|
||||
add_subdirectory(third-party/gtest-1.7.0/fused-src/gtest)
|
||||
|
||||
set(SOURCES
|
||||
db/builder.cc
|
||||
db/c.cc
|
||||
db/column_family.cc
|
||||
db/compacted_db_impl.cc
|
||||
db/compaction.cc
|
||||
db/compaction_job.cc
|
||||
db/compaction_picker.cc
|
||||
db/convenience.cc
|
||||
db/dbformat.cc
|
||||
db/db_filesnapshot.cc
|
||||
db/db_impl.cc
|
||||
db/db_impl_debug.cc
|
||||
db/db_impl_experimental.cc
|
||||
db/db_impl_readonly.cc
|
||||
db/db_iter.cc
|
||||
db/event_helpers.cc
|
||||
db/experimental.cc
|
||||
db/filename.cc
|
||||
db/file_indexer.cc
|
||||
db/flush_job.cc
|
||||
db/flush_scheduler.cc
|
||||
db/forward_iterator.cc
|
||||
db/internal_stats.cc
|
||||
db/log_reader.cc
|
||||
db/log_writer.cc
|
||||
db/managed_iterator.cc
|
||||
db/memtable.cc
|
||||
db/memtable_allocator.cc
|
||||
db/memtable_list.cc
|
||||
db/merge_helper.cc
|
||||
db/merge_operator.cc
|
||||
db/repair.cc
|
||||
db/slice.cc
|
||||
db/table_cache.cc
|
||||
db/table_properties_collector.cc
|
||||
db/transaction_log_impl.cc
|
||||
db/version_builder.cc
|
||||
db/version_edit.cc
|
||||
db/version_set.cc
|
||||
db/wal_manager.cc
|
||||
db/write_batch.cc
|
||||
db/write_batch_base.cc
|
||||
db/write_controller.cc
|
||||
db/write_thread.cc
|
||||
port/stack_trace.cc
|
||||
port/win/env_win.cc
|
||||
port/win/port_win.cc
|
||||
port/win/win_logger.cc
|
||||
table/adaptive_table_factory.cc
|
||||
table/block.cc
|
||||
table/block_based_filter_block.cc
|
||||
table/block_based_table_builder.cc
|
||||
table/block_based_table_factory.cc
|
||||
table/block_based_table_reader.cc
|
||||
table/block_builder.cc
|
||||
table/block_hash_index.cc
|
||||
table/block_prefix_index.cc
|
||||
table/bloom_block.cc
|
||||
table/cuckoo_table_builder.cc
|
||||
table/cuckoo_table_factory.cc
|
||||
table/cuckoo_table_reader.cc
|
||||
table/flush_block_policy.cc
|
||||
table/format.cc
|
||||
table/full_filter_block.cc
|
||||
table/get_context.cc
|
||||
table/iterator.cc
|
||||
table/merger.cc
|
||||
table/meta_blocks.cc
|
||||
table/mock_table.cc
|
||||
table/plain_table_builder.cc
|
||||
table/plain_table_factory.cc
|
||||
table/plain_table_index.cc
|
||||
table/plain_table_key_coding.cc
|
||||
table/plain_table_reader.cc
|
||||
table/table_properties.cc
|
||||
table/two_level_iterator.cc
|
||||
util/arena.cc
|
||||
util/auto_roll_logger.cc
|
||||
util/bloom.cc
|
||||
util/build_version.cc
|
||||
util/cache.cc
|
||||
util/coding.cc
|
||||
util/compaction_job_stats_impl.cc
|
||||
util/comparator.cc
|
||||
util/crc32c.cc
|
||||
util/db_info_dumper.cc
|
||||
util/delete_scheduler_impl.cc
|
||||
util/db_test_util.cc
|
||||
util/dynamic_bloom.cc
|
||||
util/env.cc
|
||||
util/env_hdfs.cc
|
||||
util/event_logger.cc
|
||||
util/file_util.cc
|
||||
util/file_reader_writer.cc
|
||||
util/filter_policy.cc
|
||||
util/hash.cc
|
||||
util/hash_cuckoo_rep.cc
|
||||
util/hash_linklist_rep.cc
|
||||
util/hash_skiplist_rep.cc
|
||||
util/histogram.cc
|
||||
util/instrumented_mutex.cc
|
||||
util/iostats_context.cc
|
||||
util/ldb_cmd.cc
|
||||
util/ldb_tool.cc
|
||||
util/logging.cc
|
||||
util/log_buffer.cc
|
||||
util/memenv.cc
|
||||
util/mock_env.cc
|
||||
util/murmurhash.cc
|
||||
util/mutable_cf_options.cc
|
||||
util/options.cc
|
||||
util/options_builder.cc
|
||||
util/options_helper.cc
|
||||
util/perf_context.cc
|
||||
util/perf_level.cc
|
||||
util/rate_limiter.cc
|
||||
util/skiplistrep.cc
|
||||
util/slice.cc
|
||||
util/sst_dump_tool.cc
|
||||
util/statistics.cc
|
||||
util/status.cc
|
||||
util/string_util.cc
|
||||
util/sync_point.cc
|
||||
util/testharness.cc
|
||||
util/testutil.cc
|
||||
util/thread_local.cc
|
||||
util/thread_status_impl.cc
|
||||
util/thread_status_updater.cc
|
||||
util/thread_status_updater_debug.cc
|
||||
util/thread_status_util.cc
|
||||
util/thread_status_util_debug.cc
|
||||
util/vectorrep.cc
|
||||
util/xfunc.cc
|
||||
util/xxhash.cc
|
||||
utilities/backupable/backupable_db.cc
|
||||
utilities/checkpoint/checkpoint.cc
|
||||
utilities/document/document_db.cc
|
||||
utilities/document/json_document.cc
|
||||
utilities/document/json_document_builder.cc
|
||||
utilities/flashcache/flashcache.cc
|
||||
utilities/geodb/geodb_impl.cc
|
||||
utilities/leveldb_options/leveldb_options.cc
|
||||
utilities/merge_operators/string_append/stringappend.cc
|
||||
utilities/merge_operators/string_append/stringappend2.cc
|
||||
utilities/merge_operators/put.cc
|
||||
utilities/merge_operators/uint64add.cc
|
||||
utilities/redis/redis_lists.cc
|
||||
utilities/spatialdb/spatial_db.cc
|
||||
utilities/table_properties_collectors/compact_on_deletion_collector.cc
|
||||
utilities/transactions/optimistic_transaction_impl.cc
|
||||
utilities/transactions/optimistic_transaction_db_impl.cc
|
||||
utilities/ttl/db_ttl_impl.cc
|
||||
utilities/write_batch_with_index/write_batch_with_index.cc
|
||||
utilities/write_batch_with_index/write_batch_with_index_internal.cc
|
||||
)
|
||||
|
||||
add_library(rocksdblib${ARTIFACT_SUFFIX} ${SOURCES})
|
||||
set_target_properties(rocksdblib${ARTIFACT_SUFFIX} PROPERTIES COMPILE_FLAGS "/Fd${CMAKE_CFG_INTDIR}/rocksdblib${ARTIFACT_SUFFIX}.pdb")
|
||||
add_dependencies(rocksdblib${ARTIFACT_SUFFIX} GenerateBuildVersion)
|
||||
|
||||
add_library(rocksdb${ARTIFACT_SUFFIX} SHARED ${SOURCES})
|
||||
set_target_properties(rocksdb${ARTIFACT_SUFFIX} PROPERTIES COMPILE_FLAGS "-DROCKSDB_DLL -DROCKSDB_LIBRARY_EXPORTS /Fd${CMAKE_CFG_INTDIR}/rocksdb${ARTIFACT_SUFFIX}.pdb")
|
||||
add_dependencies(rocksdb${ARTIFACT_SUFFIX} GenerateBuildVersion)
|
||||
target_link_libraries(rocksdb${ARTIFACT_SUFFIX} ${LIBS})
|
||||
|
||||
set(APPS
|
||||
db/db_bench.cc
|
||||
db/memtablerep_bench.cc
|
||||
table/table_reader_bench.cc
|
||||
tools/db_stress.cc
|
||||
tools/db_repl_stress.cc
|
||||
tools/sst_dump.cc
|
||||
tools/dump/rocksdb_dump.cc
|
||||
tools/dump/rocksdb_undump.cc
|
||||
util/cache_bench.cc
|
||||
)
|
||||
|
||||
set(TESTS
|
||||
db/c_test.c
|
||||
db/column_family_test.cc
|
||||
db/compact_files_test.cc
|
||||
db/compaction_job_test.cc
|
||||
db/compaction_job_stats_test.cc
|
||||
db/compaction_picker_test.cc
|
||||
db/comparator_db_test.cc
|
||||
db/corruption_test.cc
|
||||
db/cuckoo_table_db_test.cc
|
||||
db/db_iter_test.cc
|
||||
db/db_test.cc
|
||||
db/db_compaction_filter_test.cc
|
||||
db/db_compaction_test.cc
|
||||
db/db_dynamic_level_test.cc
|
||||
db/db_inplace_update_test.cc
|
||||
db/db_log_iter_test.cc
|
||||
db/db_universal_compaction_test.cc
|
||||
db/db_wal_test.cc
|
||||
db/db_tailing_iter_test.cc
|
||||
db/dbformat_test.cc
|
||||
db/deletefile_test.cc
|
||||
db/fault_injection_test.cc
|
||||
db/file_indexer_test.cc
|
||||
db/filename_test.cc
|
||||
db/flush_job_test.cc
|
||||
db/listener_test.cc
|
||||
db/log_test.cc
|
||||
db/memtable_list_test.cc
|
||||
db/merge_test.cc
|
||||
db/merge_helper_test.cc
|
||||
db/perf_context_test.cc
|
||||
db/plain_table_db_test.cc
|
||||
db/prefix_test.cc
|
||||
db/skiplist_test.cc
|
||||
db/table_properties_collector_test.cc
|
||||
db/version_builder_test.cc
|
||||
db/version_edit_test.cc
|
||||
db/version_set_test.cc
|
||||
db/wal_manager_test.cc
|
||||
db/write_batch_test.cc
|
||||
db/write_callback_test.cc
|
||||
db/write_controller_test.cc
|
||||
table/block_based_filter_block_test.cc
|
||||
table/block_hash_index_test.cc
|
||||
table/block_test.cc
|
||||
table/cuckoo_table_builder_test.cc
|
||||
table/cuckoo_table_reader_test.cc
|
||||
table/full_filter_block_test.cc
|
||||
table/merger_test.cc
|
||||
table/table_test.cc
|
||||
tools/db_sanity_test.cc
|
||||
tools/reduce_levels_test.cc
|
||||
util/arena_test.cc
|
||||
util/autovector_test.cc
|
||||
util/auto_roll_logger_test.cc
|
||||
util/bloom_test.cc
|
||||
util/cache_test.cc
|
||||
util/coding_test.cc
|
||||
util/crc32c_test.cc
|
||||
util/dynamic_bloom_test.cc
|
||||
util/env_test.cc
|
||||
util/event_logger_test.cc
|
||||
util/filelock_test.cc
|
||||
util/file_reader_writer_test.cc
|
||||
util/heap_test.cc
|
||||
util/histogram_test.cc
|
||||
util/manual_compaction_test.cc
|
||||
util/memenv_test.cc
|
||||
util/mock_env_test.cc
|
||||
util/options_test.cc
|
||||
util/rate_limiter_test.cc
|
||||
util/slice_transform_test.cc
|
||||
util/sst_dump_test.cc
|
||||
util/thread_list_test.cc
|
||||
util/thread_local_test.cc
|
||||
utilities/backupable/backupable_db_test.cc
|
||||
utilities/checkpoint/checkpoint_test.cc
|
||||
utilities/document/document_db_test.cc
|
||||
utilities/document/json_document_test.cc
|
||||
utilities/geodb/geodb_test.cc
|
||||
utilities/merge_operators/string_append/stringappend_test.cc
|
||||
utilities/redis/redis_lists_test.cc
|
||||
utilities/spatialdb/spatial_db_test.cc
|
||||
utilities/table_properties_collectors/compact_on_deletion_collector_test.cc
|
||||
utilities/transactions/optimistic_transaction_test.cc
|
||||
utilities/ttl/ttl_test.cc
|
||||
utilities/write_batch_with_index/write_batch_with_index_test.cc
|
||||
)
|
||||
|
||||
set(EXES ${APPS} ${TESTS})
|
||||
|
||||
foreach(sourcefile ${EXES})
|
||||
string(REPLACE ".cc" "" exename ${sourcefile})
|
||||
string(REPLACE ".c" "" exename ${exename})
|
||||
string(REGEX REPLACE "^((.+)/)+" "" exename ${exename})
|
||||
add_executable(${exename}${ARTIFACT_SUFFIX} ${sourcefile})
|
||||
target_link_libraries(${exename}${ARTIFACT_SUFFIX} ${LIBS})
|
||||
endforeach(sourcefile ${EXES})
|
||||
+20
@@ -1,10 +1,29 @@
|
||||
# Rocksdb Change Log
|
||||
|
||||
## 3.13.0 (8/6/2015)
|
||||
### New Features
|
||||
* RollbackToSavePoint() in WriteBatch/WriteBatchWithIndex
|
||||
* Add NewCompactOnDeletionCollectorFactory() in utilities/table_properties_collectors, which allows rocksdb to mark a SST file as need-compaction when it observes at least D deletion entries in any N consecutive entries in that SST file. Note that this feature depends on an experimental NeedCompact() API --- the result of this API will not persist after DB restart.
|
||||
* Add DBOptions::delete_scheduler. Use NewDeleteScheduler() in include/rocksdb/delete_scheduler.h to create a DeleteScheduler that can be shared among multiple RocksDB instances to control the file deletion rate of SST files that exist in the first db_path.
|
||||
|
||||
### Public API Changes
|
||||
* Deprecated WriteOptions::timeout_hint_us. We no longer support write timeout. If you really need this option, talk to us and we might consider returning it.
|
||||
* Deprecated purge_redundant_kvs_while_flush option.
|
||||
* Removed BackupEngine::NewBackupEngine() and NewReadOnlyBackupEngine() that were deprecated in RocksDB 3.8. Please use BackupEngine::Open() instead.
|
||||
* Deprecated Compaction Filter V2. We are not aware of any existing use-cases. If you use this filter, your compile will break with RocksDB 3.13. Please let us know if you use it and we'll put it back in RocksDB 3.14.
|
||||
* Env::FileExists now returns a Status instead of a boolean
|
||||
* Add statistics::getHistogramString() to print detailed distribution of a histogram metric.
|
||||
* Add DBOptions::skip_stats_update_on_db_open. When it is on, DB::Open() will run faster as it skips the random reads required for loading necessary stats from SST files to optimize compaction.
|
||||
|
||||
## 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().
|
||||
* Add compaction_options_universal.enable_trivial_move to true, to allow trivial move while performing universal compaction. Trivial move will happen only when all the input files are non overlapping.
|
||||
|
||||
### Public API changes
|
||||
* EventListener::OnFlushCompleted() now passes FlushJobInfo instead of a list of parameters.
|
||||
@@ -24,6 +43,7 @@
|
||||
* 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.
|
||||
* Add DB::SyncWAL() that does a WAL sync without blocking writers.
|
||||
|
||||
## 3.11.0 (5/19/2015)
|
||||
### New Features
|
||||
|
||||
@@ -80,3 +80,6 @@ your make commands, like this: `PORTABLE=1 make static_lib`
|
||||
|
||||
* **iOS**:
|
||||
* Run: `TARGET_OS=IOS make static_lib`. When building the project which uses rocksdb iOS library, make sure to define two important pre-processing macros: `ROCKSDB_LITE` and `IOS_CROSS_COMPILE`.
|
||||
|
||||
* **Windows**:
|
||||
* Read the follow the instructions at CMakeLists.txt
|
||||
@@ -167,10 +167,6 @@ default: all
|
||||
WARNING_FLAGS = -W -Wextra -Wall -Wsign-compare -Wshadow \
|
||||
-Wno-unused-parameter
|
||||
|
||||
ifndef DISABLE_WARNING_AS_ERROR
|
||||
WARNING_FLAGS += -Werror
|
||||
endif
|
||||
|
||||
CFLAGS += $(WARNING_FLAGS) -I. -I./include $(PLATFORM_CCFLAGS) $(OPT)
|
||||
CXXFLAGS += $(WARNING_FLAGS) -I. -I./include $(PLATFORM_CXXFLAGS) $(OPT) -Woverloaded-virtual -Wnon-virtual-dtor -Wno-missing-field-initializers
|
||||
|
||||
@@ -220,6 +216,14 @@ VALGRIND_OPTS = --error-exitcode=$(VALGRIND_ERROR) --leak-check=full
|
||||
TESTS = \
|
||||
db_test \
|
||||
db_iter_test \
|
||||
db_log_iter_test \
|
||||
db_compaction_filter_test \
|
||||
db_compaction_test \
|
||||
db_dynamic_level_test \
|
||||
db_inplace_update_test \
|
||||
db_tailing_iter_test \
|
||||
db_universal_compaction_test \
|
||||
db_wal_test \
|
||||
block_hash_index_test \
|
||||
autovector_test \
|
||||
column_family_test \
|
||||
@@ -241,6 +245,7 @@ TESTS = \
|
||||
fault_injection_test \
|
||||
filelock_test \
|
||||
filename_test \
|
||||
file_reader_writer_test \
|
||||
block_based_filter_block_test \
|
||||
full_filter_block_test \
|
||||
histogram_test \
|
||||
@@ -249,6 +254,7 @@ TESTS = \
|
||||
memenv_test \
|
||||
mock_env_test \
|
||||
memtable_list_test \
|
||||
merge_helper_test \
|
||||
merge_test \
|
||||
merger_test \
|
||||
redis_test \
|
||||
@@ -276,6 +282,7 @@ TESTS = \
|
||||
thread_local_test \
|
||||
geodb_test \
|
||||
rate_limiter_test \
|
||||
delete_scheduler_test \
|
||||
options_test \
|
||||
event_logger_test \
|
||||
cuckoo_table_builder_test \
|
||||
@@ -291,6 +298,8 @@ TESTS = \
|
||||
perf_context_test \
|
||||
optimistic_transaction_test \
|
||||
write_callback_test \
|
||||
heap_test \
|
||||
compact_on_deletion_collector_test \
|
||||
compaction_job_stats_test
|
||||
|
||||
SUBSET := $(shell echo $(TESTS) |sed s/^.*$(ROCKSDBTESTS_START)/$(ROCKSDBTESTS_START)/)
|
||||
@@ -335,9 +344,16 @@ SHARED_MAJOR = $(ROCKSDB_MAJOR)
|
||||
SHARED_MINOR = $(ROCKSDB_MINOR)
|
||||
SHARED_PATCH = $(ROCKSDB_PATCH)
|
||||
SHARED1 = ${LIBNAME}.$(PLATFORM_SHARED_EXT)
|
||||
ifeq ($(PLATFORM), OS_MACOSX)
|
||||
SHARED_OSX = $(LIBNAME).$(SHARED_MAJOR)
|
||||
SHARED2 = $(SHARED_OSX).$(PLATFORM_SHARED_EXT)
|
||||
SHARED3 = $(SHARED_OSX).$(SHARED_MINOR).$(PLATFORM_SHARED_EXT)
|
||||
SHARED4 = $(SHARED_OSX).$(SHARED_MINOR).$(SHARED_PATCH).$(PLATFORM_SHARED_EXT)
|
||||
else
|
||||
SHARED2 = $(SHARED1).$(SHARED_MAJOR)
|
||||
SHARED3 = $(SHARED1).$(SHARED_MAJOR).$(SHARED_MINOR)
|
||||
SHARED4 = $(SHARED1).$(SHARED_MAJOR).$(SHARED_MINOR).$(SHARED_PATCH)
|
||||
endif
|
||||
SHARED = $(SHARED1) $(SHARED2) $(SHARED3) $(SHARED4)
|
||||
$(SHARED1): $(SHARED4)
|
||||
ln -fs $(SHARED4) $(SHARED1)
|
||||
@@ -373,7 +389,7 @@ release:
|
||||
|
||||
coverage:
|
||||
$(MAKE) clean
|
||||
COVERAGEFLAGS="-fprofile-arcs -ftest-coverage" LDFLAGS+="-lgcov" $(MAKE) all check
|
||||
COVERAGEFLAGS="-fprofile-arcs -ftest-coverage" LDFLAGS+="-lgcov" $(MAKE) J=1 all check
|
||||
cd coverage && ./coverage_test.sh
|
||||
# Delete intermediate files
|
||||
find . -type f -regex ".*\.\(\(gcda\)\|\(gcno\)\)" -exec rm {} \;
|
||||
@@ -518,6 +534,7 @@ check: all
|
||||
echo "===== Running $$t"; ./$$t || exit 1; done; \
|
||||
fi
|
||||
rm -rf $(TMPD)
|
||||
python tools/ldb_test.py
|
||||
sh tools/rocksdb_dump_test.sh
|
||||
|
||||
check_some: $(SUBSET) ldb_tests
|
||||
@@ -530,9 +547,11 @@ ldb_tests: ldb
|
||||
crash_test: whitebox_crash_test blackbox_crash_test
|
||||
|
||||
blackbox_crash_test: db_stress
|
||||
python -u tools/db_crashtest.py -s
|
||||
python -u tools/db_crashtest.py
|
||||
|
||||
whitebox_crash_test: db_stress
|
||||
python -u tools/db_crashtest2.py -s
|
||||
python -u tools/db_crashtest2.py
|
||||
|
||||
asan_check:
|
||||
@@ -552,8 +571,11 @@ valgrind_check: $(TESTS)
|
||||
for t in $(filter-out skiplist_test,$(TESTS)); do \
|
||||
stime=`date '+%s'`; \
|
||||
$(VALGRIND_VER) $(VALGRIND_OPTS) ./$$t; \
|
||||
if [ $$? -eq $(VALGRIND_ERROR) ] ; then \
|
||||
ret_code=$$?; \
|
||||
if [ $$ret_code -eq $(VALGRIND_ERROR) ] ; then \
|
||||
echo $$t >> $(VALGRIND_DIR)/valgrind_failed_tests; \
|
||||
elif [ $$ret_code -ne 0 ]; then \
|
||||
exit $$ret_code; \
|
||||
fi; \
|
||||
etime=`date '+%s'`; \
|
||||
echo $$t $$((etime - stime)) >> $(VALGRIND_DIR)/valgrind_tests_times; \
|
||||
@@ -671,13 +693,36 @@ crc32c_test: util/crc32c_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
slice_transform_test: util/slice_transform_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
db_test: db/db_test.o util/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
db_test: db/db_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
db_log_iter_test: db/db_log_iter_test.o util/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
db_compaction_filter_test: db/db_compaction_filter_test.o util/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
db_compaction_test: db/db_compaction_test.o util/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
db_dynamic_level_test: db/db_dynamic_level_test.o util/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
db_inplace_update_test: db/db_inplace_update_test.o util/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
db_tailing_iter_test: db/db_tailing_iter_test.o util/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
db_iter_test: db/db_iter_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
db_universal_compaction_test: db/db_universal_compaction_test.o util/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
db_wal_test: db/db_wal_test.o util/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
log_write_bench: util/log_write_bench.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK) $(pg)
|
||||
|
||||
@@ -726,6 +771,9 @@ compaction_job_test: db/compaction_job_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
compaction_job_stats_test: db/compaction_job_stats_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
compact_on_deletion_collector_test: utilities/table_properties_collectors/compact_on_deletion_collector_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
wal_manager_test: db/wal_manager_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
@@ -741,9 +789,15 @@ fault_injection_test: db/fault_injection_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
rate_limiter_test: util/rate_limiter_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
delete_scheduler_test: util/delete_scheduler_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
filename_test: db/filename_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
file_reader_writer_test: util/file_reader_writer_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
block_based_filter_block_test: table/block_based_filter_block_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
@@ -786,6 +840,9 @@ write_batch_test: db/write_batch_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
write_controller_test: db/write_controller_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
merge_helper_test: db/merge_helper_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
merge_test: db/merge_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
@@ -855,6 +912,9 @@ memtable_list_test: db/memtable_list_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
write_callback_test: db/write_callback_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
heap_test: util/heap_test.o $(GTEST)
|
||||
$(AM_LINK)
|
||||
|
||||
sst_dump: tools/sst_dump.o $(LIBOBJECTS)
|
||||
$(AM_LINK)
|
||||
|
||||
@@ -931,7 +991,7 @@ libbz2.a:
|
||||
-rm -rf bzip2-1.0.6
|
||||
curl -O http://www.bzip.org/1.0.6/bzip2-1.0.6.tar.gz
|
||||
tar xvzf bzip2-1.0.6.tar.gz
|
||||
cd bzip2-1.0.6 && make CFLAGS='-fPIC -Wall -Winline -O2 -g -D_FILE_OFFSET_BITS=64'
|
||||
cd bzip2-1.0.6 && make CFLAGS='-fPIC -O2 -g -D_FILE_OFFSET_BITS=64'
|
||||
cp bzip2-1.0.6/libbz2.a .
|
||||
|
||||
libsnappy.a:
|
||||
@@ -950,21 +1010,23 @@ liblz4.a:
|
||||
cd lz4-r127/lib && make CFLAGS='-fPIC' all
|
||||
cp lz4-r127/lib/liblz4.a .
|
||||
|
||||
# A version of each $(LIBOBJECTS) compiled with -fPIC
|
||||
java_libobjects = $(patsubst %,jl/%,$(LIBOBJECTS))
|
||||
CLEAN_FILES += jl
|
||||
# A version of each $(LIBOBJECTS) compiled with -fPIC and a fixed set of static compression libraries
|
||||
java_static_libobjects = $(patsubst %,jls/%,$(LIBOBJECTS))
|
||||
CLEAN_FILES += jls
|
||||
|
||||
$(java_libobjects): jl/%.o: %.cc
|
||||
$(AM_V_CC)mkdir -p $(@D)
|
||||
@$(CXX) $(CXXFLAGS) -fPIC -c $< -o $@ $(COVERAGEFLAGS)
|
||||
JAVA_STATIC_FLAGS = -DZLIB -DBZIP2 -DSNAPPY -DLZ4
|
||||
JAVA_STATIC_INCLUDES = -I./zlib-1.2.8 -I./bzip2-1.0.6 -I./snappy-1.1.1 -I./lz4-r127/lib
|
||||
|
||||
rocksdbjavastatic: $(java_libobjects) libz.a libbz2.a libsnappy.a liblz4.a
|
||||
$(java_static_libobjects): jls/%.o: %.cc libz.a libbz2.a libsnappy.a liblz4.a
|
||||
$(AM_V_CC)mkdir -p $(@D) && $(CXX) $(CXXFLAGS) $(JAVA_STATIC_FLAGS) $(JAVA_STATIC_INCLUDES) -fPIC -c $< -o $@ $(COVERAGEFLAGS)
|
||||
|
||||
rocksdbjavastatic: $(java_static_libobjects)
|
||||
cd java;$(MAKE) javalib;
|
||||
rm -f ./java/target/$(ROCKSDBJNILIB)
|
||||
$(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 $(LDFLAGS)
|
||||
$(java_static_libobjects) $(COVERAGEFLAGS) \
|
||||
libz.a libbz2.a libsnappy.a liblz4.a $(JAVA_STATIC_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)
|
||||
@@ -986,6 +1048,13 @@ rocksdbjavastaticpublish: rocksdbjavastaticrelease
|
||||
mvn gpg:sign-and-deploy-file -Durl=https://oss.sonatype.org/service/local/staging/deploy/maven2/ -DrepositoryId=sonatype-nexus-staging -DpomFile=java/rocksjni.pom -Dfile=java/target/rocksdbjni-$(ROCKSDB_MAJOR).$(ROCKSDB_MINOR).$(ROCKSDB_PATCH)-osx.jar -Dclassifier=osx
|
||||
mvn gpg:sign-and-deploy-file -Durl=https://oss.sonatype.org/service/local/staging/deploy/maven2/ -DrepositoryId=sonatype-nexus-staging -DpomFile=java/rocksjni.pom -Dfile=java/target/rocksdbjni-$(ROCKSDB_MAJOR).$(ROCKSDB_MINOR).$(ROCKSDB_PATCH).jar
|
||||
|
||||
# A version of each $(LIBOBJECTS) compiled with -fPIC
|
||||
java_libobjects = $(patsubst %,jl/%,$(LIBOBJECTS))
|
||||
CLEAN_FILES += jl
|
||||
|
||||
$(java_libobjects): jl/%.o: %.cc
|
||||
$(AM_V_CC)mkdir -p $(@D) && $(CXX) $(CXXFLAGS) -fPIC -c $< -o $@ $(COVERAGEFLAGS)
|
||||
|
||||
rocksdbjava: $(java_libobjects)
|
||||
cd java;$(MAKE) javalib;
|
||||
rm -f ./java/target/$(ROCKSDBJNILIB)
|
||||
@@ -997,7 +1066,7 @@ rocksdbjava: $(java_libobjects)
|
||||
jclean:
|
||||
cd java;$(MAKE) clean;
|
||||
|
||||
jtest:
|
||||
jtest: rocksdbjava
|
||||
cd java;$(MAKE) sample;$(MAKE) test;
|
||||
|
||||
jdb_bench:
|
||||
@@ -1005,7 +1074,7 @@ jdb_bench:
|
||||
|
||||
commit-prereq:
|
||||
$(MAKE) clean && $(MAKE) all check;
|
||||
$(MAKE) clean && $(MAKE) rocksdbjava;
|
||||
$(MAKE) clean && $(MAKE) jclean && $(MAKE) rocksdbjava;
|
||||
$(MAKE) clean && USE_CLANG=1 $(MAKE) all;
|
||||
$(MAKE) clean && OPT=-DROCKSDB_LITE $(MAKE) static_lib;
|
||||
|
||||
|
||||
+228
@@ -0,0 +1,228 @@
|
||||
# Microsoft Contribution Notes
|
||||
|
||||
## Contributors
|
||||
* Alexander Zinoviev https://github.com/zinoale
|
||||
* Dmitri Smirnov https://github.com/yuslepukhin
|
||||
* Praveen Rao https://github.com/PraveenSinghRao
|
||||
* Sherlock Huang https://github.com/SherlockNoMad
|
||||
|
||||
## Introduction
|
||||
RocksDB is a well proven open source key-value persistent store, optimized for fast storage. It provides scalability with number of CPUs and storage IOPS, to support IO-bound, in-memory and write-once workloads, most importantly, to be flexible to allow for innovation.
|
||||
|
||||
As Microsoft Bing team we have been continuously pushing hard to improve the scalability, efficiency of platform and eventually benefit Bing end-user satisfaction. We would like to explore the opportunity to embrace open source, RocksDB here, to use, enhance and customize for our usage, and also contribute back to the RocksDB community. Herein, we are pleased to offer this RocksDB port for Windows platform.
|
||||
|
||||
These notes describe some decisions and changes we had to make with regards to porting RocksDB on Windows. We hope this will help both reviewers and users of the Windows port.
|
||||
We are open for comments and improvements.
|
||||
|
||||
## OS specifics
|
||||
All of the porting, testing and benchmarking was done on Windows Server 2012 R2 Datacenter 64-bit but to the best of our knowledge there is not a specific API we used during porting that is unsupported on other Windows OS after Vista.
|
||||
|
||||
## Porting goals
|
||||
We strive to achieve the following goals:
|
||||
* make use of the existing porting interface of RocksDB
|
||||
* make minimum [WY2]modifications within platform independent code.
|
||||
* make all unit test pass both in debug and release builds.
|
||||
* Note: latest introduction of SyncPoint seems to disable running db_test in Release.
|
||||
* make performance on par with published benchmarks accounting for HW differences
|
||||
* we would like to keep the port code inline with the master branch with no forking
|
||||
|
||||
## Build system
|
||||
We have chosen CMake as a widely accepted build system to build the Windows port. It is very fast and convenient.
|
||||
|
||||
At the same time it generates Visual Studio projects that are both usable from a command line and IDE.
|
||||
|
||||
The top-level CMakeLists.txt file contains description of all targets and build rules. It also provides brief instructions on how to build the software for Windows. One more build related file is thirdparty.inc that also resides on the top level. This file must be edited to point to actual third party libraries location.
|
||||
We think that it would be beneficial to merge the existing make-based build system and the new cmake-based build system into a single one to use on all platforms.
|
||||
|
||||
All building and testing was done for 64-bit. We have not conducted any testing for 32-bit and early reports indicate that it will not run on 32-bit.
|
||||
|
||||
## C++ and STL notes
|
||||
We had to make some minimum changes within the portable files that either account for OS differences or the shortcomings of C++11 support in the current version of the MS compiler. Most or all of them are expected to be fixed in the upcoming compiler releases.
|
||||
|
||||
We plan to use this port for our business purposes here at Bing and this provided business justification for this port. This also means, we do not have at present to choose the compiler version at will.
|
||||
|
||||
* Certain headers that are not present and not necessary on Windows were simply `#ifndef OS_WIN` in a few places (`unistd.h`)
|
||||
* All posix specific headers were replaced to port/port.h which worked well
|
||||
* Replaced `dirent.h` for `port/dirent.h` (very few places) with the implementation of the relevant interfaces within `rocksdb::port` namespace
|
||||
* Replaced `sys/time.h` to `port/sys_time.h` (few places) implemented equivalents within `rocksdb::port`
|
||||
* `printf %z` specification is not supported on Windows. To imitate existing standards we came up with a string macro `ROCKSDB_PRIszt` which expands to `%z` on posix systems and to Iu on windows.
|
||||
* in class member initialization were moved to a __ctors in some cases
|
||||
* `constexpr` is not supported. We had to replace `std::numeric_limits<>::max/min()` to its C macros for constants. Sometimes we had to make class members `static const` and place a definition within a .cc file.
|
||||
* `constexpr` for functions was replaced to a template specialization (1 place)
|
||||
* Union members that have non-trivial constructors were replaced to `char[]` in one place along with bug fixes (spatial experimental feature)
|
||||
* Zero-sized arrays are deemed a non-standard extension which we converted to 1 size array and that should work well for the purposes of these classes.
|
||||
* `std::chrono` lacks nanoseconds support (fixed in the upcoming release of the STL) and we had to use `QueryPerfCounter()` within env_win.cc
|
||||
* Function local statics initialization is still not safe. Used `std::once` to mitigate within WinEnv.
|
||||
|
||||
## Windows Environments notes
|
||||
We endeavored to make it functionally on par with posix_env. This means we replicated the functionality of the thread pool and other things as precise as possible, including:
|
||||
* Replicate posix logic using std:thread primitives.
|
||||
* Implement all posix_env disk access functionality.
|
||||
* Set `use_os_buffer=false` to disable OS disk buffering for WinWritableFile and WinRandomAccessFile.
|
||||
* Replace `pread/pwrite` with `WriteFile/ReadFile` with `OVERLAPPED` structure.
|
||||
* Use `SetFileInformationByHandle` to compensate absence of `fallocate`.
|
||||
|
||||
### In detail
|
||||
Even though Windows provides its own efficient thread-pool implementation we chose to replicate posix logic using `std::thread` primitives. This allows anyone to quickly detect any changes within the posix source code and replicate them within windows env. This has proven to work very well. At the same time for anyone who wishes to replace the built-in thread-pool can do so using RocksDB stackable environments.
|
||||
|
||||
For disk access we implemented all of the functionality present within the posix_env which includes memory mapped files, random access, rate-limiter support etc.
|
||||
The `use_os_buffer` flag on Posix platforms currently denotes disabling read-ahead log via `fadvise` mechanism. Windows does not have `fadvise` system call. What is more, it implements disk cache in a way that differs from Linux greatly. It’s not an uncommon practice on Windows to perform un-buffered disk access to gain control of the memory consumption. We think that in our use case this may also be a good configuration option at the expense of disk throughput. To compensate one may increase the configured in-memory cache size instead. Thus we have chosen `use_os_buffer=false` to disable OS disk buffering for `WinWritableFile` and `WinRandomAccessFile`. The OS imposes restrictions on the alignment of the disk offsets, buffers used and the amount of data that is read/written when accessing files in un-buffered mode. When the option is true, the classes behave in a standard way. This allows to perform writes and reads in cases when un-buffered access does not make sense such as WAL and MANIFEST.
|
||||
|
||||
We have replaced `pread/pwrite` with `WriteFile/ReadFile` with `OVERLAPPED` structure so we can atomically seek to the position of the disk operation but still perform the operation synchronously. Thus we able to emulate that functionality of `pread/pwrite` reasonably well. The only difference is that the file pointer is not returned to its original position but that hardly matters given the random nature of access.
|
||||
|
||||
We used `SetFileInformationByHandle` both to truncate files after writing a full final page to disk and to pre-allocate disk space for faster I/O thus compensating for the absence of `fallocate` although some differences remain. For example, the pre-allocated space is not filled with zeros like on Linux, however, on a positive note, the end of file position is also not modified after pre-allocation.
|
||||
|
||||
RocksDB renames, copies and deletes files at will even though they may be opened with another handle at the same time. We had to relax and allow nearly all the concurrent access permissions possible.
|
||||
|
||||
## Thread-Local Storage
|
||||
Thread-Local storage plays a significant role for RocksDB performance. Rather than creating a separate implementation we chose to create inline wrappers that forward `pthread_specific` calls to Windows `Tls` interfaces within `rocksdb::port` namespace. This leaves the existing meat of the logic in tact and unchanged and just as maintainable.
|
||||
|
||||
To mitigate the lack of thread local storage cleanup on thread-exit we added a limited amount of windows specific code within the same thread_local.cc file that injects a cleanup callback into a `"__tls"` structure within `".CRT$XLB"` data segment. This approach guarantees that the callback is invoked regardless of whether RocksDB used within an executable, standalone DLL or within another DLL.
|
||||
|
||||
## Jemalloc usage
|
||||
|
||||
When RocksDB is used with Jemalloc the latter needs to be initialized before any of the C++ globals or statics. To accomplish that we injected an initialization routine into `".CRT$XCT"` that is automatically invoked by the runtime before initializing static objects. je-uninit is queued to `atexit()`.
|
||||
|
||||
The jemalloc redirecting `new/delete` global operators are used by the linker providing certain conditions are met. See build section in these notes.
|
||||
|
||||
## Stack Trace and Unhandled Exception Handler
|
||||
|
||||
We decided not to implement these two features because the hosting program as a rule has these two things in it.
|
||||
We experienced no inconveniences debugging issues in the debugger or analyzing process dumps if need be and thus we did not
|
||||
see this as a priority.
|
||||
|
||||
## Performance results
|
||||
### Setup
|
||||
All of the benchmarks are run on the same set of machines. Here are the details of the test setup:
|
||||
* 2 Intel(R) Xeon(R) E5 2450 0 @ 2.10 GHz (total 16 cores)
|
||||
* 2 XK0480GDQPH SSD Device, total 894GB free disk
|
||||
* Machine has 128 GB of RAM
|
||||
* Operating System: Windows Server 2012 R2 Datacenter
|
||||
* 100 Million keys; each key is of size 10 bytes, each value is of size 800 bytes
|
||||
* total database size is ~76GB
|
||||
* The performance result is based on RocksDB 3.11.
|
||||
* The parameters used, unless specified, were exactly the same as published in the GitHub Wiki page.
|
||||
|
||||
### RocksDB on flash storage
|
||||
|
||||
#### Test 1. Bulk Load of keys in Random Order
|
||||
|
||||
Version 3.11
|
||||
|
||||
* Total Run Time: 17.6 min
|
||||
* Fillrandom: 5.480 micros/op 182465 ops/sec; 142.0 MB/s
|
||||
* Compact: 486056544.000 micros/op 0 ops/sec
|
||||
|
||||
Version 3.10
|
||||
|
||||
* Total Run Time: 16.2 min
|
||||
* Fillrandom: 5.018 micros/op 199269 ops/sec; 155.1 MB/s
|
||||
* Compact: 441313173.000 micros/op 0 ops/sec;
|
||||
|
||||
|
||||
#### Test 2. Bulk Load of keys in Sequential Order
|
||||
|
||||
Version 3.11
|
||||
|
||||
* Fillseq: 4.944 micros/op 202k ops/sec; 157.4 MB/s
|
||||
|
||||
Version 3.10
|
||||
|
||||
* Fillseq: 4.105 micros/op 243.6k ops/sec; 189.6 MB/s
|
||||
|
||||
|
||||
#### Test 3. Random Write
|
||||
|
||||
Version 3.11
|
||||
|
||||
* Unbuffered I/O enabled
|
||||
* Overwrite: 52.661 micros/op 18.9k ops/sec; 14.8 MB/s
|
||||
|
||||
Version 3.10
|
||||
|
||||
* Unbuffered I/O enabled
|
||||
* Overwrite: 52.661 micros/op 18.9k ops/sec;
|
||||
|
||||
|
||||
#### Test 4. Random Read
|
||||
|
||||
Version 3.11
|
||||
|
||||
* Unbuffered I/O enabled
|
||||
* Readrandom: 15.716 micros/op 63.6k ops/sec; 49.5 MB/s
|
||||
|
||||
Version 3.10
|
||||
|
||||
* Unbuffered I/O enabled
|
||||
* Readrandom: 15.548 micros/op 64.3k ops/sec;
|
||||
|
||||
|
||||
#### Test 5. Multi-threaded read and single-threaded write
|
||||
|
||||
Version 3.11
|
||||
|
||||
* Unbuffered I/O enabled
|
||||
* Readwhilewriting: 25.128 micros/op 39.7k ops/sec;
|
||||
|
||||
Version 3.10
|
||||
|
||||
* Unbuffered I/O enabled
|
||||
* Readwhilewriting: 24.854 micros/op 40.2k ops/sec;
|
||||
|
||||
|
||||
### RocksDB In Memory
|
||||
|
||||
#### Test 1. Point Lookup
|
||||
|
||||
Version 3.11
|
||||
|
||||
80K writes/sec
|
||||
* Write Rate Achieved: 40.5k write/sec;
|
||||
* Readwhilewriting: 0.314 micros/op 3187455 ops/sec; 364.8 MB/s (715454999 of 715454999 found)
|
||||
|
||||
Version 3.10
|
||||
|
||||
* Write Rate Achieved: 50.6k write/sec
|
||||
* Readwhilewriting: 0.316 micros/op 3162028 ops/sec; (719576999 of 719576999 found)
|
||||
|
||||
|
||||
*10K writes/sec*
|
||||
|
||||
Version 3.11
|
||||
|
||||
* Write Rate Achieved: 5.8k/s write/sec
|
||||
* Readwhilewriting: 0.246 micros/op 4062669 ops/sec; 464.9 MB/s (915481999 of 915481999 found)
|
||||
|
||||
Version 3.10
|
||||
|
||||
* Write Rate Achieved: 5.8k/s write/sec
|
||||
* Readwhilewriting: 0.244 micros/op 4106253 ops/sec; (927986999 of 927986999 found)
|
||||
|
||||
|
||||
#### Test 2. Prefix Range Query
|
||||
|
||||
Version 3.11
|
||||
|
||||
80K writes/sec
|
||||
* Write Rate Achieved: 46.3k/s write/sec
|
||||
* Readwhilewriting: 0.362 micros/op 2765052 ops/sec; 316.4 MB/s (611549999 of 611549999 found)
|
||||
|
||||
Version 3.10
|
||||
|
||||
* Write Rate Achieved: 45.8k/s write/sec
|
||||
* Readwhilewriting: 0.317 micros/op 3154941 ops/sec; (708158999 of 708158999 found)
|
||||
|
||||
Version 3.11
|
||||
|
||||
10K writes/sec
|
||||
* Write Rate Achieved: 5.78k write/sec
|
||||
* Readwhilewriting: 0.269 micros/op 3716692 ops/sec; 425.3 MB/s (837401999 of 837401999 found)
|
||||
|
||||
Version 3.10
|
||||
|
||||
* Write Rate Achieved: 5.7k write/sec
|
||||
* Readwhilewriting: 0.261 micros/op 3830152 ops/sec; (863482999 of 863482999 found)
|
||||
|
||||
|
||||
We think that there is still big room to improve the performance, which will be an ongoing effort for us.
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
# CXX C++ Compiler path
|
||||
# PLATFORM_LDFLAGS Linker flags
|
||||
# JAVA_LDFLAGS Linker flags for RocksDBJava
|
||||
# JAVA_STATIC_LDFLAGS Linker flags for RocksDBJava static build
|
||||
# PLATFORM_SHARED_EXT Extension for shared libraries
|
||||
# PLATFORM_SHARED_LDFLAGS Flags for building shared library
|
||||
# PLATFORM_SHARED_CFLAGS Flags for compiling objects for shared library
|
||||
@@ -50,12 +51,7 @@ if [ -z "$ROCKSDB_NO_FBCODE" -a -d /mnt/gvfs/third-party ]; then
|
||||
FBCODE_BUILD="true"
|
||||
# If we're compiling with TSAN we need pic build
|
||||
PIC_BUILD=$COMPILE_WITH_TSAN
|
||||
if [ -z "$ROCKSDB_FBCODE_BUILD_WITH_481" ]; then
|
||||
source "$PWD/build_tools/fbcode_config.sh"
|
||||
else
|
||||
# we need this to build with MySQL. Don't use for other purposes.
|
||||
source "$PWD/build_tools/fbcode_config4.8.1.sh"
|
||||
fi
|
||||
source "$PWD/build_tools/fbcode_config.sh"
|
||||
fi
|
||||
|
||||
# Delete existing output, if it exists
|
||||
@@ -180,6 +176,7 @@ esac
|
||||
|
||||
PLATFORM_CXXFLAGS="$PLATFORM_CXXFLAGS ${CXXFLAGS}"
|
||||
JAVA_LDFLAGS="$PLATFORM_LDFLAGS"
|
||||
JAVA_STATIC_LDFLAGS="$PLATFORM_LDFLAGS"
|
||||
|
||||
if [ "$CROSS_COMPILE" = "true" -o "$FBCODE_BUILD" = "true" ]; then
|
||||
# Cross-compiling; do not try any compilation tests.
|
||||
@@ -362,6 +359,7 @@ echo "CXX=$CXX" >> "$OUTPUT"
|
||||
echo "PLATFORM=$PLATFORM" >> "$OUTPUT"
|
||||
echo "PLATFORM_LDFLAGS=$PLATFORM_LDFLAGS" >> "$OUTPUT"
|
||||
echo "JAVA_LDFLAGS=$JAVA_LDFLAGS" >> "$OUTPUT"
|
||||
echo "JAVA_STATIC_LDFLAGS=$JAVA_STATIC_LDFLAGS" >> "$OUTPUT"
|
||||
echo "VALGRIND_VER=$VALGRIND_VER" >> "$OUTPUT"
|
||||
echo "PLATFORM_CCFLAGS=$PLATFORM_CCFLAGS" >> "$OUTPUT"
|
||||
echo "PLATFORM_CXXFLAGS=$PLATFORM_CXXFLAGS" >> "$OUTPUT"
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
|
||||
GCC_BASE=/mnt/gvfs/third-party2/gcc/7331085db891a2ef4a88a48a751d834e8d68f4cb/7.x/centos7-native/b2ef2b6
|
||||
CLANG_BASE=/mnt/gvfs/third-party2/llvm-fb/963d9aeda70cc4779885b1277484fe7544a04e3e/9.0.0/platform007/9e92d53/
|
||||
LIBGCC_BASE=/mnt/gvfs/third-party2/libgcc/6ace84e956873d53638c738b6f65f3f469cca74c/7.x/platform007/5620abc
|
||||
GLIBC_BASE=/mnt/gvfs/third-party2/glibc/192b0f42d63dcf6210d6ceae387b49af049e6e0c/2.26/platform007/f259413
|
||||
SNAPPY_BASE=/mnt/gvfs/third-party2/snappy/7f9bdaada18f59bc27ec2b0871eb8a6144343aef/1.1.3/platform007/ca4da3d
|
||||
ZLIB_BASE=/mnt/gvfs/third-party2/zlib/2d9f0b9a4274cc21f61272a9e89bdb859bce8f1f/1.2.8/platform007/ca4da3d
|
||||
BZIP2_BASE=/mnt/gvfs/third-party2/bzip2/dc49a21c5fceec6456a7a28a94dcd16690af1337/1.0.6/platform007/ca4da3d
|
||||
LZ4_BASE=/mnt/gvfs/third-party2/lz4/0f607f8fc442ea7d6b876931b1898bb573d5e5da/1.9.1/platform007/ca4da3d
|
||||
ZSTD_BASE=/mnt/gvfs/third-party2/zstd/ca22bc441a4eb709e9e0b1f9fec9750fed7b31c5/1.4.x/platform007/15a3614
|
||||
GFLAGS_BASE=/mnt/gvfs/third-party2/gflags/0b9929d2588991c65a57168bf88aff2db87c5d48/2.2.0/platform007/ca4da3d
|
||||
JEMALLOC_BASE=/mnt/gvfs/third-party2/jemalloc/c26f08f47ac35fc31da2633b7da92d6b863246eb/master/platform007/c26c002
|
||||
NUMA_BASE=/mnt/gvfs/third-party2/numa/3f3fb57a5ccc5fd21c66416c0b83e0aa76a05376/2.0.11/platform007/ca4da3d
|
||||
LIBUNWIND_BASE=/mnt/gvfs/third-party2/libunwind/40c73d874898b386a71847f1b99115d93822d11f/1.4/platform007/6f3e0a9
|
||||
TBB_BASE=/mnt/gvfs/third-party2/tbb/4ce8e8dba77cdbd81b75d6f0c32fd7a1b76a11ec/2018_U5/platform007/ca4da3d
|
||||
KERNEL_HEADERS_BASE=/mnt/gvfs/third-party2/kernel-headers/fb251ecd2f5ae16f8671f7014c246e52a748fe0b/fb/platform007/da39a3e
|
||||
BINUTILS_BASE=/mnt/gvfs/third-party2/binutils/ab9f09bba370e7066cafd4eb59752db93f2e8312/2.29.1/platform007/15a3614
|
||||
VALGRIND_BASE=/mnt/gvfs/third-party2/valgrind/d42d152a15636529b0861ec493927200ebebca8e/3.15.0/platform007/ca4da3d
|
||||
LUA_BASE=/mnt/gvfs/third-party2/lua/f0cd714433206d5139df61659eb7b28b1dea6683/5.3.4/platform007/5007832
|
||||
@@ -6,123 +6,143 @@
|
||||
# Environment variables that change the behavior of this script:
|
||||
# PIC_BUILD -- if true, it will only take pic versions of libraries from fbcode. libraries that don't have pic variant will not be included
|
||||
|
||||
|
||||
BASEDIR=`dirname $BASH_SOURCE`
|
||||
source "$BASEDIR/dependencies.sh"
|
||||
|
||||
CFLAGS=""
|
||||
|
||||
# location of libgcc
|
||||
LIBGCC_BASE="/mnt/gvfs/third-party2/libgcc/0473c80518a10d6efcbe24c5eeca3fb4ec9b519c/4.9.x/gcc-4.9-glibc-2.20/e1a7e4e"
|
||||
LIBGCC_INCLUDE="$LIBGCC_BASE/include"
|
||||
LIBGCC_LIBS=" -L $LIBGCC_BASE/libs"
|
||||
# libgcc
|
||||
LIBGCC_INCLUDE="$LIBGCC_BASE/include/c++/7.3.0"
|
||||
LIBGCC_LIBS=" -L $LIBGCC_BASE/lib"
|
||||
|
||||
# location of glibc
|
||||
GLIBC_REV=7397bed99280af5d9543439cdb7d018af7542720
|
||||
GLIBC_INCLUDE="/mnt/gvfs/third-party2/glibc/$GLIBC_REV/2.20/gcc-4.9-glibc-2.20/99df8fc/include"
|
||||
GLIBC_LIBS=" -L /mnt/gvfs/third-party2/glibc/$GLIBC_REV/2.20/gcc-4.9-glibc-2.20/99df8fc/lib"
|
||||
|
||||
SNAPPY_INCLUDE=" -I /mnt/gvfs/third-party2/snappy/b0f269b3ca47770121aa159b99e1d8d2ab260e1f/1.0.3/gcc-4.9-glibc-2.20/c32916f/include/"
|
||||
# glibc
|
||||
GLIBC_INCLUDE="$GLIBC_BASE/include"
|
||||
GLIBC_LIBS=" -L $GLIBC_BASE/lib"
|
||||
|
||||
# snappy
|
||||
SNAPPY_INCLUDE=" -I $SNAPPY_BASE/include/"
|
||||
if test -z $PIC_BUILD; then
|
||||
SNAPPY_LIBS=" /mnt/gvfs/third-party2/snappy/b0f269b3ca47770121aa159b99e1d8d2ab260e1f/1.0.3/gcc-4.9-glibc-2.20/c32916f/lib/libsnappy.a"
|
||||
SNAPPY_LIBS=" $SNAPPY_BASE/lib/libsnappy.a"
|
||||
else
|
||||
SNAPPY_LIBS=" /mnt/gvfs/third-party2/snappy/b0f269b3ca47770121aa159b99e1d8d2ab260e1f/1.0.3/gcc-4.9-glibc-2.20/c32916f/lib/libsnappy_pic.a"
|
||||
SNAPPY_LIBS=" $SNAPPY_BASE/lib/libsnappy_pic.a"
|
||||
fi
|
||||
|
||||
CFLAGS+=" -DSNAPPY"
|
||||
|
||||
if test -z $PIC_BUILD; then
|
||||
# location of zlib headers and libraries
|
||||
ZLIB_INCLUDE=" -I /mnt/gvfs/third-party2/zlib/feb983d9667f4cf5e9da07ce75abc824764b67a1/1.2.8/gcc-4.9-glibc-2.20/4230243/include/"
|
||||
ZLIB_LIBS=" /mnt/gvfs/third-party2/zlib/feb983d9667f4cf5e9da07ce75abc824764b67a1/1.2.8/gcc-4.9-glibc-2.20/4230243/lib/libz.a"
|
||||
ZLIB_INCLUDE=" -I $ZLIB_BASE/include/"
|
||||
ZLIB_LIBS=" $ZLIB_BASE/lib/libz.a"
|
||||
CFLAGS+=" -DZLIB"
|
||||
|
||||
# location of bzip headers and libraries
|
||||
BZIP_INCLUDE=" -I /mnt/gvfs/third-party2/bzip2/af004cceebb2dfd173ca29933ea5915e727aad2f/1.0.6/gcc-4.9-glibc-2.20/4230243/include/"
|
||||
BZIP_LIBS=" /mnt/gvfs/third-party2/bzip2/af004cceebb2dfd173ca29933ea5915e727aad2f/1.0.6/gcc-4.9-glibc-2.20/4230243/lib/libbz2.a"
|
||||
BZIP_INCLUDE=" -I $BZIP2_BASE/include/"
|
||||
BZIP_LIBS=" $BZIP2_BASE/lib/libbz2.a"
|
||||
CFLAGS+=" -DBZIP2"
|
||||
|
||||
LZ4_INCLUDE=" -I /mnt/gvfs/third-party2/lz4/79d2943e2dd7208a3e0b06cf95e9f85f05fe9e1b/r124/gcc-4.9-glibc-2.20/4230243/include/"
|
||||
LZ4_LIBS=" /mnt/gvfs/third-party2/lz4/79d2943e2dd7208a3e0b06cf95e9f85f05fe9e1b/r124/gcc-4.9-glibc-2.20/4230243/lib/liblz4.a"
|
||||
LZ4_INCLUDE=" -I $LZ4_BASE/include/"
|
||||
LZ4_LIBS=" $LZ4_BASE/lib/liblz4.a"
|
||||
CFLAGS+=" -DLZ4"
|
||||
|
||||
ZSTD_INCLUDE=" -I $ZSTD_BASE/include/"
|
||||
ZSTD_LIBS=" $ZSTD_BASE/lib/libzstd.a"
|
||||
CFLAGS+=" -DZSTD"
|
||||
fi
|
||||
|
||||
# location of gflags headers and libraries
|
||||
GFLAGS_INCLUDE=" -I /mnt/gvfs/third-party2/gflags/0fa60e2b88de3e469db6c482d6e6dac72f5d65f9/1.6/gcc-4.9-glibc-2.20/4230243/include/"
|
||||
GFLAGS_INCLUDE=" -I $GFLAGS_BASE/include/"
|
||||
if test -z $PIC_BUILD; then
|
||||
GFLAGS_LIBS=" /mnt/gvfs/third-party2/gflags/0fa60e2b88de3e469db6c482d6e6dac72f5d65f9/1.6/gcc-4.9-glibc-2.20/4230243/lib/libgflags.a"
|
||||
GFLAGS_LIBS=" $GFLAGS_BASE/lib/libgflags.a"
|
||||
else
|
||||
GFLAGS_LIBS=" /mnt/gvfs/third-party2/gflags/0fa60e2b88de3e469db6c482d6e6dac72f5d65f9/1.6/gcc-4.9-glibc-2.20/4230243/lib/libgflags_pic.a"
|
||||
GFLAGS_LIBS=" $GFLAGS_BASE/lib/libgflags_pic.a"
|
||||
fi
|
||||
CFLAGS+=" -DGFLAGS=google"
|
||||
CFLAGS+=" -DGFLAGS=gflags"
|
||||
|
||||
# location of jemalloc
|
||||
JEMALLOC_INCLUDE=" -I /mnt/gvfs/third-party2/jemalloc/bcd68e5e419efa4e61b9486d6854564d6d75a0b5/3.6.0/gcc-4.9-glibc-2.20/2aafc78/include/"
|
||||
JEMALLOC_LIB=" /mnt/gvfs/third-party2/jemalloc/bcd68e5e419efa4e61b9486d6854564d6d75a0b5/3.6.0/gcc-4.9-glibc-2.20/2aafc78/lib/libjemalloc.a"
|
||||
JEMALLOC_INCLUDE=" -I $JEMALLOC_BASE/include/"
|
||||
JEMALLOC_LIB=" $JEMALLOC_BASE/lib/libjemalloc.a"
|
||||
|
||||
if test -z $PIC_BUILD; then
|
||||
# location of numa
|
||||
NUMA_INCLUDE=" -I /mnt/gvfs/third-party2/numa/bbefc39ecbf31d0ca184168eb613ef8d397790ee/2.0.8/gcc-4.9-glibc-2.20/4230243/include/"
|
||||
NUMA_LIB=" /mnt/gvfs/third-party2/numa/bbefc39ecbf31d0ca184168eb613ef8d397790ee/2.0.8/gcc-4.9-glibc-2.20/4230243/lib/libnuma.a"
|
||||
NUMA_INCLUDE=" -I $NUMA_BASE/include/"
|
||||
NUMA_LIB=" $NUMA_BASE/lib/libnuma.a"
|
||||
CFLAGS+=" -DNUMA"
|
||||
|
||||
# location of libunwind
|
||||
LIBUNWIND="/mnt/gvfs/third-party2/libunwind/1de3b75e0afedfe5585b231bbb340ec7a1542335/1.1/gcc-4.9-glibc-2.20/34235e8/lib/libunwind.a"
|
||||
LIBUNWIND="$LIBUNWIND_BASE/lib/libunwind.a"
|
||||
fi
|
||||
|
||||
# location of TBB
|
||||
TBB_INCLUDE=" -isystem $TBB_BASE/include/"
|
||||
if test -z $PIC_BUILD; then
|
||||
TBB_LIBS="$TBB_BASE/lib/libtbb.a"
|
||||
else
|
||||
TBB_LIBS="$TBB_BASE/lib/libtbb_pic.a"
|
||||
fi
|
||||
CFLAGS+=" -DTBB"
|
||||
|
||||
# use Intel SSE support for checksum calculations
|
||||
export USE_SSE=1
|
||||
|
||||
BINUTILS="/mnt/gvfs/third-party2/binutils/0b6ad0c88ddd903333a48ae8bff134efac468e4a/2.25/centos6-native/da39a3e/bin"
|
||||
BINUTILS="$BINUTILS_BASE/bin"
|
||||
AR="$BINUTILS/ar"
|
||||
|
||||
DEPS_INCLUDE="$SNAPPY_INCLUDE $ZLIB_INCLUDE $BZIP_INCLUDE $LZ4_INCLUDE $GFLAGS_INCLUDE $NUMA_INCLUDE"
|
||||
DEPS_INCLUDE="$SNAPPY_INCLUDE $ZLIB_INCLUDE $BZIP_INCLUDE $LZ4_INCLUDE $ZSTD_INCLUDE $GFLAGS_INCLUDE $NUMA_INCLUDE $TBB_INCLUDE"
|
||||
|
||||
GCC_BASE="/mnt/gvfs/third-party2/gcc/1c67a0b88f64d4d9ced0382d141c76aaa7d62fba/4.9.x/centos6-native/1317bc4"
|
||||
STDLIBS="-L $GCC_BASE/lib64"
|
||||
|
||||
CLANG_BASE="/mnt/gvfs/third-party2/clang/d81444dd214df3d2466734de45bb264a0486acc3/dev"
|
||||
CLANG_BIN="$CLANG_BASE/centos6-native/af4b1a0/bin"
|
||||
CLANG_BIN="$CLANG_BASE/bin"
|
||||
CLANG_LIB="$CLANG_BASE/lib"
|
||||
CLANG_SRC="$CLANG_BASE/../../src"
|
||||
|
||||
CLANG_ANALYZER="$CLANG_BIN/clang++"
|
||||
CLANG_SCAN_BUILD="$CLANG_BASE/src/clang/tools/scan-build/scan-build"
|
||||
CLANG_SCAN_BUILD="$CLANG_SRC/llvm/tools/clang/tools/scan-build/bin/scan-build"
|
||||
|
||||
if [ -z "$USE_CLANG" ]; then
|
||||
# gcc
|
||||
CC="$GCC_BASE/bin/gcc"
|
||||
CXX="$GCC_BASE/bin/g++"
|
||||
|
||||
|
||||
CFLAGS+=" -B$BINUTILS/gold"
|
||||
CFLAGS+=" -isystem $GLIBC_INCLUDE"
|
||||
CFLAGS+=" -isystem $LIBGCC_INCLUDE"
|
||||
CFLAGS+=" -isystem $GLIBC_INCLUDE"
|
||||
JEMALLOC=1
|
||||
else
|
||||
# clang
|
||||
CLANG_INCLUDE="$CLANG_BASE/gcc-4.9-glibc-2.20/74c386f/lib/clang/dev/include/"
|
||||
# clang
|
||||
CLANG_INCLUDE="$CLANG_LIB/clang/stable/include"
|
||||
CC="$CLANG_BIN/clang"
|
||||
CXX="$CLANG_BIN/clang++"
|
||||
|
||||
KERNEL_HEADERS_INCLUDE="/mnt/gvfs/third-party2/kernel-headers/ffd14f660a43c4b92717986b1bba66722ef089d0/3.2.18_70_fbk11_00129_gc8882d0/gcc-4.9-glibc-2.20/da39a3e/include"
|
||||
KERNEL_HEADERS_INCLUDE="$KERNEL_HEADERS_BASE/include"
|
||||
|
||||
CFLAGS+=" -B$BINUTILS/gold -nostdinc -nostdlib"
|
||||
CFLAGS+=" -isystem $LIBGCC_BASE/include/c++/4.9.x "
|
||||
CFLAGS+=" -isystem $LIBGCC_BASE/include/c++/4.9.x/x86_64-facebook-linux "
|
||||
CFLAGS+=" -isystem $LIBGCC_BASE/include/c++/7.x "
|
||||
CFLAGS+=" -isystem $LIBGCC_BASE/include/c++/7.x/x86_64-facebook-linux "
|
||||
CFLAGS+=" -isystem $GLIBC_INCLUDE"
|
||||
CFLAGS+=" -isystem $LIBGCC_INCLUDE"
|
||||
CFLAGS+=" -isystem $CLANG_INCLUDE"
|
||||
CFLAGS+=" -isystem $KERNEL_HEADERS_INCLUDE/linux "
|
||||
CFLAGS+=" -isystem $KERNEL_HEADERS_INCLUDE "
|
||||
CFLAGS+=" -Wno-expansion-to-defined "
|
||||
CXXFLAGS="-nostdinc++"
|
||||
fi
|
||||
|
||||
CFLAGS+=" $DEPS_INCLUDE"
|
||||
CFLAGS+=" -DROCKSDB_PLATFORM_POSIX -DROCKSDB_FALLOCATE_PRESENT -DROCKSDB_MALLOC_USABLE_SIZE"
|
||||
CFLAGS+=" -DROCKSDB_PLATFORM_POSIX -DROCKSDB_LIB_IO_POSIX -DROCKSDB_FALLOCATE_PRESENT -DROCKSDB_MALLOC_USABLE_SIZE -DROCKSDB_RANGESYNC_PRESENT -DROCKSDB_SCHED_GETCPU_PRESENT -DROCKSDB_SUPPORT_THREAD_LOCAL -DHAVE_SSE42"
|
||||
CXXFLAGS+=" $CFLAGS"
|
||||
|
||||
EXEC_LDFLAGS=" $SNAPPY_LIBS $ZLIB_LIBS $BZIP_LIBS $LZ4_LIBS $GFLAGS_LIBS $NUMA_LIB"
|
||||
EXEC_LDFLAGS+=" -Wl,--dynamic-linker,/usr/local/fbcode/gcc-4.9-glibc-2.20/lib/ld.so"
|
||||
EXEC_LDFLAGS=" $SNAPPY_LIBS $ZLIB_LIBS $BZIP_LIBS $LZ4_LIBS $ZSTD_LIBS $GFLAGS_LIBS $NUMA_LIB $TBB_LIBS"
|
||||
EXEC_LDFLAGS+=" -B$BINUTILS/gold"
|
||||
EXEC_LDFLAGS+=" -Wl,--dynamic-linker,/usr/local/fbcode/platform007/lib/ld.so"
|
||||
EXEC_LDFLAGS+=" $LIBUNWIND"
|
||||
EXEC_LDFLAGS+=" -Wl,-rpath=/usr/local/fbcode/gcc-4.9-glibc-2.20/lib"
|
||||
EXEC_LDFLAGS+=" -Wl,-rpath=/usr/local/fbcode/platform007/lib"
|
||||
# required by libtbb
|
||||
EXEC_LDFLAGS+=" -ldl"
|
||||
|
||||
PLATFORM_LDFLAGS="$LIBGCC_LIBS $GLIBC_LIBS $STDLIBS -lgcc -lstdc++"
|
||||
|
||||
EXEC_LDFLAGS_SHARED="$SNAPPY_LIBS $ZLIB_LIBS $BZIP_LIBS $LZ4_LIBS $GFLAGS_LIBS"
|
||||
EXEC_LDFLAGS_SHARED="$SNAPPY_LIBS $ZLIB_LIBS $BZIP_LIBS $LZ4_LIBS $ZSTD_LIBS $GFLAGS_LIBS $TBB_LIBS"
|
||||
|
||||
VALGRIND_VER="/mnt/gvfs/third-party2/valgrind/6c45ef049cbf11c2df593addb712cd891049e737/3.10.0/gcc-4.9-glibc-2.20/4230243/bin/"
|
||||
VALGRIND_VER="$VALGRIND_BASE/bin/"
|
||||
|
||||
export CC CXX AR CFLAGS CXXFLAGS EXEC_LDFLAGS EXEC_LDFLAGS_SHARED VALGRIND_VER JEMALLOC_LIB JEMALLOC_INCLUDE CLANG_ANALYZER CLANG_SCAN_BUILD
|
||||
export CC CXX AR CFLAGS CXXFLAGS EXEC_LDFLAGS EXEC_LDFLAGS_SHARED VALGRIND_VER JEMALLOC_LIB JEMALLOC_INCLUDE CLANG_ANALYZER CLANG_SCAN_BUILD
|
||||
@@ -35,8 +35,8 @@ GFLAGS_INCLUDE=" -I /mnt/gvfs/third-party2/gflags/1ad047a6e6f6673991918ecadc6708
|
||||
GFLAGS_LIBS=" /mnt/gvfs/third-party2/gflags/1ad047a6e6f6673991918ecadc670868205a243a/1.6/gcc-4.8.1-glibc-2.17/c3f970a/lib/libgflags.a"
|
||||
|
||||
# location of jemalloc
|
||||
JEMALLOC_INCLUDE=" -I /mnt/gvfs/third-party2/jemalloc/c60d854f7824f334195fe7fd34b2bc9057e3c1f9/3.6.0/gcc-4.8.1-glibc-2.17/4d53c6f/include"
|
||||
JEMALLOC_LIB=" /mnt/gvfs/third-party2/jemalloc/c60d854f7824f334195fe7fd34b2bc9057e3c1f9/3.6.0/gcc-4.8.1-glibc-2.17/4d53c6f/lib/libjemalloc.a"
|
||||
JEMALLOC_INCLUDE=" -I /mnt/gvfs/third-party2/jemalloc/3691c776ac26dd8781e84f8888b6a0fbdbc0a9ed/dev/gcc-4.8.1-glibc-2.17/4d53c6f/include"
|
||||
JEMALLOC_LIB="/mnt/gvfs/third-party2/jemalloc/3691c776ac26dd8781e84f8888b6a0fbdbc0a9ed/dev/gcc-4.8.1-glibc-2.17/4d53c6f/lib/libjemalloc.a"
|
||||
|
||||
# location of numa
|
||||
NUMA_REV=829d10dac0230f99cd7e1778869d2adf3da24b65
|
||||
|
||||
+21
-19
@@ -21,6 +21,7 @@
|
||||
#include "rocksdb/options.h"
|
||||
#include "rocksdb/table.h"
|
||||
#include "table/block_based_table_builder.h"
|
||||
#include "util/file_reader_writer.h"
|
||||
#include "util/iostats_context_imp.h"
|
||||
#include "util/thread_status_util.h"
|
||||
#include "util/stop_watch.h"
|
||||
@@ -34,7 +35,7 @@ TableBuilder* NewTableBuilder(
|
||||
const InternalKeyComparator& internal_comparator,
|
||||
const std::vector<std::unique_ptr<IntTblPropCollectorFactory>>*
|
||||
int_tbl_prop_collector_factories,
|
||||
WritableFile* file, const CompressionType compression_type,
|
||||
WritableFileWriter* file, const CompressionType compression_type,
|
||||
const CompressionOptions& compression_opts, const bool skip_filters) {
|
||||
return ioptions.table_factory->NewTableBuilder(
|
||||
TableBuilderOptions(ioptions, internal_comparator,
|
||||
@@ -64,7 +65,7 @@ Status BuildTable(
|
||||
// If the sequence number of the smallest entry in the memtable is
|
||||
// smaller than the most recent snapshot, then we do not trigger
|
||||
// removal of duplicate/deleted keys as part of this builder.
|
||||
bool purge = ioptions.purge_redundant_kvs_while_flush;
|
||||
bool purge = true;
|
||||
if (earliest_seqno_in_memtable <= newest_snapshot) {
|
||||
purge = false;
|
||||
}
|
||||
@@ -72,16 +73,22 @@ Status BuildTable(
|
||||
std::string fname = TableFileName(ioptions.db_paths, meta->fd.GetNumber(),
|
||||
meta->fd.GetPathId());
|
||||
if (iter->Valid()) {
|
||||
unique_ptr<WritableFile> file;
|
||||
s = env->NewWritableFile(fname, &file, env_options);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
file->SetIOPriority(io_priority);
|
||||
TableBuilder* builder;
|
||||
unique_ptr<WritableFileWriter> file_writer;
|
||||
{
|
||||
unique_ptr<WritableFile> file;
|
||||
s = env->NewWritableFile(fname, &file, env_options);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
file->SetIOPriority(io_priority);
|
||||
|
||||
TableBuilder* builder = NewTableBuilder(
|
||||
ioptions, internal_comparator, int_tbl_prop_collector_factories,
|
||||
file.get(), compression, compression_opts);
|
||||
file_writer.reset(new WritableFileWriter(std::move(file), env_options));
|
||||
|
||||
builder = NewTableBuilder(
|
||||
ioptions, internal_comparator, int_tbl_prop_collector_factories,
|
||||
file_writer.get(), compression, compression_opts);
|
||||
}
|
||||
|
||||
{
|
||||
// the first key is the smallest key
|
||||
@@ -232,16 +239,11 @@ Status BuildTable(
|
||||
|
||||
// Finish and check for file errors
|
||||
if (s.ok() && !ioptions.disable_data_sync) {
|
||||
if (ioptions.use_fsync) {
|
||||
StopWatch sw(env, ioptions.statistics, TABLE_SYNC_MICROS);
|
||||
s = file->Fsync();
|
||||
} else {
|
||||
StopWatch sw(env, ioptions.statistics, TABLE_SYNC_MICROS);
|
||||
s = file->Sync();
|
||||
}
|
||||
StopWatch sw(env, ioptions.statistics, TABLE_SYNC_MICROS);
|
||||
file_writer->Sync(ioptions.use_fsync);
|
||||
}
|
||||
if (s.ok()) {
|
||||
s = file->Close();
|
||||
s = file_writer->Close();
|
||||
}
|
||||
|
||||
if (s.ok()) {
|
||||
|
||||
+2
-2
@@ -29,14 +29,14 @@ class Iterator;
|
||||
class TableCache;
|
||||
class VersionEdit;
|
||||
class TableBuilder;
|
||||
class WritableFile;
|
||||
class WritableFileWriter;
|
||||
|
||||
TableBuilder* NewTableBuilder(
|
||||
const ImmutableCFOptions& options,
|
||||
const InternalKeyComparator& internal_comparator,
|
||||
const std::vector<std::unique_ptr<IntTblPropCollectorFactory>>*
|
||||
int_tbl_prop_collector_factories,
|
||||
WritableFile* file, const CompressionType compression_type,
|
||||
WritableFileWriter* file, const CompressionType compression_type,
|
||||
const CompressionOptions& compression_opts,
|
||||
const bool skip_filters = false);
|
||||
|
||||
|
||||
@@ -12,10 +12,11 @@
|
||||
#include "rocksdb/c.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include "port/port.h"
|
||||
#include "rocksdb/cache.h"
|
||||
#include "rocksdb/compaction_filter.h"
|
||||
#include "rocksdb/comparator.h"
|
||||
#include "rocksdb/convenience.h"
|
||||
#include "rocksdb/db.h"
|
||||
#include "rocksdb/env.h"
|
||||
#include "rocksdb/filter_policy.h"
|
||||
@@ -38,8 +39,6 @@ using rocksdb::ColumnFamilyHandle;
|
||||
using rocksdb::ColumnFamilyOptions;
|
||||
using rocksdb::CompactionFilter;
|
||||
using rocksdb::CompactionFilterFactory;
|
||||
using rocksdb::CompactionFilterV2;
|
||||
using rocksdb::CompactionFilterFactoryV2;
|
||||
using rocksdb::CompactionFilterContext;
|
||||
using rocksdb::CompactionOptionsFIFO;
|
||||
using rocksdb::Comparator;
|
||||
@@ -171,99 +170,6 @@ struct rocksdb_compactionfilterfactory_t : public CompactionFilterFactory {
|
||||
virtual const char* Name() const override { return (*name_)(state_); }
|
||||
};
|
||||
|
||||
struct rocksdb_compactionfilterv2_t : public CompactionFilterV2 {
|
||||
void* state_;
|
||||
void (*destructor_)(void*);
|
||||
const char* (*name_)(void*);
|
||||
void (*filter_)(void*, int level, size_t num_keys,
|
||||
const char* const* keys_list, const size_t* keys_list_sizes,
|
||||
const char* const* existing_values_list, const size_t* existing_values_list_sizes,
|
||||
char** new_values_list, size_t* new_values_list_sizes,
|
||||
unsigned char* to_delete_list);
|
||||
|
||||
virtual ~rocksdb_compactionfilterv2_t() {
|
||||
(*destructor_)(state_);
|
||||
}
|
||||
|
||||
virtual const char* Name() const override { return (*name_)(state_); }
|
||||
|
||||
virtual std::vector<bool> Filter(
|
||||
int level, const SliceVector& keys, const SliceVector& existing_values,
|
||||
std::vector<std::string>* new_values,
|
||||
std::vector<bool>* values_changed) const override {
|
||||
// Make a vector pointing to the underlying key data.
|
||||
size_t num_keys = keys.size();
|
||||
std::vector<const char*> keys_list(num_keys);
|
||||
std::vector<size_t> keys_list_sizes(num_keys);
|
||||
for (size_t i = 0; i < num_keys; ++i) {
|
||||
keys_list[i] = keys[i].data();
|
||||
keys_list_sizes[i] = keys[i].size();
|
||||
}
|
||||
// Make a vector pointing to the underlying value data.
|
||||
std::vector<const char*> existing_values_list(num_keys);
|
||||
std::vector<size_t> existing_values_list_sizes(num_keys);
|
||||
for (size_t i = 0; i < num_keys; ++i) {
|
||||
existing_values_list[i] = existing_values[i].data();
|
||||
existing_values_list_sizes[i] = existing_values[i].size();
|
||||
}
|
||||
// Make a vector which will accept newly-allocated char* arrays
|
||||
// which we will take ownership of and assign to strings in new_values.
|
||||
new_values->clear();
|
||||
std::vector<char*> new_values_list(num_keys);
|
||||
std::vector<size_t> new_values_list_sizes(num_keys);
|
||||
// Resize values_changed to hold all keys.
|
||||
values_changed->resize(num_keys);
|
||||
// Make a vector for bools indicating a value should be deleted
|
||||
// on compaction (true) or maintained (false).
|
||||
std::vector<unsigned char> to_delete_list(num_keys);
|
||||
|
||||
(*filter_)(
|
||||
state_, level, num_keys, &keys_list[0], &keys_list_sizes[0],
|
||||
&existing_values_list[0], &existing_values_list_sizes[0],
|
||||
&new_values_list[0], &new_values_list_sizes[0], &to_delete_list[0]);
|
||||
|
||||
// Now, we transfer any changed values, setting values_changed and
|
||||
// initializing new_values in the event a value changed.
|
||||
std::vector<bool> to_delete(num_keys);
|
||||
for (size_t i = 0; i < num_keys; ++i) {
|
||||
to_delete[i] = to_delete_list[i];
|
||||
(*values_changed)[i] = new_values_list[i] != nullptr;
|
||||
if ((*values_changed)[i]) {
|
||||
new_values->push_back(std::string(new_values_list[i], new_values_list_sizes[i]));
|
||||
free(new_values_list[i]);
|
||||
}
|
||||
}
|
||||
return to_delete;
|
||||
}
|
||||
};
|
||||
|
||||
struct rocksdb_compactionfilterfactoryv2_t : public CompactionFilterFactoryV2 {
|
||||
void* state_;
|
||||
void (*destructor_)(void*);
|
||||
const char* (*name_)(void*);
|
||||
rocksdb_compactionfilterv2_t* (*create_compaction_filter_v2_)(
|
||||
void* state, const rocksdb_compactionfiltercontext_t* context);
|
||||
|
||||
rocksdb_compactionfilterfactoryv2_t(const SliceTransform* prefix_extractor)
|
||||
: CompactionFilterFactoryV2(prefix_extractor) {
|
||||
}
|
||||
|
||||
virtual ~rocksdb_compactionfilterfactoryv2_t() {
|
||||
(*destructor_)(state_);
|
||||
}
|
||||
|
||||
virtual const char* Name() const override { return (*name_)(state_); }
|
||||
|
||||
virtual std::unique_ptr<CompactionFilterV2> CreateCompactionFilterV2(
|
||||
const CompactionFilterContext& context) override {
|
||||
struct rocksdb_compactionfiltercontext_t c_context;
|
||||
c_context.rep.is_full_compaction = context.is_full_compaction;
|
||||
c_context.rep.is_manual_compaction = context.is_manual_compaction;
|
||||
return std::unique_ptr<CompactionFilterV2>(
|
||||
(*create_compaction_filter_v2_)(state_, &c_context));
|
||||
}
|
||||
};
|
||||
|
||||
struct rocksdb_comparator_t : public Comparator {
|
||||
void* state_;
|
||||
void (*destructor_)(void*);
|
||||
@@ -483,6 +389,7 @@ static bool SaveError(char** errptr, const Status& s) {
|
||||
*errptr = strdup(s.ToString().c_str());
|
||||
} else {
|
||||
// TODO(sanjay): Merge with existing error?
|
||||
// This is a bug if *errptr is not created by malloc()
|
||||
free(*errptr);
|
||||
*errptr = strdup(s.ToString().c_str());
|
||||
}
|
||||
@@ -1488,11 +1395,6 @@ void rocksdb_options_set_merge_operator(
|
||||
opt->rep.merge_operator = std::shared_ptr<MergeOperator>(merge_operator);
|
||||
}
|
||||
|
||||
void rocksdb_options_set_compaction_filter_factory_v2(
|
||||
rocksdb_options_t* opt,
|
||||
rocksdb_compactionfilterfactoryv2_t* compaction_filter_factory_v2) {
|
||||
opt->rep.compaction_filter_factory_v2 = std::shared_ptr<CompactionFilterFactoryV2>(compaction_filter_factory_v2);
|
||||
}
|
||||
|
||||
void rocksdb_options_set_create_if_missing(
|
||||
rocksdb_options_t* opt, unsigned char v) {
|
||||
@@ -1607,10 +1509,8 @@ void rocksdb_options_set_level0_stop_writes_trigger(
|
||||
opt->rep.level0_stop_writes_trigger = n;
|
||||
}
|
||||
|
||||
void rocksdb_options_set_max_mem_compaction_level(
|
||||
rocksdb_options_t* opt, int n) {
|
||||
opt->rep.max_mem_compaction_level = n;
|
||||
}
|
||||
void rocksdb_options_set_max_mem_compaction_level(rocksdb_options_t* opt,
|
||||
int n) {}
|
||||
|
||||
void rocksdb_options_set_compression(rocksdb_options_t* opt, int t) {
|
||||
opt->rep.compression = static_cast<CompressionType>(t);
|
||||
@@ -1672,10 +1572,9 @@ void rocksdb_options_set_manifest_preallocation_size(
|
||||
opt->rep.manifest_preallocation_size = v;
|
||||
}
|
||||
|
||||
void rocksdb_options_set_purge_redundant_kvs_while_flush(
|
||||
rocksdb_options_t* opt, unsigned char v) {
|
||||
opt->rep.purge_redundant_kvs_while_flush = v;
|
||||
}
|
||||
// noop
|
||||
void rocksdb_options_set_purge_redundant_kvs_while_flush(rocksdb_options_t* opt,
|
||||
unsigned char v) {}
|
||||
|
||||
void rocksdb_options_set_allow_os_buffer(rocksdb_options_t* opt,
|
||||
unsigned char v) {
|
||||
@@ -2006,46 +1905,6 @@ void rocksdb_compactionfilterfactory_destroy(
|
||||
delete factory;
|
||||
}
|
||||
|
||||
rocksdb_compactionfilterv2_t* rocksdb_compactionfilterv2_create(
|
||||
void* state,
|
||||
void (*destructor)(void*),
|
||||
void (*filter)(void*, int level, size_t num_keys,
|
||||
const char* const* keys_list, const size_t* keys_list_sizes,
|
||||
const char* const* existing_values_list, const size_t* existing_values_list_sizes,
|
||||
char** new_values_list, size_t* new_values_list_sizes,
|
||||
unsigned char* to_delete_list),
|
||||
const char* (*name)(void*)) {
|
||||
rocksdb_compactionfilterv2_t* result = new rocksdb_compactionfilterv2_t;
|
||||
result->state_ = state;
|
||||
result->destructor_ = destructor;
|
||||
result->filter_ = filter;
|
||||
result->name_ = name;
|
||||
return result;
|
||||
}
|
||||
|
||||
void rocksdb_compactionfilterv2_destroy(rocksdb_compactionfilterv2_t* filter) {
|
||||
delete filter;
|
||||
}
|
||||
|
||||
rocksdb_compactionfilterfactoryv2_t* rocksdb_compactionfilterfactoryv2_create(
|
||||
void* state,
|
||||
rocksdb_slicetransform_t* prefix_extractor,
|
||||
void (*destructor)(void*),
|
||||
rocksdb_compactionfilterv2_t* (*create_compaction_filter_v2)(
|
||||
void* state, const rocksdb_compactionfiltercontext_t* context),
|
||||
const char* (*name)(void*)) {
|
||||
rocksdb_compactionfilterfactoryv2_t* result = new rocksdb_compactionfilterfactoryv2_t(prefix_extractor);
|
||||
result->state_ = state;
|
||||
result->destructor_ = destructor;
|
||||
result->create_compaction_filter_v2_ = create_compaction_filter_v2;
|
||||
result->name_ = name;
|
||||
return result;
|
||||
}
|
||||
|
||||
void rocksdb_compactionfilterfactoryv2_destroy(rocksdb_compactionfilterfactoryv2_t* factory) {
|
||||
delete factory;
|
||||
}
|
||||
|
||||
rocksdb_comparator_t* rocksdb_comparator_create(
|
||||
void* state,
|
||||
void (*destructor)(void*),
|
||||
@@ -2253,6 +2112,10 @@ void rocksdb_env_set_high_priority_background_threads(rocksdb_env_t* env, int n)
|
||||
env->rep->SetBackgroundThreads(n, Env::HIGH);
|
||||
}
|
||||
|
||||
void rocksdb_env_join_all_threads(rocksdb_env_t* env) {
|
||||
env->rep->WaitForJoin();
|
||||
}
|
||||
|
||||
void rocksdb_env_destroy(rocksdb_env_t* env) {
|
||||
if (!env->is_default) delete env->rep;
|
||||
delete env;
|
||||
@@ -2443,6 +2306,17 @@ extern void rocksdb_livefiles_destroy(
|
||||
delete lf;
|
||||
}
|
||||
|
||||
void rocksdb_get_options_from_string(const rocksdb_options_t* base_options,
|
||||
const char* opts_str,
|
||||
rocksdb_options_t* new_options,
|
||||
char** errptr) {
|
||||
SaveError(errptr,
|
||||
GetOptionsFromString(base_options->rep, std::string(opts_str),
|
||||
&new_options->rep));
|
||||
}
|
||||
|
||||
void rocksdb_free(void* ptr) { free(ptr); }
|
||||
|
||||
} // end extern "C"
|
||||
|
||||
#endif // !ROCKSDB_LITE
|
||||
+23
-118
@@ -11,9 +11,31 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
#ifndef OS_WIN
|
||||
# include <unistd.h>
|
||||
#endif
|
||||
#include <inttypes.h>
|
||||
|
||||
// Can not use port/port.h macros as this is a c file
|
||||
#ifdef OS_WIN
|
||||
|
||||
#include <Windows.h>
|
||||
|
||||
# define snprintf _snprintf
|
||||
|
||||
// Ok for uniqueness
|
||||
int geteuid() {
|
||||
|
||||
int result = 0;
|
||||
|
||||
result = ((int)GetCurrentProcessId() << 16);
|
||||
result |= (int)GetCurrentThreadId();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
const char* phase = "";
|
||||
static char dbname[200];
|
||||
static char dbbackupname[200];
|
||||
@@ -229,79 +251,6 @@ static rocksdb_t* CheckCompaction(rocksdb_t* db, rocksdb_options_t* options,
|
||||
return db;
|
||||
}
|
||||
|
||||
// Custom compaction filter V2.
|
||||
static void CompactionFilterV2Destroy(void* arg) { }
|
||||
static const char* CompactionFilterV2Name(void* arg) {
|
||||
return "TestCompactionFilterV2";
|
||||
}
|
||||
static void CompactionFilterV2Filter(
|
||||
void* arg, int level, size_t num_keys,
|
||||
const char* const* keys_list, const size_t* keys_list_sizes,
|
||||
const char* const* existing_values_list, const size_t* existing_values_list_sizes,
|
||||
char** new_values_list, size_t* new_values_list_sizes,
|
||||
unsigned char* to_delete_list) {
|
||||
size_t i;
|
||||
for (i = 0; i < num_keys; i++) {
|
||||
// If any value is "gc", it's removed.
|
||||
if (existing_values_list_sizes[i] == 2 && memcmp(existing_values_list[i], "gc", 2) == 0) {
|
||||
to_delete_list[i] = 1;
|
||||
} else if (existing_values_list_sizes[i] == 6 && memcmp(existing_values_list[i], "gc all", 6) == 0) {
|
||||
// If any value is "gc all", all keys are removed.
|
||||
size_t j;
|
||||
for (j = 0; j < num_keys; j++) {
|
||||
to_delete_list[j] = 1;
|
||||
}
|
||||
return;
|
||||
} else if (existing_values_list_sizes[i] == 6 && memcmp(existing_values_list[i], "change", 6) == 0) {
|
||||
// If value is "change", set changed value to "changed".
|
||||
size_t len;
|
||||
len = strlen("changed");
|
||||
new_values_list[i] = malloc(len);
|
||||
memcpy(new_values_list[i], "changed", len);
|
||||
new_values_list_sizes[i] = len;
|
||||
} else {
|
||||
// Otherwise, no keys are removed.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Custom prefix extractor for compaction filter V2 which extracts first 3 characters.
|
||||
static void CFV2PrefixExtractorDestroy(void* arg) { }
|
||||
static char* CFV2PrefixExtractorTransform(void* arg, const char* key, size_t length, size_t* dst_length) {
|
||||
// Verify keys are maximum length 4; this verifies fix for a
|
||||
// prior bug which was passing the RocksDB-encoded key with
|
||||
// logical timestamp suffix instead of parsed user key.
|
||||
if (length > 4) {
|
||||
fprintf(stderr, "%s:%d: %s: key %s is not user key\n", __FILE__, __LINE__, phase, key);
|
||||
abort();
|
||||
}
|
||||
*dst_length = length < 3 ? length : 3;
|
||||
return (char*)key;
|
||||
}
|
||||
static unsigned char CFV2PrefixExtractorInDomain(void* state, const char* key, size_t length) {
|
||||
return 1;
|
||||
}
|
||||
static unsigned char CFV2PrefixExtractorInRange(void* state, const char* key, size_t length) {
|
||||
return 1;
|
||||
}
|
||||
static const char* CFV2PrefixExtractorName(void* state) {
|
||||
return "TestCFV2PrefixExtractor";
|
||||
}
|
||||
|
||||
// Custom compaction filter factory V2.
|
||||
static void CompactionFilterFactoryV2Destroy(void* arg) {
|
||||
rocksdb_slicetransform_destroy((rocksdb_slicetransform_t*)arg);
|
||||
}
|
||||
static const char* CompactionFilterFactoryV2Name(void* arg) {
|
||||
return "TestCompactionFilterFactoryV2";
|
||||
}
|
||||
static rocksdb_compactionfilterv2_t* CompactionFilterFactoryV2Create(
|
||||
void* state, const rocksdb_compactionfiltercontext_t* context) {
|
||||
return rocksdb_compactionfilterv2_create(state, CompactionFilterV2Destroy,
|
||||
CompactionFilterV2Filter,
|
||||
CompactionFilterV2Name);
|
||||
}
|
||||
|
||||
// Custom merge operator
|
||||
static void MergeOperatorDestroy(void* arg) { }
|
||||
static const char* MergeOperatorName(void* arg) {
|
||||
@@ -700,50 +649,6 @@ int main(int argc, char** argv) {
|
||||
rocksdb_options_destroy(options_with_filter_factory);
|
||||
}
|
||||
|
||||
StartPhase("compaction_filter_v2");
|
||||
{
|
||||
rocksdb_compactionfilterfactoryv2_t* factory;
|
||||
rocksdb_slicetransform_t* prefix_extractor;
|
||||
prefix_extractor = rocksdb_slicetransform_create(
|
||||
NULL, CFV2PrefixExtractorDestroy, CFV2PrefixExtractorTransform,
|
||||
CFV2PrefixExtractorInDomain, CFV2PrefixExtractorInRange,
|
||||
CFV2PrefixExtractorName);
|
||||
factory = rocksdb_compactionfilterfactoryv2_create(
|
||||
prefix_extractor, prefix_extractor, CompactionFilterFactoryV2Destroy,
|
||||
CompactionFilterFactoryV2Create, CompactionFilterFactoryV2Name);
|
||||
// Create new database
|
||||
rocksdb_close(db);
|
||||
rocksdb_destroy_db(options, dbname, &err);
|
||||
rocksdb_options_set_compaction_filter_factory_v2(options, factory);
|
||||
db = rocksdb_open(options, dbname, &err);
|
||||
CheckNoError(err);
|
||||
// Only foo2 is GC'd, foo3 is changed.
|
||||
rocksdb_put(db, woptions, "foo1", 4, "no gc", 5, &err);
|
||||
CheckNoError(err);
|
||||
rocksdb_put(db, woptions, "foo2", 4, "gc", 2, &err);
|
||||
CheckNoError(err);
|
||||
rocksdb_put(db, woptions, "foo3", 4, "change", 6, &err);
|
||||
CheckNoError(err);
|
||||
// All bars are GC'd.
|
||||
rocksdb_put(db, woptions, "bar1", 4, "no gc", 5, &err);
|
||||
CheckNoError(err);
|
||||
rocksdb_put(db, woptions, "bar2", 4, "gc all", 6, &err);
|
||||
CheckNoError(err);
|
||||
rocksdb_put(db, woptions, "bar3", 4, "no gc", 5, &err);
|
||||
CheckNoError(err);
|
||||
// Compact the DB to garbage collect.
|
||||
rocksdb_compact_range(db, NULL, 0, NULL, 0);
|
||||
|
||||
// Verify foo entries.
|
||||
CheckGet(db, roptions, "foo1", "no gc");
|
||||
CheckGet(db, roptions, "foo2", NULL);
|
||||
CheckGet(db, roptions, "foo3", "changed");
|
||||
// Verify bar entries were all deleted.
|
||||
CheckGet(db, roptions, "bar1", NULL);
|
||||
CheckGet(db, roptions, "bar2", NULL);
|
||||
CheckGet(db, roptions, "bar3", NULL);
|
||||
}
|
||||
|
||||
StartPhase("merge_operator");
|
||||
{
|
||||
rocksdb_mergeoperator_t* merge_operator;
|
||||
|
||||
+1
-3
@@ -59,6 +59,7 @@ ColumnFamilyHandleImpl::~ColumnFamilyHandleImpl() {
|
||||
if (job_context.HaveSomethingToDelete()) {
|
||||
db_->PurgeObsoleteFiles(job_context);
|
||||
}
|
||||
job_context.Clean();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,9 +138,6 @@ ColumnFamilyOptions SanitizeOptions(const DBOptions& db_options,
|
||||
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.max_write_buffer_number < 2) {
|
||||
result.max_write_buffer_number = 2;
|
||||
}
|
||||
|
||||
@@ -381,13 +381,6 @@ class ColumnFamilyTest : public testing::Test {
|
||||
Random rnd_;
|
||||
};
|
||||
|
||||
class DumbLogger : public Logger {
|
||||
public:
|
||||
using Logger::Logv;
|
||||
virtual void Logv(const char* format, va_list ap) override {}
|
||||
virtual size_t GetLogFileSize() const override { return 0; }
|
||||
};
|
||||
|
||||
TEST_F(ColumnFamilyTest, DontReuseColumnFamilyID) {
|
||||
for (int iter = 0; iter < 3; ++iter) {
|
||||
Open();
|
||||
@@ -875,7 +868,6 @@ TEST_F(ColumnFamilyTest, DifferentCompactionStyles) {
|
||||
|
||||
two.compaction_style = kCompactionStyleLevel;
|
||||
two.num_levels = 4;
|
||||
two.max_mem_compaction_level = 0;
|
||||
two.level0_file_num_compaction_trigger = 3;
|
||||
two.write_buffer_size = 100000;
|
||||
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
// 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 <mutex>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
@@ -89,7 +91,7 @@ TEST_F(CompactFilesTest, ObsoleteFiles) {
|
||||
|
||||
// verify all compaction input files are deleted
|
||||
for (auto fname : l0_files) {
|
||||
ASSERT_TRUE(!env_->FileExists(fname));
|
||||
ASSERT_EQ(Status::NotFound(), env_->FileExists(fname));
|
||||
}
|
||||
delete db;
|
||||
}
|
||||
@@ -100,3 +102,14 @@ int main(int argc, char** argv) {
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
|
||||
#else
|
||||
#include <stdio.h>
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
fprintf(stderr,
|
||||
"SKIPPED as DBImpl::CompactFiles is not supported in ROCKSDB_LITE\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif // !ROCKSDB_LITE
|
||||
@@ -4,7 +4,7 @@
|
||||
// of patent rights can be found in the PATENTS file in the same directory.
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
#include "utilities/compacted_db/compacted_db_impl.h"
|
||||
#include "db/compacted_db_impl.h"
|
||||
#include "db/db_impl.h"
|
||||
#include "db/version_set.h"
|
||||
#include "table/get_context.h"
|
||||
File renamed without changes.
+11
-18
@@ -160,15 +160,21 @@ bool Compaction::IsTrivialMove() const {
|
||||
|
||||
if (is_manual_compaction_ &&
|
||||
(cfd_->ioptions()->compaction_filter != nullptr ||
|
||||
cfd_->ioptions()->compaction_filter_factory != nullptr ||
|
||||
cfd_->ioptions()->compaction_filter_factory_v2 != nullptr)) {
|
||||
cfd_->ioptions()->compaction_filter_factory != nullptr)) {
|
||||
// This is a manual compaction and we have a compaction filter that should
|
||||
// be executed, we cannot do a trivial move
|
||||
return false;
|
||||
}
|
||||
|
||||
// Used in universal compaction, where trivial move can be done if the
|
||||
// input files are non overlapping
|
||||
if ((cfd_->ioptions()->compaction_options_universal.allow_trivial_move) &&
|
||||
(output_level_ != 0)) {
|
||||
return is_trivial_move_;
|
||||
}
|
||||
|
||||
return (start_level_ != output_level_ && num_input_levels() == 1 &&
|
||||
input(0, 0)->fd.GetPathId() == GetOutputPathId() &&
|
||||
input(0, 0)->fd.GetPathId() == output_path_id() &&
|
||||
InputCompressionMatchesOutput() &&
|
||||
TotalFileSize(grandparents_) <= max_grandparent_overlap_bytes_);
|
||||
}
|
||||
@@ -264,7 +270,8 @@ const char* Compaction::InputLevelSummary(
|
||||
is_first = false;
|
||||
}
|
||||
len += snprintf(scratch->buffer + len, sizeof(scratch->buffer) - len,
|
||||
"%zu@%d", input_level.size(), input_level.level);
|
||||
"%" ROCKSDB_PRIszt "@%d", input_level.size(),
|
||||
input_level.level);
|
||||
}
|
||||
snprintf(scratch->buffer + len, sizeof(scratch->buffer) - len,
|
||||
" files to L%d", output_level());
|
||||
@@ -369,18 +376,4 @@ std::unique_ptr<CompactionFilter> Compaction::CreateCompactionFilter() const {
|
||||
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
|
||||
+35
-12
@@ -30,7 +30,6 @@ class Version;
|
||||
class ColumnFamilyData;
|
||||
class VersionStorageInfo;
|
||||
class CompactionFilter;
|
||||
class CompactionFilterV2;
|
||||
|
||||
// A Compaction encapsulates information about a compaction.
|
||||
class Compaction {
|
||||
@@ -109,22 +108,20 @@ class Compaction {
|
||||
}
|
||||
|
||||
// Maximum size of files to build during this compaction.
|
||||
uint64_t MaxOutputFileSize() const { return max_output_file_size_; }
|
||||
uint64_t max_output_file_size() const { return max_output_file_size_; }
|
||||
|
||||
// What compression for output
|
||||
CompressionType OutputCompressionType() const { return output_compression_; }
|
||||
CompressionType output_compression() const { return output_compression_; }
|
||||
|
||||
// Whether need to write output file to second DB path.
|
||||
uint32_t GetOutputPathId() const { return output_path_id_; }
|
||||
uint32_t output_path_id() const { return output_path_id_; }
|
||||
|
||||
// Is this a trivial compaction that can be implemented by just
|
||||
// moving a single input file to the next level (no merging or splitting)
|
||||
bool IsTrivialMove() const;
|
||||
|
||||
// If true, then the compaction can be done by simply deleting input files.
|
||||
bool IsDeletionCompaction() const {
|
||||
return deletion_compaction_;
|
||||
}
|
||||
bool deletion_compaction() const { return deletion_compaction_; }
|
||||
|
||||
// Add all inputs to this compaction as delete operations to *edit.
|
||||
void AddInputDeletions(VersionEdit* edit);
|
||||
@@ -150,13 +147,26 @@ class Compaction {
|
||||
double score() const { return score_; }
|
||||
|
||||
// Is this compaction creating a file in the bottom most level?
|
||||
bool BottomMostLevel() { return bottommost_level_; }
|
||||
bool bottommost_level() { return bottommost_level_; }
|
||||
|
||||
// Does this compaction include all sst files?
|
||||
bool IsFullCompaction() { return is_full_compaction_; }
|
||||
bool is_full_compaction() { return is_full_compaction_; }
|
||||
|
||||
// Was this compaction triggered manually by the client?
|
||||
bool IsManualCompaction() { return is_manual_compaction_; }
|
||||
bool is_manual_compaction() { return is_manual_compaction_; }
|
||||
|
||||
// Used when allow_trivial_move option is set in
|
||||
// Universal compaction. If all the input files are
|
||||
// non overlapping, then is_trivial_move_ variable
|
||||
// will be set true, else false
|
||||
void set_is_trivial_move(bool trivial_move) {
|
||||
is_trivial_move_ = trivial_move;
|
||||
}
|
||||
|
||||
// Used when allow_trivial_move option is set in
|
||||
// Universal compaction. Returns true, if the input files
|
||||
// are non-overlapping and can be trivially moved.
|
||||
bool is_trivial_move() { return is_trivial_move_; }
|
||||
|
||||
// Return the MutableCFOptions that should be used throughout the compaction
|
||||
// procedure
|
||||
@@ -184,8 +194,16 @@ class Compaction {
|
||||
// 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;
|
||||
// Should this compaction be broken up into smaller ones run in parallel?
|
||||
bool IsSubCompaction() const {
|
||||
return start_level_ == 0 && output_level_ == 1
|
||||
&& mutable_cf_options_.num_subcompactions > 1;
|
||||
}
|
||||
|
||||
// If is_sub_compaction == true, how many smaller compactions should execute
|
||||
int NumSubCompactions() const {
|
||||
return mutable_cf_options_.num_subcompactions;
|
||||
}
|
||||
|
||||
private:
|
||||
// mark (or clear) all files that are being compacted
|
||||
@@ -238,6 +256,11 @@ class Compaction {
|
||||
// Is this compaction requested by the client?
|
||||
const bool is_manual_compaction_;
|
||||
|
||||
// True if we can do trivial move in Universal multi level
|
||||
// compaction
|
||||
|
||||
bool is_trivial_move_;
|
||||
|
||||
// "level_ptrs_" holds indices into "input_version_->levels_", where each
|
||||
// index remembers which file of an associated level we are currently used
|
||||
// to check KeyNotExistsBeyondOutputLevel() for deletion operation.
|
||||
|
||||
+363
-637
File diff suppressed because it is too large.
Load diff
+19
-12
@@ -58,7 +58,6 @@ class CompactionJob {
|
||||
Statistics* stats,
|
||||
std::vector<SequenceNumber> existing_snapshots,
|
||||
std::shared_ptr<Cache> table_cache,
|
||||
std::function<uint64_t()> yield_callback,
|
||||
EventLogger* event_logger, bool paranoid_file_checks,
|
||||
const std::string& dbname,
|
||||
CompactionJobStats* compaction_job_stats);
|
||||
@@ -74,23 +73,31 @@ class CompactionJob {
|
||||
void Prepare();
|
||||
// REQUIRED mutex not held
|
||||
Status Run();
|
||||
|
||||
// REQUIRED: mutex held
|
||||
// status is the return of Run()
|
||||
void Install(Status* status, const MutableCFOptions& mutable_cf_options,
|
||||
InstrumentedMutex* db_mutex);
|
||||
|
||||
private:
|
||||
// REQUIRED: mutex not held
|
||||
Status SubCompactionRun(Slice* start, Slice* end);
|
||||
|
||||
void GetSubCompactionBoundaries();
|
||||
// update the thread status for starting a compaction.
|
||||
void ReportStartedCompaction(Compaction* compaction);
|
||||
void AllocateCompactionOutputFileNumbers();
|
||||
// Call compaction filter if is_compaction_v2 is not true. Then iterate
|
||||
// through input and compact the kv-pairs
|
||||
// Call compaction filter. Then iterate through input and compact the
|
||||
// kv-pairs
|
||||
Status ProcessKeyValueCompaction(int64_t* imm_micros, Iterator* input,
|
||||
bool is_compaction_v2);
|
||||
// Call compaction_filter_v2->Filter() on kv-pairs in compact
|
||||
void CallCompactionFilterV2(CompactionFilterV2* compaction_filter_v2,
|
||||
uint64_t* time);
|
||||
Status FinishCompactionOutputFile(Iterator* input);
|
||||
Slice* start = nullptr,
|
||||
Slice* end = nullptr);
|
||||
|
||||
Status WriteKeyValue(const Slice& key, const Slice& value,
|
||||
const ParsedInternalKey& ikey,
|
||||
const Status& input_status);
|
||||
|
||||
Status FinishCompactionOutputFile(const Status& input_status);
|
||||
Status InstallCompactionResults(InstrumentedMutex* db_mutex,
|
||||
const MutableCFOptions& mutable_cf_options);
|
||||
SequenceNumber findEarliestVisibleSnapshot(
|
||||
@@ -105,10 +112,12 @@ class CompactionJob {
|
||||
int64_t* key_drop_newer_entry,
|
||||
int64_t* key_drop_obsolete);
|
||||
|
||||
void UpdateCompactionInputStats();
|
||||
void UpdateCompactionStats();
|
||||
void UpdateCompactionInputStatsHelper(
|
||||
int* num_files, uint64_t* bytes_read, int input_level);
|
||||
|
||||
void LogCompaction(ColumnFamilyData* cfd, Compaction* compaction);
|
||||
|
||||
int job_id_;
|
||||
|
||||
// CompactionJob state
|
||||
@@ -141,12 +150,10 @@ class CompactionJob {
|
||||
std::vector<SequenceNumber> existing_snapshots_;
|
||||
std::shared_ptr<Cache> table_cache_;
|
||||
|
||||
// yield callback
|
||||
std::function<uint64_t()> yield_callback_;
|
||||
|
||||
EventLogger* event_logger_;
|
||||
|
||||
bool paranoid_file_checks_;
|
||||
std::vector<Slice> sub_compaction_boundaries_;
|
||||
};
|
||||
|
||||
} // namespace rocksdb
|
||||
+205
-15
@@ -30,6 +30,7 @@
|
||||
#include "port/stack_trace.h"
|
||||
#include "rocksdb/cache.h"
|
||||
#include "rocksdb/compaction_filter.h"
|
||||
#include "rocksdb/convenience.h"
|
||||
#include "rocksdb/db.h"
|
||||
#include "rocksdb/env.h"
|
||||
#include "rocksdb/experimental.h"
|
||||
@@ -42,7 +43,6 @@
|
||||
#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"
|
||||
@@ -64,7 +64,7 @@
|
||||
#include "util/xfunc.h"
|
||||
#include "utilities/merge_operators.h"
|
||||
|
||||
#if !defined(IOS_CROSS_COMPILE)
|
||||
#if !defined(IOS_CROSS_COMPILE) && (!defined(NDEBUG) || !defined(OS_WIN))
|
||||
#ifndef ROCKSDB_LITE
|
||||
namespace rocksdb {
|
||||
|
||||
@@ -84,13 +84,15 @@ std::string Key(uint64_t key, int length) {
|
||||
return std::string(buf);
|
||||
}
|
||||
|
||||
class CompactionJobStatsTest : public testing::Test {
|
||||
class CompactionJobStatsTest : public testing::Test,
|
||||
public testing::WithParamInterface<bool> {
|
||||
public:
|
||||
std::string dbname_;
|
||||
std::string alternative_wal_dir_;
|
||||
Env* env_;
|
||||
DB* db_;
|
||||
std::vector<ColumnFamilyHandle*> handles_;
|
||||
uint32_t num_subcompactions_;
|
||||
|
||||
Options last_options_;
|
||||
|
||||
@@ -101,6 +103,8 @@ class CompactionJobStatsTest : public testing::Test {
|
||||
alternative_wal_dir_ = dbname_ + "/wal";
|
||||
Options options;
|
||||
options.create_if_missing = true;
|
||||
num_subcompactions_ = GetParam();
|
||||
options.num_subcompactions = num_subcompactions_;
|
||||
auto delete_options = options;
|
||||
delete_options.wal_dir = alternative_wal_dir_;
|
||||
EXPECT_OK(DestroyDB(dbname_, delete_options));
|
||||
@@ -123,6 +127,10 @@ class CompactionJobStatsTest : public testing::Test {
|
||||
EXPECT_OK(DestroyDB(dbname_, options));
|
||||
}
|
||||
|
||||
// Required if inheriting from testing::WithParamInterface<>
|
||||
static void SetUpTestCase() {}
|
||||
static void TearDownTestCase() {}
|
||||
|
||||
DBImpl* dbfull() {
|
||||
return reinterpret_cast<DBImpl*>(db_);
|
||||
}
|
||||
@@ -339,6 +347,14 @@ class CompactionJobStatsTest : public testing::Test {
|
||||
}
|
||||
}
|
||||
|
||||
static void SetDeletionCompactionStats(
|
||||
CompactionJobStats *stats, uint64_t input_deletions,
|
||||
uint64_t expired_deletions, uint64_t records_replaced) {
|
||||
stats->num_input_deletion_records = input_deletions;
|
||||
stats->num_expired_deletion_records = expired_deletions;
|
||||
stats->num_records_replaced = records_replaced;
|
||||
}
|
||||
|
||||
void MakeTableWithKeyValues(
|
||||
Random* rnd, uint64_t smallest, uint64_t largest,
|
||||
int key_size, int value_size, uint64_t interval,
|
||||
@@ -349,6 +365,52 @@ class CompactionJobStatsTest : public testing::Test {
|
||||
}
|
||||
ASSERT_OK(Flush(cf));
|
||||
}
|
||||
|
||||
// This function behaves with the implicit understanding that two
|
||||
// rounds of keys are inserted into the database, as per the behavior
|
||||
// of the DeletionStatsTest.
|
||||
void SelectivelyDeleteKeys(uint64_t smallest, uint64_t largest,
|
||||
uint64_t interval, int deletion_interval, int key_size,
|
||||
uint64_t cutoff_key_num, CompactionJobStats* stats, int cf = 0) {
|
||||
|
||||
// interval needs to be >= 2 so that deletion entries can be inserted
|
||||
// that are intended to not result in an actual key deletion by using
|
||||
// an offset of 1 from another existing key
|
||||
ASSERT_GE(interval, 2);
|
||||
|
||||
uint64_t ctr = 1;
|
||||
uint32_t deletions_made = 0;
|
||||
uint32_t num_deleted = 0;
|
||||
uint32_t num_expired = 0;
|
||||
for (auto key = smallest; key <= largest; key += interval, ctr++) {
|
||||
if (ctr % deletion_interval == 0) {
|
||||
ASSERT_OK(Delete(cf, Key(key, key_size)));
|
||||
deletions_made++;
|
||||
num_deleted++;
|
||||
|
||||
if (key > cutoff_key_num) {
|
||||
num_expired++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Insert some deletions for keys that don't exist that
|
||||
// are both in and out of the key range
|
||||
ASSERT_OK(Delete(cf, Key(smallest+1, key_size)));
|
||||
deletions_made++;
|
||||
|
||||
ASSERT_OK(Delete(cf, Key(smallest-1, key_size)));
|
||||
deletions_made++;
|
||||
num_expired++;
|
||||
|
||||
ASSERT_OK(Delete(cf, Key(smallest-9, key_size)));
|
||||
deletions_made++;
|
||||
num_expired++;
|
||||
|
||||
ASSERT_OK(Flush(cf));
|
||||
SetDeletionCompactionStats(stats, deletions_made, num_expired,
|
||||
num_deleted);
|
||||
}
|
||||
};
|
||||
|
||||
// An EventListener which helps verify the compaction results in
|
||||
@@ -359,12 +421,11 @@ class CompactionJobStatsChecker : public EventListener {
|
||||
|
||||
size_t NumberOfUnverifiedStats() { return expected_stats_.size(); }
|
||||
|
||||
// Once a compaction completed, this functionw will verify the returned
|
||||
// Once a compaction completed, this function 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();
|
||||
@@ -376,7 +437,7 @@ class CompactionJobStatsChecker : public EventListener {
|
||||
// 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,
|
||||
virtual void Verify(const CompactionJobStats& current_stats,
|
||||
const CompactionJobStats& stats) {
|
||||
// time
|
||||
ASSERT_GT(current_stats.elapsed_micros, 0U);
|
||||
@@ -414,6 +475,9 @@ class CompactionJobStatsChecker : public EventListener {
|
||||
ASSERT_EQ(current_stats.num_records_replaced,
|
||||
stats.num_records_replaced);
|
||||
|
||||
ASSERT_EQ(current_stats.num_corrupt_keys,
|
||||
stats.num_corrupt_keys);
|
||||
|
||||
ASSERT_EQ(
|
||||
std::string(current_stats.smallest_output_key_prefix),
|
||||
std::string(stats.smallest_output_key_prefix));
|
||||
@@ -440,6 +504,28 @@ class CompactionJobStatsChecker : public EventListener {
|
||||
bool compression_enabled_;
|
||||
};
|
||||
|
||||
// An EventListener which helps verify the compaction statistics in
|
||||
// the test DeletionStatsTest.
|
||||
class CompactionJobDeletionStatsChecker : public CompactionJobStatsChecker {
|
||||
public:
|
||||
// Verifies whether two CompactionJobStats match.
|
||||
void Verify(const CompactionJobStats& current_stats,
|
||||
const CompactionJobStats& stats) {
|
||||
ASSERT_EQ(
|
||||
current_stats.num_input_deletion_records,
|
||||
stats.num_input_deletion_records);
|
||||
ASSERT_EQ(
|
||||
current_stats.num_expired_deletion_records,
|
||||
stats.num_expired_deletion_records);
|
||||
ASSERT_EQ(
|
||||
current_stats.num_records_replaced,
|
||||
stats.num_records_replaced);
|
||||
|
||||
ASSERT_EQ(current_stats.num_corrupt_keys,
|
||||
stats.num_corrupt_keys);
|
||||
}
|
||||
};
|
||||
|
||||
namespace {
|
||||
|
||||
uint64_t EstimatedFileSize(
|
||||
@@ -529,7 +615,7 @@ CompressionType GetAnyCompression() {
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST_F(CompactionJobStatsTest, CompactionJobStatsTest) {
|
||||
TEST_P(CompactionJobStatsTest, CompactionJobStatsTest) {
|
||||
Random rnd(301);
|
||||
const int kBufSize = 100;
|
||||
char buf[kBufSize];
|
||||
@@ -552,11 +638,11 @@ TEST_F(CompactionJobStatsTest, CompactionJobStatsTest) {
|
||||
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;
|
||||
options.num_subcompactions = num_subcompactions_;
|
||||
|
||||
for (int test = 0; test < 2; ++test) {
|
||||
DestroyAndReopen(options);
|
||||
@@ -634,6 +720,11 @@ TEST_F(CompactionJobStatsTest, CompactionJobStatsTest) {
|
||||
}
|
||||
|
||||
// 4th Phase: perform L0 -> L1 compaction again, expect higher write amp
|
||||
// When subcompactions are enabled, the number of output files increases
|
||||
// by 1 because multiple threads are consuming the input and generating
|
||||
// output files without coordinating to see if the output could fit into
|
||||
// a smaller number of files like it does when it runs sequentially
|
||||
int num_output_files = options.num_subcompactions > 1 ? 2 : 1;
|
||||
for (uint64_t start_key = key_base;
|
||||
num_L0_files > 1;
|
||||
start_key += key_base * sparseness) {
|
||||
@@ -645,13 +736,18 @@ TEST_F(CompactionJobStatsTest, CompactionJobStatsTest) {
|
||||
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.
|
||||
num_output_files,
|
||||
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);
|
||||
// TODO(aekmekji): account for whether parallel L0-L1 compaction is
|
||||
// enabled or not. If so then num_L1_files will increase by 1
|
||||
if (options.num_subcompactions == 1) {
|
||||
--num_L1_files;
|
||||
}
|
||||
snprintf(buf, kBufSize, "%d,%d", --num_L0_files, num_L1_files);
|
||||
ASSERT_EQ(std::string(buf), FilesPerLevel(1));
|
||||
}
|
||||
|
||||
@@ -670,7 +766,11 @@ TEST_F(CompactionJobStatsTest, CompactionJobStatsTest) {
|
||||
num_keys_per_L0_file));
|
||||
ASSERT_EQ(stats_checker->NumberOfUnverifiedStats(), 1U);
|
||||
Compact(1, smallest_key, largest_key);
|
||||
ASSERT_EQ("0,4", FilesPerLevel(1));
|
||||
num_L1_files = options.num_subcompactions > 1 ? 7 : 4;
|
||||
char L1_buf[4];
|
||||
snprintf(L1_buf, sizeof(L1_buf), "0,%d", num_L1_files);
|
||||
std::string L1_files(L1_buf);
|
||||
ASSERT_EQ(L1_files, FilesPerLevel(1));
|
||||
options.compression = GetAnyCompression();
|
||||
if (options.compression == kNoCompression) {
|
||||
break;
|
||||
@@ -681,6 +781,89 @@ TEST_F(CompactionJobStatsTest, CompactionJobStatsTest) {
|
||||
ASSERT_EQ(stats_checker->NumberOfUnverifiedStats(), 0U);
|
||||
}
|
||||
|
||||
TEST_P(CompactionJobStatsTest, DeletionStatsTest) {
|
||||
Random rnd(301);
|
||||
uint64_t key_base = 100000l;
|
||||
// Note: key_base must be multiple of num_keys_per_L0_file
|
||||
int num_keys_per_L0_file = 20;
|
||||
const int kTestScale = 8; // make sure this is even
|
||||
const int kKeySize = 10;
|
||||
const int kValueSize = 100;
|
||||
double compression_ratio = 1.0;
|
||||
uint64_t key_interval = key_base / num_keys_per_L0_file;
|
||||
uint64_t largest_key_num = key_base * (kTestScale + 1) - key_interval;
|
||||
uint64_t cutoff_key_num = key_base * (kTestScale / 2 + 1) - key_interval;
|
||||
const std::string smallest_key = Key(key_base - 10, kKeySize);
|
||||
const std::string largest_key = Key(largest_key_num + 10, kKeySize);
|
||||
|
||||
// Whenever a compaction completes, this listener will try to
|
||||
// verify whether the returned CompactionJobStats matches
|
||||
// what we expect.
|
||||
auto* stats_checker = new CompactionJobDeletionStatsChecker();
|
||||
Options options;
|
||||
options.listeners.emplace_back(stats_checker);
|
||||
options.create_if_missing = true;
|
||||
options.max_background_flushes = 0;
|
||||
options.level0_file_num_compaction_trigger = kTestScale+1;
|
||||
options.num_levels = 3;
|
||||
options.compression = kNoCompression;
|
||||
options.max_bytes_for_level_multiplier = 2;
|
||||
options.num_subcompactions = num_subcompactions_;
|
||||
|
||||
DestroyAndReopen(options);
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
|
||||
// Stage 1: Generate several L0 files and then send them to L2 by
|
||||
// using CompactRangeOptions and CompactRange(). These files will
|
||||
// have a strict subset of the keys from the full key-range
|
||||
for (uint64_t start_key = key_base;
|
||||
start_key <= key_base * kTestScale / 2;
|
||||
start_key += key_base) {
|
||||
MakeTableWithKeyValues(
|
||||
&rnd, start_key, start_key + key_base - 1,
|
||||
kKeySize, kValueSize, key_interval,
|
||||
compression_ratio, 1);
|
||||
}
|
||||
|
||||
CompactRangeOptions cr_options;
|
||||
cr_options.change_level = true;
|
||||
cr_options.target_level = 2;
|
||||
db_->CompactRange(cr_options, handles_[1], nullptr, nullptr);
|
||||
ASSERT_GT(NumTableFilesAtLevel(2, 1), 0);
|
||||
|
||||
// Stage 2: Generate files including keys from the entire key range
|
||||
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);
|
||||
}
|
||||
|
||||
// Send these L0 files to L1
|
||||
TEST_Compact(0, 1, smallest_key, largest_key);
|
||||
ASSERT_GT(NumTableFilesAtLevel(1, 1), 0);
|
||||
|
||||
// Add a new record and flush so now there is a L0 file
|
||||
// with a value too (not just deletions from the next step)
|
||||
ASSERT_OK(Put(1, Key(key_base-6, kKeySize), "test"));
|
||||
ASSERT_OK(Flush(1));
|
||||
|
||||
// Stage 3: Generate L0 files with some deletions so now
|
||||
// there are files with the same key range in L0, L1, and L2
|
||||
int deletion_interval = 3;
|
||||
CompactionJobStats first_compaction_stats;
|
||||
SelectivelyDeleteKeys(key_base, largest_key_num,
|
||||
key_interval, deletion_interval, kKeySize, cutoff_key_num,
|
||||
&first_compaction_stats, 1);
|
||||
|
||||
stats_checker->AddExpectedStats(first_compaction_stats);
|
||||
|
||||
// Stage 4: Trigger compaction and verify the stats
|
||||
TEST_Compact(0, 1, smallest_key, largest_key);
|
||||
}
|
||||
|
||||
namespace {
|
||||
int GetUniversalCompactionInputUnits(uint32_t num_flushes) {
|
||||
uint32_t compaction_input_units;
|
||||
@@ -695,7 +878,7 @@ int GetUniversalCompactionInputUnits(uint32_t num_flushes) {
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST_F(CompactionJobStatsTest, UniversalCompactionTest) {
|
||||
TEST_P(CompactionJobStatsTest, UniversalCompactionTest) {
|
||||
Random rnd(301);
|
||||
uint64_t key_base = 100000000l;
|
||||
// Note: key_base must be multiple of num_keys_per_L0_file
|
||||
@@ -710,8 +893,6 @@ TEST_F(CompactionJobStatsTest, UniversalCompactionTest) {
|
||||
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;
|
||||
@@ -719,6 +900,8 @@ TEST_F(CompactionJobStatsTest, UniversalCompactionTest) {
|
||||
options.compaction_style = kCompactionStyleUniversal;
|
||||
options.compaction_options_universal.size_ratio = 1;
|
||||
options.compaction_options_universal.max_size_amplification_percent = 1000;
|
||||
options.num_subcompactions = num_subcompactions_;
|
||||
|
||||
DestroyAndReopen(options);
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
|
||||
@@ -762,9 +945,12 @@ TEST_F(CompactionJobStatsTest, UniversalCompactionTest) {
|
||||
kKeySize, kValueSize, key_interval,
|
||||
compression_ratio, 1);
|
||||
}
|
||||
reinterpret_cast<DBImpl*>(db_)->TEST_WaitForCompact();
|
||||
ASSERT_EQ(stats_checker->NumberOfUnverifiedStats(), 0U);
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(CompactionJobStatsTest, CompactionJobStatsTest,
|
||||
::testing::Values(1, 4));
|
||||
} // namespace rocksdb
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
@@ -774,4 +960,8 @@ int main(int argc, char** argv) {
|
||||
}
|
||||
|
||||
#endif // !ROCKSDB_LITE
|
||||
|
||||
#else
|
||||
|
||||
int main(int argc, char** argv) { return 0; }
|
||||
#endif // !defined(IOS_CROSS_COMPILE)
|
||||
+115
-93
@@ -13,6 +13,7 @@
|
||||
#include "rocksdb/cache.h"
|
||||
#include "rocksdb/options.h"
|
||||
#include "rocksdb/db.h"
|
||||
#include "util/file_reader_writer.h"
|
||||
#include "util/string_util.h"
|
||||
#include "util/testharness.h"
|
||||
#include "util/testutil.h"
|
||||
@@ -20,6 +21,41 @@
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
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, false);
|
||||
|
||||
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);
|
||||
|
||||
ASSERT_EQ(compaction_job_stats.num_input_deletion_records, 0U);
|
||||
ASSERT_EQ(compaction_job_stats.num_expired_deletion_records, 0U);
|
||||
|
||||
ASSERT_EQ(compaction_job_stats.num_corrupt_keys, 0U);
|
||||
#endif // !defined(IOS_CROSS_COMPILE)
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// TODO(icanadi) Make it simpler once we mock out VersionSet
|
||||
class CompactionJobTest : public testing::Test {
|
||||
public:
|
||||
@@ -53,22 +89,40 @@ class CompactionJobTest : public testing::Test {
|
||||
return TableFileName(db_paths, meta.fd.GetNumber(), meta.fd.GetPathId());
|
||||
}
|
||||
|
||||
// Corrupts key by changing the type
|
||||
void CorruptKey(InternalKey* ikey) {
|
||||
std::string keystr = ikey->Encode().ToString();
|
||||
keystr[keystr.size() - 8] = kTypeLogData;
|
||||
ikey->DecodeFrom(Slice(keystr.data(), keystr.size()));
|
||||
}
|
||||
|
||||
// returns expected result after compaction
|
||||
mock::MockFileContents CreateTwoFiles() {
|
||||
mock::MockFileContents CreateTwoFiles(bool gen_corrupted_keys) {
|
||||
mock::MockFileContents expected_results;
|
||||
const int kKeysPerFile = 10000;
|
||||
const int kCorruptKeysPerFile = 200;
|
||||
const int kMatchingKeys = kKeysPerFile / 2;
|
||||
SequenceNumber sequence_number = 0;
|
||||
|
||||
auto corrupt_id = [&](int id) {
|
||||
return gen_corrupted_keys && id > 0 && id <= kCorruptKeysPerFile;
|
||||
};
|
||||
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
mock::MockFileContents contents;
|
||||
SequenceNumber smallest_seqno = 0, largest_seqno = 0;
|
||||
InternalKey smallest, largest;
|
||||
for (int k = 0; k < kKeysPerFile; ++k) {
|
||||
auto key = ToString(i * (kKeysPerFile / 2) + k);
|
||||
auto key = ToString(i * kMatchingKeys + k);
|
||||
auto value = ToString(i * kKeysPerFile + k);
|
||||
InternalKey internal_key(key, ++sequence_number, kTypeValue);
|
||||
// This is how the key will look like once it's written in bottommost
|
||||
// file
|
||||
InternalKey bottommost_internal_key(key, 0, kTypeValue);
|
||||
if (corrupt_id(k)) {
|
||||
CorruptKey(&internal_key);
|
||||
CorruptKey(&bottommost_internal_key);
|
||||
}
|
||||
if (k == 0) {
|
||||
smallest = internal_key;
|
||||
smallest_seqno = sequence_number;
|
||||
@@ -76,11 +130,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);
|
||||
if (i == 1 || k < kKeysPerFile / 2) {
|
||||
expected_results.insert(key_value);
|
||||
contents.insert({internal_key.Encode().ToString(), value});
|
||||
if (i == 1 || k < kMatchingKeys || corrupt_id(k - kMatchingKeys)) {
|
||||
expected_results.insert(
|
||||
{bottommost_internal_key.Encode().ToString(), value});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,7 +150,7 @@ class CompactionJobTest : public testing::Test {
|
||||
mutable_cf_options_, &edit, &mutex_);
|
||||
mutex_.Unlock();
|
||||
}
|
||||
versions_->SetLastSequence(sequence_number);
|
||||
versions_->SetLastSequence(sequence_number + 1);
|
||||
return expected_results;
|
||||
}
|
||||
|
||||
@@ -112,8 +165,10 @@ class CompactionJobTest : public testing::Test {
|
||||
Status s = env_->NewWritableFile(
|
||||
manifest, &file, env_->OptimizeForManifestWrite(env_options_));
|
||||
ASSERT_OK(s);
|
||||
unique_ptr<WritableFileWriter> file_writer(
|
||||
new WritableFileWriter(std::move(file), EnvOptions()));
|
||||
{
|
||||
log::Writer log(std::move(file));
|
||||
log::Writer log(std::move(file_writer));
|
||||
std::string record;
|
||||
new_db.EncodeTo(&record);
|
||||
s = log.AddRecord(record);
|
||||
@@ -123,6 +178,44 @@ class CompactionJobTest : public testing::Test {
|
||||
s = SetCurrentFile(env_, dbname_, 1, nullptr);
|
||||
}
|
||||
|
||||
void RunCompaction(const std::vector<FileMetaData*>& files) {
|
||||
auto cfd = versions_->GetColumnFamilySet()->GetDefault();
|
||||
|
||||
CompactionInputFiles compaction_input_files;
|
||||
compaction_input_files.level = 0;
|
||||
for (auto file : files) {
|
||||
compaction_input_files.files.push_back(file);
|
||||
}
|
||||
Compaction compaction(cfd->current()->storage_info(),
|
||||
*cfd->GetLatestMutableCFOptions(),
|
||||
{compaction_input_files}, 1, 1024 * 1024, 10, 0,
|
||||
kNoCompression, {});
|
||||
compaction.SetInputVersion(cfd->current());
|
||||
|
||||
LogBuffer log_buffer(InfoLogLevel::INFO_LEVEL, db_options_.info_log.get());
|
||||
mutex_.Lock();
|
||||
EventLogger event_logger(db_options_.info_log.get());
|
||||
CompactionJob compaction_job(
|
||||
0, &compaction, db_options_, env_options_, versions_.get(),
|
||||
&shutting_down_, &log_buffer, nullptr, nullptr, nullptr, {},
|
||||
table_cache_, &event_logger, false, dbname_, &compaction_job_stats_);
|
||||
|
||||
VerifyInitializationOfCompactionJobStats(compaction_job_stats_);
|
||||
|
||||
compaction_job.Prepare();
|
||||
mutex_.Unlock();
|
||||
ASSERT_OK(compaction_job.Run());
|
||||
mutex_.Lock();
|
||||
Status s;
|
||||
compaction_job.Install(&s, *cfd->GetLatestMutableCFOptions(), &mutex_);
|
||||
ASSERT_OK(s);
|
||||
mutex_.Unlock();
|
||||
|
||||
ASSERT_GE(compaction_job_stats_.elapsed_micros, 0U);
|
||||
ASSERT_EQ(compaction_job_stats_.num_input_files, files.size());
|
||||
ASSERT_EQ(compaction_job_stats_.num_output_files, 1U);
|
||||
}
|
||||
|
||||
Env* env_;
|
||||
std::string dbname_;
|
||||
EnvOptions env_options_;
|
||||
@@ -136,98 +229,27 @@ class CompactionJobTest : public testing::Test {
|
||||
InstrumentedMutex mutex_;
|
||||
std::atomic<bool> shutting_down_;
|
||||
std::shared_ptr<mock::MockTableFactory> mock_table_factory_;
|
||||
CompactionJobStats compaction_job_stats_;
|
||||
};
|
||||
|
||||
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 expected_results = CreateTwoFiles(false);
|
||||
auto cfd = versions_->GetColumnFamilySet()->GetDefault();
|
||||
|
||||
auto expected_results = CreateTwoFiles();
|
||||
|
||||
auto files = cfd->current()->storage_info()->LevelFiles(0);
|
||||
ASSERT_EQ(2U, files.size());
|
||||
|
||||
CompactionInputFiles compaction_input_files;
|
||||
compaction_input_files.level = 0;
|
||||
compaction_input_files.files.push_back(files[0]);
|
||||
compaction_input_files.files.push_back(files[1]);
|
||||
std::unique_ptr<Compaction> compaction(new Compaction(
|
||||
cfd->current()->storage_info(), *cfd->GetLatestMutableCFOptions(),
|
||||
{compaction_input_files}, 1, 1024 * 1024, 10, 0, kNoCompression, {}));
|
||||
compaction->SetInputVersion(cfd->current());
|
||||
|
||||
int yield_callback_called = 0;
|
||||
std::function<uint64_t()> yield_callback = [&]() {
|
||||
yield_callback_called++;
|
||||
return 0;
|
||||
};
|
||||
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,
|
||||
db_name, &compaction_job_stats);
|
||||
|
||||
auto start_micros = Env::Default()->NowMicros();
|
||||
VerifyInitializationOfCompactionJobStats(compaction_job_stats);
|
||||
|
||||
compaction_job.Prepare();
|
||||
mutex_.Unlock();
|
||||
ASSERT_OK(compaction_job.Run());
|
||||
mutex_.Lock();
|
||||
Status s;
|
||||
compaction_job.Install(&s, *cfd->GetLatestMutableCFOptions(), &mutex_);
|
||||
ASSERT_OK(s);
|
||||
mutex_.Unlock();
|
||||
|
||||
VerifyCompactionJobStats(
|
||||
compaction_job_stats,
|
||||
files, 1, (Env::Default()->NowMicros() - start_micros) / 2);
|
||||
|
||||
RunCompaction(files);
|
||||
mock_table_factory_->AssertLatestFile(expected_results);
|
||||
}
|
||||
|
||||
TEST_F(CompactionJobTest, SimpleCorrupted) {
|
||||
auto expected_results = CreateTwoFiles(true);
|
||||
auto cfd = versions_->GetColumnFamilySet()->GetDefault();
|
||||
auto files = cfd->current()->storage_info()->LevelFiles(0);
|
||||
|
||||
RunCompaction(files);
|
||||
ASSERT_EQ(compaction_job_stats_.num_corrupt_keys, 400U);
|
||||
mock_table_factory_->AssertLatestFile(expected_results);
|
||||
ASSERT_EQ(yield_callback_called, 20000);
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
+138
-10
@@ -15,6 +15,7 @@
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <limits>
|
||||
#include <queue>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
@@ -37,6 +38,69 @@ uint64_t TotalCompensatedFileSize(const std::vector<FileMetaData*>& files) {
|
||||
return sum;
|
||||
}
|
||||
|
||||
// Universal compaction is not supported in ROCKSDB_LITE
|
||||
#ifndef ROCKSDB_LITE
|
||||
|
||||
// Used in universal compaction when trivial move is enabled.
|
||||
// This structure is used for the construction of min heap
|
||||
// that contains the file meta data, the level of the file
|
||||
// and the index of the file in that level
|
||||
|
||||
struct InputFileInfo {
|
||||
InputFileInfo() : f(nullptr) {}
|
||||
|
||||
FileMetaData* f;
|
||||
size_t level;
|
||||
size_t index;
|
||||
};
|
||||
|
||||
// Used in universal compaction when trivial move is enabled.
|
||||
// This comparator is used for the construction of min heap
|
||||
// based on the smallest key of the file.
|
||||
struct UserKeyComparator {
|
||||
explicit UserKeyComparator(const Comparator* ucmp) { ucmp_ = ucmp; }
|
||||
|
||||
bool operator()(InputFileInfo i1, InputFileInfo i2) const {
|
||||
return (ucmp_->Compare(i1.f->smallest.user_key(),
|
||||
i2.f->smallest.user_key()) > 0);
|
||||
}
|
||||
|
||||
private:
|
||||
const Comparator* ucmp_;
|
||||
};
|
||||
|
||||
typedef std::priority_queue<InputFileInfo, std::vector<InputFileInfo>,
|
||||
UserKeyComparator> SmallestKeyHeap;
|
||||
|
||||
// This function creates the heap that is used to find if the files are
|
||||
// overlapping during universal compaction when the allow_trivial_move
|
||||
// is set.
|
||||
SmallestKeyHeap create_level_heap(Compaction* c, const Comparator* ucmp) {
|
||||
SmallestKeyHeap smallest_key_priority_q =
|
||||
SmallestKeyHeap(UserKeyComparator(ucmp));
|
||||
|
||||
InputFileInfo input_file;
|
||||
|
||||
for (size_t l = 0; l < c->num_input_levels(); l++) {
|
||||
if (c->num_input_files(l) != 0) {
|
||||
if (l == 0 && c->start_level() == 0) {
|
||||
for (size_t i = 0; i < c->num_input_files(0); i++) {
|
||||
input_file.f = c->input(0, i);
|
||||
input_file.level = 0;
|
||||
input_file.index = i;
|
||||
smallest_key_priority_q.push(std::move(input_file));
|
||||
}
|
||||
} else {
|
||||
input_file.f = c->input(l, 0);
|
||||
input_file.level = l;
|
||||
input_file.index = 0;
|
||||
smallest_key_priority_q.push(std::move(input_file));
|
||||
}
|
||||
}
|
||||
}
|
||||
return smallest_key_priority_q;
|
||||
}
|
||||
#endif // !ROCKSDB_LITE
|
||||
} // anonymous namespace
|
||||
|
||||
// Determine compression type, based on user options, level of the output
|
||||
@@ -342,8 +406,9 @@ bool CompactionPicker::SetupOtherInputs(
|
||||
if (expanded1.size() == output_level_inputs->size() &&
|
||||
!FilesInCompaction(expanded1)) {
|
||||
Log(InfoLogLevel::INFO_LEVEL, ioptions_.info_log,
|
||||
"[%s] Expanding@%d %zu+%zu (%" PRIu64 "+%" PRIu64
|
||||
" bytes) to %zu+%zu (%" PRIu64 "+%" PRIu64 "bytes)\n",
|
||||
"[%s] Expanding@%d %" ROCKSDB_PRIszt "+%" ROCKSDB_PRIszt "(%" PRIu64
|
||||
"+%" PRIu64 " bytes) to %" ROCKSDB_PRIszt "+%" ROCKSDB_PRIszt
|
||||
" (%" PRIu64 "+%" PRIu64 "bytes)\n",
|
||||
cf_name.c_str(), input_level, inputs->size(),
|
||||
output_level_inputs->size(), inputs0_size, inputs1_size,
|
||||
expanded0.size(), expanded1.size(), expanded0_size, inputs1_size);
|
||||
@@ -645,7 +710,12 @@ Status CompactionPicker::SanitizeCompactionInputFilesForAllLevels(
|
||||
aggregated_file_meta.largestkey = largestkey;
|
||||
|
||||
// For all lower levels, include all overlapping files.
|
||||
for (int m = l + 1; m <= output_level; ++m) {
|
||||
// We need to add overlapping files from the current level too because even
|
||||
// if there no input_files in level l, we would still need to add files
|
||||
// which overlap with the range containing the input_files in levels 0 to l
|
||||
// Level 0 doesn't need to be handled this way because files are sorted by
|
||||
// time and not by key
|
||||
for (int m = std::max(l, 1); m <= output_level; ++m) {
|
||||
for (auto& next_lv_file : levels[m].files) {
|
||||
if (HaveOverlappingKeyRanges(
|
||||
comparator, aggregated_file_meta, next_lv_file)) {
|
||||
@@ -1049,7 +1119,7 @@ void UniversalCompactionPicker::SortedRun::DumpSizeInfo(
|
||||
|
||||
std::vector<UniversalCompactionPicker::SortedRun>
|
||||
UniversalCompactionPicker::CalculateSortedRuns(
|
||||
const VersionStorageInfo& vstorage) {
|
||||
const VersionStorageInfo& vstorage, const ImmutableCFOptions& ioptions) {
|
||||
std::vector<UniversalCompactionPicker::SortedRun> ret;
|
||||
for (FileMetaData* f : vstorage.LevelFiles(0)) {
|
||||
ret.emplace_back(0, f, f->fd.GetFileSize(), f->compensated_file_size,
|
||||
@@ -1063,10 +1133,18 @@ UniversalCompactionPicker::CalculateSortedRuns(
|
||||
for (FileMetaData* f : vstorage.LevelFiles(level)) {
|
||||
total_compensated_size += f->compensated_file_size;
|
||||
total_size += f->fd.GetFileSize();
|
||||
// Compaction always includes all files for a non-zero level, so for a
|
||||
// non-zero level, all the files should share the same being_compacted
|
||||
// value.
|
||||
assert(is_first || f->being_compacted == being_compacted);
|
||||
if (ioptions.compaction_options_universal.allow_trivial_move == true) {
|
||||
if (f->being_compacted) {
|
||||
being_compacted = f->being_compacted;
|
||||
}
|
||||
} else {
|
||||
// Compaction always includes all files for a non-zero level, so for a
|
||||
// non-zero level, all the files should share the same being_compacted
|
||||
// value.
|
||||
// This assumption is only valid when
|
||||
// ioptions.compaction_options_universal.allow_trivial_move is false
|
||||
assert(is_first || f->being_compacted == being_compacted);
|
||||
}
|
||||
if (is_first) {
|
||||
being_compacted = f->being_compacted;
|
||||
is_first = false;
|
||||
@@ -1106,6 +1184,50 @@ void GetSmallestLargestSeqno(const std::vector<FileMetaData*>& files,
|
||||
} // namespace
|
||||
#endif
|
||||
|
||||
// Algorithm that checks to see if there are any overlapping
|
||||
// files in the input
|
||||
bool CompactionPicker::IsInputNonOverlapping(Compaction* c) {
|
||||
auto comparator = icmp_->user_comparator();
|
||||
int first_iter = 1;
|
||||
|
||||
InputFileInfo prev, curr, next;
|
||||
|
||||
SmallestKeyHeap smallest_key_priority_q =
|
||||
create_level_heap(c, icmp_->user_comparator());
|
||||
|
||||
while (!smallest_key_priority_q.empty()) {
|
||||
curr = smallest_key_priority_q.top();
|
||||
smallest_key_priority_q.pop();
|
||||
|
||||
if (first_iter) {
|
||||
prev = curr;
|
||||
first_iter = 0;
|
||||
} else {
|
||||
if (comparator->Compare(prev.f->largest.user_key(),
|
||||
curr.f->smallest.user_key()) >= 0) {
|
||||
// found overlapping files, return false
|
||||
return false;
|
||||
}
|
||||
assert(comparator->Compare(curr.f->largest.user_key(),
|
||||
prev.f->largest.user_key()) > 0);
|
||||
prev = curr;
|
||||
}
|
||||
|
||||
next.f = nullptr;
|
||||
|
||||
if (curr.level != 0 && curr.index < c->num_input_files(curr.level) - 1) {
|
||||
next.f = c->input(curr.level, curr.index + 1);
|
||||
next.level = curr.level;
|
||||
next.index = curr.index + 1;
|
||||
}
|
||||
|
||||
if (next.f) {
|
||||
smallest_key_priority_q.push(std::move(next));
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Universal style of compaction. Pick files that are contiguous in
|
||||
// time-range to compact.
|
||||
//
|
||||
@@ -1114,7 +1236,8 @@ Compaction* UniversalCompactionPicker::PickCompaction(
|
||||
VersionStorageInfo* vstorage, LogBuffer* log_buffer) {
|
||||
const int kLevel0 = 0;
|
||||
double score = vstorage->CompactionScore(kLevel0);
|
||||
std::vector<SortedRun> sorted_runs = CalculateSortedRuns(*vstorage);
|
||||
std::vector<SortedRun> sorted_runs =
|
||||
CalculateSortedRuns(*vstorage, ioptions_);
|
||||
|
||||
if (sorted_runs.size() <
|
||||
(unsigned int)mutable_cf_options.level0_file_num_compaction_trigger) {
|
||||
@@ -1122,7 +1245,8 @@ Compaction* UniversalCompactionPicker::PickCompaction(
|
||||
return nullptr;
|
||||
}
|
||||
VersionStorageInfo::LevelSummaryStorage tmp;
|
||||
LogToBuffer(log_buffer, 3072, "[%s] Universal: sorted runs files(%zu): %s\n",
|
||||
LogToBuffer(log_buffer, 3072,
|
||||
"[%s] Universal: sorted runs files(%" ROCKSDB_PRIszt "): %s\n",
|
||||
cf_name.c_str(), sorted_runs.size(),
|
||||
vstorage->LevelSummary(&tmp));
|
||||
|
||||
@@ -1168,6 +1292,10 @@ Compaction* UniversalCompactionPicker::PickCompaction(
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (ioptions_.compaction_options_universal.allow_trivial_move == true) {
|
||||
c->set_is_trivial_move(IsInputNonOverlapping(c));
|
||||
}
|
||||
|
||||
// validate that all the chosen files of L0 are non overlapping in time
|
||||
#ifndef NDEBUG
|
||||
SequenceNumber prev_smallest_seqno = 0U;
|
||||
|
||||
@@ -105,6 +105,12 @@ class CompactionPicker {
|
||||
const VersionStorageInfo* vstorage,
|
||||
const CompactionOptions& compact_options) const;
|
||||
|
||||
// Used in universal compaction when the enabled_trivial_move
|
||||
// option is set. Checks whether there are any overlapping files
|
||||
// in the input. Returns true if the input files are non
|
||||
// overlapping.
|
||||
bool IsInputNonOverlapping(Compaction* c);
|
||||
|
||||
protected:
|
||||
int NumberLevels() const { return ioptions_.num_levels; }
|
||||
|
||||
@@ -269,7 +275,7 @@ class UniversalCompactionPicker : public CompactionPicker {
|
||||
const std::vector<SortedRun>& sorted_runs, LogBuffer* log_buffer);
|
||||
|
||||
static std::vector<SortedRun> CalculateSortedRuns(
|
||||
const VersionStorageInfo& vstorage);
|
||||
const VersionStorageInfo& vstorage, const ImmutableCFOptions& ioptions);
|
||||
|
||||
// Pick a path ID to place a newly generated file, with its estimated file
|
||||
// size.
|
||||
|
||||
@@ -77,6 +77,8 @@ class CompactionPickerTest : public testing::Test {
|
||||
f->fd = FileDescriptor(file_number, path_id, file_size);
|
||||
f->smallest = InternalKey(smallest, smallest_seq, kTypeValue);
|
||||
f->largest = InternalKey(largest, largest_seq, kTypeValue);
|
||||
f->smallest_seqno = smallest_seq;
|
||||
f->largest_seqno = largest_seq;
|
||||
f->compensated_file_size = file_size;
|
||||
f->refs = 0;
|
||||
vstorage_->AddFile(level, f);
|
||||
@@ -344,6 +346,8 @@ TEST_F(CompactionPickerTest, LevelTriggerDynamic4) {
|
||||
ASSERT_EQ(num_levels - 1, compaction->output_level());
|
||||
}
|
||||
|
||||
// Universal and FIFO Compactions are not supported in ROCKSDB_LITE
|
||||
#ifndef ROCKSDB_LITE
|
||||
TEST_F(CompactionPickerTest, NeedsCompactionUniversal) {
|
||||
NewVersionStorage(1, kCompactionStyleUniversal);
|
||||
UniversalCompactionPicker universal_compaction_picker(
|
||||
@@ -365,6 +369,64 @@ TEST_F(CompactionPickerTest, NeedsCompactionUniversal) {
|
||||
vstorage_->CompactionScore(0) >= 1);
|
||||
}
|
||||
}
|
||||
// Tests if the files can be trivially moved in multi level
|
||||
// universal compaction when allow_trivial_move option is set
|
||||
// In this test as the input files overlaps, they cannot
|
||||
// be trivially moved.
|
||||
|
||||
TEST_F(CompactionPickerTest, CannotTrivialMoveUniversal) {
|
||||
const uint64_t kFileSize = 100000;
|
||||
|
||||
ioptions_.compaction_options_universal.allow_trivial_move = true;
|
||||
NewVersionStorage(1, kCompactionStyleUniversal);
|
||||
UniversalCompactionPicker universal_compaction_picker(ioptions_, &icmp_);
|
||||
// must return false when there's no files.
|
||||
ASSERT_EQ(universal_compaction_picker.NeedsCompaction(vstorage_.get()),
|
||||
false);
|
||||
|
||||
NewVersionStorage(3, kCompactionStyleUniversal);
|
||||
|
||||
Add(0, 1U, "150", "200", kFileSize, 0, 500, 550);
|
||||
Add(0, 2U, "201", "250", kFileSize, 0, 401, 450);
|
||||
Add(0, 4U, "260", "300", kFileSize, 0, 260, 300);
|
||||
Add(1, 5U, "100", "151", kFileSize, 0, 200, 251);
|
||||
Add(1, 3U, "301", "350", kFileSize, 0, 101, 150);
|
||||
Add(2, 6U, "120", "200", kFileSize, 0, 20, 100);
|
||||
|
||||
UpdateVersionStorageInfo();
|
||||
|
||||
std::unique_ptr<Compaction> compaction(
|
||||
universal_compaction_picker.PickCompaction(
|
||||
cf_name_, mutable_cf_options_, vstorage_.get(), &log_buffer_));
|
||||
|
||||
ASSERT_TRUE(!compaction->is_trivial_move());
|
||||
}
|
||||
// Tests if the files can be trivially moved in multi level
|
||||
// universal compaction when allow_trivial_move option is set
|
||||
// In this test as the input files doesn't overlaps, they should
|
||||
// be trivially moved.
|
||||
TEST_F(CompactionPickerTest, AllowsTrivialMoveUniversal) {
|
||||
const uint64_t kFileSize = 100000;
|
||||
|
||||
ioptions_.compaction_options_universal.allow_trivial_move = true;
|
||||
UniversalCompactionPicker universal_compaction_picker(ioptions_, &icmp_);
|
||||
|
||||
NewVersionStorage(3, kCompactionStyleUniversal);
|
||||
|
||||
Add(0, 1U, "150", "200", kFileSize, 0, 500, 550);
|
||||
Add(0, 2U, "201", "250", kFileSize, 0, 401, 450);
|
||||
Add(0, 4U, "260", "300", kFileSize, 0, 260, 300);
|
||||
Add(1, 5U, "010", "080", kFileSize, 0, 200, 251);
|
||||
Add(2, 3U, "301", "350", kFileSize, 0, 101, 150);
|
||||
|
||||
UpdateVersionStorageInfo();
|
||||
|
||||
std::unique_ptr<Compaction> compaction(
|
||||
universal_compaction_picker.PickCompaction(
|
||||
cf_name_, mutable_cf_options_, vstorage_.get(), &log_buffer_));
|
||||
|
||||
ASSERT_TRUE(compaction->is_trivial_move());
|
||||
}
|
||||
|
||||
TEST_F(CompactionPickerTest, NeedsCompactionFIFO) {
|
||||
NewVersionStorage(1, kCompactionStyleFIFO);
|
||||
@@ -395,6 +457,7 @@ TEST_F(CompactionPickerTest, NeedsCompactionFIFO) {
|
||||
vstorage_->CompactionScore(0) >= 1);
|
||||
}
|
||||
}
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
// This test exhibits the bug where we don't properly reset parent_index in
|
||||
// PickCompaction()
|
||||
@@ -419,6 +482,76 @@ TEST_F(CompactionPickerTest, ParentIndexResetBug) {
|
||||
cf_name_, mutable_cf_options_, vstorage_.get(), &log_buffer_));
|
||||
}
|
||||
|
||||
// This test checks ExpandWhileOverlapping() by having overlapping user keys
|
||||
// ranges (with different sequence numbers) in the input files.
|
||||
TEST_F(CompactionPickerTest, OverlappingUserKeys) {
|
||||
NewVersionStorage(6, kCompactionStyleLevel);
|
||||
Add(1, 1U, "100", "150", 1U);
|
||||
// Overlapping user keys
|
||||
Add(1, 2U, "200", "400", 1U);
|
||||
Add(1, 3U, "400", "500", 1000000000U, 0, 0);
|
||||
Add(2, 4U, "600", "700", 1U);
|
||||
UpdateVersionStorageInfo();
|
||||
|
||||
std::unique_ptr<Compaction> compaction(level_compaction_picker.PickCompaction(
|
||||
cf_name_, mutable_cf_options_, vstorage_.get(), &log_buffer_));
|
||||
ASSERT_TRUE(compaction.get() != nullptr);
|
||||
ASSERT_EQ(1U, compaction->num_input_levels());
|
||||
ASSERT_EQ(2U, compaction->num_input_files(0));
|
||||
ASSERT_EQ(2U, compaction->input(0, 0)->fd.GetNumber());
|
||||
ASSERT_EQ(3U, compaction->input(0, 1)->fd.GetNumber());
|
||||
}
|
||||
|
||||
TEST_F(CompactionPickerTest, OverlappingUserKeys2) {
|
||||
NewVersionStorage(6, kCompactionStyleLevel);
|
||||
// Overlapping user keys on same level and output level
|
||||
Add(1, 1U, "200", "400", 1000000000U);
|
||||
Add(1, 2U, "400", "500", 1U, 0, 0);
|
||||
Add(2, 3U, "400", "600", 1U);
|
||||
// The following file is not in the compaction despite overlapping user keys
|
||||
Add(2, 4U, "600", "700", 1U, 0, 0);
|
||||
UpdateVersionStorageInfo();
|
||||
|
||||
std::unique_ptr<Compaction> compaction(level_compaction_picker.PickCompaction(
|
||||
cf_name_, mutable_cf_options_, vstorage_.get(), &log_buffer_));
|
||||
ASSERT_TRUE(compaction.get() != nullptr);
|
||||
ASSERT_EQ(2U, compaction->num_input_levels());
|
||||
ASSERT_EQ(2U, compaction->num_input_files(0));
|
||||
ASSERT_EQ(1U, compaction->num_input_files(1));
|
||||
ASSERT_EQ(1U, compaction->input(0, 0)->fd.GetNumber());
|
||||
ASSERT_EQ(2U, compaction->input(0, 1)->fd.GetNumber());
|
||||
ASSERT_EQ(3U, compaction->input(1, 0)->fd.GetNumber());
|
||||
}
|
||||
|
||||
TEST_F(CompactionPickerTest, OverlappingUserKeys3) {
|
||||
NewVersionStorage(6, kCompactionStyleLevel);
|
||||
// Chain of overlapping user key ranges (forces ExpandWhileOverlapping() to
|
||||
// expand multiple times)
|
||||
Add(1, 1U, "100", "150", 1U);
|
||||
Add(1, 2U, "150", "200", 1U, 0, 0);
|
||||
Add(1, 3U, "200", "250", 1000000000U, 0, 0);
|
||||
Add(1, 4U, "250", "300", 1U, 0, 0);
|
||||
Add(1, 5U, "300", "350", 1U, 0, 0);
|
||||
// Output level overlaps with the beginning and the end of the chain
|
||||
Add(2, 6U, "050", "100", 1U);
|
||||
Add(2, 7U, "350", "400", 1U);
|
||||
UpdateVersionStorageInfo();
|
||||
|
||||
std::unique_ptr<Compaction> compaction(level_compaction_picker.PickCompaction(
|
||||
cf_name_, mutable_cf_options_, vstorage_.get(), &log_buffer_));
|
||||
ASSERT_TRUE(compaction.get() != nullptr);
|
||||
ASSERT_EQ(2U, compaction->num_input_levels());
|
||||
ASSERT_EQ(5U, compaction->num_input_files(0));
|
||||
ASSERT_EQ(2U, compaction->num_input_files(1));
|
||||
ASSERT_EQ(1U, compaction->input(0, 0)->fd.GetNumber());
|
||||
ASSERT_EQ(2U, compaction->input(0, 1)->fd.GetNumber());
|
||||
ASSERT_EQ(3U, compaction->input(0, 2)->fd.GetNumber());
|
||||
ASSERT_EQ(4U, compaction->input(0, 3)->fd.GetNumber());
|
||||
ASSERT_EQ(5U, compaction->input(0, 4)->fd.GetNumber());
|
||||
ASSERT_EQ(6U, compaction->input(1, 0)->fd.GetNumber());
|
||||
ASSERT_EQ(7U, compaction->input(1, 1)->fd.GetNumber());
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
|
||||
#include "rocksdb/utilities/convenience.h"
|
||||
#include "rocksdb/convenience.h"
|
||||
|
||||
#include "db/db_impl.h"
|
||||
|
||||
+23
-6
@@ -57,6 +57,11 @@ class CorruptionTest : public testing::Test {
|
||||
DestroyDB(dbname_, Options());
|
||||
}
|
||||
|
||||
void CloseDb() {
|
||||
delete db_;
|
||||
db_ = nullptr;
|
||||
}
|
||||
|
||||
Status TryReopen(Options* options = nullptr) {
|
||||
delete db_;
|
||||
db_ = nullptr;
|
||||
@@ -229,6 +234,16 @@ class CorruptionTest : public testing::Test {
|
||||
TEST_F(CorruptionTest, Recovery) {
|
||||
Build(100);
|
||||
Check(100, 100);
|
||||
#ifdef OS_WIN
|
||||
// On Wndows OS Disk cache does not behave properly
|
||||
// We do not call FlushBuffers on every Flush. If we do not close
|
||||
// the log file prior to the corruption we end up with the first
|
||||
// block not corrupted but only the second. However, under the debugger
|
||||
// things work just fine but never pass when running normally
|
||||
// For that reason people may want to run with unbuffered I/O. That option
|
||||
// is not available for WAL though.
|
||||
CloseDb();
|
||||
#endif
|
||||
Corrupt(kLogFile, 19, 1); // WriteBatch tag for first record
|
||||
Corrupt(kLogFile, log::kBlockSize + 1000, 1); // Somewhere in second block
|
||||
ASSERT_TRUE(!TryReopen().ok());
|
||||
@@ -336,13 +351,13 @@ TEST_F(CorruptionTest, CorruptedDescriptor) {
|
||||
|
||||
TEST_F(CorruptionTest, CompactionInputError) {
|
||||
Options options;
|
||||
options.max_background_flushes = 0;
|
||||
Reopen(&options);
|
||||
Build(10);
|
||||
DBImpl* dbi = reinterpret_cast<DBImpl*>(db_);
|
||||
dbi->TEST_FlushMemTable();
|
||||
const int last = dbi->MaxMemCompactionLevel();
|
||||
ASSERT_EQ(1, Property("rocksdb.num-files-at-level" + NumberToString(last)));
|
||||
dbi->TEST_CompactRange(0, nullptr, nullptr);
|
||||
dbi->TEST_CompactRange(1, nullptr, nullptr);
|
||||
ASSERT_EQ(1, Property("rocksdb.num-files-at-level2"));
|
||||
|
||||
Corrupt(kTableFile, 100, 1);
|
||||
Check(9, 9);
|
||||
@@ -357,18 +372,20 @@ TEST_F(CorruptionTest, CompactionInputErrorParanoid) {
|
||||
options.paranoid_checks = true;
|
||||
options.write_buffer_size = 131072;
|
||||
options.max_write_buffer_number = 2;
|
||||
options.max_background_flushes = 0;
|
||||
Reopen(&options);
|
||||
DBImpl* dbi = reinterpret_cast<DBImpl*>(db_);
|
||||
|
||||
// Fill levels >= 1 so memtable flush outputs to level 0
|
||||
// Fill levels >= 1
|
||||
for (int level = 1; level < dbi->NumberLevels(); level++) {
|
||||
dbi->Put(WriteOptions(), "", "begin");
|
||||
dbi->Put(WriteOptions(), "~", "end");
|
||||
dbi->TEST_FlushMemTable();
|
||||
for (int comp_level = 0; comp_level < dbi->NumberLevels() - level;
|
||||
++comp_level) {
|
||||
dbi->TEST_CompactRange(comp_level, nullptr, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
options.max_mem_compaction_level = 0;
|
||||
Reopen(&options);
|
||||
|
||||
dbi = reinterpret_cast<DBImpl*>(db_);
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
// 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 "db/db_impl.h"
|
||||
#include "rocksdb/db.h"
|
||||
#include "rocksdb/env.h"
|
||||
@@ -39,7 +41,6 @@ class CuckooTableDBTest : public testing::Test {
|
||||
options.memtable_factory.reset(NewHashLinkListRepFactory(4, 0, 3, true));
|
||||
options.allow_mmap_reads = true;
|
||||
options.create_if_missing = true;
|
||||
options.max_mem_compaction_level = 0;
|
||||
return options;
|
||||
}
|
||||
|
||||
@@ -320,3 +321,13 @@ int main(int argc, char** argv) {
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
|
||||
#else
|
||||
#include <stdio.h>
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
fprintf(stderr, "SKIPPED as Cuckoo table is not supported in ROCKSDB_LITE\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif // ROCKSDB_LITE
|
||||
+20
-6
@@ -24,7 +24,9 @@ int main() {
|
||||
#include <numaif.h>
|
||||
#endif
|
||||
|
||||
#ifndef OS_WIN
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
#include <fcntl.h>
|
||||
#include <inttypes.h>
|
||||
#include <cstddef>
|
||||
@@ -68,6 +70,10 @@ int main() {
|
||||
#include "hdfs/env_hdfs.h"
|
||||
#include "utilities/merge_operators.h"
|
||||
|
||||
#ifdef OS_WIN
|
||||
#include <io.h> // open/close
|
||||
#endif
|
||||
|
||||
using GFLAGS::ParseCommandLineFlags;
|
||||
using GFLAGS::RegisterFlagValidator;
|
||||
using GFLAGS::SetUsageMessage;
|
||||
@@ -124,8 +130,6 @@ DEFINE_string(benchmarks,
|
||||
"\treadreverse -- read N times in reverse order\n"
|
||||
"\treadrandom -- read N times in random order\n"
|
||||
"\treadmissing -- read N missing keys in random order\n"
|
||||
"\treadhot -- read N times in random order from 1% section "
|
||||
"of DB\n"
|
||||
"\treadwhilewriting -- 1 writer, N threads doing random "
|
||||
"reads\n"
|
||||
"\treadwhilemerging -- 1 merger, N threads doing random "
|
||||
@@ -226,7 +230,7 @@ DEFINE_double(compression_ratio, 0.5, "Arrange to generate values that shrink"
|
||||
|
||||
DEFINE_double(read_random_exp_range, 0.0,
|
||||
"Read random's key will be generated using distribution of "
|
||||
"num * exp(r) where r is uniform number from 0 to this value. "
|
||||
"num * exp(-r) where r is uniform number from 0 to this value. "
|
||||
"The larger the number is, the more skewed the reads are. "
|
||||
"Only used in readrandom and multireadrandom benchmarks.");
|
||||
|
||||
@@ -307,9 +311,15 @@ DEFINE_int32(universal_compression_size_percent, -1,
|
||||
"The percentage of the database to compress for universal "
|
||||
"compaction. -1 means compress everything.");
|
||||
|
||||
DEFINE_bool(universal_allow_trivial_move, false,
|
||||
"Sllow trivial move in universal compaction.");
|
||||
|
||||
DEFINE_int64(cache_size, -1, "Number of bytes to use as a cache of uncompressed"
|
||||
"data. Negative means use default settings.");
|
||||
|
||||
DEFINE_bool(cache_index_and_filter_blocks, false,
|
||||
"Cache index/filter blocks in block cache.");
|
||||
|
||||
DEFINE_int32(block_size,
|
||||
static_cast<int32_t>(rocksdb::BlockBasedTableOptions().block_size),
|
||||
"Number of bytes in a block.");
|
||||
@@ -547,7 +557,7 @@ DEFINE_int32(thread_status_per_interval, 0,
|
||||
DEFINE_int32(perf_level, 0, "Level of perf collection");
|
||||
|
||||
static bool ValidateRateLimit(const char* flagname, double value) {
|
||||
static constexpr double EPSILON = 1e-10;
|
||||
const double EPSILON = 1e-10;
|
||||
if ( value < -EPSILON ) {
|
||||
fprintf(stderr, "Invalid value for --%s: %12.6f, must be >= 0.0\n",
|
||||
flagname, value);
|
||||
@@ -2371,6 +2381,8 @@ class Benchmark {
|
||||
if (cache_ == nullptr) {
|
||||
block_based_options.no_block_cache = true;
|
||||
}
|
||||
block_based_options.cache_index_and_filter_blocks =
|
||||
FLAGS_cache_index_and_filter_blocks;
|
||||
block_based_options.block_cache = cache_;
|
||||
block_based_options.block_cache_compressed = compressed_cache_;
|
||||
block_based_options.block_size = FLAGS_block_size;
|
||||
@@ -2464,6 +2476,8 @@ class Benchmark {
|
||||
options.compaction_options_universal.compression_size_percent =
|
||||
FLAGS_universal_compression_size_percent;
|
||||
}
|
||||
options.compaction_options_universal.allow_trivial_move =
|
||||
FLAGS_universal_allow_trivial_move;
|
||||
if (FLAGS_thread_status_per_interval > 0) {
|
||||
options.enable_thread_tracking = true;
|
||||
}
|
||||
@@ -3533,8 +3547,8 @@ class Benchmark {
|
||||
|
||||
char msg[100];
|
||||
snprintf(msg, sizeof(msg),
|
||||
"(reads:%" PRIu64 " merges:%" PRIu64 " total:%" PRIu64 " hits:%" \
|
||||
PRIu64 " maxlength:%zu)",
|
||||
"(reads:%" PRIu64 " merges:%" PRIu64 " total:%" PRIu64
|
||||
" hits:%" PRIu64 " maxlength:%" ROCKSDB_PRIszt ")",
|
||||
num_gets, num_merges, readwrites_, num_hits, max_length);
|
||||
thread->stats.AddMessage(msg);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,548 @@
|
||||
// Copyright (c) 2013, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the root directory of this source tree. An additional grant
|
||||
// of patent rights can be found in the PATENTS file in the same directory.
|
||||
//
|
||||
// Copyright (c) 2011 The LevelDB Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
|
||||
#include "port/stack_trace.h"
|
||||
#include "util/db_test_util.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
static int cfilter_count = 0;
|
||||
|
||||
// This is a static filter used for filtering
|
||||
// kvs during the compaction process.
|
||||
static std::string NEW_VALUE = "NewValue";
|
||||
|
||||
class DBTestCompactionFilter : public DBTestBase {
|
||||
public:
|
||||
DBTestCompactionFilter() : DBTestBase("/db_compaction_filter_test") {}
|
||||
};
|
||||
|
||||
class KeepFilter : public CompactionFilter {
|
||||
public:
|
||||
virtual bool Filter(int level, const Slice& key, const Slice& value,
|
||||
std::string* new_value, bool* value_changed) const
|
||||
override {
|
||||
cfilter_count++;
|
||||
return false;
|
||||
}
|
||||
|
||||
virtual const char* Name() const override { return "KeepFilter"; }
|
||||
};
|
||||
|
||||
class DeleteFilter : public CompactionFilter {
|
||||
public:
|
||||
virtual bool Filter(int level, const Slice& key, const Slice& value,
|
||||
std::string* new_value, bool* value_changed) const
|
||||
override {
|
||||
cfilter_count++;
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual const char* Name() const override { return "DeleteFilter"; }
|
||||
};
|
||||
|
||||
class DelayFilter : public CompactionFilter {
|
||||
public:
|
||||
explicit DelayFilter(DBTestBase* d) : db_test(d) {}
|
||||
virtual bool Filter(int level, const Slice& key, const Slice& value,
|
||||
std::string* new_value,
|
||||
bool* value_changed) const override {
|
||||
db_test->env_->addon_time_.fetch_add(1000);
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual const char* Name() const override { return "DelayFilter"; }
|
||||
|
||||
private:
|
||||
DBTestBase* db_test;
|
||||
};
|
||||
|
||||
class ConditionalFilter : public CompactionFilter {
|
||||
public:
|
||||
explicit ConditionalFilter(const std::string* filtered_value)
|
||||
: filtered_value_(filtered_value) {}
|
||||
virtual bool Filter(int level, const Slice& key, const Slice& value,
|
||||
std::string* new_value,
|
||||
bool* value_changed) const override {
|
||||
return value.ToString() == *filtered_value_;
|
||||
}
|
||||
|
||||
virtual const char* Name() const override { return "ConditionalFilter"; }
|
||||
|
||||
private:
|
||||
const std::string* filtered_value_;
|
||||
};
|
||||
|
||||
class ChangeFilter : public CompactionFilter {
|
||||
public:
|
||||
explicit ChangeFilter() {}
|
||||
|
||||
virtual bool Filter(int level, const Slice& key, const Slice& value,
|
||||
std::string* new_value, bool* value_changed) const
|
||||
override {
|
||||
assert(new_value != nullptr);
|
||||
*new_value = NEW_VALUE;
|
||||
*value_changed = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
virtual const char* Name() const override { return "ChangeFilter"; }
|
||||
};
|
||||
|
||||
class KeepFilterFactory : public CompactionFilterFactory {
|
||||
public:
|
||||
explicit KeepFilterFactory(bool check_context = false)
|
||||
: check_context_(check_context) {}
|
||||
|
||||
virtual std::unique_ptr<CompactionFilter> CreateCompactionFilter(
|
||||
const CompactionFilter::Context& context) override {
|
||||
if (check_context_) {
|
||||
EXPECT_EQ(expect_full_compaction_.load(), context.is_full_compaction);
|
||||
EXPECT_EQ(expect_manual_compaction_.load(), context.is_manual_compaction);
|
||||
}
|
||||
return std::unique_ptr<CompactionFilter>(new KeepFilter());
|
||||
}
|
||||
|
||||
virtual const char* Name() const override { return "KeepFilterFactory"; }
|
||||
bool check_context_;
|
||||
std::atomic_bool expect_full_compaction_;
|
||||
std::atomic_bool expect_manual_compaction_;
|
||||
};
|
||||
|
||||
class DeleteFilterFactory : public CompactionFilterFactory {
|
||||
public:
|
||||
virtual std::unique_ptr<CompactionFilter> CreateCompactionFilter(
|
||||
const CompactionFilter::Context& context) override {
|
||||
if (context.is_manual_compaction) {
|
||||
return std::unique_ptr<CompactionFilter>(new DeleteFilter());
|
||||
} else {
|
||||
return std::unique_ptr<CompactionFilter>(nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
virtual const char* Name() const override { return "DeleteFilterFactory"; }
|
||||
};
|
||||
|
||||
class DelayFilterFactory : public CompactionFilterFactory {
|
||||
public:
|
||||
explicit DelayFilterFactory(DBTestBase* d) : db_test(d) {}
|
||||
virtual std::unique_ptr<CompactionFilter> CreateCompactionFilter(
|
||||
const CompactionFilter::Context& context) override {
|
||||
return std::unique_ptr<CompactionFilter>(new DelayFilter(db_test));
|
||||
}
|
||||
|
||||
virtual const char* Name() const override { return "DelayFilterFactory"; }
|
||||
|
||||
private:
|
||||
DBTestBase* db_test;
|
||||
};
|
||||
|
||||
class ConditionalFilterFactory : public CompactionFilterFactory {
|
||||
public:
|
||||
explicit ConditionalFilterFactory(const Slice& filtered_value)
|
||||
: filtered_value_(filtered_value.ToString()) {}
|
||||
|
||||
virtual std::unique_ptr<CompactionFilter> CreateCompactionFilter(
|
||||
const CompactionFilter::Context& context) override {
|
||||
return std::unique_ptr<CompactionFilter>(
|
||||
new ConditionalFilter(&filtered_value_));
|
||||
}
|
||||
|
||||
virtual const char* Name() const override {
|
||||
return "ConditionalFilterFactory";
|
||||
}
|
||||
|
||||
private:
|
||||
std::string filtered_value_;
|
||||
};
|
||||
|
||||
class ChangeFilterFactory : public CompactionFilterFactory {
|
||||
public:
|
||||
explicit ChangeFilterFactory() {}
|
||||
|
||||
virtual std::unique_ptr<CompactionFilter> CreateCompactionFilter(
|
||||
const CompactionFilter::Context& context) override {
|
||||
return std::unique_ptr<CompactionFilter>(new ChangeFilter());
|
||||
}
|
||||
|
||||
virtual const char* Name() const override { return "ChangeFilterFactory"; }
|
||||
};
|
||||
|
||||
TEST_F(DBTestCompactionFilter, CompactionFilter) {
|
||||
Options options = CurrentOptions();
|
||||
options.max_open_files = -1;
|
||||
options.num_levels = 3;
|
||||
options.compaction_filter_factory = std::make_shared<KeepFilterFactory>();
|
||||
options = CurrentOptions(options);
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
|
||||
// Write 100K keys, these are written to a few files in L0.
|
||||
const std::string value(10, 'x');
|
||||
for (int i = 0; i < 100000; i++) {
|
||||
char key[100];
|
||||
snprintf(key, sizeof(key), "B%010d", i);
|
||||
Put(1, key, value);
|
||||
}
|
||||
ASSERT_OK(Flush(1));
|
||||
|
||||
// Push all files to the highest level L2. Verify that
|
||||
// the compaction is each level invokes the filter for
|
||||
// all the keys in that level.
|
||||
cfilter_count = 0;
|
||||
dbfull()->TEST_CompactRange(0, nullptr, nullptr, handles_[1]);
|
||||
ASSERT_EQ(cfilter_count, 100000);
|
||||
cfilter_count = 0;
|
||||
dbfull()->TEST_CompactRange(1, nullptr, nullptr, handles_[1]);
|
||||
ASSERT_EQ(cfilter_count, 100000);
|
||||
|
||||
ASSERT_EQ(NumTableFilesAtLevel(0, 1), 0);
|
||||
ASSERT_EQ(NumTableFilesAtLevel(1, 1), 0);
|
||||
ASSERT_NE(NumTableFilesAtLevel(2, 1), 0);
|
||||
cfilter_count = 0;
|
||||
|
||||
// All the files are in the lowest level.
|
||||
// Verify that all but the 100001st record
|
||||
// has sequence number zero. The 100001st record
|
||||
// is at the tip of this snapshot and cannot
|
||||
// be zeroed out.
|
||||
int count = 0;
|
||||
int total = 0;
|
||||
Arena arena;
|
||||
{
|
||||
ScopedArenaIterator iter(
|
||||
dbfull()->TEST_NewInternalIterator(&arena, handles_[1]));
|
||||
iter->SeekToFirst();
|
||||
ASSERT_OK(iter->status());
|
||||
while (iter->Valid()) {
|
||||
ParsedInternalKey ikey(Slice(), 0, kTypeValue);
|
||||
ikey.sequence = -1;
|
||||
ASSERT_EQ(ParseInternalKey(iter->key(), &ikey), true);
|
||||
total++;
|
||||
if (ikey.sequence != 0) {
|
||||
count++;
|
||||
}
|
||||
iter->Next();
|
||||
}
|
||||
}
|
||||
ASSERT_EQ(total, 100000);
|
||||
ASSERT_EQ(count, 1);
|
||||
|
||||
// overwrite all the 100K keys once again.
|
||||
for (int i = 0; i < 100000; i++) {
|
||||
char key[100];
|
||||
snprintf(key, sizeof(key), "B%010d", i);
|
||||
ASSERT_OK(Put(1, key, value));
|
||||
}
|
||||
ASSERT_OK(Flush(1));
|
||||
|
||||
// push all files to the highest level L2. This
|
||||
// means that all keys should pass at least once
|
||||
// via the compaction filter
|
||||
cfilter_count = 0;
|
||||
dbfull()->TEST_CompactRange(0, nullptr, nullptr, handles_[1]);
|
||||
ASSERT_EQ(cfilter_count, 100000);
|
||||
cfilter_count = 0;
|
||||
dbfull()->TEST_CompactRange(1, nullptr, nullptr, handles_[1]);
|
||||
ASSERT_EQ(cfilter_count, 100000);
|
||||
ASSERT_EQ(NumTableFilesAtLevel(0, 1), 0);
|
||||
ASSERT_EQ(NumTableFilesAtLevel(1, 1), 0);
|
||||
ASSERT_NE(NumTableFilesAtLevel(2, 1), 0);
|
||||
|
||||
// create a new database with the compaction
|
||||
// filter in such a way that it deletes all keys
|
||||
options.compaction_filter_factory = std::make_shared<DeleteFilterFactory>();
|
||||
options.create_if_missing = true;
|
||||
DestroyAndReopen(options);
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
|
||||
// write all the keys once again.
|
||||
for (int i = 0; i < 100000; i++) {
|
||||
char key[100];
|
||||
snprintf(key, sizeof(key), "B%010d", i);
|
||||
ASSERT_OK(Put(1, key, value));
|
||||
}
|
||||
ASSERT_OK(Flush(1));
|
||||
ASSERT_NE(NumTableFilesAtLevel(0, 1), 0);
|
||||
ASSERT_EQ(NumTableFilesAtLevel(1, 1), 0);
|
||||
ASSERT_EQ(NumTableFilesAtLevel(2, 1), 0);
|
||||
|
||||
// Push all files to the highest level L2. This
|
||||
// triggers the compaction filter to delete all keys,
|
||||
// verify that at the end of the compaction process,
|
||||
// nothing is left.
|
||||
cfilter_count = 0;
|
||||
dbfull()->TEST_CompactRange(0, nullptr, nullptr, handles_[1]);
|
||||
ASSERT_EQ(cfilter_count, 100000);
|
||||
cfilter_count = 0;
|
||||
dbfull()->TEST_CompactRange(1, nullptr, nullptr, handles_[1]);
|
||||
ASSERT_EQ(cfilter_count, 0);
|
||||
ASSERT_EQ(NumTableFilesAtLevel(0, 1), 0);
|
||||
ASSERT_EQ(NumTableFilesAtLevel(1, 1), 0);
|
||||
|
||||
{
|
||||
// Scan the entire database to ensure that nothing is left
|
||||
std::unique_ptr<Iterator> iter(
|
||||
db_->NewIterator(ReadOptions(), handles_[1]));
|
||||
iter->SeekToFirst();
|
||||
count = 0;
|
||||
while (iter->Valid()) {
|
||||
count++;
|
||||
iter->Next();
|
||||
}
|
||||
ASSERT_EQ(count, 0);
|
||||
}
|
||||
|
||||
// The sequence number of the remaining record
|
||||
// is not zeroed out even though it is at the
|
||||
// level Lmax because this record is at the tip
|
||||
count = 0;
|
||||
{
|
||||
ScopedArenaIterator iter(
|
||||
dbfull()->TEST_NewInternalIterator(&arena, handles_[1]));
|
||||
iter->SeekToFirst();
|
||||
ASSERT_OK(iter->status());
|
||||
while (iter->Valid()) {
|
||||
ParsedInternalKey ikey(Slice(), 0, kTypeValue);
|
||||
ASSERT_EQ(ParseInternalKey(iter->key(), &ikey), true);
|
||||
ASSERT_NE(ikey.sequence, (unsigned)0);
|
||||
count++;
|
||||
iter->Next();
|
||||
}
|
||||
ASSERT_EQ(count, 0);
|
||||
}
|
||||
}
|
||||
|
||||
// Tests the edge case where compaction does not produce any output -- all
|
||||
// entries are deleted. The compaction should create bunch of 'DeleteFile'
|
||||
// entries in VersionEdit, but none of the 'AddFile's.
|
||||
TEST_F(DBTestCompactionFilter, CompactionFilterDeletesAll) {
|
||||
Options options;
|
||||
options.compaction_filter_factory = std::make_shared<DeleteFilterFactory>();
|
||||
options.disable_auto_compactions = true;
|
||||
options.create_if_missing = true;
|
||||
options = CurrentOptions(options);
|
||||
DestroyAndReopen(options);
|
||||
|
||||
// put some data
|
||||
for (int table = 0; table < 4; ++table) {
|
||||
for (int i = 0; i < 10 + table; ++i) {
|
||||
Put(ToString(table * 100 + i), "val");
|
||||
}
|
||||
Flush();
|
||||
}
|
||||
|
||||
// this will produce empty file (delete compaction filter)
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
||||
ASSERT_EQ(0U, CountLiveFiles());
|
||||
|
||||
Reopen(options);
|
||||
|
||||
Iterator* itr = db_->NewIterator(ReadOptions());
|
||||
itr->SeekToFirst();
|
||||
// empty db
|
||||
ASSERT_TRUE(!itr->Valid());
|
||||
|
||||
delete itr;
|
||||
}
|
||||
|
||||
TEST_F(DBTestCompactionFilter, CompactionFilterWithValueChange) {
|
||||
do {
|
||||
Options options;
|
||||
options.num_levels = 3;
|
||||
options.compaction_filter_factory =
|
||||
std::make_shared<ChangeFilterFactory>();
|
||||
options = CurrentOptions(options);
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
|
||||
// Write 100K+1 keys, these are written to a few files
|
||||
// in L0. We do this so that the current snapshot points
|
||||
// to the 100001 key.The compaction filter is not invoked
|
||||
// on keys that are visible via a snapshot because we
|
||||
// anyways cannot delete it.
|
||||
const std::string value(10, 'x');
|
||||
for (int i = 0; i < 100001; i++) {
|
||||
char key[100];
|
||||
snprintf(key, sizeof(key), "B%010d", i);
|
||||
Put(1, key, value);
|
||||
}
|
||||
|
||||
// push all files to lower levels
|
||||
ASSERT_OK(Flush(1));
|
||||
if (option_config_ != kUniversalCompactionMultiLevel) {
|
||||
dbfull()->TEST_CompactRange(0, nullptr, nullptr, handles_[1]);
|
||||
dbfull()->TEST_CompactRange(1, nullptr, nullptr, handles_[1]);
|
||||
} else {
|
||||
dbfull()->CompactRange(CompactRangeOptions(), handles_[1], nullptr,
|
||||
nullptr);
|
||||
}
|
||||
|
||||
// re-write all data again
|
||||
for (int i = 0; i < 100001; i++) {
|
||||
char key[100];
|
||||
snprintf(key, sizeof(key), "B%010d", i);
|
||||
Put(1, key, value);
|
||||
}
|
||||
|
||||
// push all files to lower levels. This should
|
||||
// invoke the compaction filter for all 100000 keys.
|
||||
ASSERT_OK(Flush(1));
|
||||
if (option_config_ != kUniversalCompactionMultiLevel) {
|
||||
dbfull()->TEST_CompactRange(0, nullptr, nullptr, handles_[1]);
|
||||
dbfull()->TEST_CompactRange(1, nullptr, nullptr, handles_[1]);
|
||||
} else {
|
||||
dbfull()->CompactRange(CompactRangeOptions(), handles_[1], nullptr,
|
||||
nullptr);
|
||||
}
|
||||
|
||||
// verify that all keys now have the new value that
|
||||
// was set by the compaction process.
|
||||
for (int i = 0; i < 100001; i++) {
|
||||
char key[100];
|
||||
snprintf(key, sizeof(key), "B%010d", i);
|
||||
std::string newvalue = Get(1, key);
|
||||
ASSERT_EQ(newvalue.compare(NEW_VALUE), 0);
|
||||
}
|
||||
} while (ChangeCompactOptions());
|
||||
}
|
||||
|
||||
TEST_F(DBTestCompactionFilter, CompactionFilterWithMergeOperator) {
|
||||
std::string one, two, three, four;
|
||||
PutFixed64(&one, 1);
|
||||
PutFixed64(&two, 2);
|
||||
PutFixed64(&three, 3);
|
||||
PutFixed64(&four, 4);
|
||||
|
||||
Options options;
|
||||
options = CurrentOptions(options);
|
||||
options.create_if_missing = true;
|
||||
options.merge_operator = MergeOperators::CreateUInt64AddOperator();
|
||||
options.num_levels = 3;
|
||||
// Filter out keys with value is 2.
|
||||
options.compaction_filter_factory =
|
||||
std::make_shared<ConditionalFilterFactory>(two);
|
||||
DestroyAndReopen(options);
|
||||
|
||||
// In the same compaction, a value type needs to be deleted based on
|
||||
// compaction filter, and there is a merge type for the key. compaction
|
||||
// filter result is ignored.
|
||||
ASSERT_OK(db_->Put(WriteOptions(), "foo", two));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(db_->Merge(WriteOptions(), "foo", one));
|
||||
ASSERT_OK(Flush());
|
||||
std::string newvalue = Get("foo");
|
||||
ASSERT_EQ(newvalue, three);
|
||||
dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr);
|
||||
newvalue = Get("foo");
|
||||
ASSERT_EQ(newvalue, three);
|
||||
|
||||
// value key can be deleted based on compaction filter, leaving only
|
||||
// merge keys.
|
||||
ASSERT_OK(db_->Put(WriteOptions(), "bar", two));
|
||||
ASSERT_OK(Flush());
|
||||
dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr);
|
||||
newvalue = Get("bar");
|
||||
ASSERT_EQ("NOT_FOUND", newvalue);
|
||||
ASSERT_OK(db_->Merge(WriteOptions(), "bar", two));
|
||||
ASSERT_OK(Flush());
|
||||
dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr);
|
||||
newvalue = Get("bar");
|
||||
ASSERT_EQ(two, two);
|
||||
|
||||
// Compaction filter never applies to merge keys.
|
||||
ASSERT_OK(db_->Put(WriteOptions(), "foobar", one));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(db_->Merge(WriteOptions(), "foobar", two));
|
||||
ASSERT_OK(Flush());
|
||||
newvalue = Get("foobar");
|
||||
ASSERT_EQ(newvalue, three);
|
||||
dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr);
|
||||
newvalue = Get("foobar");
|
||||
ASSERT_EQ(newvalue, three);
|
||||
|
||||
// In the same compaction, both of value type and merge type keys need to be
|
||||
// deleted based on compaction filter, and there is a merge type for the key.
|
||||
// For both keys, compaction filter results are ignored.
|
||||
ASSERT_OK(db_->Put(WriteOptions(), "barfoo", two));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(db_->Merge(WriteOptions(), "barfoo", two));
|
||||
ASSERT_OK(Flush());
|
||||
newvalue = Get("barfoo");
|
||||
ASSERT_EQ(newvalue, four);
|
||||
dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr);
|
||||
newvalue = Get("barfoo");
|
||||
ASSERT_EQ(newvalue, four);
|
||||
}
|
||||
|
||||
TEST_F(DBTestCompactionFilter, CompactionFilterContextManual) {
|
||||
KeepFilterFactory* filter = new KeepFilterFactory();
|
||||
|
||||
Options options = CurrentOptions();
|
||||
options.compaction_style = kCompactionStyleUniversal;
|
||||
options.compaction_filter_factory.reset(filter);
|
||||
options.compression = kNoCompression;
|
||||
options.level0_file_num_compaction_trigger = 8;
|
||||
Reopen(options);
|
||||
int num_keys_per_file = 400;
|
||||
for (int j = 0; j < 3; j++) {
|
||||
// Write several keys.
|
||||
const std::string value(10, 'x');
|
||||
for (int i = 0; i < num_keys_per_file; i++) {
|
||||
char key[100];
|
||||
snprintf(key, sizeof(key), "B%08d%02d", i, j);
|
||||
Put(key, value);
|
||||
}
|
||||
dbfull()->TEST_FlushMemTable();
|
||||
// Make sure next file is much smaller so automatic compaction will not
|
||||
// be triggered.
|
||||
num_keys_per_file /= 2;
|
||||
}
|
||||
|
||||
// Force a manual compaction
|
||||
cfilter_count = 0;
|
||||
filter->expect_manual_compaction_.store(true);
|
||||
filter->expect_full_compaction_.store(false); // Manual compaction always
|
||||
// set this flag.
|
||||
dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr);
|
||||
ASSERT_EQ(cfilter_count, 700);
|
||||
ASSERT_EQ(NumSortedRuns(0), 1);
|
||||
|
||||
// Verify total number of keys is correct after manual compaction.
|
||||
{
|
||||
int count = 0;
|
||||
int total = 0;
|
||||
Arena arena;
|
||||
ScopedArenaIterator iter(dbfull()->TEST_NewInternalIterator(&arena));
|
||||
iter->SeekToFirst();
|
||||
ASSERT_OK(iter->status());
|
||||
while (iter->Valid()) {
|
||||
ParsedInternalKey ikey(Slice(), 0, kTypeValue);
|
||||
ikey.sequence = -1;
|
||||
ASSERT_EQ(ParseInternalKey(iter->key(), &ikey), true);
|
||||
total++;
|
||||
if (ikey.sequence != 0) {
|
||||
count++;
|
||||
}
|
||||
iter->Next();
|
||||
}
|
||||
ASSERT_EQ(total, 700);
|
||||
ASSERT_EQ(count, 1);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
#if !(defined NDEBUG) || !defined(OS_WIN)
|
||||
rocksdb::port::InstallStackTraceHandler();
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
#else
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,497 @@
|
||||
// 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.
|
||||
|
||||
// Introduction of SyncPoint effectively disabled building and running this test
|
||||
// in Release build.
|
||||
// which is a pity, it is a good test
|
||||
#if !(defined NDEBUG) || !defined(OS_WIN)
|
||||
|
||||
#include "port/stack_trace.h"
|
||||
#include "util/db_test_util.h"
|
||||
|
||||
namespace rocksdb {
|
||||
class DBTestDynamicLevel : public DBTestBase {
|
||||
public:
|
||||
DBTestDynamicLevel() : DBTestBase("/db_dynamic_level_test") {}
|
||||
};
|
||||
|
||||
TEST_F(DBTestDynamicLevel, DynamicLevelMaxBytesBase) {
|
||||
if (!Snappy_Supported() || !LZ4_Supported()) {
|
||||
return;
|
||||
}
|
||||
// Use InMemoryEnv, or it would be too slow.
|
||||
unique_ptr<Env> env(new MockEnv(env_));
|
||||
|
||||
const int kNKeys = 1000;
|
||||
int keys[kNKeys];
|
||||
|
||||
auto verify_func = [&]() {
|
||||
for (int i = 0; i < kNKeys; i++) {
|
||||
ASSERT_NE("NOT_FOUND", Get(Key(i)));
|
||||
ASSERT_NE("NOT_FOUND", Get(Key(kNKeys * 2 + i)));
|
||||
if (i < kNKeys / 10) {
|
||||
ASSERT_EQ("NOT_FOUND", Get(Key(kNKeys + keys[i])));
|
||||
} else {
|
||||
ASSERT_NE("NOT_FOUND", Get(Key(kNKeys + keys[i])));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Random rnd(301);
|
||||
for (int ordered_insert = 0; ordered_insert <= 1; ordered_insert++) {
|
||||
for (int i = 0; i < kNKeys; i++) {
|
||||
keys[i] = i;
|
||||
}
|
||||
if (ordered_insert == 0) {
|
||||
std::random_shuffle(std::begin(keys), std::end(keys));
|
||||
}
|
||||
for (int max_background_compactions = 1; max_background_compactions < 4;
|
||||
max_background_compactions += 2) {
|
||||
Options options;
|
||||
options.env = env.get();
|
||||
options.create_if_missing = true;
|
||||
options.db_write_buffer_size = 2048;
|
||||
options.write_buffer_size = 2048;
|
||||
options.max_write_buffer_number = 2;
|
||||
options.level0_file_num_compaction_trigger = 2;
|
||||
options.level0_slowdown_writes_trigger = 2;
|
||||
options.level0_stop_writes_trigger = 2;
|
||||
options.target_file_size_base = 2048;
|
||||
options.level_compaction_dynamic_level_bytes = true;
|
||||
options.max_bytes_for_level_base = 10240;
|
||||
options.max_bytes_for_level_multiplier = 4;
|
||||
options.soft_rate_limit = 1.1;
|
||||
options.max_background_compactions = max_background_compactions;
|
||||
options.num_levels = 5;
|
||||
|
||||
options.compression_per_level.resize(3);
|
||||
options.compression_per_level[0] = kNoCompression;
|
||||
options.compression_per_level[1] = kLZ4Compression;
|
||||
options.compression_per_level[2] = kSnappyCompression;
|
||||
|
||||
DestroyAndReopen(options);
|
||||
|
||||
for (int i = 0; i < kNKeys; i++) {
|
||||
int key = keys[i];
|
||||
ASSERT_OK(Put(Key(kNKeys + key), RandomString(&rnd, 102)));
|
||||
ASSERT_OK(Put(Key(key), RandomString(&rnd, 102)));
|
||||
ASSERT_OK(Put(Key(kNKeys * 2 + key), RandomString(&rnd, 102)));
|
||||
ASSERT_OK(Delete(Key(kNKeys + keys[i / 10])));
|
||||
env_->SleepForMicroseconds(5000);
|
||||
}
|
||||
|
||||
uint64_t int_prop;
|
||||
ASSERT_TRUE(db_->GetIntProperty("rocksdb.background-errors", &int_prop));
|
||||
ASSERT_EQ(0U, int_prop);
|
||||
|
||||
// Verify DB
|
||||
for (int j = 0; j < 2; j++) {
|
||||
verify_func();
|
||||
if (j == 0) {
|
||||
Reopen(options);
|
||||
}
|
||||
}
|
||||
|
||||
// Test compact range works
|
||||
dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr);
|
||||
// All data should be in the last level.
|
||||
ColumnFamilyMetaData cf_meta;
|
||||
db_->GetColumnFamilyMetaData(&cf_meta);
|
||||
ASSERT_EQ(5U, cf_meta.levels.size());
|
||||
for (int i = 0; i < 4; i++) {
|
||||
ASSERT_EQ(0U, cf_meta.levels[i].files.size());
|
||||
}
|
||||
ASSERT_GT(cf_meta.levels[4U].files.size(), 0U);
|
||||
verify_func();
|
||||
|
||||
Close();
|
||||
}
|
||||
}
|
||||
|
||||
env_->SetBackgroundThreads(1, Env::LOW);
|
||||
env_->SetBackgroundThreads(1, Env::HIGH);
|
||||
}
|
||||
|
||||
// Test specific cases in dynamic max bytes
|
||||
TEST_F(DBTestDynamicLevel, DynamicLevelMaxBytesBase2) {
|
||||
Random rnd(301);
|
||||
int kMaxKey = 1000000;
|
||||
|
||||
Options options = CurrentOptions();
|
||||
options.create_if_missing = true;
|
||||
options.db_write_buffer_size = 2048;
|
||||
options.write_buffer_size = 2048;
|
||||
options.max_write_buffer_number = 2;
|
||||
options.level0_file_num_compaction_trigger = 2;
|
||||
options.level0_slowdown_writes_trigger = 9999;
|
||||
options.level0_stop_writes_trigger = 9999;
|
||||
options.target_file_size_base = 2048;
|
||||
options.level_compaction_dynamic_level_bytes = true;
|
||||
options.max_bytes_for_level_base = 10240;
|
||||
options.max_bytes_for_level_multiplier = 4;
|
||||
options.max_background_compactions = 2;
|
||||
options.num_levels = 5;
|
||||
options.expanded_compaction_factor = 0; // Force not expanding in compactions
|
||||
BlockBasedTableOptions table_options;
|
||||
table_options.block_size = 1024;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
|
||||
DestroyAndReopen(options);
|
||||
ASSERT_OK(dbfull()->SetOptions({
|
||||
{"disable_auto_compactions", "true"},
|
||||
}));
|
||||
|
||||
uint64_t int_prop;
|
||||
std::string str_prop;
|
||||
|
||||
// Initial base level is the last level
|
||||
ASSERT_TRUE(db_->GetIntProperty("rocksdb.base-level", &int_prop));
|
||||
ASSERT_EQ(4U, int_prop);
|
||||
|
||||
// Put about 7K to L0
|
||||
for (int i = 0; i < 70; i++) {
|
||||
ASSERT_OK(Put(Key(static_cast<int>(rnd.Uniform(kMaxKey))),
|
||||
RandomString(&rnd, 80)));
|
||||
}
|
||||
ASSERT_OK(dbfull()->SetOptions({
|
||||
{"disable_auto_compactions", "false"},
|
||||
}));
|
||||
Flush();
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
ASSERT_TRUE(db_->GetIntProperty("rocksdb.base-level", &int_prop));
|
||||
ASSERT_EQ(4U, int_prop);
|
||||
|
||||
// Insert extra about 3.5K to L0. After they are compacted to L4, base level
|
||||
// should be changed to L3.
|
||||
ASSERT_OK(dbfull()->SetOptions({
|
||||
{"disable_auto_compactions", "true"},
|
||||
}));
|
||||
for (int i = 0; i < 70; i++) {
|
||||
ASSERT_OK(Put(Key(static_cast<int>(rnd.Uniform(kMaxKey))),
|
||||
RandomString(&rnd, 80)));
|
||||
}
|
||||
|
||||
ASSERT_OK(dbfull()->SetOptions({
|
||||
{"disable_auto_compactions", "false"},
|
||||
}));
|
||||
Flush();
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
ASSERT_TRUE(db_->GetIntProperty("rocksdb.base-level", &int_prop));
|
||||
ASSERT_EQ(3U, int_prop);
|
||||
ASSERT_TRUE(db_->GetProperty("rocksdb.num-files-at-level1", &str_prop));
|
||||
ASSERT_EQ("0", str_prop);
|
||||
ASSERT_TRUE(db_->GetProperty("rocksdb.num-files-at-level2", &str_prop));
|
||||
ASSERT_EQ("0", str_prop);
|
||||
|
||||
// Trigger parallel compaction, and the first one would change the base
|
||||
// level.
|
||||
// Hold compaction jobs to make sure
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"CompactionJob::Run():Start",
|
||||
[&](void* arg) { env_->SleepForMicroseconds(100000); });
|
||||
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
||||
ASSERT_OK(dbfull()->SetOptions({
|
||||
{"disable_auto_compactions", "true"},
|
||||
}));
|
||||
// Write about 10K more
|
||||
for (int i = 0; i < 100; i++) {
|
||||
ASSERT_OK(Put(Key(static_cast<int>(rnd.Uniform(kMaxKey))),
|
||||
RandomString(&rnd, 80)));
|
||||
}
|
||||
ASSERT_OK(dbfull()->SetOptions({
|
||||
{"disable_auto_compactions", "false"},
|
||||
}));
|
||||
Flush();
|
||||
// Wait for 200 milliseconds before proceeding compactions to make sure two
|
||||
// parallel ones are executed.
|
||||
env_->SleepForMicroseconds(200000);
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
ASSERT_TRUE(db_->GetIntProperty("rocksdb.base-level", &int_prop));
|
||||
ASSERT_EQ(3U, int_prop);
|
||||
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
||||
|
||||
// Trigger a condition that the compaction changes base level and L0->Lbase
|
||||
// happens at the same time.
|
||||
// We try to make last levels' targets to be 10K, 40K, 160K, add triggers
|
||||
// another compaction from 40K->160K.
|
||||
ASSERT_OK(dbfull()->SetOptions({
|
||||
{"disable_auto_compactions", "true"},
|
||||
}));
|
||||
// Write about 150K more
|
||||
for (int i = 0; i < 1350; i++) {
|
||||
ASSERT_OK(Put(Key(static_cast<int>(rnd.Uniform(kMaxKey))),
|
||||
RandomString(&rnd, 80)));
|
||||
}
|
||||
ASSERT_OK(dbfull()->SetOptions({
|
||||
{"disable_auto_compactions", "false"},
|
||||
}));
|
||||
Flush();
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
ASSERT_TRUE(db_->GetIntProperty("rocksdb.base-level", &int_prop));
|
||||
ASSERT_EQ(2U, int_prop);
|
||||
|
||||
// Keep Writing data until base level changed 2->1. There will be L0->L2
|
||||
// compaction going on at the same time.
|
||||
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
||||
for (int attempt = 0; attempt <= 20; attempt++) {
|
||||
// Write about 5K more data with two flushes. It should be flush to level 2
|
||||
// but when it is applied, base level is already 1.
|
||||
for (int i = 0; i < 50; i++) {
|
||||
ASSERT_OK(Put(Key(static_cast<int>(rnd.Uniform(kMaxKey))),
|
||||
RandomString(&rnd, 80)));
|
||||
}
|
||||
Flush();
|
||||
|
||||
ASSERT_TRUE(db_->GetIntProperty("rocksdb.base-level", &int_prop));
|
||||
if (int_prop == 2U) {
|
||||
env_->SleepForMicroseconds(50000);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
||||
rocksdb::SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
|
||||
env_->SleepForMicroseconds(200000);
|
||||
|
||||
ASSERT_TRUE(db_->GetIntProperty("rocksdb.base-level", &int_prop));
|
||||
ASSERT_EQ(1U, int_prop);
|
||||
}
|
||||
|
||||
// Test specific cases in dynamic max bytes
|
||||
TEST_F(DBTestDynamicLevel, DynamicLevelMaxBytesCompactRange) {
|
||||
Random rnd(301);
|
||||
int kMaxKey = 1000000;
|
||||
|
||||
Options options = CurrentOptions();
|
||||
options.create_if_missing = true;
|
||||
options.db_write_buffer_size = 2048;
|
||||
options.write_buffer_size = 2048;
|
||||
options.max_write_buffer_number = 2;
|
||||
options.level0_file_num_compaction_trigger = 2;
|
||||
options.level0_slowdown_writes_trigger = 9999;
|
||||
options.level0_stop_writes_trigger = 9999;
|
||||
options.target_file_size_base = 2;
|
||||
options.level_compaction_dynamic_level_bytes = true;
|
||||
options.max_bytes_for_level_base = 10240;
|
||||
options.max_bytes_for_level_multiplier = 4;
|
||||
options.max_background_compactions = 1;
|
||||
const int kNumLevels = 5;
|
||||
options.num_levels = kNumLevels;
|
||||
options.expanded_compaction_factor = 0; // Force not expanding in compactions
|
||||
BlockBasedTableOptions table_options;
|
||||
table_options.block_size = 1024;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
|
||||
DestroyAndReopen(options);
|
||||
|
||||
// Compact against empty DB
|
||||
dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr);
|
||||
|
||||
uint64_t int_prop;
|
||||
std::string str_prop;
|
||||
|
||||
// Initial base level is the last level
|
||||
ASSERT_TRUE(db_->GetIntProperty("rocksdb.base-level", &int_prop));
|
||||
ASSERT_EQ(4U, int_prop);
|
||||
|
||||
// Put about 7K to L0
|
||||
for (int i = 0; i < 140; i++) {
|
||||
ASSERT_OK(Put(Key(static_cast<int>(rnd.Uniform(kMaxKey))),
|
||||
RandomString(&rnd, 80)));
|
||||
}
|
||||
Flush();
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
if (NumTableFilesAtLevel(0) == 0) {
|
||||
// Make sure level 0 is not empty
|
||||
ASSERT_OK(Put(Key(static_cast<int>(rnd.Uniform(kMaxKey))),
|
||||
RandomString(&rnd, 80)));
|
||||
Flush();
|
||||
}
|
||||
|
||||
ASSERT_TRUE(db_->GetIntProperty("rocksdb.base-level", &int_prop));
|
||||
ASSERT_EQ(3U, int_prop);
|
||||
ASSERT_TRUE(db_->GetProperty("rocksdb.num-files-at-level1", &str_prop));
|
||||
ASSERT_EQ("0", str_prop);
|
||||
ASSERT_TRUE(db_->GetProperty("rocksdb.num-files-at-level2", &str_prop));
|
||||
ASSERT_EQ("0", str_prop);
|
||||
|
||||
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
||||
rocksdb::SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
|
||||
std::set<int> output_levels;
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"CompactionPicker::CompactRange:Return", [&](void* arg) {
|
||||
Compaction* compaction = reinterpret_cast<Compaction*>(arg);
|
||||
output_levels.insert(compaction->output_level());
|
||||
});
|
||||
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr);
|
||||
ASSERT_EQ(output_levels.size(), 2);
|
||||
ASSERT_TRUE(output_levels.find(3) != output_levels.end());
|
||||
ASSERT_TRUE(output_levels.find(4) != output_levels.end());
|
||||
ASSERT_TRUE(db_->GetProperty("rocksdb.num-files-at-level0", &str_prop));
|
||||
ASSERT_EQ("0", str_prop);
|
||||
ASSERT_TRUE(db_->GetProperty("rocksdb.num-files-at-level3", &str_prop));
|
||||
ASSERT_EQ("0", str_prop);
|
||||
// Base level is still level 3.
|
||||
ASSERT_TRUE(db_->GetIntProperty("rocksdb.base-level", &int_prop));
|
||||
ASSERT_EQ(3U, int_prop);
|
||||
}
|
||||
|
||||
TEST_F(DBTestDynamicLevel, DynamicLevelMaxBytesBaseInc) {
|
||||
Options options = CurrentOptions();
|
||||
options.create_if_missing = true;
|
||||
options.db_write_buffer_size = 2048;
|
||||
options.write_buffer_size = 2048;
|
||||
options.max_write_buffer_number = 2;
|
||||
options.level0_file_num_compaction_trigger = 2;
|
||||
options.level0_slowdown_writes_trigger = 2;
|
||||
options.level0_stop_writes_trigger = 2;
|
||||
options.target_file_size_base = 2048;
|
||||
options.level_compaction_dynamic_level_bytes = true;
|
||||
options.max_bytes_for_level_base = 10240;
|
||||
options.max_bytes_for_level_multiplier = 4;
|
||||
options.soft_rate_limit = 1.1;
|
||||
options.max_background_compactions = 2;
|
||||
options.num_levels = 5;
|
||||
|
||||
DestroyAndReopen(options);
|
||||
|
||||
int non_trivial = 0;
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::BackgroundCompaction:NonTrivial",
|
||||
[&](void* arg) { non_trivial++; });
|
||||
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
Random rnd(301);
|
||||
const int total_keys = 3000;
|
||||
const int random_part_size = 100;
|
||||
for (int i = 0; i < total_keys; i++) {
|
||||
std::string value = RandomString(&rnd, random_part_size);
|
||||
PutFixed32(&value, static_cast<uint32_t>(i));
|
||||
ASSERT_OK(Put(Key(i), value));
|
||||
}
|
||||
Flush();
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
||||
|
||||
ASSERT_EQ(non_trivial, 0);
|
||||
|
||||
for (int i = 0; i < total_keys; i++) {
|
||||
std::string value = Get(Key(i));
|
||||
ASSERT_EQ(DecodeFixed32(value.c_str() + random_part_size),
|
||||
static_cast<uint32_t>(i));
|
||||
}
|
||||
|
||||
env_->SetBackgroundThreads(1, Env::LOW);
|
||||
env_->SetBackgroundThreads(1, Env::HIGH);
|
||||
}
|
||||
|
||||
TEST_F(DBTestDynamicLevel, MigrateToDynamicLevelMaxBytesBase) {
|
||||
Random rnd(301);
|
||||
const int kMaxKey = 2000;
|
||||
|
||||
Options options;
|
||||
options.create_if_missing = true;
|
||||
options.db_write_buffer_size = 2048;
|
||||
options.write_buffer_size = 2048;
|
||||
options.max_write_buffer_number = 8;
|
||||
options.level0_file_num_compaction_trigger = 4;
|
||||
options.level0_slowdown_writes_trigger = 4;
|
||||
options.level0_stop_writes_trigger = 8;
|
||||
options.target_file_size_base = 2048;
|
||||
options.level_compaction_dynamic_level_bytes = false;
|
||||
options.max_bytes_for_level_base = 10240;
|
||||
options.max_bytes_for_level_multiplier = 4;
|
||||
options.soft_rate_limit = 1.1;
|
||||
options.num_levels = 8;
|
||||
|
||||
DestroyAndReopen(options);
|
||||
|
||||
auto verify_func = [&](int num_keys, bool if_sleep) {
|
||||
for (int i = 0; i < num_keys; i++) {
|
||||
ASSERT_NE("NOT_FOUND", Get(Key(kMaxKey + i)));
|
||||
if (i < num_keys / 10) {
|
||||
ASSERT_EQ("NOT_FOUND", Get(Key(i)));
|
||||
} else {
|
||||
ASSERT_NE("NOT_FOUND", Get(Key(i)));
|
||||
}
|
||||
if (if_sleep && i % 1000 == 0) {
|
||||
// Without it, valgrind may choose not to give another
|
||||
// thread a chance to run before finishing the function,
|
||||
// causing the test to be extremely slow.
|
||||
env_->SleepForMicroseconds(1);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
int total_keys = 1000;
|
||||
for (int i = 0; i < total_keys; i++) {
|
||||
ASSERT_OK(Put(Key(i), RandomString(&rnd, 102)));
|
||||
ASSERT_OK(Put(Key(kMaxKey + i), RandomString(&rnd, 102)));
|
||||
ASSERT_OK(Delete(Key(i / 10)));
|
||||
}
|
||||
verify_func(total_keys, false);
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
|
||||
options.level_compaction_dynamic_level_bytes = true;
|
||||
options.disable_auto_compactions = true;
|
||||
Reopen(options);
|
||||
verify_func(total_keys, false);
|
||||
|
||||
std::atomic_bool compaction_finished;
|
||||
compaction_finished = false;
|
||||
// Issue manual compaction in one thread and still verify DB state
|
||||
// in main thread.
|
||||
std::thread t([&]() {
|
||||
CompactRangeOptions compact_options;
|
||||
compact_options.change_level = true;
|
||||
compact_options.target_level = options.num_levels - 1;
|
||||
dbfull()->CompactRange(compact_options, nullptr, nullptr);
|
||||
compaction_finished.store(true);
|
||||
});
|
||||
do {
|
||||
verify_func(total_keys, true);
|
||||
} while (!compaction_finished.load());
|
||||
t.join();
|
||||
|
||||
ASSERT_OK(dbfull()->SetOptions({
|
||||
{"disable_auto_compactions", "false"},
|
||||
}));
|
||||
|
||||
int total_keys2 = 2000;
|
||||
for (int i = total_keys; i < total_keys2; i++) {
|
||||
ASSERT_OK(Put(Key(i), RandomString(&rnd, 102)));
|
||||
ASSERT_OK(Put(Key(kMaxKey + i), RandomString(&rnd, 102)));
|
||||
ASSERT_OK(Delete(Key(i / 10)));
|
||||
}
|
||||
|
||||
verify_func(total_keys2, false);
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
verify_func(total_keys2, false);
|
||||
|
||||
// Base level is not level 1
|
||||
ASSERT_EQ(NumTableFilesAtLevel(1), 0);
|
||||
ASSERT_EQ(NumTableFilesAtLevel(2), 0);
|
||||
}
|
||||
} // namespace rocksdb
|
||||
|
||||
#endif // !(defined NDEBUG) || !defined(OS_WIN)
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
#if !(defined NDEBUG) || !defined(OS_WIN)
|
||||
rocksdb::port::InstallStackTraceHandler();
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
#else
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
+307
-242
@@ -54,6 +54,7 @@
|
||||
#include "port/likely.h"
|
||||
#include "rocksdb/compaction_filter.h"
|
||||
#include "rocksdb/db.h"
|
||||
#include "rocksdb/delete_scheduler.h"
|
||||
#include "rocksdb/env.h"
|
||||
#include "rocksdb/merge_operator.h"
|
||||
#include "rocksdb/version.h"
|
||||
@@ -70,7 +71,9 @@
|
||||
#include "util/build_version.h"
|
||||
#include "util/coding.h"
|
||||
#include "util/compression.h"
|
||||
#include "util/crc32c.h"
|
||||
#include "util/db_info_dumper.h"
|
||||
#include "util/file_reader_writer.h"
|
||||
#include "util/file_util.h"
|
||||
#include "util/hash_skiplist_rep.h"
|
||||
#include "util/hash_linklist_rep.h"
|
||||
@@ -194,7 +197,7 @@ CompressionType GetCompressionFlush(const ImmutableCFOptions& ioptions) {
|
||||
}
|
||||
}
|
||||
|
||||
void DumpCompressionInfo(Logger* logger) {
|
||||
void DumpSupportInfo(Logger* logger) {
|
||||
Log(InfoLogLevel::INFO_LEVEL, logger, "Compression algorithms supported:");
|
||||
Log(InfoLogLevel::INFO_LEVEL, logger, "\tSnappy supported: %d",
|
||||
Snappy_Supported());
|
||||
@@ -203,6 +206,8 @@ void DumpCompressionInfo(Logger* logger) {
|
||||
Log(InfoLogLevel::INFO_LEVEL, logger, "\tBzip supported: %d",
|
||||
BZip2_Supported());
|
||||
Log(InfoLogLevel::INFO_LEVEL, logger, "\tLZ4 supported: %d", LZ4_Supported());
|
||||
Log(InfoLogLevel::INFO_LEVEL, logger, "Fast CRC32 supported: %d",
|
||||
crc32c::IsFastCrc32Supported());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
@@ -220,6 +225,7 @@ DBImpl::DBImpl(const DBOptions& options, const std::string& dbname)
|
||||
log_dir_synced_(false),
|
||||
log_empty_(true),
|
||||
default_cf_handle_(nullptr),
|
||||
log_sync_cv_(&mutex_),
|
||||
total_log_size_(0),
|
||||
max_total_in_memory_state_(0),
|
||||
is_snapshot_supported_(true),
|
||||
@@ -265,7 +271,7 @@ DBImpl::DBImpl(const DBOptions& options, const std::string& dbname)
|
||||
DumpRocksDBBuildVersion(db_options_.info_log.get());
|
||||
DumpDBFileSummary(db_options_, dbname_);
|
||||
db_options_.Dump(db_options_.info_log.get());
|
||||
DumpCompressionInfo(db_options_.info_log.get());
|
||||
DumpSupportInfo(db_options_.info_log.get());
|
||||
|
||||
LogFlush(db_options_.info_log);
|
||||
}
|
||||
@@ -363,6 +369,10 @@ DBImpl::~DBImpl() {
|
||||
for (auto l : logs_to_free_) {
|
||||
delete l;
|
||||
}
|
||||
for (auto& log : logs_) {
|
||||
log.ClearWriter();
|
||||
}
|
||||
logs_.clear();
|
||||
|
||||
// versions need to be destroyed before table_cache since it can hold
|
||||
// references to table_cache.
|
||||
@@ -381,18 +391,22 @@ Status DBImpl::NewDB() {
|
||||
new_db.SetNextFile(2);
|
||||
new_db.SetLastSequence(0);
|
||||
|
||||
Status s;
|
||||
|
||||
Log(InfoLogLevel::INFO_LEVEL,
|
||||
db_options_.info_log, "Creating manifest 1 \n");
|
||||
const std::string manifest = DescriptorFileName(dbname_, 1);
|
||||
unique_ptr<WritableFile> file;
|
||||
Status s = env_->NewWritableFile(
|
||||
manifest, &file, env_->OptimizeForManifestWrite(env_options_));
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
file->SetPreallocationBlockSize(db_options_.manifest_preallocation_size);
|
||||
{
|
||||
log::Writer log(std::move(file));
|
||||
unique_ptr<WritableFile> file;
|
||||
EnvOptions env_options = env_->OptimizeForManifestWrite(env_options_);
|
||||
s = env_->NewWritableFile(manifest, &file, env_options);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
file->SetPreallocationBlockSize(db_options_.manifest_preallocation_size);
|
||||
unique_ptr<WritableFileWriter> file_writer(
|
||||
new WritableFileWriter(std::move(file), env_options));
|
||||
log::Writer log(std::move(file_writer));
|
||||
std::string record;
|
||||
new_db.EncodeTo(&record);
|
||||
s = log.AddRecord(record);
|
||||
@@ -515,6 +529,9 @@ void DBImpl::FindObsoleteFiles(JobContext* job_context, bool force,
|
||||
|
||||
// don't delete files that might be currently written to from compaction
|
||||
// threads
|
||||
// Since job_context->min_pending_output is set, until file scan finishes,
|
||||
// mutex_ cannot be released. Otherwise, we might see no min_pending_output
|
||||
// here but later find newer generated unfinalized files while scannint.
|
||||
if (!pending_outputs_.empty()) {
|
||||
job_context->min_pending_output = *pending_outputs_.begin();
|
||||
} else {
|
||||
@@ -568,6 +585,37 @@ void DBImpl::FindObsoleteFiles(JobContext* job_context, bool force,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!alive_log_files_.empty()) {
|
||||
uint64_t min_log_number = versions_->MinLogNumber();
|
||||
// find newly obsoleted log files
|
||||
while (alive_log_files_.begin()->number < min_log_number) {
|
||||
auto& earliest = *alive_log_files_.begin();
|
||||
job_context->log_delete_files.push_back(earliest.number);
|
||||
total_log_size_ -= earliest.size;
|
||||
alive_log_files_.pop_front();
|
||||
// Current log should always stay alive since it can't have
|
||||
// number < MinLogNumber().
|
||||
assert(alive_log_files_.size());
|
||||
}
|
||||
while (!logs_.empty() && logs_.front().number < min_log_number) {
|
||||
auto& log = logs_.front();
|
||||
if (log.getting_synced) {
|
||||
log_sync_cv_.Wait();
|
||||
// logs_ could have changed while we were waiting.
|
||||
continue;
|
||||
}
|
||||
logs_to_free_.push_back(log.ReleaseWriter());
|
||||
logs_.pop_front();
|
||||
}
|
||||
// Current log cannot be obsolete.
|
||||
assert(!logs_.empty());
|
||||
}
|
||||
|
||||
// We're just cleaning up for DB::Write().
|
||||
assert(job_context->logs_to_free.empty());
|
||||
job_context->logs_to_free = logs_to_free_;
|
||||
logs_to_free_.clear();
|
||||
}
|
||||
|
||||
namespace {
|
||||
@@ -704,7 +752,13 @@ void DBImpl::PurgeObsoleteFiles(const JobContext& state) {
|
||||
continue;
|
||||
}
|
||||
#endif // !ROCKSDB_LITE
|
||||
auto file_deletion_status = env_->DeleteFile(fname);
|
||||
Status file_deletion_status;
|
||||
if (db_options_.delete_scheduler != nullptr && type == kTableFile &&
|
||||
path_id == 0) {
|
||||
file_deletion_status = db_options_.delete_scheduler->DeleteFile(fname);
|
||||
} else {
|
||||
file_deletion_status = env_->DeleteFile(fname);
|
||||
}
|
||||
if (file_deletion_status.ok()) {
|
||||
Log(InfoLogLevel::DEBUG_LEVEL, db_options_.info_log,
|
||||
"[JOB %d] Delete %s type=%d #%" PRIu64 " -- %s\n", state.job_id,
|
||||
@@ -841,7 +895,8 @@ Status DBImpl::Recover(
|
||||
return s;
|
||||
}
|
||||
|
||||
if (!env_->FileExists(CurrentFileName(dbname_))) {
|
||||
s = env_->FileExists(CurrentFileName(dbname_));
|
||||
if (s.IsNotFound()) {
|
||||
if (db_options_.create_if_missing) {
|
||||
s = NewDB();
|
||||
is_new_db = true;
|
||||
@@ -852,18 +907,26 @@ Status DBImpl::Recover(
|
||||
return Status::InvalidArgument(
|
||||
dbname_, "does not exist (create_if_missing is false)");
|
||||
}
|
||||
} else {
|
||||
} else if (s.ok()) {
|
||||
if (db_options_.error_if_exists) {
|
||||
return Status::InvalidArgument(
|
||||
dbname_, "exists (error_if_exists is true)");
|
||||
}
|
||||
} else {
|
||||
// Unexpected error reading file
|
||||
assert(s.IsIOError());
|
||||
return s;
|
||||
}
|
||||
// Check for the IDENTITY file and create it if not there
|
||||
if (!env_->FileExists(IdentityFileName(dbname_))) {
|
||||
s = env_->FileExists(IdentityFileName(dbname_));
|
||||
if (s.IsNotFound()) {
|
||||
s = SetIdentityFile(env_, dbname_);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
} else if (!s.ok()) {
|
||||
assert(s.IsIOError());
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -991,17 +1054,21 @@ Status DBImpl::RecoverLogFiles(const std::vector<uint64_t>& log_numbers,
|
||||
versions_->MarkFileNumberUsedDuringRecovery(log_number);
|
||||
// Open the log file
|
||||
std::string fname = LogFileName(db_options_.wal_dir, log_number);
|
||||
unique_ptr<SequentialFile> file;
|
||||
status = env_->NewSequentialFile(fname, &file, env_options_);
|
||||
if (!status.ok()) {
|
||||
MaybeIgnoreError(&status);
|
||||
unique_ptr<SequentialFileReader> file_reader;
|
||||
{
|
||||
unique_ptr<SequentialFile> file;
|
||||
status = env_->NewSequentialFile(fname, &file, env_options_);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
} else {
|
||||
// Fail with one log file, but that's ok.
|
||||
// Try next one.
|
||||
continue;
|
||||
MaybeIgnoreError(&status);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
} else {
|
||||
// Fail with one log file, but that's ok.
|
||||
// Try next one.
|
||||
continue;
|
||||
}
|
||||
}
|
||||
file_reader.reset(new SequentialFileReader(std::move(file)));
|
||||
}
|
||||
|
||||
// Create the log reader.
|
||||
@@ -1020,7 +1087,7 @@ Status DBImpl::RecoverLogFiles(const std::vector<uint64_t>& log_numbers,
|
||||
// paranoid_checks==false so that corruptions cause entire commits
|
||||
// to be skipped instead of propagating bad information (like overly
|
||||
// large sequence numbers).
|
||||
log::Reader reader(std::move(file), &reporter, true /*checksum*/,
|
||||
log::Reader reader(std::move(file_reader), &reporter, true /*checksum*/,
|
||||
0 /*initial_offset*/);
|
||||
Log(InfoLogLevel::INFO_LEVEL, db_options_.info_log,
|
||||
"Recovering log #%" PRIu64 " mode %d skip-recovery %d", log_number,
|
||||
@@ -1309,17 +1376,6 @@ Status DBImpl::FlushMemTableToOutputFile(
|
||||
VersionStorageInfo::LevelSummaryStorage tmp;
|
||||
LogToBuffer(log_buffer, "[%s] Level summary: %s\n", cfd->GetName().c_str(),
|
||||
cfd->current()->storage_info()->LevelSummary(&tmp));
|
||||
|
||||
if (disable_delete_obsolete_files_ == 0) {
|
||||
// add to deletion state
|
||||
while (alive_log_files_.size() &&
|
||||
alive_log_files_.begin()->number < versions_->MinLogNumber()) {
|
||||
const auto& earliest = *alive_log_files_.begin();
|
||||
job_context->log_delete_files.push_back(earliest.number);
|
||||
total_log_size_ -= earliest.size;
|
||||
alive_log_files_.pop_front();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!s.ok() && !s.IsShutdownInProgress() && db_options_.paranoid_checks &&
|
||||
@@ -1435,8 +1491,7 @@ Status DBImpl::CompactRange(const CompactRangeOptions& options,
|
||||
} else if (options.bottommost_level_compaction ==
|
||||
BottommostLevelCompaction::kIfHaveCompactionFilter &&
|
||||
cfd->ioptions()->compaction_filter == nullptr &&
|
||||
cfd->ioptions()->compaction_filter_factory == nullptr &&
|
||||
cfd->ioptions()->compaction_filter_factory_v2 == nullptr) {
|
||||
cfd->ioptions()->compaction_filter_factory == nullptr) {
|
||||
// Skip bottommost level compaction since we dont have
|
||||
// compaction filter
|
||||
continue;
|
||||
@@ -1609,20 +1664,15 @@ Status DBImpl::CompactFilesImpl(
|
||||
assert(c);
|
||||
c->SetInputVersion(version);
|
||||
// deletion compaction currently not allowed in CompactFiles.
|
||||
assert(!c->IsDeletionCompaction());
|
||||
assert(!c->deletion_compaction());
|
||||
|
||||
auto yield_callback = [&]() {
|
||||
return CallFlushDuringCompaction(
|
||||
c->column_family_data(), *c->mutable_cf_options(),
|
||||
job_context, log_buffer);
|
||||
};
|
||||
assert(is_snapshot_supported_ || snapshots_.empty());
|
||||
CompactionJob compaction_job(
|
||||
job_context->job_id, c.get(), db_options_, env_options_, versions_.get(),
|
||||
&shutting_down_, log_buffer, directories_.GetDbDir(),
|
||||
directories_.GetDataDir(c->GetOutputPathId()), stats_,
|
||||
snapshots_.GetAll(), table_cache_, std::move(yield_callback),
|
||||
&event_logger_, c->mutable_cf_options()->paranoid_file_checks, dbname_,
|
||||
directories_.GetDataDir(c->output_path_id()), stats_, snapshots_.GetAll(),
|
||||
table_cache_, &event_logger_,
|
||||
c->mutable_cf_options()->paranoid_file_checks, dbname_,
|
||||
nullptr); // Here we pass a nullptr for CompactionJobStats because
|
||||
// CompactFiles does not trigger OnCompactionCompleted(),
|
||||
// which is the only place where CompactionJobStats is
|
||||
@@ -1886,10 +1936,7 @@ int DBImpl::NumberLevels(ColumnFamilyHandle* column_family) {
|
||||
}
|
||||
|
||||
int DBImpl::MaxMemCompactionLevel(ColumnFamilyHandle* column_family) {
|
||||
auto cfh = reinterpret_cast<ColumnFamilyHandleImpl*>(column_family);
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
return cfh->cfd()->GetSuperVersion()->
|
||||
mutable_cf_options.max_mem_compaction_level;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int DBImpl::Level0StopWriteTrigger(ColumnFamilyHandle* column_family) {
|
||||
@@ -1905,6 +1952,86 @@ Status DBImpl::Flush(const FlushOptions& flush_options,
|
||||
return FlushMemTable(cfh->cfd(), flush_options);
|
||||
}
|
||||
|
||||
Status DBImpl::SyncWAL() {
|
||||
autovector<log::Writer*, 1> logs_to_sync;
|
||||
bool need_log_dir_sync;
|
||||
uint64_t current_log_number;
|
||||
|
||||
{
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
assert(!logs_.empty());
|
||||
|
||||
// This SyncWAL() call only cares about logs up to this number.
|
||||
current_log_number = logfile_number_;
|
||||
|
||||
while (logs_.front().number <= current_log_number &&
|
||||
logs_.front().getting_synced) {
|
||||
log_sync_cv_.Wait();
|
||||
}
|
||||
// First check that logs are safe to sync in background.
|
||||
for (auto it = logs_.begin();
|
||||
it != logs_.end() && it->number <= current_log_number; ++it) {
|
||||
if (!it->writer->file()->writable_file()->IsSyncThreadSafe()) {
|
||||
return Status::NotSupported(
|
||||
"SyncWAL() is not supported for this implementation of WAL file",
|
||||
db_options_.allow_mmap_writes
|
||||
? "try setting Options::allow_mmap_writes to false"
|
||||
: Slice());
|
||||
}
|
||||
}
|
||||
for (auto it = logs_.begin();
|
||||
it != logs_.end() && it->number <= current_log_number; ++it) {
|
||||
auto& log = *it;
|
||||
assert(!log.getting_synced);
|
||||
log.getting_synced = true;
|
||||
logs_to_sync.push_back(log.writer);
|
||||
}
|
||||
|
||||
need_log_dir_sync = !log_dir_synced_;
|
||||
}
|
||||
|
||||
Status status;
|
||||
for (log::Writer* log : logs_to_sync) {
|
||||
status = log->file()->SyncWithoutFlush(db_options_.use_fsync);
|
||||
if (!status.ok()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (status.ok() && need_log_dir_sync) {
|
||||
status = directories_.GetWalDir()->Fsync();
|
||||
}
|
||||
|
||||
{
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
MarkLogsSynced(current_log_number, need_log_dir_sync, status);
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
void DBImpl::MarkLogsSynced(
|
||||
uint64_t up_to, bool synced_dir, const Status& status) {
|
||||
mutex_.AssertHeld();
|
||||
if (synced_dir &&
|
||||
logfile_number_ == up_to &&
|
||||
status.ok()) {
|
||||
log_dir_synced_ = true;
|
||||
}
|
||||
for (auto it = logs_.begin(); it != logs_.end() && it->number <= up_to;) {
|
||||
auto& log = *it;
|
||||
assert(log.getting_synced);
|
||||
if (status.ok() && logs_.size() > 1) {
|
||||
logs_to_free_.push_back(log.ReleaseWriter());
|
||||
it = logs_.erase(it);
|
||||
} else {
|
||||
log.getting_synced = false;
|
||||
++it;
|
||||
}
|
||||
}
|
||||
assert(logs_.empty() || (logs_.size() == 1 && !logs_[0].getting_synced));
|
||||
log_sync_cv_.SignalAll();
|
||||
}
|
||||
|
||||
SequenceNumber DBImpl::GetLatestSequenceNumber() const {
|
||||
return versions_->LastSequence();
|
||||
}
|
||||
@@ -2008,8 +2135,8 @@ Status DBImpl::FlushMemTable(ColumnFamilyData* cfd,
|
||||
}
|
||||
|
||||
WriteThread::Writer w(&mutex_);
|
||||
s = write_thread_.EnterWriteThread(&w, 0);
|
||||
assert(s.ok() && !w.done); // No timeout and nobody should do our job
|
||||
write_thread_.EnterWriteThread(&w);
|
||||
assert(!w.done); // Nobody should do our job
|
||||
|
||||
// SwitchMemtable() will release and reacquire mutex
|
||||
// during execution
|
||||
@@ -2048,6 +2175,10 @@ Status DBImpl::WaitForFlushMemTable(ColumnFamilyData* cfd) {
|
||||
|
||||
void DBImpl::MaybeScheduleFlushOrCompaction() {
|
||||
mutex_.AssertHeld();
|
||||
if (!opened_successfully_) {
|
||||
// Compaction may introduce data race to DB open
|
||||
return;
|
||||
}
|
||||
if (bg_work_gate_closed_) {
|
||||
// gate closed for background work
|
||||
return;
|
||||
@@ -2063,27 +2194,24 @@ void DBImpl::MaybeScheduleFlushOrCompaction() {
|
||||
env_->Schedule(&DBImpl::BGWorkFlush, this, Env::Priority::HIGH, this);
|
||||
}
|
||||
|
||||
// special case -- if max_background_flushes == 0, then schedule flush on a
|
||||
// compaction thread
|
||||
if (db_options_.max_background_flushes == 0) {
|
||||
while (unscheduled_flushes_ > 0 &&
|
||||
bg_flush_scheduled_ + bg_compaction_scheduled_ <
|
||||
db_options_.max_background_compactions) {
|
||||
unscheduled_flushes_--;
|
||||
bg_flush_scheduled_++;
|
||||
env_->Schedule(&DBImpl::BGWorkFlush, this, Env::Priority::LOW, this);
|
||||
}
|
||||
}
|
||||
|
||||
if (bg_manual_only_) {
|
||||
// only manual compactions are allowed to run. don't schedule automatic
|
||||
// compactions
|
||||
return;
|
||||
}
|
||||
|
||||
if (db_options_.max_background_flushes == 0 &&
|
||||
bg_compaction_scheduled_ < db_options_.max_background_compactions &&
|
||||
unscheduled_flushes_ > 0) {
|
||||
// special case where flush is executed by compaction thread
|
||||
// (if max_background_flushes == 0).
|
||||
// Compaction thread will execute all the flushes
|
||||
unscheduled_flushes_ = 0;
|
||||
if (unscheduled_compactions_ > 0) {
|
||||
// bg compaction will execute one compaction
|
||||
unscheduled_compactions_--;
|
||||
}
|
||||
bg_compaction_scheduled_++;
|
||||
env_->Schedule(&DBImpl::BGWorkCompaction, this, Env::Priority::LOW, this);
|
||||
}
|
||||
|
||||
while (bg_compaction_scheduled_ < db_options_.max_background_compactions &&
|
||||
unscheduled_compactions_ > 0) {
|
||||
bg_compaction_scheduled_++;
|
||||
@@ -2145,7 +2273,9 @@ void DBImpl::RecordFlushIOStats() {
|
||||
|
||||
void DBImpl::BGWorkFlush(void* db) {
|
||||
IOSTATS_SET_THREAD_POOL_ID(Env::Priority::HIGH);
|
||||
TEST_SYNC_POINT("DBImpl::BGWorkFlush");
|
||||
reinterpret_cast<DBImpl*>(db)->BackgroundCallFlush();
|
||||
TEST_SYNC_POINT("DBImpl::BGWorkFlush:done");
|
||||
}
|
||||
|
||||
void DBImpl::BGWorkCompaction(void* db) {
|
||||
@@ -2238,10 +2368,6 @@ void DBImpl::BackgroundCallFlush() {
|
||||
|
||||
ReleaseFileNumberFromPendingOutputs(pending_outputs_inserted_elem);
|
||||
|
||||
// We're just cleaning up for DB::Write()
|
||||
job_context.logs_to_free = logs_to_free_;
|
||||
logs_to_free_.clear();
|
||||
|
||||
// If flush failed, we want to delete all temporary files that we might have
|
||||
// created. Thus, we force full scan in FindObsoleteFiles()
|
||||
FindObsoleteFiles(&job_context, !s.ok() && !s.IsShutdownInProgress());
|
||||
@@ -2308,10 +2434,6 @@ void DBImpl::BackgroundCallCompaction() {
|
||||
|
||||
ReleaseFileNumberFromPendingOutputs(pending_outputs_inserted_elem);
|
||||
|
||||
// We're just cleaning up for DB::Write()
|
||||
job_context.logs_to_free = logs_to_free_;
|
||||
logs_to_free_.clear();
|
||||
|
||||
// If compaction failed, we want to delete all temporary files that we might
|
||||
// have created (they might not be all recorded in job_context in case of a
|
||||
// failure). Thus, we force full scan in FindObsoleteFiles()
|
||||
@@ -2390,35 +2512,6 @@ Status DBImpl::BackgroundCompaction(bool* madeProgress, JobContext* job_context,
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
// If there are no flush threads, then compaction thread needs to execute the
|
||||
// flushes
|
||||
if (db_options_.max_background_flushes == 0) {
|
||||
// BackgroundFlush() will only execute a single flush. We keep calling it as
|
||||
// long as there's more flushes to be done
|
||||
while (!flush_queue_.empty()) {
|
||||
LogToBuffer(
|
||||
log_buffer,
|
||||
"BackgroundCompaction calling BackgroundFlush. flush slots available "
|
||||
"%d, compaction slots available %d",
|
||||
db_options_.max_background_flushes - bg_flush_scheduled_,
|
||||
db_options_.max_background_compactions - bg_compaction_scheduled_);
|
||||
auto flush_status =
|
||||
BackgroundFlush(madeProgress, job_context, log_buffer);
|
||||
// the second condition will be false when a column family is dropped. we
|
||||
// don't want to fail compaction because of that (because it might be a
|
||||
// different column family)
|
||||
if (!flush_status.ok() && !flush_status.IsShutdownInProgress()) {
|
||||
if (is_manual) {
|
||||
manual_compaction_->status = flush_status;
|
||||
manual_compaction_->done = true;
|
||||
manual_compaction_->in_progress = false;
|
||||
manual_compaction_ = nullptr;
|
||||
}
|
||||
return flush_status;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unique_ptr<Compaction> c;
|
||||
InternalKey manual_end_storage;
|
||||
InternalKey* manual_end = &manual_end_storage;
|
||||
@@ -2502,7 +2595,7 @@ Status DBImpl::BackgroundCompaction(bool* madeProgress, JobContext* job_context,
|
||||
if (!c) {
|
||||
// Nothing to do
|
||||
LogToBuffer(log_buffer, "Compaction nothing to do");
|
||||
} else if (c->IsDeletionCompaction()) {
|
||||
} else if (c->deletion_compaction()) {
|
||||
// TODO(icanadi) Do we want to honor snapshots here? i.e. not delete old
|
||||
// file if there is alive snapshot pointing to it
|
||||
assert(c->num_input_files(1) == 0);
|
||||
@@ -2536,21 +2629,27 @@ Status DBImpl::BackgroundCompaction(bool* madeProgress, JobContext* job_context,
|
||||
// Move files to next level
|
||||
int32_t moved_files = 0;
|
||||
int64_t moved_bytes = 0;
|
||||
for (size_t i = 0; i < c->num_input_files(0); i++) {
|
||||
FileMetaData* f = c->input(0, i);
|
||||
c->edit()->DeleteFile(c->level(), f->fd.GetNumber());
|
||||
c->edit()->AddFile(c->output_level(), f->fd.GetNumber(),
|
||||
f->fd.GetPathId(), f->fd.GetFileSize(), f->smallest,
|
||||
f->largest, f->smallest_seqno, f->largest_seqno,
|
||||
f->marked_for_compaction);
|
||||
for (unsigned int l = 0; l < c->num_input_levels(); l++) {
|
||||
if (c->level(l) == c->output_level()) {
|
||||
continue;
|
||||
}
|
||||
for (size_t i = 0; i < c->num_input_files(l); i++) {
|
||||
FileMetaData* f = c->input(l, i);
|
||||
c->edit()->DeleteFile(c->level(l), f->fd.GetNumber());
|
||||
c->edit()->AddFile(c->output_level(), f->fd.GetNumber(),
|
||||
f->fd.GetPathId(), f->fd.GetFileSize(), f->smallest,
|
||||
f->largest, f->smallest_seqno, f->largest_seqno,
|
||||
f->marked_for_compaction);
|
||||
|
||||
LogToBuffer(log_buffer,
|
||||
"[%s] Moving #%" PRIu64 " to level-%d %" PRIu64 " bytes\n",
|
||||
c->column_family_data()->GetName().c_str(), f->fd.GetNumber(),
|
||||
c->output_level(), f->fd.GetFileSize());
|
||||
++moved_files;
|
||||
moved_bytes += f->fd.GetFileSize();
|
||||
LogToBuffer(log_buffer,
|
||||
"[%s] Moving #%" PRIu64 " to level-%d %" PRIu64 " bytes\n",
|
||||
c->column_family_data()->GetName().c_str(),
|
||||
f->fd.GetNumber(), c->output_level(), f->fd.GetFileSize());
|
||||
++moved_files;
|
||||
moved_bytes += f->fd.GetFileSize();
|
||||
}
|
||||
}
|
||||
|
||||
status = versions_->LogAndApply(c->column_family_data(),
|
||||
*c->mutable_cf_options(), c->edit(),
|
||||
&mutex_, directories_.GetDbDir());
|
||||
@@ -2579,24 +2678,22 @@ Status DBImpl::BackgroundCompaction(bool* madeProgress, JobContext* job_context,
|
||||
// Clear Instrument
|
||||
ThreadStatusUtil::ResetThreadStatus();
|
||||
} else {
|
||||
TEST_SYNC_POINT("DBImpl::BackgroundCompaction:NonTrivial");
|
||||
auto yield_callback = [&]() {
|
||||
return CallFlushDuringCompaction(c->column_family_data(),
|
||||
*c->mutable_cf_options(), job_context,
|
||||
log_buffer);
|
||||
};
|
||||
int output_level __attribute__((unused)) = c->output_level();
|
||||
TEST_SYNC_POINT_CALLBACK("DBImpl::BackgroundCompaction:NonTrivial",
|
||||
&output_level);
|
||||
assert(is_snapshot_supported_ || snapshots_.empty());
|
||||
CompactionJob compaction_job(
|
||||
job_context->job_id, c.get(), db_options_, env_options_,
|
||||
versions_.get(), &shutting_down_, log_buffer, directories_.GetDbDir(),
|
||||
directories_.GetDataDir(c->GetOutputPathId()), stats_,
|
||||
snapshots_.GetAll(), table_cache_, std::move(yield_callback),
|
||||
&event_logger_, c->mutable_cf_options()->paranoid_file_checks,
|
||||
dbname_, &compaction_job_stats);
|
||||
directories_.GetDataDir(c->output_path_id()), stats_,
|
||||
snapshots_.GetAll(), table_cache_, &event_logger_,
|
||||
c->mutable_cf_options()->paranoid_file_checks, dbname_,
|
||||
&compaction_job_stats);
|
||||
compaction_job.Prepare();
|
||||
|
||||
mutex_.Unlock();
|
||||
status = compaction_job.Run();
|
||||
TEST_SYNC_POINT("DBImpl::BackgroundCompaction:NonTrivial:AfterRun");
|
||||
mutex_.Lock();
|
||||
|
||||
compaction_job.Install(&status, *c->mutable_cf_options(), &mutex_);
|
||||
@@ -2667,30 +2764,6 @@ Status DBImpl::BackgroundCompaction(bool* madeProgress, JobContext* job_context,
|
||||
return status;
|
||||
}
|
||||
|
||||
uint64_t DBImpl::CallFlushDuringCompaction(
|
||||
ColumnFamilyData* cfd, const MutableCFOptions& mutable_cf_options,
|
||||
JobContext* job_context, LogBuffer* log_buffer) {
|
||||
if (db_options_.max_background_flushes > 0) {
|
||||
// flush thread will take care of this
|
||||
return 0;
|
||||
}
|
||||
if (cfd->imm()->imm_flush_needed.load(std::memory_order_relaxed)) {
|
||||
const uint64_t imm_start = env_->NowMicros();
|
||||
mutex_.Lock();
|
||||
if (cfd->imm()->IsFlushPending()) {
|
||||
cfd->Ref();
|
||||
FlushMemTableToOutputFile(cfd, mutable_cf_options, nullptr, job_context,
|
||||
log_buffer);
|
||||
cfd->Unref();
|
||||
bg_cv_.SignalAll(); // Wakeup DelayWrite() if necessary
|
||||
}
|
||||
mutex_.Unlock();
|
||||
log_buffer->FlushBufferToLog();
|
||||
return env_->NowMicros() - imm_start;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
namespace {
|
||||
struct IterState {
|
||||
IterState(DBImpl* _db, InstrumentedMutex* _mu, SuperVersion* _super_version)
|
||||
@@ -3003,8 +3076,8 @@ Status DBImpl::CreateColumnFamily(const ColumnFamilyOptions& cf_options,
|
||||
Options opt(db_options_, cf_options);
|
||||
{ // write thread
|
||||
WriteThread::Writer w(&mutex_);
|
||||
s = write_thread_.EnterWriteThread(&w, 0);
|
||||
assert(s.ok() && !w.done); // No timeout and nobody should do our job
|
||||
write_thread_.EnterWriteThread(&w);
|
||||
assert(!w.done); // Nobody should do our job
|
||||
// LogAndApply will both write the creation in MANIFEST and create
|
||||
// ColumnFamilyData object
|
||||
s = versions_->LogAndApply(
|
||||
@@ -3065,8 +3138,8 @@ Status DBImpl::DropColumnFamily(ColumnFamilyHandle* column_family) {
|
||||
if (s.ok()) {
|
||||
// we drop column family from a single write thread
|
||||
WriteThread::Writer w(&mutex_);
|
||||
s = write_thread_.EnterWriteThread(&w, 0);
|
||||
assert(s.ok() && !w.done); // No timeout and nobody should do our job
|
||||
write_thread_.EnterWriteThread(&w);
|
||||
assert(!w.done); // Nobody should do our job
|
||||
s = versions_->LogAndApply(cfd, *cfd->GetLatestMutableCFOptions(),
|
||||
&edit, &mutex_);
|
||||
write_thread_.ExitWriteThread(&w, &w, s);
|
||||
@@ -3353,6 +3426,9 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options,
|
||||
if (my_batch == nullptr) {
|
||||
return Status::Corruption("Batch is nullptr!");
|
||||
}
|
||||
if (write_options.timeout_hint_us != 0) {
|
||||
return Status::InvalidArgument("timeout_hint_us is deprecated");
|
||||
}
|
||||
|
||||
Status status;
|
||||
bool xfunc_attempted_write = false;
|
||||
@@ -3373,16 +3449,6 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options,
|
||||
w.in_batch_group = false;
|
||||
w.done = false;
|
||||
w.has_callback = (callback != nullptr) ? true : false;
|
||||
w.timeout_hint_us = write_options.timeout_hint_us;
|
||||
|
||||
uint64_t expiration_time = 0;
|
||||
bool has_timeout = false;
|
||||
if (w.timeout_hint_us == 0) {
|
||||
w.timeout_hint_us = WriteThread::kNoTimeOut;
|
||||
} else {
|
||||
expiration_time = env_->NowMicros() + w.timeout_hint_us;
|
||||
has_timeout = true;
|
||||
}
|
||||
|
||||
if (!write_options.disableWAL) {
|
||||
RecordTick(stats_, WRITE_WITH_WAL);
|
||||
@@ -3397,13 +3463,7 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options,
|
||||
default_cf_internal_stats_->AddDBStats(InternalStats::WRITE_WITH_WAL, 1);
|
||||
}
|
||||
|
||||
status = write_thread_.EnterWriteThread(&w, expiration_time);
|
||||
assert(status.ok() || status.IsTimedOut());
|
||||
if (status.IsTimedOut()) {
|
||||
mutex_.Unlock();
|
||||
RecordTick(stats_, WRITE_TIMEDOUT);
|
||||
return Status::TimedOut();
|
||||
}
|
||||
write_thread_.EnterWriteThread(&w);
|
||||
if (w.done) { // write was done by someone else
|
||||
default_cf_internal_stats_->AddDBStats(InternalStats::WRITE_DONE_BY_OTHER,
|
||||
1);
|
||||
@@ -3489,23 +3549,30 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options,
|
||||
// for previous one. It might create a fairness issue that expiration
|
||||
// might happen for smaller writes but larger writes can go through.
|
||||
// Can optimize it if it is an issue.
|
||||
status = DelayWrite(last_batch_group_size_, expiration_time);
|
||||
status = DelayWrite(last_batch_group_size_);
|
||||
PERF_TIMER_START(write_pre_and_post_process_time);
|
||||
}
|
||||
|
||||
if (UNLIKELY(status.ok() && has_timeout &&
|
||||
env_->NowMicros() > expiration_time)) {
|
||||
status = Status::TimedOut();
|
||||
}
|
||||
|
||||
uint64_t last_sequence = versions_->LastSequence();
|
||||
WriteThread::Writer* last_writer = &w;
|
||||
autovector<WriteBatch*> write_batch_group;
|
||||
bool need_log_sync = !write_options.disableWAL && write_options.sync;
|
||||
bool need_log_dir_sync = need_log_sync && !log_dir_synced_;
|
||||
|
||||
if (status.ok()) {
|
||||
last_batch_group_size_ =
|
||||
write_thread_.BuildBatchGroup(&last_writer, &write_batch_group);
|
||||
|
||||
if (need_log_sync) {
|
||||
while (logs_.front().getting_synced) {
|
||||
log_sync_cv_.Wait();
|
||||
}
|
||||
for (auto& log : logs_) {
|
||||
assert(!log.getting_synced);
|
||||
log.getting_synced = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Add to log and apply to memtable. We can release the lock
|
||||
// during this phase since &w is currently responsible for logging
|
||||
// and protects against concurrent loggers and concurrent writes
|
||||
@@ -3552,27 +3619,34 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options,
|
||||
if (!write_options.disableWAL) {
|
||||
PERF_TIMER_GUARD(write_wal_time);
|
||||
Slice log_entry = WriteBatchInternal::Contents(updates);
|
||||
status = log_->AddRecord(log_entry);
|
||||
status = logs_.back().writer->AddRecord(log_entry);
|
||||
total_log_size_ += log_entry.size();
|
||||
alive_log_files_.back().AddSize(log_entry.size());
|
||||
log_empty_ = false;
|
||||
log_size = log_entry.size();
|
||||
RecordTick(stats_, WAL_FILE_BYTES, log_size);
|
||||
if (status.ok() && write_options.sync) {
|
||||
if (status.ok() && need_log_sync) {
|
||||
RecordTick(stats_, WAL_FILE_SYNCED);
|
||||
StopWatch sw(env_, stats_, WAL_FILE_SYNC_MICROS);
|
||||
if (db_options_.use_fsync) {
|
||||
status = log_->file()->Fsync();
|
||||
} else {
|
||||
status = log_->file()->Sync();
|
||||
// It's safe to access logs_ with unlocked mutex_ here because:
|
||||
// - we've set getting_synced=true for all logs,
|
||||
// so other threads won't pop from logs_ while we're here,
|
||||
// - only writer thread can push to logs_, and we're in
|
||||
// writer thread, so no one will push to logs_,
|
||||
// - as long as other threads don't modify it, it's safe to read
|
||||
// from std::deque from multiple threads concurrently.
|
||||
for (auto& log : logs_) {
|
||||
status = log.writer->file()->Sync(db_options_.use_fsync);
|
||||
if (!status.ok()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (status.ok() && !log_dir_synced_) {
|
||||
if (status.ok() && need_log_dir_sync) {
|
||||
// We only sync WAL directory the first time WAL syncing is
|
||||
// requested, so that in case users never turn on WAL sync,
|
||||
// we can avoid the disk I/O in the write code path.
|
||||
status = directories_.GetWalDir()->Fsync();
|
||||
}
|
||||
log_dir_synced_ = true;
|
||||
}
|
||||
}
|
||||
if (status.ok()) {
|
||||
@@ -3614,68 +3688,47 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options,
|
||||
} else {
|
||||
// Operation failed. Make sure sure mutex is held for cleanup code below.
|
||||
mutex_.Lock();
|
||||
}
|
||||
}
|
||||
|
||||
if (db_options_.paranoid_checks && !status.ok() && !status.IsTimedOut() &&
|
||||
!status.IsBusy() && bg_error_.ok()) {
|
||||
if (db_options_.paranoid_checks && !status.ok() &&
|
||||
!status.IsBusy() && bg_error_.ok()) {
|
||||
bg_error_ = status; // stop compaction & fail any further writes
|
||||
}
|
||||
|
||||
mutex_.AssertHeld();
|
||||
|
||||
if (need_log_sync) {
|
||||
MarkLogsSynced(logfile_number_, need_log_dir_sync, status);
|
||||
}
|
||||
|
||||
write_thread_.ExitWriteThread(&w, last_writer, status);
|
||||
|
||||
mutex_.Unlock();
|
||||
|
||||
if (status.IsTimedOut()) {
|
||||
RecordTick(stats_, WRITE_TIMEDOUT);
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
// REQUIRES: mutex_ is held
|
||||
// REQUIRES: this thread is currently at the front of the writer queue
|
||||
Status DBImpl::DelayWrite(uint64_t num_bytes, uint64_t expiration_time) {
|
||||
Status DBImpl::DelayWrite(uint64_t num_bytes) {
|
||||
uint64_t time_delayed = 0;
|
||||
bool delayed = false;
|
||||
bool timed_out = false;
|
||||
{
|
||||
StopWatch sw(env_, stats_, WRITE_STALL, &time_delayed);
|
||||
bool has_timeout = (expiration_time > 0);
|
||||
auto delay = write_controller_.GetDelay(env_, num_bytes);
|
||||
if (delay > 0) {
|
||||
mutex_.Unlock();
|
||||
delayed = true;
|
||||
// hopefully we don't have to sleep more than 2 billion microseconds
|
||||
TEST_SYNC_POINT("DBImpl::DelayWrite:Sleep");
|
||||
if (has_timeout) {
|
||||
auto time_now = env_->NowMicros();
|
||||
if (time_now + delay >= expiration_time) {
|
||||
if (expiration_time > time_now) {
|
||||
env_->SleepForMicroseconds(
|
||||
static_cast<int>(expiration_time - time_now));
|
||||
}
|
||||
timed_out = true;
|
||||
}
|
||||
}
|
||||
if (!timed_out) {
|
||||
env_->SleepForMicroseconds(static_cast<int>(delay));
|
||||
}
|
||||
// hopefully we don't have to sleep more than 2 billion microseconds
|
||||
env_->SleepForMicroseconds(static_cast<int>(delay));
|
||||
mutex_.Lock();
|
||||
}
|
||||
|
||||
while (!timed_out && bg_error_.ok() && write_controller_.IsStopped()) {
|
||||
while (bg_error_.ok() && write_controller_.IsStopped()) {
|
||||
delayed = true;
|
||||
if (has_timeout) {
|
||||
TEST_SYNC_POINT("DBImpl::DelayWrite:TimedWait");
|
||||
bg_cv_.TimedWait(expiration_time);
|
||||
if (env_->NowMicros() > expiration_time) {
|
||||
timed_out = true;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
bg_cv_.Wait();
|
||||
}
|
||||
TEST_SYNC_POINT("DBImpl::DelayWrite:Wait");
|
||||
bg_cv_.Wait();
|
||||
}
|
||||
}
|
||||
if (delayed) {
|
||||
@@ -3684,10 +3737,6 @@ Status DBImpl::DelayWrite(uint64_t num_bytes, uint64_t expiration_time) {
|
||||
RecordTick(stats_, STALL_MICROS, time_delayed);
|
||||
}
|
||||
|
||||
if (timed_out) {
|
||||
return Status::TimedOut();
|
||||
}
|
||||
|
||||
return bg_error_;
|
||||
}
|
||||
|
||||
@@ -3725,16 +3774,19 @@ Status DBImpl::SwitchMemtable(ColumnFamilyData* cfd, WriteContext* context) {
|
||||
Status s;
|
||||
{
|
||||
if (creating_new_log) {
|
||||
EnvOptions opt_env_opt =
|
||||
env_->OptimizeForLogWrite(env_options_, db_options_);
|
||||
s = env_->NewWritableFile(
|
||||
LogFileName(db_options_.wal_dir, new_log_number), &lfile,
|
||||
env_->OptimizeForLogWrite(env_options_, db_options_));
|
||||
opt_env_opt);
|
||||
if (s.ok()) {
|
||||
// Our final size should be less than write_buffer_size
|
||||
// (compression, etc) but err on the side of caution.
|
||||
lfile->SetPreallocationBlockSize(
|
||||
1.1 * mutable_cf_options.write_buffer_size);
|
||||
new_log = new log::Writer(std::move(lfile));
|
||||
log_dir_synced_ = false;
|
||||
unique_ptr<WritableFileWriter> file_writer(
|
||||
new WritableFileWriter(std::move(lfile), opt_env_opt));
|
||||
new_log = new log::Writer(std::move(file_writer));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3758,9 +3810,9 @@ Status DBImpl::SwitchMemtable(ColumnFamilyData* cfd, WriteContext* context) {
|
||||
if (creating_new_log) {
|
||||
logfile_number_ = new_log_number;
|
||||
assert(new_log != nullptr);
|
||||
logs_to_free_.push_back(log_.release());
|
||||
log_.reset(new_log);
|
||||
log_empty_ = true;
|
||||
log_dir_synced_ = false;
|
||||
logs_.emplace_back(logfile_number_, new_log);
|
||||
alive_log_files_.push_back(LogFileNumberSize(logfile_number_));
|
||||
for (auto loop_cfd : *versions_->GetColumnFamilySet()) {
|
||||
// all this is just optimization to delete logs that
|
||||
@@ -4132,20 +4184,26 @@ Status DBImpl::CheckConsistency() {
|
||||
|
||||
Status DBImpl::GetDbIdentity(std::string& identity) const {
|
||||
std::string idfilename = IdentityFileName(dbname_);
|
||||
unique_ptr<SequentialFile> idfile;
|
||||
const EnvOptions soptions;
|
||||
Status s = env_->NewSequentialFile(idfilename, &idfile, soptions);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
unique_ptr<SequentialFileReader> id_file_reader;
|
||||
Status s;
|
||||
{
|
||||
unique_ptr<SequentialFile> idfile;
|
||||
s = env_->NewSequentialFile(idfilename, &idfile, soptions);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
id_file_reader.reset(new SequentialFileReader(std::move(idfile)));
|
||||
}
|
||||
|
||||
uint64_t file_size;
|
||||
s = env_->GetFileSize(idfilename, &file_size);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
char buffer[file_size];
|
||||
char* buffer = reinterpret_cast<char*>(alloca(file_size));
|
||||
Slice id;
|
||||
s = idfile->Read(static_cast<size_t>(file_size), &id, buffer);
|
||||
s = id_file_reader->Read(static_cast<size_t>(file_size), &id, buffer);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
@@ -4277,14 +4335,18 @@ Status DB::Open(const DBOptions& db_options, const std::string& dbname,
|
||||
uint64_t new_log_number = impl->versions_->NewFileNumber();
|
||||
unique_ptr<WritableFile> lfile;
|
||||
EnvOptions soptions(db_options);
|
||||
EnvOptions opt_env_options =
|
||||
impl->db_options_.env->OptimizeForLogWrite(soptions, impl->db_options_);
|
||||
s = impl->db_options_.env->NewWritableFile(
|
||||
LogFileName(impl->db_options_.wal_dir, new_log_number), &lfile,
|
||||
impl->db_options_.env->OptimizeForLogWrite(soptions,
|
||||
impl->db_options_));
|
||||
opt_env_options);
|
||||
if (s.ok()) {
|
||||
lfile->SetPreallocationBlockSize(1.1 * max_write_buffer_size);
|
||||
impl->logfile_number_ = new_log_number;
|
||||
impl->log_.reset(new log::Writer(std::move(lfile)));
|
||||
unique_ptr<WritableFileWriter> file_writer(
|
||||
new WritableFileWriter(std::move(lfile), opt_env_options));
|
||||
impl->logs_.emplace_back(new_log_number,
|
||||
new log::Writer(std::move(file_writer)));
|
||||
|
||||
// set column family handles
|
||||
for (auto cf : column_families) {
|
||||
@@ -4353,11 +4415,14 @@ Status DB::Open(const DBOptions& db_options, const std::string& dbname,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_SYNC_POINT("DBImpl::Open:Opened");
|
||||
if (s.ok()) {
|
||||
impl->opened_successfully_ = true;
|
||||
impl->MaybeScheduleFlushOrCompaction();
|
||||
}
|
||||
impl->mutex_.Unlock();
|
||||
|
||||
if (s.ok()) {
|
||||
impl->opened_successfully_ = true;
|
||||
Log(InfoLogLevel::INFO_LEVEL, impl->db_options_.info_log, "DB pointer %p",
|
||||
impl);
|
||||
*dbptr = impl;
|
||||
|
||||
+45
-12
@@ -158,6 +158,7 @@ class DBImpl : public DB {
|
||||
using DB::Flush;
|
||||
virtual Status Flush(const FlushOptions& options,
|
||||
ColumnFamilyHandle* column_family) override;
|
||||
virtual Status SyncWAL() override;
|
||||
|
||||
virtual SequenceNumber GetLatestSequenceNumber() const override;
|
||||
|
||||
@@ -290,6 +291,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
|
||||
@@ -435,7 +438,7 @@ class DBImpl : public DB {
|
||||
|
||||
// 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 DelayWrite(uint64_t num_bytes);
|
||||
|
||||
Status ScheduleFlushes(WriteContext* context);
|
||||
|
||||
@@ -472,13 +475,6 @@ class DBImpl : public DB {
|
||||
Status BackgroundFlush(bool* madeProgress, JobContext* job_context,
|
||||
LogBuffer* log_buffer);
|
||||
|
||||
// This function is called as part of compaction. It enables Flush process to
|
||||
// preempt compaction, since it's higher prioirty
|
||||
uint64_t CallFlushDuringCompaction(ColumnFamilyData* cfd,
|
||||
const MutableCFOptions& mutable_cf_options,
|
||||
JobContext* job_context,
|
||||
LogBuffer* log_buffer);
|
||||
|
||||
void PrintStatistics();
|
||||
|
||||
// dump rocksdb.stats to LOG
|
||||
@@ -500,6 +496,9 @@ class DBImpl : public DB {
|
||||
void AddToFlushQueue(ColumnFamilyData* cfd);
|
||||
ColumnFamilyData* PopFirstFromFlushQueue();
|
||||
|
||||
// helper function to call after some of the logs_ were synced
|
||||
void MarkLogsSynced(uint64_t up_to, bool synced_dir, const Status& status);
|
||||
|
||||
// table_cache_ provides its own synchronization
|
||||
std::shared_ptr<Cache> table_cache_;
|
||||
|
||||
@@ -519,7 +518,6 @@ class DBImpl : public DB {
|
||||
// * whenever there is an error in background flush or compaction
|
||||
InstrumentedCondVar bg_cv_;
|
||||
uint64_t logfile_number_;
|
||||
unique_ptr<log::Writer> log_;
|
||||
bool log_dir_synced_;
|
||||
bool log_empty_;
|
||||
ColumnFamilyHandleImpl* default_cf_handle_;
|
||||
@@ -527,13 +525,45 @@ class DBImpl : public DB {
|
||||
unique_ptr<ColumnFamilyMemTablesImpl> column_family_memtables_;
|
||||
struct LogFileNumberSize {
|
||||
explicit LogFileNumberSize(uint64_t _number)
|
||||
: number(_number), size(0), getting_flushed(false) {}
|
||||
: number(_number) {}
|
||||
void AddSize(uint64_t new_size) { size += new_size; }
|
||||
uint64_t number;
|
||||
uint64_t size;
|
||||
bool getting_flushed;
|
||||
uint64_t size = 0;
|
||||
bool getting_flushed = false;
|
||||
};
|
||||
struct LogWriterNumber {
|
||||
// pass ownership of _writer
|
||||
LogWriterNumber(uint64_t _number, log::Writer* _writer)
|
||||
: number(_number), writer(_writer) {}
|
||||
|
||||
log::Writer* ReleaseWriter() {
|
||||
auto* w = writer;
|
||||
writer = nullptr;
|
||||
return w;
|
||||
}
|
||||
void ClearWriter() {
|
||||
delete writer;
|
||||
writer = nullptr;
|
||||
}
|
||||
|
||||
uint64_t number;
|
||||
// Visual Studio doesn't support deque's member to be noncopyable because
|
||||
// of a unique_ptr as a member.
|
||||
log::Writer* writer; // own
|
||||
// true for some prefix of logs_
|
||||
bool getting_synced = false;
|
||||
};
|
||||
std::deque<LogFileNumberSize> alive_log_files_;
|
||||
// Log files that aren't fully synced, and the current log file.
|
||||
// Synchronization:
|
||||
// - push_back() is done from write thread with locked mutex_,
|
||||
// - pop_front() is done from any thread with locked mutex_,
|
||||
// - back() and items with getting_synced=true are not popped,
|
||||
// - it follows that write thread with unlocked mutex_ can safely access
|
||||
// back() and items with getting_synced=true.
|
||||
std::deque<LogWriterNumber> logs_;
|
||||
// Signaled when getting_synced becomes false for some of the logs_.
|
||||
InstrumentedCondVar log_sync_cv_;
|
||||
uint64_t total_log_size_;
|
||||
// only used for dynamically adjusting max_total_wal_size. it is a sum of
|
||||
// [write_buffer_size * max_write_buffer_number] over all column families
|
||||
@@ -681,6 +711,9 @@ class DBImpl : public DB {
|
||||
bool flush_on_destroy_; // Used when disableWAL is true.
|
||||
|
||||
static const int KEEP_LOG_FILE_NUM = 1000;
|
||||
// MSVC version 1800 still does not have constexpr for ::max()
|
||||
static const uint64_t kNoTimeOut = port::kMaxUint64;
|
||||
|
||||
std::string db_absolute_path_;
|
||||
|
||||
// The options to access storage files
|
||||
|
||||
+7
-2
@@ -132,8 +132,8 @@ void DBImpl::TEST_UnlockMutex() {
|
||||
|
||||
void* DBImpl::TEST_BeginWrite() {
|
||||
auto w = new WriteThread::Writer(&mutex_);
|
||||
Status s = write_thread_.EnterWriteThread(w, 0);
|
||||
assert(s.ok() && !w->done); // No timeout and nobody should do our job
|
||||
write_thread_.EnterWriteThread(w);
|
||||
assert(!w->done); // Nobody should do our job
|
||||
return reinterpret_cast<void*>(w);
|
||||
}
|
||||
|
||||
@@ -148,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
|
||||
@@ -5,7 +5,8 @@
|
||||
|
||||
|
||||
#include "db/db_impl_readonly.h"
|
||||
#include "utilities/compacted_db/compacted_db_impl.h"
|
||||
|
||||
#include "db/compacted_db_impl.h"
|
||||
#include "db/db_impl.h"
|
||||
#include "db/merge_context.h"
|
||||
#include "db/db_iter.h"
|
||||
|
||||
@@ -0,0 +1,171 @@
|
||||
// Copyright (c) 2013, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the root directory of this source tree. An additional grant
|
||||
// of patent rights can be found in the PATENTS file in the same directory.
|
||||
//
|
||||
// Copyright (c) 2011 The LevelDB Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
#include "port/stack_trace.h"
|
||||
#include "util/db_test_util.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
class DBTestInPlaceUpdate : public DBTestBase {
|
||||
public:
|
||||
DBTestInPlaceUpdate() : DBTestBase("/db_inplace_update_test") {}
|
||||
};
|
||||
|
||||
TEST_F(DBTestInPlaceUpdate, InPlaceUpdate) {
|
||||
do {
|
||||
Options options;
|
||||
options.create_if_missing = true;
|
||||
options.inplace_update_support = true;
|
||||
options.env = env_;
|
||||
options.write_buffer_size = 100000;
|
||||
options = CurrentOptions(options);
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
|
||||
// Update key with values of smaller size
|
||||
int numValues = 10;
|
||||
for (int i = numValues; i > 0; i--) {
|
||||
std::string value = DummyString(i, 'a');
|
||||
ASSERT_OK(Put(1, "key", value));
|
||||
ASSERT_EQ(value, Get(1, "key"));
|
||||
}
|
||||
|
||||
// Only 1 instance for that key.
|
||||
validateNumberOfEntries(1, 1);
|
||||
} while (ChangeCompactOptions());
|
||||
}
|
||||
|
||||
TEST_F(DBTestInPlaceUpdate, InPlaceUpdateLargeNewValue) {
|
||||
do {
|
||||
Options options;
|
||||
options.create_if_missing = true;
|
||||
options.inplace_update_support = true;
|
||||
options.env = env_;
|
||||
options.write_buffer_size = 100000;
|
||||
options = CurrentOptions(options);
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
|
||||
// Update key with values of larger size
|
||||
int numValues = 10;
|
||||
for (int i = 0; i < numValues; i++) {
|
||||
std::string value = DummyString(i, 'a');
|
||||
ASSERT_OK(Put(1, "key", value));
|
||||
ASSERT_EQ(value, Get(1, "key"));
|
||||
}
|
||||
|
||||
// All 10 updates exist in the internal iterator
|
||||
validateNumberOfEntries(numValues, 1);
|
||||
} while (ChangeCompactOptions());
|
||||
}
|
||||
|
||||
TEST_F(DBTestInPlaceUpdate, InPlaceUpdateCallbackSmallerSize) {
|
||||
do {
|
||||
Options options;
|
||||
options.create_if_missing = true;
|
||||
options.inplace_update_support = true;
|
||||
|
||||
options.env = env_;
|
||||
options.write_buffer_size = 100000;
|
||||
options.inplace_callback =
|
||||
rocksdb::DBTestInPlaceUpdate::updateInPlaceSmallerSize;
|
||||
options = CurrentOptions(options);
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
|
||||
// Update key with values of smaller size
|
||||
int numValues = 10;
|
||||
ASSERT_OK(Put(1, "key", DummyString(numValues, 'a')));
|
||||
ASSERT_EQ(DummyString(numValues, 'c'), Get(1, "key"));
|
||||
|
||||
for (int i = numValues; i > 0; i--) {
|
||||
ASSERT_OK(Put(1, "key", DummyString(i, 'a')));
|
||||
ASSERT_EQ(DummyString(i - 1, 'b'), Get(1, "key"));
|
||||
}
|
||||
|
||||
// Only 1 instance for that key.
|
||||
validateNumberOfEntries(1, 1);
|
||||
} while (ChangeCompactOptions());
|
||||
}
|
||||
|
||||
TEST_F(DBTestInPlaceUpdate, InPlaceUpdateCallbackSmallerVarintSize) {
|
||||
do {
|
||||
Options options;
|
||||
options.create_if_missing = true;
|
||||
options.inplace_update_support = true;
|
||||
|
||||
options.env = env_;
|
||||
options.write_buffer_size = 100000;
|
||||
options.inplace_callback =
|
||||
rocksdb::DBTestInPlaceUpdate::updateInPlaceSmallerVarintSize;
|
||||
options = CurrentOptions(options);
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
|
||||
// Update key with values of smaller varint size
|
||||
int numValues = 265;
|
||||
ASSERT_OK(Put(1, "key", DummyString(numValues, 'a')));
|
||||
ASSERT_EQ(DummyString(numValues, 'c'), Get(1, "key"));
|
||||
|
||||
for (int i = numValues; i > 0; i--) {
|
||||
ASSERT_OK(Put(1, "key", DummyString(i, 'a')));
|
||||
ASSERT_EQ(DummyString(1, 'b'), Get(1, "key"));
|
||||
}
|
||||
|
||||
// Only 1 instance for that key.
|
||||
validateNumberOfEntries(1, 1);
|
||||
} while (ChangeCompactOptions());
|
||||
}
|
||||
|
||||
TEST_F(DBTestInPlaceUpdate, InPlaceUpdateCallbackLargeNewValue) {
|
||||
do {
|
||||
Options options;
|
||||
options.create_if_missing = true;
|
||||
options.inplace_update_support = true;
|
||||
|
||||
options.env = env_;
|
||||
options.write_buffer_size = 100000;
|
||||
options.inplace_callback =
|
||||
rocksdb::DBTestInPlaceUpdate::updateInPlaceLargerSize;
|
||||
options = CurrentOptions(options);
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
|
||||
// Update key with values of larger size
|
||||
int numValues = 10;
|
||||
for (int i = 0; i < numValues; i++) {
|
||||
ASSERT_OK(Put(1, "key", DummyString(i, 'a')));
|
||||
ASSERT_EQ(DummyString(i, 'c'), Get(1, "key"));
|
||||
}
|
||||
|
||||
// No inplace updates. All updates are puts with new seq number
|
||||
// All 10 updates exist in the internal iterator
|
||||
validateNumberOfEntries(numValues, 1);
|
||||
} while (ChangeCompactOptions());
|
||||
}
|
||||
|
||||
TEST_F(DBTestInPlaceUpdate, InPlaceUpdateCallbackNoAction) {
|
||||
do {
|
||||
Options options;
|
||||
options.create_if_missing = true;
|
||||
options.inplace_update_support = true;
|
||||
|
||||
options.env = env_;
|
||||
options.write_buffer_size = 100000;
|
||||
options.inplace_callback =
|
||||
rocksdb::DBTestInPlaceUpdate::updateInPlaceNoAction;
|
||||
options = CurrentOptions(options);
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
|
||||
// Callback function requests no actions from db
|
||||
ASSERT_OK(Put(1, "key", DummyString(1, 'a')));
|
||||
ASSERT_EQ(Get(1, "key"), "NOT_FOUND");
|
||||
} while (ChangeCompactOptions());
|
||||
}
|
||||
} // namespace rocksdb
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
rocksdb::port::InstallStackTraceHandler();
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
+32
-7
@@ -115,6 +115,7 @@ class DBIter: public Iterator {
|
||||
virtual void SeekToLast() override;
|
||||
|
||||
private:
|
||||
void ReverseToBackward();
|
||||
void PrevInternal();
|
||||
void FindParseableKey(ParsedInternalKey* ikey, Direction direction);
|
||||
bool FindValueForCurrentKey();
|
||||
@@ -255,12 +256,13 @@ void DBIter::FindNextUserEntryInternal(bool skipping) {
|
||||
// If we have sequentially iterated via numerous keys and still not
|
||||
// found the next user-key, then it is better to seek so that we can
|
||||
// avoid too many key comparisons. We seek to the last occurrence of
|
||||
// our current key by looking for sequence number 0.
|
||||
// our current key by looking for sequence number 0 and type deletion
|
||||
// (the smallest type).
|
||||
if (skipping && num_skipped > max_skip_) {
|
||||
num_skipped = 0;
|
||||
std::string last_key;
|
||||
AppendInternalKey(&last_key, ParsedInternalKey(saved_key_.GetKey(), 0,
|
||||
kValueTypeForSeek));
|
||||
kTypeDeletion));
|
||||
iter_->Seek(last_key);
|
||||
RecordTick(statistics_, NUMBER_OF_RESEEKS_IN_ITERATION);
|
||||
} else {
|
||||
@@ -350,13 +352,36 @@ void DBIter::MergeValuesNewToOld() {
|
||||
void DBIter::Prev() {
|
||||
assert(valid_);
|
||||
if (direction_ == kForward) {
|
||||
ReverseToBackward();
|
||||
}
|
||||
PrevInternal();
|
||||
}
|
||||
|
||||
void DBIter::ReverseToBackward() {
|
||||
if (current_entry_is_merged_) {
|
||||
// Not placed in the same key. Need to call Prev() until finding the
|
||||
// previous key.
|
||||
if (!iter_->Valid()) {
|
||||
iter_->SeekToLast();
|
||||
}
|
||||
FindPrevUserKey();
|
||||
direction_ = kReverse;
|
||||
ParsedInternalKey ikey;
|
||||
FindParseableKey(&ikey, kReverse);
|
||||
while (iter_->Valid() &&
|
||||
user_comparator_->Compare(ikey.user_key, saved_key_.GetKey()) > 0) {
|
||||
iter_->Prev();
|
||||
FindParseableKey(&ikey, kReverse);
|
||||
}
|
||||
}
|
||||
PrevInternal();
|
||||
#ifndef NDEBUG
|
||||
if (iter_->Valid()) {
|
||||
ParsedInternalKey ikey;
|
||||
assert(ParseKey(&ikey));
|
||||
assert(user_comparator_->Compare(ikey.user_key, saved_key_.GetKey()) <= 0);
|
||||
}
|
||||
#endif
|
||||
|
||||
FindPrevUserKey();
|
||||
direction_ = kReverse;
|
||||
}
|
||||
|
||||
void DBIter::PrevInternal() {
|
||||
@@ -556,7 +581,7 @@ void DBIter::FindNextUserKey() {
|
||||
ParsedInternalKey ikey;
|
||||
FindParseableKey(&ikey, kForward);
|
||||
while (iter_->Valid() &&
|
||||
user_comparator_->Compare(ikey.user_key, saved_key_.GetKey()) <= 0) {
|
||||
user_comparator_->Compare(ikey.user_key, saved_key_.GetKey()) != 0) {
|
||||
iter_->Next();
|
||||
FindParseableKey(&ikey, kForward);
|
||||
}
|
||||
@@ -571,7 +596,7 @@ void DBIter::FindPrevUserKey() {
|
||||
ParsedInternalKey ikey;
|
||||
FindParseableKey(&ikey, kReverse);
|
||||
while (iter_->Valid() &&
|
||||
user_comparator_->Compare(ikey.user_key, saved_key_.GetKey()) >= 0) {
|
||||
user_comparator_->Compare(ikey.user_key, saved_key_.GetKey()) == 0) {
|
||||
if (num_skipped >= max_skip_) {
|
||||
num_skipped = 0;
|
||||
IterKey last_key;
|
||||
|
||||
@@ -1644,6 +1644,7 @@ TEST_F(DBIteratorTest, DBIterator7) {
|
||||
ASSERT_TRUE(!db_iter->Valid());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBIteratorTest, DBIterator8) {
|
||||
Options options;
|
||||
options.merge_operator = MergeOperators::CreateFromStringId("stringappend");
|
||||
@@ -1668,6 +1669,8 @@ 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, DBIterator9) {
|
||||
Options options;
|
||||
options.merge_operator = MergeOperators::CreateFromStringId("stringappend");
|
||||
@@ -1716,6 +1719,8 @@ TEST_F(DBIteratorTest, DBIterator9) {
|
||||
}
|
||||
}
|
||||
|
||||
// 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, DBIterator10) {
|
||||
Options options;
|
||||
|
||||
@@ -1743,6 +1748,30 @@ TEST_F(DBIteratorTest, DBIterator10) {
|
||||
ASSERT_EQ(db_iter->value().ToString(), "3");
|
||||
}
|
||||
|
||||
TEST_F(DBIteratorTest, SeekToLastOccurrenceSeq0) {
|
||||
Options options;
|
||||
options.merge_operator = nullptr;
|
||||
|
||||
TestIterator* internal_iter = new TestIterator(BytewiseComparator());
|
||||
internal_iter->AddPut("a", "1");
|
||||
internal_iter->AddPut("b", "2");
|
||||
internal_iter->Finish();
|
||||
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
env_, ImmutableCFOptions(options), BytewiseComparator(), internal_iter,
|
||||
10, 0 /* force seek */));
|
||||
db_iter->SeekToFirst();
|
||||
ASSERT_TRUE(db_iter->Valid());
|
||||
ASSERT_EQ(db_iter->key().ToString(), "a");
|
||||
ASSERT_EQ(db_iter->value().ToString(), "1");
|
||||
db_iter->Next();
|
||||
ASSERT_TRUE(db_iter->Valid());
|
||||
ASSERT_EQ(db_iter->key().ToString(), "b");
|
||||
ASSERT_EQ(db_iter->value().ToString(), "2");
|
||||
db_iter->Next();
|
||||
ASSERT_FALSE(db_iter->Valid());
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
@@ -0,0 +1,290 @@
|
||||
// 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.
|
||||
|
||||
// Introduction of SyncPoint effectively disabled building and running this test
|
||||
// in Release build.
|
||||
// which is a pity, it is a good test
|
||||
#if !(defined NDEBUG) || !defined(OS_WIN)
|
||||
|
||||
#include "port/stack_trace.h"
|
||||
#include "util/db_test_util.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
class DBTestXactLogIterator : public DBTestBase {
|
||||
public:
|
||||
DBTestXactLogIterator() : DBTestBase("/db_log_iter_test") {}
|
||||
|
||||
std::unique_ptr<TransactionLogIterator> OpenTransactionLogIter(
|
||||
const SequenceNumber seq) {
|
||||
unique_ptr<TransactionLogIterator> iter;
|
||||
Status status = dbfull()->GetUpdatesSince(seq, &iter);
|
||||
EXPECT_OK(status);
|
||||
EXPECT_TRUE(iter->Valid());
|
||||
return std::move(iter);
|
||||
}
|
||||
};
|
||||
|
||||
namespace {
|
||||
SequenceNumber ReadRecords(
|
||||
std::unique_ptr<TransactionLogIterator>& iter,
|
||||
int& count) {
|
||||
count = 0;
|
||||
SequenceNumber lastSequence = 0;
|
||||
BatchResult res;
|
||||
while (iter->Valid()) {
|
||||
res = iter->GetBatch();
|
||||
EXPECT_TRUE(res.sequence > lastSequence);
|
||||
++count;
|
||||
lastSequence = res.sequence;
|
||||
EXPECT_OK(iter->status());
|
||||
iter->Next();
|
||||
}
|
||||
return res.sequence;
|
||||
}
|
||||
|
||||
void ExpectRecords(
|
||||
const int expected_no_records,
|
||||
std::unique_ptr<TransactionLogIterator>& iter) {
|
||||
int num_records;
|
||||
ReadRecords(iter, num_records);
|
||||
ASSERT_EQ(num_records, expected_no_records);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST_F(DBTestXactLogIterator, TransactionLogIterator) {
|
||||
do {
|
||||
Options options = OptionsForLogIterTest();
|
||||
DestroyAndReopen(options);
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
Put(0, "key1", DummyString(1024));
|
||||
Put(1, "key2", DummyString(1024));
|
||||
Put(1, "key2", DummyString(1024));
|
||||
ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 3U);
|
||||
{
|
||||
auto iter = OpenTransactionLogIter(0);
|
||||
ExpectRecords(3, iter);
|
||||
}
|
||||
ReopenWithColumnFamilies({"default", "pikachu"}, options);
|
||||
env_->SleepForMicroseconds(2 * 1000 * 1000);
|
||||
{
|
||||
Put(0, "key4", DummyString(1024));
|
||||
Put(1, "key5", DummyString(1024));
|
||||
Put(0, "key6", DummyString(1024));
|
||||
}
|
||||
{
|
||||
auto iter = OpenTransactionLogIter(0);
|
||||
ExpectRecords(6, iter);
|
||||
}
|
||||
} while (ChangeCompactOptions());
|
||||
}
|
||||
|
||||
#ifndef NDEBUG // sync point is not included with DNDEBUG build
|
||||
TEST_F(DBTestXactLogIterator, TransactionLogIteratorRace) {
|
||||
static const int LOG_ITERATOR_RACE_TEST_COUNT = 2;
|
||||
static const char* sync_points[LOG_ITERATOR_RACE_TEST_COUNT][4] = {
|
||||
{"WalManager::GetSortedWalFiles:1", "WalManager::PurgeObsoleteFiles:1",
|
||||
"WalManager::PurgeObsoleteFiles:2", "WalManager::GetSortedWalFiles:2"},
|
||||
{"WalManager::GetSortedWalsOfType:1",
|
||||
"WalManager::PurgeObsoleteFiles:1",
|
||||
"WalManager::PurgeObsoleteFiles:2",
|
||||
"WalManager::GetSortedWalsOfType:2"}};
|
||||
for (int test = 0; test < LOG_ITERATOR_RACE_TEST_COUNT; ++test) {
|
||||
// Setup sync point dependency to reproduce the race condition of
|
||||
// a log file moved to archived dir, in the middle of GetSortedWalFiles
|
||||
rocksdb::SyncPoint::GetInstance()->LoadDependency(
|
||||
{ { sync_points[test][0], sync_points[test][1] },
|
||||
{ sync_points[test][2], sync_points[test][3] },
|
||||
});
|
||||
|
||||
do {
|
||||
rocksdb::SyncPoint::GetInstance()->ClearTrace();
|
||||
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
||||
Options options = OptionsForLogIterTest();
|
||||
DestroyAndReopen(options);
|
||||
Put("key1", DummyString(1024));
|
||||
dbfull()->Flush(FlushOptions());
|
||||
Put("key2", DummyString(1024));
|
||||
dbfull()->Flush(FlushOptions());
|
||||
Put("key3", DummyString(1024));
|
||||
dbfull()->Flush(FlushOptions());
|
||||
Put("key4", DummyString(1024));
|
||||
ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 4U);
|
||||
|
||||
{
|
||||
auto iter = OpenTransactionLogIter(0);
|
||||
ExpectRecords(4, iter);
|
||||
}
|
||||
|
||||
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
||||
// trigger async flush, and log move. Well, log move will
|
||||
// wait until the GetSortedWalFiles:1 to reproduce the race
|
||||
// condition
|
||||
FlushOptions flush_options;
|
||||
flush_options.wait = false;
|
||||
dbfull()->Flush(flush_options);
|
||||
|
||||
// "key5" would be written in a new memtable and log
|
||||
Put("key5", DummyString(1024));
|
||||
{
|
||||
// this iter would miss "key4" if not fixed
|
||||
auto iter = OpenTransactionLogIter(0);
|
||||
ExpectRecords(5, iter);
|
||||
}
|
||||
} while (ChangeCompactOptions());
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST_F(DBTestXactLogIterator, TransactionLogIteratorStallAtLastRecord) {
|
||||
do {
|
||||
Options options = OptionsForLogIterTest();
|
||||
DestroyAndReopen(options);
|
||||
Put("key1", DummyString(1024));
|
||||
auto iter = OpenTransactionLogIter(0);
|
||||
ASSERT_OK(iter->status());
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
iter->Next();
|
||||
ASSERT_TRUE(!iter->Valid());
|
||||
ASSERT_OK(iter->status());
|
||||
Put("key2", DummyString(1024));
|
||||
iter->Next();
|
||||
ASSERT_OK(iter->status());
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
} while (ChangeCompactOptions());
|
||||
}
|
||||
|
||||
TEST_F(DBTestXactLogIterator, TransactionLogIteratorCheckAfterRestart) {
|
||||
do {
|
||||
Options options = OptionsForLogIterTest();
|
||||
DestroyAndReopen(options);
|
||||
Put("key1", DummyString(1024));
|
||||
Put("key2", DummyString(1023));
|
||||
dbfull()->Flush(FlushOptions());
|
||||
Reopen(options);
|
||||
auto iter = OpenTransactionLogIter(0);
|
||||
ExpectRecords(2, iter);
|
||||
} while (ChangeCompactOptions());
|
||||
}
|
||||
|
||||
TEST_F(DBTestXactLogIterator, TransactionLogIteratorCorruptedLog) {
|
||||
do {
|
||||
Options options = OptionsForLogIterTest();
|
||||
DestroyAndReopen(options);
|
||||
for (int i = 0; i < 1024; i++) {
|
||||
Put("key"+ToString(i), DummyString(10));
|
||||
}
|
||||
dbfull()->Flush(FlushOptions());
|
||||
// Corrupt this log to create a gap
|
||||
rocksdb::VectorLogPtr wal_files;
|
||||
ASSERT_OK(dbfull()->GetSortedWalFiles(wal_files));
|
||||
const auto logfile_path = dbname_ + "/" + wal_files.front()->PathName();
|
||||
if (mem_env_) {
|
||||
mem_env_->Truncate(logfile_path, wal_files.front()->SizeFileBytes() / 2);
|
||||
} else {
|
||||
ASSERT_EQ(0, truncate(logfile_path.c_str(),
|
||||
wal_files.front()->SizeFileBytes() / 2));
|
||||
}
|
||||
|
||||
// Insert a new entry to a new log file
|
||||
Put("key1025", DummyString(10));
|
||||
// Try to read from the beginning. Should stop before the gap and read less
|
||||
// than 1025 entries
|
||||
auto iter = OpenTransactionLogIter(0);
|
||||
int count;
|
||||
SequenceNumber last_sequence_read = ReadRecords(iter, count);
|
||||
ASSERT_LT(last_sequence_read, 1025U);
|
||||
// Try to read past the gap, should be able to seek to key1025
|
||||
auto iter2 = OpenTransactionLogIter(last_sequence_read + 1);
|
||||
ExpectRecords(1, iter2);
|
||||
} while (ChangeCompactOptions());
|
||||
}
|
||||
|
||||
TEST_F(DBTestXactLogIterator, TransactionLogIteratorBatchOperations) {
|
||||
do {
|
||||
Options options = OptionsForLogIterTest();
|
||||
DestroyAndReopen(options);
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
WriteBatch batch;
|
||||
batch.Put(handles_[1], "key1", DummyString(1024));
|
||||
batch.Put(handles_[0], "key2", DummyString(1024));
|
||||
batch.Put(handles_[1], "key3", DummyString(1024));
|
||||
batch.Delete(handles_[0], "key2");
|
||||
dbfull()->Write(WriteOptions(), &batch);
|
||||
Flush(1);
|
||||
Flush(0);
|
||||
ReopenWithColumnFamilies({"default", "pikachu"}, options);
|
||||
Put(1, "key4", DummyString(1024));
|
||||
auto iter = OpenTransactionLogIter(3);
|
||||
ExpectRecords(2, iter);
|
||||
} while (ChangeCompactOptions());
|
||||
}
|
||||
|
||||
TEST_F(DBTestXactLogIterator, TransactionLogIteratorBlobs) {
|
||||
Options options = OptionsForLogIterTest();
|
||||
DestroyAndReopen(options);
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
{
|
||||
WriteBatch batch;
|
||||
batch.Put(handles_[1], "key1", DummyString(1024));
|
||||
batch.Put(handles_[0], "key2", DummyString(1024));
|
||||
batch.PutLogData(Slice("blob1"));
|
||||
batch.Put(handles_[1], "key3", DummyString(1024));
|
||||
batch.PutLogData(Slice("blob2"));
|
||||
batch.Delete(handles_[0], "key2");
|
||||
dbfull()->Write(WriteOptions(), &batch);
|
||||
ReopenWithColumnFamilies({"default", "pikachu"}, options);
|
||||
}
|
||||
|
||||
auto res = OpenTransactionLogIter(0)->GetBatch();
|
||||
struct Handler : public WriteBatch::Handler {
|
||||
std::string seen;
|
||||
virtual Status PutCF(uint32_t cf, const Slice& key,
|
||||
const Slice& value) override {
|
||||
seen += "Put(" + ToString(cf) + ", " + key.ToString() + ", " +
|
||||
ToString(value.size()) + ")";
|
||||
return Status::OK();
|
||||
}
|
||||
virtual Status MergeCF(uint32_t cf, const Slice& key,
|
||||
const Slice& value) override {
|
||||
seen += "Merge(" + ToString(cf) + ", " + key.ToString() + ", " +
|
||||
ToString(value.size()) + ")";
|
||||
return Status::OK();
|
||||
}
|
||||
virtual void LogData(const Slice& blob) override {
|
||||
seen += "LogData(" + blob.ToString() + ")";
|
||||
}
|
||||
virtual Status DeleteCF(uint32_t cf, const Slice& key) override {
|
||||
seen += "Delete(" + ToString(cf) + ", " + key.ToString() + ")";
|
||||
return Status::OK();
|
||||
}
|
||||
} handler;
|
||||
res.writeBatchPtr->Iterate(&handler);
|
||||
ASSERT_EQ(
|
||||
"Put(1, key1, 1024)"
|
||||
"Put(0, key2, 1024)"
|
||||
"LogData(blob1)"
|
||||
"Put(1, key3, 1024)"
|
||||
"LogData(blob2)"
|
||||
"Delete(0, key2)",
|
||||
handler.seen);
|
||||
}
|
||||
} // namespace rocksdb
|
||||
|
||||
#endif // !(defined NDEBUG) || !defined(OS_WIN)
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
#if !(defined NDEBUG) || !defined(OS_WIN)
|
||||
rocksdb::port::InstallStackTraceHandler();
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
#else
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,481 @@
|
||||
// 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.
|
||||
|
||||
// Introduction of SyncPoint effectively disabled building and running this test
|
||||
// in Release build.
|
||||
// which is a pity, it is a good test
|
||||
#if !(defined NDEBUG) || !defined(OS_WIN)
|
||||
|
||||
#include "port/stack_trace.h"
|
||||
#include "util/db_test_util.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
class DBTestTailingIterator : public DBTestBase {
|
||||
public:
|
||||
DBTestTailingIterator() : DBTestBase("/db_tailing_iterator_test") {}
|
||||
};
|
||||
|
||||
TEST_F(DBTestTailingIterator, TailingIteratorSingle) {
|
||||
ReadOptions read_options;
|
||||
read_options.tailing = true;
|
||||
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options));
|
||||
iter->SeekToFirst();
|
||||
ASSERT_TRUE(!iter->Valid());
|
||||
|
||||
// add a record and check that iter can see it
|
||||
ASSERT_OK(db_->Put(WriteOptions(), "mirko", "fodor"));
|
||||
iter->SeekToFirst();
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ(iter->key().ToString(), "mirko");
|
||||
|
||||
iter->Next();
|
||||
ASSERT_TRUE(!iter->Valid());
|
||||
}
|
||||
|
||||
TEST_F(DBTestTailingIterator, TailingIteratorKeepAdding) {
|
||||
CreateAndReopenWithCF({"pikachu"}, CurrentOptions());
|
||||
ReadOptions read_options;
|
||||
read_options.tailing = true;
|
||||
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options, handles_[1]));
|
||||
std::string value(1024, 'a');
|
||||
|
||||
const int num_records = 10000;
|
||||
for (int i = 0; i < num_records; ++i) {
|
||||
char buf[32];
|
||||
snprintf(buf, sizeof(buf), "%016d", i);
|
||||
|
||||
Slice key(buf, 16);
|
||||
ASSERT_OK(Put(1, key, value));
|
||||
|
||||
iter->Seek(key);
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ(iter->key().compare(key), 0);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBTestTailingIterator, TailingIteratorSeekToNext) {
|
||||
CreateAndReopenWithCF({"pikachu"}, CurrentOptions());
|
||||
ReadOptions read_options;
|
||||
read_options.tailing = true;
|
||||
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options, handles_[1]));
|
||||
std::string value(1024, 'a');
|
||||
|
||||
const int num_records = 1000;
|
||||
for (int i = 1; i < num_records; ++i) {
|
||||
char buf1[32];
|
||||
char buf2[32];
|
||||
snprintf(buf1, sizeof(buf1), "00a0%016d", i * 5);
|
||||
|
||||
Slice key(buf1, 20);
|
||||
ASSERT_OK(Put(1, key, value));
|
||||
|
||||
if (i % 100 == 99) {
|
||||
ASSERT_OK(Flush(1));
|
||||
}
|
||||
|
||||
snprintf(buf2, sizeof(buf2), "00a0%016d", i * 5 - 2);
|
||||
Slice target(buf2, 20);
|
||||
iter->Seek(target);
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ(iter->key().compare(key), 0);
|
||||
}
|
||||
for (int i = 2 * num_records; i > 0; --i) {
|
||||
char buf1[32];
|
||||
char buf2[32];
|
||||
snprintf(buf1, sizeof(buf1), "00a0%016d", i * 5);
|
||||
|
||||
Slice key(buf1, 20);
|
||||
ASSERT_OK(Put(1, key, value));
|
||||
|
||||
if (i % 100 == 99) {
|
||||
ASSERT_OK(Flush(1));
|
||||
}
|
||||
|
||||
snprintf(buf2, sizeof(buf2), "00a0%016d", i * 5 - 2);
|
||||
Slice target(buf2, 20);
|
||||
iter->Seek(target);
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ(iter->key().compare(key), 0);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBTestTailingIterator, TailingIteratorDeletes) {
|
||||
CreateAndReopenWithCF({"pikachu"}, CurrentOptions());
|
||||
ReadOptions read_options;
|
||||
read_options.tailing = true;
|
||||
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options, handles_[1]));
|
||||
|
||||
// write a single record, read it using the iterator, then delete it
|
||||
ASSERT_OK(Put(1, "0test", "test"));
|
||||
iter->SeekToFirst();
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ(iter->key().ToString(), "0test");
|
||||
ASSERT_OK(Delete(1, "0test"));
|
||||
|
||||
// write many more records
|
||||
const int num_records = 10000;
|
||||
std::string value(1024, 'A');
|
||||
|
||||
for (int i = 0; i < num_records; ++i) {
|
||||
char buf[32];
|
||||
snprintf(buf, sizeof(buf), "1%015d", i);
|
||||
|
||||
Slice key(buf, 16);
|
||||
ASSERT_OK(Put(1, key, value));
|
||||
}
|
||||
|
||||
// force a flush to make sure that no records are read from memtable
|
||||
ASSERT_OK(Flush(1));
|
||||
|
||||
// skip "0test"
|
||||
iter->Next();
|
||||
|
||||
// make sure we can read all new records using the existing iterator
|
||||
int count = 0;
|
||||
for (; iter->Valid(); iter->Next(), ++count) ;
|
||||
|
||||
ASSERT_EQ(count, num_records);
|
||||
}
|
||||
|
||||
TEST_F(DBTestTailingIterator, TailingIteratorPrefixSeek) {
|
||||
XFUNC_TEST("", "dbtest_prefix", prefix_skip1, XFuncPoint::SetSkip,
|
||||
kSkipNoPrefix);
|
||||
ReadOptions read_options;
|
||||
read_options.tailing = true;
|
||||
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
options.create_if_missing = true;
|
||||
options.disable_auto_compactions = true;
|
||||
options.prefix_extractor.reset(NewFixedPrefixTransform(2));
|
||||
options.memtable_factory.reset(NewHashSkipListRepFactory(16));
|
||||
DestroyAndReopen(options);
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options, handles_[1]));
|
||||
ASSERT_OK(Put(1, "0101", "test"));
|
||||
|
||||
ASSERT_OK(Flush(1));
|
||||
|
||||
ASSERT_OK(Put(1, "0202", "test"));
|
||||
|
||||
// Seek(0102) shouldn't find any records since 0202 has a different prefix
|
||||
iter->Seek("0102");
|
||||
ASSERT_TRUE(!iter->Valid());
|
||||
|
||||
iter->Seek("0202");
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ(iter->key().ToString(), "0202");
|
||||
|
||||
iter->Next();
|
||||
ASSERT_TRUE(!iter->Valid());
|
||||
XFUNC_TEST("", "dbtest_prefix", prefix_skip1, XFuncPoint::SetSkip, 0);
|
||||
}
|
||||
|
||||
TEST_F(DBTestTailingIterator, TailingIteratorIncomplete) {
|
||||
CreateAndReopenWithCF({"pikachu"}, CurrentOptions());
|
||||
ReadOptions read_options;
|
||||
read_options.tailing = true;
|
||||
read_options.read_tier = kBlockCacheTier;
|
||||
|
||||
std::string key("key");
|
||||
std::string value("value");
|
||||
|
||||
ASSERT_OK(db_->Put(WriteOptions(), key, value));
|
||||
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options));
|
||||
iter->SeekToFirst();
|
||||
// we either see the entry or it's not in cache
|
||||
ASSERT_TRUE(iter->Valid() || iter->status().IsIncomplete());
|
||||
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
||||
iter->SeekToFirst();
|
||||
// should still be true after compaction
|
||||
ASSERT_TRUE(iter->Valid() || iter->status().IsIncomplete());
|
||||
}
|
||||
|
||||
TEST_F(DBTestTailingIterator, TailingIteratorSeekToSame) {
|
||||
Options options = CurrentOptions();
|
||||
options.compaction_style = kCompactionStyleUniversal;
|
||||
options.write_buffer_size = 1000;
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
|
||||
ReadOptions read_options;
|
||||
read_options.tailing = true;
|
||||
|
||||
const int NROWS = 10000;
|
||||
// Write rows with keys 00000, 00002, 00004 etc.
|
||||
for (int i = 0; i < NROWS; ++i) {
|
||||
char buf[100];
|
||||
snprintf(buf, sizeof(buf), "%05d", 2*i);
|
||||
std::string key(buf);
|
||||
std::string value("value");
|
||||
ASSERT_OK(db_->Put(WriteOptions(), key, value));
|
||||
}
|
||||
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options));
|
||||
// Seek to 00001. We expect to find 00002.
|
||||
std::string start_key = "00001";
|
||||
iter->Seek(start_key);
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
|
||||
std::string found = iter->key().ToString();
|
||||
ASSERT_EQ("00002", found);
|
||||
|
||||
// Now seek to the same key. The iterator should remain in the same
|
||||
// position.
|
||||
iter->Seek(found);
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ(found, iter->key().ToString());
|
||||
}
|
||||
|
||||
TEST_F(DBTestTailingIterator, ManagedTailingIteratorSingle) {
|
||||
ReadOptions read_options;
|
||||
read_options.tailing = true;
|
||||
read_options.managed = true;
|
||||
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options));
|
||||
iter->SeekToFirst();
|
||||
ASSERT_TRUE(!iter->Valid());
|
||||
|
||||
// add a record and check that iter can see it
|
||||
ASSERT_OK(db_->Put(WriteOptions(), "mirko", "fodor"));
|
||||
iter->SeekToFirst();
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ(iter->key().ToString(), "mirko");
|
||||
|
||||
iter->Next();
|
||||
ASSERT_TRUE(!iter->Valid());
|
||||
}
|
||||
|
||||
TEST_F(DBTestTailingIterator, ManagedTailingIteratorKeepAdding) {
|
||||
CreateAndReopenWithCF({"pikachu"}, CurrentOptions());
|
||||
ReadOptions read_options;
|
||||
read_options.tailing = true;
|
||||
read_options.managed = true;
|
||||
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options, handles_[1]));
|
||||
std::string value(1024, 'a');
|
||||
|
||||
const int num_records = 10000;
|
||||
for (int i = 0; i < num_records; ++i) {
|
||||
char buf[32];
|
||||
snprintf(buf, sizeof(buf), "%016d", i);
|
||||
|
||||
Slice key(buf, 16);
|
||||
ASSERT_OK(Put(1, key, value));
|
||||
|
||||
iter->Seek(key);
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ(iter->key().compare(key), 0);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBTestTailingIterator, ManagedTailingIteratorSeekToNext) {
|
||||
CreateAndReopenWithCF({"pikachu"}, CurrentOptions());
|
||||
ReadOptions read_options;
|
||||
read_options.tailing = true;
|
||||
read_options.managed = true;
|
||||
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options, handles_[1]));
|
||||
std::string value(1024, 'a');
|
||||
|
||||
const int num_records = 1000;
|
||||
for (int i = 1; i < num_records; ++i) {
|
||||
char buf1[32];
|
||||
char buf2[32];
|
||||
snprintf(buf1, sizeof(buf1), "00a0%016d", i * 5);
|
||||
|
||||
Slice key(buf1, 20);
|
||||
ASSERT_OK(Put(1, key, value));
|
||||
|
||||
if (i % 100 == 99) {
|
||||
ASSERT_OK(Flush(1));
|
||||
}
|
||||
|
||||
snprintf(buf2, sizeof(buf2), "00a0%016d", i * 5 - 2);
|
||||
Slice target(buf2, 20);
|
||||
iter->Seek(target);
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ(iter->key().compare(key), 0);
|
||||
}
|
||||
for (int i = 2 * num_records; i > 0; --i) {
|
||||
char buf1[32];
|
||||
char buf2[32];
|
||||
snprintf(buf1, sizeof(buf1), "00a0%016d", i * 5);
|
||||
|
||||
Slice key(buf1, 20);
|
||||
ASSERT_OK(Put(1, key, value));
|
||||
|
||||
if (i % 100 == 99) {
|
||||
ASSERT_OK(Flush(1));
|
||||
}
|
||||
|
||||
snprintf(buf2, sizeof(buf2), "00a0%016d", i * 5 - 2);
|
||||
Slice target(buf2, 20);
|
||||
iter->Seek(target);
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ(iter->key().compare(key), 0);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBTestTailingIterator, ManagedTailingIteratorDeletes) {
|
||||
CreateAndReopenWithCF({"pikachu"}, CurrentOptions());
|
||||
ReadOptions read_options;
|
||||
read_options.tailing = true;
|
||||
read_options.managed = true;
|
||||
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options, handles_[1]));
|
||||
|
||||
// write a single record, read it using the iterator, then delete it
|
||||
ASSERT_OK(Put(1, "0test", "test"));
|
||||
iter->SeekToFirst();
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ(iter->key().ToString(), "0test");
|
||||
ASSERT_OK(Delete(1, "0test"));
|
||||
|
||||
// write many more records
|
||||
const int num_records = 10000;
|
||||
std::string value(1024, 'A');
|
||||
|
||||
for (int i = 0; i < num_records; ++i) {
|
||||
char buf[32];
|
||||
snprintf(buf, sizeof(buf), "1%015d", i);
|
||||
|
||||
Slice key(buf, 16);
|
||||
ASSERT_OK(Put(1, key, value));
|
||||
}
|
||||
|
||||
// force a flush to make sure that no records are read from memtable
|
||||
ASSERT_OK(Flush(1));
|
||||
|
||||
// skip "0test"
|
||||
iter->Next();
|
||||
|
||||
// make sure we can read all new records using the existing iterator
|
||||
int count = 0;
|
||||
for (; iter->Valid(); iter->Next(), ++count) {
|
||||
}
|
||||
|
||||
ASSERT_EQ(count, num_records);
|
||||
}
|
||||
|
||||
TEST_F(DBTestTailingIterator, ManagedTailingIteratorPrefixSeek) {
|
||||
XFUNC_TEST("", "dbtest_prefix", prefix_skip1, XFuncPoint::SetSkip,
|
||||
kSkipNoPrefix);
|
||||
ReadOptions read_options;
|
||||
read_options.tailing = true;
|
||||
read_options.managed = true;
|
||||
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
options.create_if_missing = true;
|
||||
options.disable_auto_compactions = true;
|
||||
options.prefix_extractor.reset(NewFixedPrefixTransform(2));
|
||||
options.memtable_factory.reset(NewHashSkipListRepFactory(16));
|
||||
DestroyAndReopen(options);
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options, handles_[1]));
|
||||
ASSERT_OK(Put(1, "0101", "test"));
|
||||
|
||||
ASSERT_OK(Flush(1));
|
||||
|
||||
ASSERT_OK(Put(1, "0202", "test"));
|
||||
|
||||
// Seek(0102) shouldn't find any records since 0202 has a different prefix
|
||||
iter->Seek("0102");
|
||||
ASSERT_TRUE(!iter->Valid());
|
||||
|
||||
iter->Seek("0202");
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ(iter->key().ToString(), "0202");
|
||||
|
||||
iter->Next();
|
||||
ASSERT_TRUE(!iter->Valid());
|
||||
XFUNC_TEST("", "dbtest_prefix", prefix_skip1, XFuncPoint::SetSkip, 0);
|
||||
}
|
||||
|
||||
TEST_F(DBTestTailingIterator, ManagedTailingIteratorIncomplete) {
|
||||
CreateAndReopenWithCF({"pikachu"}, CurrentOptions());
|
||||
ReadOptions read_options;
|
||||
read_options.tailing = true;
|
||||
read_options.managed = true;
|
||||
read_options.read_tier = kBlockCacheTier;
|
||||
|
||||
std::string key = "key";
|
||||
std::string value = "value";
|
||||
|
||||
ASSERT_OK(db_->Put(WriteOptions(), key, value));
|
||||
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options));
|
||||
iter->SeekToFirst();
|
||||
// we either see the entry or it's not in cache
|
||||
ASSERT_TRUE(iter->Valid() || iter->status().IsIncomplete());
|
||||
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
||||
iter->SeekToFirst();
|
||||
// should still be true after compaction
|
||||
ASSERT_TRUE(iter->Valid() || iter->status().IsIncomplete());
|
||||
}
|
||||
|
||||
TEST_F(DBTestTailingIterator, ManagedTailingIteratorSeekToSame) {
|
||||
Options options = CurrentOptions();
|
||||
options.compaction_style = kCompactionStyleUniversal;
|
||||
options.write_buffer_size = 1000;
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
|
||||
ReadOptions read_options;
|
||||
read_options.tailing = true;
|
||||
read_options.managed = true;
|
||||
|
||||
const int NROWS = 10000;
|
||||
// Write rows with keys 00000, 00002, 00004 etc.
|
||||
for (int i = 0; i < NROWS; ++i) {
|
||||
char buf[100];
|
||||
snprintf(buf, sizeof(buf), "%05d", 2 * i);
|
||||
std::string key(buf);
|
||||
std::string value("value");
|
||||
ASSERT_OK(db_->Put(WriteOptions(), key, value));
|
||||
}
|
||||
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options));
|
||||
// Seek to 00001. We expect to find 00002.
|
||||
std::string start_key = "00001";
|
||||
iter->Seek(start_key);
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
|
||||
std::string found = iter->key().ToString();
|
||||
ASSERT_EQ("00002", found);
|
||||
|
||||
// Now seek to the same key. The iterator should remain in the same
|
||||
// position.
|
||||
iter->Seek(found);
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ(found, iter->key().ToString());
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
#endif // !(defined NDEBUG) || !defined(OS_WIN)
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
#if !(defined NDEBUG) || !defined(OS_WIN)
|
||||
rocksdb::port::InstallStackTraceHandler();
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
#else
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
+599
-6327
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,144 @@
|
||||
// Copyright (c) 2013, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the root directory of this source tree. An additional grant
|
||||
// of patent rights can be found in the PATENTS file in the same directory.
|
||||
//
|
||||
// Copyright (c) 2011 The LevelDB Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
|
||||
#include "port/stack_trace.h"
|
||||
#include "util/db_test_util.h"
|
||||
#if !(defined NDEBUG) || !defined(OS_WIN)
|
||||
#include "util/sync_point.h"
|
||||
#endif
|
||||
|
||||
namespace rocksdb {
|
||||
class DBWALTest : public DBTestBase {
|
||||
public:
|
||||
DBWALTest() : DBTestBase("/db_wal_test") {}
|
||||
};
|
||||
|
||||
TEST_F(DBWALTest, WAL) {
|
||||
do {
|
||||
CreateAndReopenWithCF({"pikachu"}, CurrentOptions());
|
||||
WriteOptions writeOpt = WriteOptions();
|
||||
writeOpt.disableWAL = true;
|
||||
ASSERT_OK(dbfull()->Put(writeOpt, handles_[1], "foo", "v1"));
|
||||
ASSERT_OK(dbfull()->Put(writeOpt, handles_[1], "bar", "v1"));
|
||||
|
||||
ReopenWithColumnFamilies({"default", "pikachu"}, CurrentOptions());
|
||||
ASSERT_EQ("v1", Get(1, "foo"));
|
||||
ASSERT_EQ("v1", Get(1, "bar"));
|
||||
|
||||
writeOpt.disableWAL = false;
|
||||
ASSERT_OK(dbfull()->Put(writeOpt, handles_[1], "bar", "v2"));
|
||||
writeOpt.disableWAL = true;
|
||||
ASSERT_OK(dbfull()->Put(writeOpt, handles_[1], "foo", "v2"));
|
||||
|
||||
ReopenWithColumnFamilies({"default", "pikachu"}, CurrentOptions());
|
||||
// Both value's should be present.
|
||||
ASSERT_EQ("v2", Get(1, "bar"));
|
||||
ASSERT_EQ("v2", Get(1, "foo"));
|
||||
|
||||
writeOpt.disableWAL = true;
|
||||
ASSERT_OK(dbfull()->Put(writeOpt, handles_[1], "bar", "v3"));
|
||||
writeOpt.disableWAL = false;
|
||||
ASSERT_OK(dbfull()->Put(writeOpt, handles_[1], "foo", "v3"));
|
||||
|
||||
ReopenWithColumnFamilies({"default", "pikachu"}, CurrentOptions());
|
||||
// again both values should be present.
|
||||
ASSERT_EQ("v3", Get(1, "foo"));
|
||||
ASSERT_EQ("v3", Get(1, "bar"));
|
||||
} while (ChangeCompactOptions());
|
||||
}
|
||||
|
||||
TEST_F(DBWALTest, RollLog) {
|
||||
do {
|
||||
CreateAndReopenWithCF({"pikachu"}, CurrentOptions());
|
||||
ASSERT_OK(Put(1, "foo", "v1"));
|
||||
ASSERT_OK(Put(1, "baz", "v5"));
|
||||
|
||||
ReopenWithColumnFamilies({"default", "pikachu"}, CurrentOptions());
|
||||
for (int i = 0; i < 10; i++) {
|
||||
ReopenWithColumnFamilies({"default", "pikachu"}, CurrentOptions());
|
||||
}
|
||||
ASSERT_OK(Put(1, "foo", "v4"));
|
||||
for (int i = 0; i < 10; i++) {
|
||||
ReopenWithColumnFamilies({"default", "pikachu"}, CurrentOptions());
|
||||
}
|
||||
} while (ChangeOptions());
|
||||
}
|
||||
|
||||
#if !(defined NDEBUG) || !defined(OS_WIN)
|
||||
TEST_F(DBWALTest, SyncWALNotBlockWrite) {
|
||||
Options options = CurrentOptions();
|
||||
options.max_write_buffer_number = 4;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
ASSERT_OK(Put("foo1", "bar1"));
|
||||
ASSERT_OK(Put("foo5", "bar5"));
|
||||
|
||||
rocksdb::SyncPoint::GetInstance()->LoadDependency({
|
||||
{"WritableFileWriter::SyncWithoutFlush:1",
|
||||
"DBWALTest::SyncWALNotBlockWrite:1"},
|
||||
{"DBWALTest::SyncWALNotBlockWrite:2",
|
||||
"WritableFileWriter::SyncWithoutFlush:2"},
|
||||
});
|
||||
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
std::thread thread([&]() { ASSERT_OK(db_->SyncWAL()); });
|
||||
|
||||
TEST_SYNC_POINT("DBWALTest::SyncWALNotBlockWrite:1");
|
||||
ASSERT_OK(Put("foo2", "bar2"));
|
||||
ASSERT_OK(Put("foo3", "bar3"));
|
||||
FlushOptions fo;
|
||||
fo.wait = false;
|
||||
ASSERT_OK(db_->Flush(fo));
|
||||
ASSERT_OK(Put("foo4", "bar4"));
|
||||
|
||||
TEST_SYNC_POINT("DBWALTest::SyncWALNotBlockWrite:2");
|
||||
|
||||
thread.join();
|
||||
|
||||
ASSERT_EQ(Get("foo1"), "bar1");
|
||||
ASSERT_EQ(Get("foo2"), "bar2");
|
||||
ASSERT_EQ(Get("foo3"), "bar3");
|
||||
ASSERT_EQ(Get("foo4"), "bar4");
|
||||
ASSERT_EQ(Get("foo5"), "bar5");
|
||||
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
|
||||
TEST_F(DBWALTest, SyncWALNotWaitWrite) {
|
||||
ASSERT_OK(Put("foo1", "bar1"));
|
||||
ASSERT_OK(Put("foo3", "bar3"));
|
||||
|
||||
rocksdb::SyncPoint::GetInstance()->LoadDependency({
|
||||
{"SpecialEnv::WalFile::Append:1", "DBWALTest::SyncWALNotWaitWrite:1"},
|
||||
{"DBWALTest::SyncWALNotWaitWrite:2", "SpecialEnv::WalFile::Append:2"},
|
||||
});
|
||||
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
std::thread thread([&]() { ASSERT_OK(Put("foo2", "bar2")); });
|
||||
TEST_SYNC_POINT("DBWALTest::SyncWALNotWaitWrite:1");
|
||||
ASSERT_OK(db_->SyncWAL());
|
||||
TEST_SYNC_POINT("DBWALTest::SyncWALNotWaitWrite:2");
|
||||
|
||||
thread.join();
|
||||
|
||||
ASSERT_EQ(Get("foo1"), "bar1");
|
||||
ASSERT_EQ(Get("foo2"), "bar2");
|
||||
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
#endif
|
||||
} // namespace rocksdb
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
#if !(defined NDEBUG) || !defined(OS_WIN)
|
||||
rocksdb::port::InstallStackTraceHandler();
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
#else
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
+9
-6
@@ -195,14 +195,17 @@ inline bool ParseInternalKey(const Slice& internal_key,
|
||||
return (c <= static_cast<unsigned char>(kValueTypeForSeek));
|
||||
}
|
||||
|
||||
// Update the sequence number in the internal key
|
||||
inline void UpdateInternalKey(char* internal_key,
|
||||
const size_t internal_key_size,
|
||||
// Update the sequence number in the internal key.
|
||||
// Guarantees not to invalidate ikey.data().
|
||||
inline void UpdateInternalKey(std::string* ikey,
|
||||
uint64_t seq, ValueType t) {
|
||||
assert(internal_key_size >= 8);
|
||||
char* seqtype = internal_key + internal_key_size - 8;
|
||||
size_t ikey_sz = ikey->size();
|
||||
assert(ikey_sz >= 8);
|
||||
uint64_t newval = (seq << 8) | t;
|
||||
EncodeFixed64(seqtype, newval);
|
||||
|
||||
// Note: Since C++11, strings are guaranteed to be stored contiguously and
|
||||
// string::operator[]() is guaranteed not to change ikey.data().
|
||||
EncodeFixed64(&(*ikey)[ikey_sz - 8], newval);
|
||||
}
|
||||
|
||||
// Get the sequence number from the internal key
|
||||
|
||||
@@ -149,6 +149,25 @@ TEST_F(FormatTest, IterKeyOperation) {
|
||||
"abcdefghijklmnopqrstuvwxyz"));
|
||||
}
|
||||
|
||||
TEST_F(FormatTest, UpdateInternalKey) {
|
||||
std::string user_key("abcdefghijklmnopqrstuvwxyz");
|
||||
uint64_t new_seq = 0x123456;
|
||||
ValueType new_val_type = kTypeDeletion;
|
||||
|
||||
std::string ikey;
|
||||
AppendInternalKey(&ikey, ParsedInternalKey(user_key, 100U, kTypeValue));
|
||||
size_t ikey_size = ikey.size();
|
||||
UpdateInternalKey(&ikey, new_seq, new_val_type);
|
||||
ASSERT_EQ(ikey_size, ikey.size());
|
||||
|
||||
Slice in(ikey);
|
||||
ParsedInternalKey decoded;
|
||||
ASSERT_TRUE(ParseInternalKey(in, &decoded));
|
||||
ASSERT_EQ(user_key, decoded.user_key.ToString());
|
||||
ASSERT_EQ(new_seq, decoded.sequence);
|
||||
ASSERT_EQ(new_val_type, decoded.type);
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
+22
-7
@@ -7,6 +7,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
|
||||
|
||||
#include "rocksdb/db.h"
|
||||
#include "db/db_impl.h"
|
||||
#include "db/filename.h"
|
||||
@@ -36,7 +38,6 @@ class DeleteFileTest : public testing::Test {
|
||||
db_ = nullptr;
|
||||
env_ = Env::Default();
|
||||
options_.enable_thread_tracking = true;
|
||||
options_.max_background_flushes = 0;
|
||||
options_.write_buffer_size = 1024*1024*1000;
|
||||
options_.target_file_size_base = 1024*1024*1000;
|
||||
options_.max_bytes_for_level_base = 1024*1024*1000;
|
||||
@@ -117,10 +118,14 @@ class DeleteFileTest : public testing::Test {
|
||||
DBImpl* dbi = reinterpret_cast<DBImpl*>(db_);
|
||||
ASSERT_OK(dbi->TEST_FlushMemTable());
|
||||
ASSERT_OK(dbi->TEST_WaitForFlushMemTable());
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
ASSERT_OK(dbi->TEST_CompactRange(i, nullptr, nullptr));
|
||||
}
|
||||
|
||||
AddKeys(50000, 10000);
|
||||
ASSERT_OK(dbi->TEST_FlushMemTable());
|
||||
ASSERT_OK(dbi->TEST_WaitForFlushMemTable());
|
||||
ASSERT_OK(dbi->TEST_CompactRange(0, nullptr, nullptr));
|
||||
}
|
||||
|
||||
void CheckFileTypeCounts(std::string& dir,
|
||||
@@ -264,11 +269,11 @@ TEST_F(DeleteFileTest, DeleteLogFiles) {
|
||||
// Should not succeed because live logs are not allowed to be deleted
|
||||
std::unique_ptr<LogFile> alive_log = std::move(logfiles.back());
|
||||
ASSERT_EQ(alive_log->Type(), kAliveLogFile);
|
||||
ASSERT_TRUE(env_->FileExists(options_.wal_dir + "/" + alive_log->PathName()));
|
||||
ASSERT_OK(env_->FileExists(options_.wal_dir + "/" + alive_log->PathName()));
|
||||
fprintf(stdout, "Deleting alive log file %s\n",
|
||||
alive_log->PathName().c_str());
|
||||
ASSERT_TRUE(!db_->DeleteFile(alive_log->PathName()).ok());
|
||||
ASSERT_TRUE(env_->FileExists(options_.wal_dir + "/" + alive_log->PathName()));
|
||||
ASSERT_OK(env_->FileExists(options_.wal_dir + "/" + alive_log->PathName()));
|
||||
logfiles.clear();
|
||||
|
||||
// Call Flush to bring about a new working log file and add more keys
|
||||
@@ -282,13 +287,13 @@ TEST_F(DeleteFileTest, DeleteLogFiles) {
|
||||
ASSERT_GT(logfiles.size(), 0UL);
|
||||
std::unique_ptr<LogFile> archived_log = std::move(logfiles.front());
|
||||
ASSERT_EQ(archived_log->Type(), kArchivedLogFile);
|
||||
ASSERT_TRUE(env_->FileExists(options_.wal_dir + "/" +
|
||||
archived_log->PathName()));
|
||||
ASSERT_OK(
|
||||
env_->FileExists(options_.wal_dir + "/" + archived_log->PathName()));
|
||||
fprintf(stdout, "Deleting archived log file %s\n",
|
||||
archived_log->PathName().c_str());
|
||||
ASSERT_OK(db_->DeleteFile(archived_log->PathName()));
|
||||
ASSERT_TRUE(!env_->FileExists(options_.wal_dir + "/" +
|
||||
archived_log->PathName()));
|
||||
ASSERT_EQ(Status::NotFound(), env_->FileExists(options_.wal_dir + "/" +
|
||||
archived_log->PathName()));
|
||||
CloseDB();
|
||||
}
|
||||
|
||||
@@ -368,3 +373,13 @@ int main(int argc, char** argv) {
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
|
||||
#else
|
||||
#include <stdio.h>
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
fprintf(stderr,
|
||||
"SKIPPED as DBImpl::DeleteFile is not supported in ROCKSDB_LITE\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif // !ROCKSDB_LITE
|
||||
+148
-15
@@ -25,6 +25,7 @@
|
||||
#include "util/logging.h"
|
||||
#include "util/mock_env.h"
|
||||
#include "util/mutexlock.h"
|
||||
#include "util/sync_point.h"
|
||||
#include "util/testharness.h"
|
||||
#include "util/testutil.h"
|
||||
|
||||
@@ -77,9 +78,12 @@ Status Truncate(Env* env, const std::string& filename, uint64_t length) {
|
||||
return s;
|
||||
}
|
||||
|
||||
char* scratch = new char[length];
|
||||
std::unique_ptr<char[]> scratch(new char[length]);
|
||||
rocksdb::Slice result;
|
||||
s = orig_file->Read(length, &result, scratch);
|
||||
s = orig_file->Read(length, &result, scratch.get());
|
||||
#ifdef OS_WIN
|
||||
orig_file.reset();
|
||||
#endif
|
||||
if (s.ok()) {
|
||||
std::string tmp_name = GetDirName(filename) + "/truncate.tmp";
|
||||
unique_ptr<WritableFile> tmp_file;
|
||||
@@ -100,8 +104,6 @@ Status Truncate(Env* env, const std::string& filename, uint64_t length) {
|
||||
s.ToString().c_str());
|
||||
}
|
||||
|
||||
delete[] scratch;
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
@@ -128,7 +130,7 @@ struct FileState {
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
// A wrapper around WritableFile which informs another Env whenever this file
|
||||
// A wrapper around WritableFileWriter* file
|
||||
// is written to or sync'ed.
|
||||
class TestWritableFile : public WritableFile {
|
||||
public:
|
||||
@@ -140,6 +142,7 @@ class TestWritableFile : public WritableFile {
|
||||
virtual Status Close() override;
|
||||
virtual Status Flush() override;
|
||||
virtual Status Sync() override;
|
||||
virtual bool IsSyncThreadSafe() const override { return true; }
|
||||
|
||||
private:
|
||||
FileState state_;
|
||||
@@ -185,10 +188,21 @@ class FaultInjectionTestEnv : public EnvWrapper {
|
||||
Status NewWritableFile(const std::string& fname,
|
||||
unique_ptr<WritableFile>* result,
|
||||
const EnvOptions& soptions) override {
|
||||
Status s = target()->NewWritableFile(fname, result, soptions);
|
||||
if (!IsFilesystemActive()) {
|
||||
return Status::Corruption("Not Active");
|
||||
}
|
||||
// Not allow overwriting files
|
||||
Status s = target()->FileExists(fname);
|
||||
if (s.ok()) {
|
||||
return Status::Corruption("File already exists.");
|
||||
} else if (!s.IsNotFound()) {
|
||||
assert(s.IsIOError());
|
||||
return s;
|
||||
}
|
||||
s = target()->NewWritableFile(fname, result, soptions);
|
||||
if (s.ok()) {
|
||||
result->reset(new TestWritableFile(fname, std::move(*result), this));
|
||||
// WritableFile doesn't append to files, so if the same file is opened
|
||||
// WritableFileWriter* file is opened
|
||||
// again then it will be truncated - so forget our saved state.
|
||||
UntrackFile(fname);
|
||||
MutexLock l(&mutex_);
|
||||
@@ -201,6 +215,9 @@ class FaultInjectionTestEnv : public EnvWrapper {
|
||||
}
|
||||
|
||||
virtual Status DeleteFile(const std::string& f) override {
|
||||
if (!IsFilesystemActive()) {
|
||||
return Status::Corruption("Not Active");
|
||||
}
|
||||
Status s = EnvWrapper::DeleteFile(f);
|
||||
if (!s.ok()) {
|
||||
fprintf(stderr, "Cannot delete file %s: %s\n", f.c_str(),
|
||||
@@ -215,6 +232,9 @@ class FaultInjectionTestEnv : public EnvWrapper {
|
||||
|
||||
virtual Status RenameFile(const std::string& s,
|
||||
const std::string& t) override {
|
||||
if (!IsFilesystemActive()) {
|
||||
return Status::Corruption("Not Active");
|
||||
}
|
||||
Status ret = EnvWrapper::RenameFile(s, t);
|
||||
|
||||
if (ret.ok()) {
|
||||
@@ -373,8 +393,11 @@ TestWritableFile::~TestWritableFile() {
|
||||
}
|
||||
|
||||
Status TestWritableFile::Append(const Slice& data) {
|
||||
if (!env_->IsFilesystemActive()) {
|
||||
return Status::Corruption("Not Active");
|
||||
}
|
||||
Status s = target_->Append(data);
|
||||
if (s.ok() && env_->IsFilesystemActive()) {
|
||||
if (s.ok()) {
|
||||
state_.pos_ += data.size();
|
||||
}
|
||||
return s;
|
||||
@@ -406,7 +429,8 @@ Status TestWritableFile::Sync() {
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
class FaultInjectionTest : public testing::Test {
|
||||
class FaultInjectionTest : public testing::Test,
|
||||
public testing::WithParamInterface<bool> {
|
||||
protected:
|
||||
enum OptionConfig {
|
||||
kDefault,
|
||||
@@ -423,6 +447,8 @@ class FaultInjectionTest : public testing::Test {
|
||||
// When need to make sure data is persistent, call DB::CompactRange()
|
||||
bool sync_use_compact_;
|
||||
|
||||
bool sequential_order_;
|
||||
|
||||
protected:
|
||||
public:
|
||||
enum ExpectedVerifResult { kValExpectFound, kValExpectNoError };
|
||||
@@ -449,6 +475,11 @@ class FaultInjectionTest : public testing::Test {
|
||||
db_(NULL) {
|
||||
}
|
||||
|
||||
~FaultInjectionTest() {
|
||||
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
||||
rocksdb::SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
}
|
||||
|
||||
bool ChangeOptions() {
|
||||
option_config_++;
|
||||
if (option_config_ >= kEnd) {
|
||||
@@ -529,7 +560,10 @@ class FaultInjectionTest : public testing::Test {
|
||||
return s;
|
||||
}
|
||||
|
||||
void SetUp() override { ASSERT_OK(NewDB()); }
|
||||
void SetUp() override {
|
||||
sequential_order_ = GetParam();
|
||||
ASSERT_OK(NewDB());
|
||||
}
|
||||
|
||||
void TearDown() override {
|
||||
CloseDB();
|
||||
@@ -591,8 +625,15 @@ class FaultInjectionTest : public testing::Test {
|
||||
|
||||
// Return the ith key
|
||||
Slice Key(int i, std::string* storage) const {
|
||||
int num = i;
|
||||
if (!sequential_order_) {
|
||||
// random transfer
|
||||
const int m = 0x5bd1e995;
|
||||
num *= m;
|
||||
num ^= num << 24;
|
||||
}
|
||||
char buf[100];
|
||||
snprintf(buf, sizeof(buf), "%016d", i);
|
||||
snprintf(buf, sizeof(buf), "%016d", num);
|
||||
storage->assign(buf, strlen(buf));
|
||||
return Slice(*storage);
|
||||
}
|
||||
@@ -675,6 +716,10 @@ class FaultInjectionTest : public testing::Test {
|
||||
ASSERT_OK(Verify(0, num_pre_sync, FaultInjectionTest::kValExpectFound));
|
||||
ASSERT_OK(Verify(num_pre_sync, num_post_sync,
|
||||
FaultInjectionTest::kValExpectNoError));
|
||||
WaitCompactionFinish();
|
||||
ASSERT_OK(Verify(0, num_pre_sync, FaultInjectionTest::kValExpectFound));
|
||||
ASSERT_OK(Verify(num_pre_sync, num_post_sync,
|
||||
FaultInjectionTest::kValExpectNoError));
|
||||
}
|
||||
|
||||
void NoWriteTestPreFault() {
|
||||
@@ -685,9 +730,14 @@ class FaultInjectionTest : public testing::Test {
|
||||
ResetDBState(reset_method);
|
||||
ASSERT_OK(OpenDB());
|
||||
}
|
||||
|
||||
void WaitCompactionFinish() {
|
||||
static_cast<DBImpl*>(db_)->TEST_WaitForCompact();
|
||||
ASSERT_OK(db_->Put(WriteOptions(), "", ""));
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(FaultInjectionTest, FaultTest) {
|
||||
TEST_P(FaultInjectionTest, FaultTest) {
|
||||
do {
|
||||
Random rnd(301);
|
||||
|
||||
@@ -758,10 +808,8 @@ class SleepingBackgroundTask {
|
||||
bool done_with_sleep_;
|
||||
};
|
||||
|
||||
// Disable the test because it is not passing.
|
||||
// Previous log file is not fsynced if sync is forced after log rolling.
|
||||
// TODO(FB internal task#6730880) Fix the bug
|
||||
TEST_F(FaultInjectionTest, DISABLED_WriteOptionSyncTest) {
|
||||
TEST_P(FaultInjectionTest, WriteOptionSyncTest) {
|
||||
SleepingBackgroundTask sleeping_task_low;
|
||||
env_->SetBackgroundThreads(1, Env::HIGH);
|
||||
// Block the job queue to prevent flush job from running.
|
||||
@@ -796,6 +844,91 @@ TEST_F(FaultInjectionTest, DISABLED_WriteOptionSyncTest) {
|
||||
ASSERT_EQ(value_space, val);
|
||||
}
|
||||
|
||||
TEST_P(FaultInjectionTest, UninstalledCompaction) {
|
||||
options_.target_file_size_base = 32 * 1024;
|
||||
options_.write_buffer_size = 100 << 10; // 100KB
|
||||
options_.level0_file_num_compaction_trigger = 6;
|
||||
options_.level0_stop_writes_trigger = 1 << 10;
|
||||
options_.level0_slowdown_writes_trigger = 1 << 10;
|
||||
options_.max_background_compactions = 1;
|
||||
OpenDB();
|
||||
|
||||
if (!sequential_order_) {
|
||||
rocksdb::SyncPoint::GetInstance()->LoadDependency({
|
||||
{"FaultInjectionTest::FaultTest:0", "DBImpl::BGWorkCompaction"},
|
||||
{"CompactionJob::Run():End", "FaultInjectionTest::FaultTest:1"},
|
||||
{"FaultInjectionTest::FaultTest:2",
|
||||
"DBImpl::BackgroundCompaction:NonTrivial:AfterRun"},
|
||||
});
|
||||
}
|
||||
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
int kNumKeys = 1000;
|
||||
Build(WriteOptions(), 0, kNumKeys);
|
||||
FlushOptions flush_options;
|
||||
flush_options.wait = true;
|
||||
db_->Flush(flush_options);
|
||||
ASSERT_OK(db_->Put(WriteOptions(), "", ""));
|
||||
TEST_SYNC_POINT("FaultInjectionTest::FaultTest:0");
|
||||
TEST_SYNC_POINT("FaultInjectionTest::FaultTest:1");
|
||||
env_->SetFilesystemActive(false);
|
||||
TEST_SYNC_POINT("FaultInjectionTest::FaultTest:2");
|
||||
CloseDB();
|
||||
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
||||
ResetDBState(kResetDropUnsyncedData);
|
||||
|
||||
std::atomic<bool> opened(false);
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::Open:Opened", [&](void* arg) { opened.store(true); });
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::BGWorkCompaction",
|
||||
[&](void* arg) { ASSERT_TRUE(opened.load()); });
|
||||
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
||||
ASSERT_OK(OpenDB());
|
||||
ASSERT_OK(Verify(0, kNumKeys, FaultInjectionTest::kValExpectFound));
|
||||
WaitCompactionFinish();
|
||||
ASSERT_OK(Verify(0, kNumKeys, FaultInjectionTest::kValExpectFound));
|
||||
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
||||
rocksdb::SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
}
|
||||
|
||||
TEST_P(FaultInjectionTest, ManualLogSyncTest) {
|
||||
SleepingBackgroundTask sleeping_task_low;
|
||||
env_->SetBackgroundThreads(1, Env::HIGH);
|
||||
// Block the job queue to prevent flush job from running.
|
||||
env_->Schedule(&SleepingBackgroundTask::DoSleepTask, &sleeping_task_low,
|
||||
Env::Priority::HIGH);
|
||||
|
||||
WriteOptions write_options;
|
||||
write_options.sync = false;
|
||||
|
||||
std::string key_space, value_space;
|
||||
ASSERT_OK(
|
||||
db_->Put(write_options, Key(1, &key_space), Value(1, &value_space)));
|
||||
FlushOptions flush_options;
|
||||
flush_options.wait = false;
|
||||
ASSERT_OK(db_->Flush(flush_options));
|
||||
ASSERT_OK(
|
||||
db_->Put(write_options, Key(2, &key_space), Value(2, &value_space)));
|
||||
ASSERT_OK(db_->SyncWAL());
|
||||
|
||||
env_->SetFilesystemActive(false);
|
||||
NoWriteTestReopenWithFault(kResetDropAndDeleteUnsynced);
|
||||
sleeping_task_low.WakeUp();
|
||||
|
||||
ASSERT_OK(OpenDB());
|
||||
std::string val;
|
||||
Value(2, &value_space);
|
||||
ASSERT_OK(ReadValue(2, &val));
|
||||
ASSERT_EQ(value_space, val);
|
||||
|
||||
Value(1, &value_space);
|
||||
ASSERT_OK(ReadValue(1, &val));
|
||||
ASSERT_EQ(value_space, val);
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(FaultTest, FaultInjectionTest, ::testing::Bool());
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
+3
-1
@@ -12,6 +12,7 @@
|
||||
#include <functional>
|
||||
#include <limits>
|
||||
#include <vector>
|
||||
#include "port/port.h"
|
||||
#include "util/arena.h"
|
||||
#include "util/autovector.h"
|
||||
|
||||
@@ -58,7 +59,8 @@ class FileIndexer {
|
||||
std::vector<FileMetaData*>* const files);
|
||||
|
||||
enum {
|
||||
kLevelMaxIndex = std::numeric_limits<int32_t>::max()
|
||||
// MSVC version 1800 still does not have constexpr for ::max()
|
||||
kLevelMaxIndex = rocksdb::port::kMaxInt32
|
||||
};
|
||||
|
||||
private:
|
||||
|
||||
+4
-7
@@ -18,6 +18,7 @@
|
||||
#include <vector>
|
||||
#include "db/dbformat.h"
|
||||
#include "rocksdb/env.h"
|
||||
#include "util/file_reader_writer.h"
|
||||
#include "util/logging.h"
|
||||
#include "util/stop_watch.h"
|
||||
|
||||
@@ -103,8 +104,6 @@ std::string TableFileName(const std::vector<DbPath>& db_paths, uint64_t number,
|
||||
return MakeTableFileName(path, number);
|
||||
}
|
||||
|
||||
const size_t kFormatFileNumberBufSize = 38;
|
||||
|
||||
void FormatFileNumber(uint64_t number, uint32_t path_id, char* out_buf,
|
||||
size_t out_buf_size) {
|
||||
if (path_id == 0) {
|
||||
@@ -330,15 +329,13 @@ Status SetIdentityFile(Env* env, const std::string& dbname) {
|
||||
return s;
|
||||
}
|
||||
|
||||
Status SyncManifest(Env* env, const DBOptions* db_options, WritableFile* file) {
|
||||
Status SyncManifest(Env* env, const DBOptions* db_options,
|
||||
WritableFileWriter* file) {
|
||||
if (db_options->disableDataSync) {
|
||||
return Status::OK();
|
||||
} else if (db_options->use_fsync) {
|
||||
StopWatch sw(env, db_options->statistics.get(), MANIFEST_FILE_SYNC_MICROS);
|
||||
return file->Fsync();
|
||||
} else {
|
||||
StopWatch sw(env, db_options->statistics.get(), MANIFEST_FILE_SYNC_MICROS);
|
||||
return file->Sync();
|
||||
return file->Sync(db_options->use_fsync);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+3
-3
@@ -25,7 +25,7 @@ namespace rocksdb {
|
||||
|
||||
class Env;
|
||||
class Directory;
|
||||
class WritableFile;
|
||||
class WritableFileWriter;
|
||||
|
||||
enum FileType {
|
||||
kLogFile,
|
||||
@@ -66,7 +66,7 @@ extern std::string TableFileName(const std::vector<DbPath>& db_paths,
|
||||
uint64_t number, uint32_t path_id);
|
||||
|
||||
// Sufficient buffer size for FormatFileNumber.
|
||||
extern const size_t kFormatFileNumberBufSize;
|
||||
const size_t kFormatFileNumberBufSize = 38;
|
||||
|
||||
extern void FormatFileNumber(uint64_t number, uint32_t path_id, char* out_buf,
|
||||
size_t out_buf_size);
|
||||
@@ -140,6 +140,6 @@ extern Status SetIdentityFile(Env* env, const std::string& dbname);
|
||||
|
||||
// Sync manifest file `file`.
|
||||
extern Status SyncManifest(Env* env, const DBOptions* db_options,
|
||||
WritableFile* file);
|
||||
WritableFileWriter* file);
|
||||
|
||||
} // namespace rocksdb
|
||||
+3
-17
@@ -276,27 +276,13 @@ 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 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
|
||||
// that key range.
|
||||
if (base != nullptr && db_options_.max_background_compactions <= 1 &&
|
||||
db_options_.max_background_flushes == 0 &&
|
||||
cfd_->ioptions()->compaction_style == kCompactionStyleLevel) {
|
||||
level = base->storage_info()->PickLevelForMemTableOutput(
|
||||
mutable_cf_options_, min_user_key, max_user_key);
|
||||
// If level does not match path id, reset level back to 0
|
||||
uint32_t fdpath = LevelCompactionPicker::GetPathId(
|
||||
*cfd_->ioptions(), mutable_cf_options_, level);
|
||||
if (fdpath != 0) {
|
||||
level = 0;
|
||||
}
|
||||
}
|
||||
edit->AddFile(level, meta->fd.GetNumber(), meta->fd.GetPathId(),
|
||||
// Add file to L0
|
||||
edit->AddFile(0 /* level */, meta->fd.GetNumber(), meta->fd.GetPathId(),
|
||||
meta->fd.GetFileSize(), meta->smallest, meta->largest,
|
||||
meta->smallest_seqno, meta->largest_seqno,
|
||||
meta->marked_for_compaction);
|
||||
@@ -305,7 +291,7 @@ Status FlushJob::WriteLevel0Table(const autovector<MemTable*>& mems,
|
||||
InternalStats::CompactionStats stats(1);
|
||||
stats.micros = db_options_.env->NowMicros() - start_micros;
|
||||
stats.bytes_written = meta->fd.GetFileSize();
|
||||
cfd_->internal_stats()->AddCompactionStats(level, stats);
|
||||
cfd_->internal_stats()->AddCompactionStats(0 /* level */, stats);
|
||||
cfd_->internal_stats()->AddCFStats(InternalStats::BYTES_FLUSHED,
|
||||
meta->fd.GetFileSize());
|
||||
RecordTick(stats_, COMPACT_WRITE_BYTES, meta->fd.GetFileSize());
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include "db/version_set.h"
|
||||
#include "db/writebuffer.h"
|
||||
#include "rocksdb/cache.h"
|
||||
#include "util/file_reader_writer.h"
|
||||
#include "util/string_util.h"
|
||||
#include "util/testharness.h"
|
||||
#include "util/testutil.h"
|
||||
@@ -56,8 +57,10 @@ class FlushJobTest : public testing::Test {
|
||||
Status s = env_->NewWritableFile(
|
||||
manifest, &file, env_->OptimizeForManifestWrite(env_options_));
|
||||
ASSERT_OK(s);
|
||||
unique_ptr<WritableFileWriter> file_writer(
|
||||
new WritableFileWriter(std::move(file), EnvOptions()));
|
||||
{
|
||||
log::Writer log(std::move(file));
|
||||
log::Writer log(std::move(file_writer));
|
||||
std::string record;
|
||||
new_db.EncodeTo(&record);
|
||||
s = log.AddRecord(record);
|
||||
|
||||
@@ -169,6 +169,7 @@ void ForwardIterator::Cleanup(bool release_sv) {
|
||||
if (job_context.HaveSomethingToDelete()) {
|
||||
db_->PurgeObsoleteFiles(job_context);
|
||||
}
|
||||
job_context.Clean();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+18
-8
@@ -118,6 +118,7 @@ static const std::string is_file_deletions_enabled =
|
||||
static const std::string num_snapshots = "num-snapshots";
|
||||
static const std::string oldest_snapshot_time = "oldest-snapshot-time";
|
||||
static const std::string num_live_versions = "num-live-versions";
|
||||
static const std::string estimate_live_data_size = "estimate-live-data-size";
|
||||
static const std::string base_level = "base-level";
|
||||
|
||||
const std::string DB::Properties::kNumFilesAtLevelPrefix =
|
||||
@@ -158,6 +159,8 @@ const std::string DB::Properties::kOldestSnapshotTime =
|
||||
rocksdb_prefix + oldest_snapshot_time;
|
||||
const std::string DB::Properties::kNumLiveVersions =
|
||||
rocksdb_prefix + num_live_versions;
|
||||
const std::string DB::Properties::kEstimateLiveDataSize =
|
||||
rocksdb_prefix + estimate_live_data_size;
|
||||
|
||||
DBPropertyType GetPropertyType(const Slice& property, bool* is_int_property,
|
||||
bool* need_out_of_mutex) {
|
||||
@@ -222,6 +225,9 @@ DBPropertyType GetPropertyType(const Slice& property, bool* is_int_property,
|
||||
return kOldestSnapshotTime;
|
||||
} else if (in == num_live_versions) {
|
||||
return kNumLiveVersions;
|
||||
} else if (in == estimate_live_data_size) {
|
||||
*need_out_of_mutex = true;
|
||||
return kEstimateLiveDataSize;
|
||||
} else if (in == base_level) {
|
||||
return kBaseLevel;
|
||||
}
|
||||
@@ -232,15 +238,19 @@ bool InternalStats::GetIntPropertyOutOfMutex(DBPropertyType property_type,
|
||||
Version* version,
|
||||
uint64_t* value) const {
|
||||
assert(value != nullptr);
|
||||
if (property_type != kEstimatedUsageByTableReaders) {
|
||||
return false;
|
||||
const auto* vstorage = cfd_->current()->storage_info();
|
||||
|
||||
switch (property_type) {
|
||||
case kEstimatedUsageByTableReaders:
|
||||
*value = (version == nullptr) ?
|
||||
0 : version->GetMemoryUsageByTableReaders();
|
||||
return true;
|
||||
case kEstimateLiveDataSize:
|
||||
*value = vstorage->EstimateLiveDataSize();
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
if (version == nullptr) {
|
||||
*value = 0;
|
||||
} else {
|
||||
*value = version->GetMemoryUsageByTableReaders();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool InternalStats::GetStringProperty(DBPropertyType property_type,
|
||||
|
||||
@@ -58,6 +58,7 @@ enum DBPropertyType : uint32_t {
|
||||
kNumSnapshots, // Number of snapshots in the system
|
||||
kOldestSnapshotTime, // Unix timestamp of the first snapshot
|
||||
kNumLiveVersions,
|
||||
kEstimateLiveDataSize, // Estimated amount of live data in bytes
|
||||
kBaseLevel, // The level that L0 data is compacted to
|
||||
};
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
+14
-13
@@ -216,11 +216,10 @@ 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) {}
|
||||
explicit TestFlushListener(Env* env)
|
||||
: slowdown_count(0), stop_count(0), db_closed(), env_(env) {
|
||||
db_closed = false;
|
||||
}
|
||||
void OnTableFileCreated(
|
||||
const TableFileCreationInfo& info) override {
|
||||
// remember the info for later checking the FlushJobInfo.
|
||||
@@ -439,10 +438,13 @@ TEST_F(EventListenerTest, DisableBGCompaction) {
|
||||
options.enable_thread_tracking = true;
|
||||
#endif // ROCKSDB_USING_THREAD_STATUS
|
||||
TestFlushListener* listener = new TestFlushListener(options.env);
|
||||
const int kCompactionTrigger = 1;
|
||||
const int kSlowdownTrigger = 5;
|
||||
const int kStopTrigger = 10;
|
||||
const int kStopTrigger = 100;
|
||||
options.level0_file_num_compaction_trigger = kCompactionTrigger;
|
||||
options.level0_slowdown_writes_trigger = kSlowdownTrigger;
|
||||
options.level0_stop_writes_trigger = kStopTrigger;
|
||||
options.max_write_buffer_number = 10;
|
||||
options.listeners.emplace_back(listener);
|
||||
// BG compaction is disabled. Number of L0 files will simply keeps
|
||||
// increasing in this test.
|
||||
@@ -451,18 +453,17 @@ TEST_F(EventListenerTest, DisableBGCompaction) {
|
||||
options.write_buffer_size = 100000; // Small write buffer
|
||||
|
||||
CreateAndReopenWithCF({"pikachu"}, &options);
|
||||
WriteOptions wopts;
|
||||
wopts.timeout_hint_us = 100000;
|
||||
ColumnFamilyMetaData cf_meta;
|
||||
db_->GetColumnFamilyMetaData(handles_[1], &cf_meta);
|
||||
|
||||
// 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());
|
||||
for (int i = 0; static_cast<int>(cf_meta.file_count) < kSlowdownTrigger * 10;
|
||||
++i) {
|
||||
Put(1, ToString(i), std::string(10000, 'x'), WriteOptions());
|
||||
db_->Flush(FlushOptions(), handles_[1]);
|
||||
db_->GetColumnFamilyMetaData(handles_[1], &cf_meta);
|
||||
}
|
||||
ASSERT_GE(listener->slowdown_count, kStopTrigger - kSlowdownTrigger);
|
||||
ASSERT_GE(listener->stop_count, 1);
|
||||
ASSERT_GE(listener->slowdown_count, kSlowdownTrigger * 9);
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
+2
-1
@@ -13,6 +13,7 @@
|
||||
#include "rocksdb/env.h"
|
||||
#include "util/coding.h"
|
||||
#include "util/crc32c.h"
|
||||
#include "util/file_reader_writer.h"
|
||||
|
||||
namespace rocksdb {
|
||||
namespace log {
|
||||
@@ -20,7 +21,7 @@ namespace log {
|
||||
Reader::Reporter::~Reporter() {
|
||||
}
|
||||
|
||||
Reader::Reader(unique_ptr<SequentialFile>&& _file, Reporter* reporter,
|
||||
Reader::Reader(unique_ptr<SequentialFileReader>&& _file, Reporter* reporter,
|
||||
bool checksum, uint64_t initial_offset)
|
||||
: file_(std::move(_file)),
|
||||
reporter_(reporter),
|
||||
|
||||
+4
-4
@@ -17,7 +17,7 @@
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
class SequentialFile;
|
||||
class SequentialFileReader;
|
||||
using std::unique_ptr;
|
||||
|
||||
namespace log {
|
||||
@@ -51,7 +51,7 @@ class Reader {
|
||||
//
|
||||
// The Reader will start reading at the first record located at physical
|
||||
// position >= initial_offset within the file.
|
||||
Reader(unique_ptr<SequentialFile>&& file, Reporter* reporter,
|
||||
Reader(unique_ptr<SequentialFileReader>&& file, Reporter* reporter,
|
||||
bool checksum, uint64_t initial_offset);
|
||||
|
||||
~Reader();
|
||||
@@ -81,10 +81,10 @@ class Reader {
|
||||
// block that was partially read.
|
||||
void UnmarkEOF();
|
||||
|
||||
SequentialFile* file() { return file_.get(); }
|
||||
SequentialFileReader* file() { return file_.get(); }
|
||||
|
||||
private:
|
||||
const unique_ptr<SequentialFile> file_;
|
||||
const unique_ptr<SequentialFileReader> file_;
|
||||
Reporter* const reporter_;
|
||||
bool const checksum_;
|
||||
char* const backing_store_;
|
||||
|
||||
+32
-63
@@ -12,8 +12,10 @@
|
||||
#include "rocksdb/env.h"
|
||||
#include "util/coding.h"
|
||||
#include "util/crc32c.h"
|
||||
#include "util/file_reader_writer.h"
|
||||
#include "util/random.h"
|
||||
#include "util/testharness.h"
|
||||
#include "util/testutil.h"
|
||||
|
||||
namespace rocksdb {
|
||||
namespace log {
|
||||
@@ -43,46 +45,6 @@ static std::string RandomSkewedString(int i, Random* rnd) {
|
||||
|
||||
class LogTest : public testing::Test {
|
||||
private:
|
||||
class StringDest : public WritableFile {
|
||||
public:
|
||||
std::string contents_;
|
||||
|
||||
explicit StringDest(Slice& reader_contents) :
|
||||
WritableFile(),
|
||||
contents_(""),
|
||||
reader_contents_(reader_contents),
|
||||
last_flush_(0) {
|
||||
reader_contents_ = Slice(contents_.data(), 0);
|
||||
};
|
||||
|
||||
virtual Status Close() override { return Status::OK(); }
|
||||
virtual Status Flush() override {
|
||||
EXPECT_TRUE(reader_contents_.size() <= last_flush_);
|
||||
size_t offset = last_flush_ - reader_contents_.size();
|
||||
reader_contents_ = Slice(
|
||||
contents_.data() + offset,
|
||||
contents_.size() - offset);
|
||||
last_flush_ = contents_.size();
|
||||
|
||||
return Status::OK();
|
||||
}
|
||||
virtual Status Sync() override { return Status::OK(); }
|
||||
virtual Status Append(const Slice& slice) override {
|
||||
contents_.append(slice.data(), slice.size());
|
||||
return Status::OK();
|
||||
}
|
||||
void Drop(size_t bytes) {
|
||||
contents_.resize(contents_.size() - bytes);
|
||||
reader_contents_ = Slice(
|
||||
reader_contents_.data(), reader_contents_.size() - bytes);
|
||||
last_flush_ = contents_.size();
|
||||
}
|
||||
|
||||
private:
|
||||
Slice& reader_contents_;
|
||||
size_t last_flush_;
|
||||
};
|
||||
|
||||
class StringSource : public SequentialFile {
|
||||
public:
|
||||
Slice& contents_;
|
||||
@@ -163,26 +125,28 @@ class LogTest : public testing::Test {
|
||||
};
|
||||
|
||||
std::string& dest_contents() {
|
||||
auto dest = dynamic_cast<StringDest*>(writer_.file());
|
||||
auto dest =
|
||||
dynamic_cast<test::StringSink*>(writer_.file()->writable_file());
|
||||
assert(dest);
|
||||
return dest->contents_;
|
||||
}
|
||||
|
||||
const std::string& dest_contents() const {
|
||||
auto dest = dynamic_cast<const StringDest*>(writer_.file());
|
||||
auto dest =
|
||||
dynamic_cast<const test::StringSink*>(writer_.file()->writable_file());
|
||||
assert(dest);
|
||||
return dest->contents_;
|
||||
}
|
||||
|
||||
void reset_source_contents() {
|
||||
auto src = dynamic_cast<StringSource*>(reader_.file());
|
||||
auto src = dynamic_cast<StringSource*>(reader_.file()->file());
|
||||
assert(src);
|
||||
src->contents_ = dest_contents();
|
||||
}
|
||||
|
||||
Slice reader_contents_;
|
||||
unique_ptr<StringDest> dest_holder_;
|
||||
unique_ptr<StringSource> source_holder_;
|
||||
unique_ptr<WritableFileWriter> dest_holder_;
|
||||
unique_ptr<SequentialFileReader> source_holder_;
|
||||
ReportCollector report_;
|
||||
Writer writer_;
|
||||
Reader reader_;
|
||||
@@ -192,13 +156,16 @@ class LogTest : public testing::Test {
|
||||
static uint64_t initial_offset_last_record_offsets_[];
|
||||
|
||||
public:
|
||||
LogTest() : reader_contents_(),
|
||||
dest_holder_(new StringDest(reader_contents_)),
|
||||
source_holder_(new StringSource(reader_contents_)),
|
||||
writer_(std::move(dest_holder_)),
|
||||
reader_(std::move(source_holder_), &report_, true/*checksum*/,
|
||||
0/*initial_offset*/) {
|
||||
}
|
||||
LogTest()
|
||||
: reader_contents_(),
|
||||
dest_holder_(
|
||||
test::GetWritableFileWriter(
|
||||
new test::StringSink(&reader_contents_))),
|
||||
source_holder_(
|
||||
test::GetSequentialFileReader(new StringSource(reader_contents_))),
|
||||
writer_(std::move(dest_holder_)),
|
||||
reader_(std::move(source_holder_), &report_, true /*checksum*/,
|
||||
0 /*initial_offset*/) {}
|
||||
|
||||
void Write(const std::string& msg) {
|
||||
writer_.AddRecord(Slice(msg));
|
||||
@@ -227,7 +194,8 @@ class LogTest : public testing::Test {
|
||||
}
|
||||
|
||||
void ShrinkSize(int bytes) {
|
||||
auto dest = dynamic_cast<StringDest*>(writer_.file());
|
||||
auto dest =
|
||||
dynamic_cast<test::StringSink*>(writer_.file()->writable_file());
|
||||
assert(dest);
|
||||
dest->Drop(bytes);
|
||||
}
|
||||
@@ -240,7 +208,7 @@ class LogTest : public testing::Test {
|
||||
}
|
||||
|
||||
void ForceError(size_t position = 0) {
|
||||
auto src = dynamic_cast<StringSource*>(reader_.file());
|
||||
auto src = dynamic_cast<StringSource*>(reader_.file()->file());
|
||||
src->force_error_ = true;
|
||||
src->force_error_position_ = position;
|
||||
}
|
||||
@@ -254,13 +222,13 @@ class LogTest : public testing::Test {
|
||||
}
|
||||
|
||||
void ForceEOF(size_t position = 0) {
|
||||
auto src = dynamic_cast<StringSource*>(reader_.file());
|
||||
auto src = dynamic_cast<StringSource*>(reader_.file()->file());
|
||||
src->force_eof_ = true;
|
||||
src->force_eof_position_ = position;
|
||||
}
|
||||
|
||||
void UnmarkEOF() {
|
||||
auto src = dynamic_cast<StringSource*>(reader_.file());
|
||||
auto src = dynamic_cast<StringSource*>(reader_.file()->file());
|
||||
src->returned_partial_ = false;
|
||||
reader_.UnmarkEOF();
|
||||
}
|
||||
@@ -288,10 +256,11 @@ class LogTest : public testing::Test {
|
||||
|
||||
void CheckOffsetPastEndReturnsNoRecords(uint64_t offset_past_end) {
|
||||
WriteInitialOffsetLog();
|
||||
unique_ptr<StringSource> source(new StringSource(reader_contents_));
|
||||
unique_ptr<SequentialFileReader> file_reader(
|
||||
test::GetSequentialFileReader(new StringSource(reader_contents_)));
|
||||
unique_ptr<Reader> offset_reader(
|
||||
new Reader(std::move(source), &report_, true/*checksum*/,
|
||||
WrittenBytes() + offset_past_end));
|
||||
new Reader(std::move(file_reader), &report_, true /*checksum*/,
|
||||
WrittenBytes() + offset_past_end));
|
||||
Slice record;
|
||||
std::string scratch;
|
||||
ASSERT_TRUE(!offset_reader->ReadRecord(&record, &scratch));
|
||||
@@ -300,10 +269,10 @@ class LogTest : public testing::Test {
|
||||
void CheckInitialOffsetRecord(uint64_t initial_offset,
|
||||
int expected_record_offset) {
|
||||
WriteInitialOffsetLog();
|
||||
unique_ptr<StringSource> source(new StringSource(reader_contents_));
|
||||
unique_ptr<Reader> offset_reader(
|
||||
new Reader(std::move(source), &report_, true/*checksum*/,
|
||||
initial_offset));
|
||||
unique_ptr<SequentialFileReader> file_reader(
|
||||
test::GetSequentialFileReader(new StringSource(reader_contents_)));
|
||||
unique_ptr<Reader> offset_reader(new Reader(
|
||||
std::move(file_reader), &report_, true /*checksum*/, initial_offset));
|
||||
Slice record;
|
||||
std::string scratch;
|
||||
ASSERT_TRUE(offset_reader->ReadRecord(&record, &scratch));
|
||||
|
||||
+3
-3
@@ -13,13 +13,13 @@
|
||||
#include "rocksdb/env.h"
|
||||
#include "util/coding.h"
|
||||
#include "util/crc32c.h"
|
||||
#include "util/file_reader_writer.h"
|
||||
|
||||
namespace rocksdb {
|
||||
namespace log {
|
||||
|
||||
Writer::Writer(unique_ptr<WritableFile>&& dest)
|
||||
: dest_(std::move(dest)),
|
||||
block_offset_(0) {
|
||||
Writer::Writer(unique_ptr<WritableFileWriter>&& dest)
|
||||
: dest_(std::move(dest)), block_offset_(0) {
|
||||
for (int i = 0; i <= kMaxRecordType; i++) {
|
||||
char t = static_cast<char>(i);
|
||||
type_crc_[i] = crc32c::Value(&t, 1);
|
||||
|
||||
+5
-5
@@ -16,7 +16,7 @@
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
class WritableFile;
|
||||
class WritableFileWriter;
|
||||
|
||||
using std::unique_ptr;
|
||||
|
||||
@@ -61,16 +61,16 @@ class Writer {
|
||||
// Create a writer that will append data to "*dest".
|
||||
// "*dest" must be initially empty.
|
||||
// "*dest" must remain live while this Writer is in use.
|
||||
explicit Writer(unique_ptr<WritableFile>&& dest);
|
||||
explicit Writer(unique_ptr<WritableFileWriter>&& dest);
|
||||
~Writer();
|
||||
|
||||
Status AddRecord(const Slice& slice);
|
||||
|
||||
WritableFile* file() { return dest_.get(); }
|
||||
const WritableFile* file() const { return dest_.get(); }
|
||||
WritableFileWriter* file() { return dest_.get(); }
|
||||
const WritableFileWriter* file() const { return dest_.get(); }
|
||||
|
||||
private:
|
||||
unique_ptr<WritableFile> dest_;
|
||||
unique_ptr<WritableFileWriter> dest_;
|
||||
int block_offset_; // Current offset in block
|
||||
|
||||
// crc32c values for all supported record types. These are
|
||||
|
||||
@@ -93,6 +93,7 @@ ManagedIterator::~ManagedIterator() {
|
||||
snapshot_created_ = false;
|
||||
read_options_.snapshot = nullptr;
|
||||
}
|
||||
UnLock();
|
||||
}
|
||||
|
||||
bool ManagedIterator::Valid() const { return valid_; }
|
||||
|
||||
@@ -13,18 +13,12 @@
|
||||
#include "db/writebuffer.h"
|
||||
#include "rocksdb/db.h"
|
||||
#include "rocksdb/status.h"
|
||||
#include "util/testutil.h"
|
||||
#include "util/string_util.h"
|
||||
#include "util/testharness.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
class DumbLogger : public Logger {
|
||||
public:
|
||||
using Logger::Logv;
|
||||
virtual void Logv(const char* format, va_list ap) override {}
|
||||
virtual size_t GetLogFileSize() const override { return 0; }
|
||||
};
|
||||
|
||||
class MemTableListTest : public testing::Test {
|
||||
public:
|
||||
std::string dbname;
|
||||
@@ -58,7 +52,7 @@ class MemTableListTest : public testing::Test {
|
||||
MemTableList* list, const MutableCFOptions& mutable_cf_options,
|
||||
const autovector<MemTable*>& m, autovector<MemTable*>* to_delete) {
|
||||
// Create a mock Logger
|
||||
DumbLogger logger;
|
||||
test::NullLogger logger;
|
||||
LogBuffer log_buffer(DEBUG_LEVEL, &logger);
|
||||
|
||||
// Create a mock VersionSet
|
||||
|
||||
+14
-55
@@ -58,8 +58,8 @@ Status MergeHelper::TimedFullMerge(const Slice& key, const Slice* value,
|
||||
// keys_ stores the list of keys encountered while merging.
|
||||
// operands_ stores the list of merge operands encountered while merging.
|
||||
// keys_[i] corresponds to operands_[i] for each i.
|
||||
void MergeHelper::MergeUntil(Iterator* iter, SequenceNumber stop_before,
|
||||
bool at_bottom, Statistics* stats, int* steps,
|
||||
void MergeHelper::MergeUntil(Iterator* iter, const SequenceNumber stop_before,
|
||||
const bool at_bottom, Statistics* stats,
|
||||
Env* env_) {
|
||||
// Get a copy of the internal key, before it's invalidated by iter->Next()
|
||||
// Also maintain the list of merge operands seen.
|
||||
@@ -81,9 +81,6 @@ void MergeHelper::MergeUntil(Iterator* iter, SequenceNumber stop_before,
|
||||
ParseInternalKey(keys_.back(), &orig_ikey);
|
||||
|
||||
bool hit_the_next_user_key = false;
|
||||
if (steps) {
|
||||
++(*steps);
|
||||
}
|
||||
for (iter->Next(); iter->Valid(); iter->Next()) {
|
||||
ParsedInternalKey ikey;
|
||||
assert(operands_.size() >= 1); // Should be invariants!
|
||||
@@ -110,48 +107,20 @@ void MergeHelper::MergeUntil(Iterator* iter, SequenceNumber stop_before,
|
||||
|
||||
// At this point we are guaranteed that we need to process this key.
|
||||
|
||||
if (kTypeDeletion == ikey.type) {
|
||||
// hit a delete
|
||||
// => merge nullptr with operands_
|
||||
// => store result in operands_.back() (and update keys_.back())
|
||||
// => 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);
|
||||
|
||||
// We store the result in keys_.back() and operands_.back()
|
||||
// if nothing went wrong (i.e.: no operand corruption on disk)
|
||||
if (s.ok()) {
|
||||
std::string& original_key =
|
||||
keys_.back(); // The original key encountered
|
||||
orig_ikey.type = kTypeValue;
|
||||
UpdateInternalKey(&original_key[0], original_key.size(),
|
||||
orig_ikey.sequence, orig_ikey.type);
|
||||
operands_.back() = std::move(merge_result);
|
||||
}
|
||||
|
||||
// move iter to the next entry (before doing anything else)
|
||||
iter->Next();
|
||||
if (steps) {
|
||||
++(*steps);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (kTypeValue == ikey.type) {
|
||||
// hit a put
|
||||
// => merge the put value with operands_
|
||||
assert(ikey.type <= kValueTypeForSeek);
|
||||
if (ikey.type != kTypeMerge) {
|
||||
// hit a put/delete
|
||||
// => merge the put value or a nullptr with operands_
|
||||
// => store result in operands_.back() (and update keys_.back())
|
||||
// => change the entry type to kTypeValue for keys_.back()
|
||||
// We are done! Success!
|
||||
const Slice val = iter->value();
|
||||
const Slice* val_ptr = (kTypeValue == ikey.type) ? &val : nullptr;
|
||||
std::string merge_result;
|
||||
Status s =
|
||||
TimedFullMerge(ikey.user_key, &val, operands_, user_merge_operator_,
|
||||
stats, env_, logger_, &merge_result);
|
||||
TimedFullMerge(ikey.user_key, val_ptr, operands_,
|
||||
user_merge_operator_, stats, env_, logger_,
|
||||
&merge_result);
|
||||
|
||||
// We store the result in keys_.back() and operands_.back()
|
||||
// if nothing went wrong (i.e.: no operand corruption on disk)
|
||||
@@ -159,20 +128,15 @@ void MergeHelper::MergeUntil(Iterator* iter, SequenceNumber stop_before,
|
||||
std::string& original_key =
|
||||
keys_.back(); // The original key encountered
|
||||
orig_ikey.type = kTypeValue;
|
||||
UpdateInternalKey(&original_key[0], original_key.size(),
|
||||
orig_ikey.sequence, orig_ikey.type);
|
||||
UpdateInternalKey(&original_key, orig_ikey.sequence, orig_ikey.type);
|
||||
operands_.back() = std::move(merge_result);
|
||||
success_ = true;
|
||||
}
|
||||
|
||||
// move iter to the next entry
|
||||
iter->Next();
|
||||
if (steps) {
|
||||
++(*steps);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (kTypeMerge == ikey.type) {
|
||||
} else {
|
||||
// hit a merge
|
||||
// => merge the operand into the front of the operands_ list
|
||||
// => use the user's associative merge function to determine how.
|
||||
@@ -183,9 +147,6 @@ void MergeHelper::MergeUntil(Iterator* iter, SequenceNumber stop_before,
|
||||
// request or later did a partial merge.
|
||||
keys_.push_front(iter->key().ToString());
|
||||
operands_.push_front(iter->value().ToString());
|
||||
if (steps) {
|
||||
++(*steps);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -223,9 +184,7 @@ void MergeHelper::MergeUntil(Iterator* iter, SequenceNumber stop_before,
|
||||
if (success_) {
|
||||
std::string& original_key = keys_.back(); // The original key encountered
|
||||
orig_ikey.type = kTypeValue;
|
||||
UpdateInternalKey(&original_key[0], original_key.size(),
|
||||
orig_ikey.sequence, orig_ikey.type);
|
||||
|
||||
UpdateInternalKey(&original_key, orig_ikey.sequence, orig_ikey.type);
|
||||
operands_.back() = std::move(merge_result);
|
||||
} else {
|
||||
RecordTick(stats, NUMBER_MERGE_FAILURES);
|
||||
|
||||
+7
-6
@@ -6,11 +6,12 @@
|
||||
#ifndef MERGE_HELPER_H
|
||||
#define MERGE_HELPER_H
|
||||
|
||||
#include "db/dbformat.h"
|
||||
#include "rocksdb/slice.h"
|
||||
#include <string>
|
||||
#include <deque>
|
||||
#include <string>
|
||||
|
||||
#include "db/dbformat.h"
|
||||
#include "rocksdb/env.h"
|
||||
#include "rocksdb/slice.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
@@ -56,9 +57,9 @@ class MergeHelper {
|
||||
// 0 means no restriction
|
||||
// at_bottom: (IN) true if the iterator covers the bottem level, which means
|
||||
// we could reach the start of the history of this user key.
|
||||
void MergeUntil(Iterator* iter, SequenceNumber stop_before = 0,
|
||||
bool at_bottom = false, Statistics* stats = nullptr,
|
||||
int* steps = nullptr, Env* env_ = nullptr);
|
||||
void MergeUntil(Iterator* iter, const SequenceNumber stop_before = 0,
|
||||
const bool at_bottom = false, Statistics* stats = nullptr,
|
||||
Env* env_ = nullptr);
|
||||
|
||||
// Query the merge result
|
||||
// These are valid until the next MergeUntil call
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
// 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 <algorithm>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "db/merge_helper.h"
|
||||
#include "rocksdb/comparator.h"
|
||||
#include "util/coding.h"
|
||||
#include "util/testharness.h"
|
||||
#include "util/testutil.h"
|
||||
#include "utilities/merge_operators.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
class MergeHelperTest : public testing::Test {
|
||||
public:
|
||||
MergeHelperTest() = default;
|
||||
~MergeHelperTest() = default;
|
||||
|
||||
void RunUInt64MergeHelper(SequenceNumber stop_before, bool at_bottom) {
|
||||
InitIterator();
|
||||
merge_op_ = MergeOperators::CreateUInt64AddOperator();
|
||||
merge_helper_.reset(new MergeHelper(BytewiseComparator(), merge_op_.get(),
|
||||
nullptr, 2U, true));
|
||||
merge_helper_->MergeUntil(iter_.get(), stop_before, at_bottom, nullptr,
|
||||
Env::Default());
|
||||
}
|
||||
|
||||
void RunStringAppendMergeHelper(SequenceNumber stop_before, bool at_bottom) {
|
||||
InitIterator();
|
||||
merge_op_ = MergeOperators::CreateStringAppendTESTOperator();
|
||||
merge_helper_.reset(new MergeHelper(BytewiseComparator(), merge_op_.get(),
|
||||
nullptr, 2U, true));
|
||||
merge_helper_->MergeUntil(iter_.get(), stop_before, at_bottom, nullptr,
|
||||
Env::Default());
|
||||
}
|
||||
|
||||
std::string Key(const std::string& user_key, const SequenceNumber& seq,
|
||||
const ValueType& t) {
|
||||
std::string ikey;
|
||||
AppendInternalKey(&ikey, ParsedInternalKey(Slice(user_key), seq, t));
|
||||
return ikey;
|
||||
}
|
||||
|
||||
void AddKeyVal(const std::string& user_key, const SequenceNumber& seq,
|
||||
const ValueType& t, const std::string& val) {
|
||||
ks_.push_back(Key(user_key, seq, t));
|
||||
vs_.push_back(val);
|
||||
}
|
||||
|
||||
void InitIterator() {
|
||||
iter_.reset(new test::VectorIterator(ks_, vs_));
|
||||
iter_->SeekToFirst();
|
||||
}
|
||||
|
||||
std::string EncodeInt(uint64_t x) {
|
||||
std::string result;
|
||||
PutFixed64(&result, x);
|
||||
return result;
|
||||
}
|
||||
|
||||
void CheckState(bool success, int iter_pos) {
|
||||
ASSERT_EQ(success, merge_helper_->IsSuccess());
|
||||
if (iter_pos == -1) {
|
||||
ASSERT_FALSE(iter_->Valid());
|
||||
} else {
|
||||
ASSERT_EQ(ks_[iter_pos], iter_->key());
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<test::VectorIterator> iter_;
|
||||
std::shared_ptr<MergeOperator> merge_op_;
|
||||
std::unique_ptr<MergeHelper> merge_helper_;
|
||||
std::vector<std::string> ks_;
|
||||
std::vector<std::string> vs_;
|
||||
};
|
||||
|
||||
// If MergeHelper encounters a new key on the last level, we know that
|
||||
// the key has no more history and it can merge keys.
|
||||
TEST_F(MergeHelperTest, MergeAtBottomSuccess) {
|
||||
AddKeyVal("a", 20, kTypeMerge, EncodeInt(1U));
|
||||
AddKeyVal("a", 10, kTypeMerge, EncodeInt(3U));
|
||||
AddKeyVal("b", 10, kTypeMerge, EncodeInt(4U)); // <- Iterator after merge
|
||||
|
||||
RunUInt64MergeHelper(0, true);
|
||||
CheckState(true, 2);
|
||||
ASSERT_EQ(Key("a", 20, kTypeValue), merge_helper_->key());
|
||||
ASSERT_EQ(EncodeInt(4U), merge_helper_->value());
|
||||
}
|
||||
|
||||
// Merging with a value results in a successful merge.
|
||||
TEST_F(MergeHelperTest, MergeValue) {
|
||||
AddKeyVal("a", 40, kTypeMerge, EncodeInt(1U));
|
||||
AddKeyVal("a", 30, kTypeMerge, EncodeInt(3U));
|
||||
AddKeyVal("a", 20, kTypeValue, EncodeInt(4U)); // <- Iterator after merge
|
||||
AddKeyVal("a", 10, kTypeMerge, EncodeInt(1U));
|
||||
|
||||
RunUInt64MergeHelper(0, false);
|
||||
CheckState(true, 3);
|
||||
ASSERT_EQ(Key("a", 40, kTypeValue), merge_helper_->key());
|
||||
ASSERT_EQ(EncodeInt(8U), merge_helper_->value());
|
||||
}
|
||||
|
||||
// Merging stops before a snapshot.
|
||||
TEST_F(MergeHelperTest, SnapshotBeforeValue) {
|
||||
AddKeyVal("a", 50, kTypeMerge, EncodeInt(1U));
|
||||
AddKeyVal("a", 40, kTypeMerge, EncodeInt(3U)); // <- Iterator after merge
|
||||
AddKeyVal("a", 30, kTypeMerge, EncodeInt(1U));
|
||||
AddKeyVal("a", 20, kTypeValue, EncodeInt(4U));
|
||||
AddKeyVal("a", 10, kTypeMerge, EncodeInt(1U));
|
||||
|
||||
RunUInt64MergeHelper(31, true);
|
||||
CheckState(false, 2);
|
||||
ASSERT_EQ(Key("a", 50, kTypeMerge), merge_helper_->keys()[0]);
|
||||
ASSERT_EQ(EncodeInt(4U), merge_helper_->values()[0]);
|
||||
}
|
||||
|
||||
// MergeHelper preserves the operand stack for merge operators that
|
||||
// cannot do a partial merge.
|
||||
TEST_F(MergeHelperTest, NoPartialMerge) {
|
||||
AddKeyVal("a", 50, kTypeMerge, "v2");
|
||||
AddKeyVal("a", 40, kTypeMerge, "v"); // <- Iterator after merge
|
||||
AddKeyVal("a", 30, kTypeMerge, "v");
|
||||
|
||||
RunStringAppendMergeHelper(31, true);
|
||||
CheckState(false, 2);
|
||||
ASSERT_EQ(Key("a", 40, kTypeMerge), merge_helper_->keys()[0]);
|
||||
ASSERT_EQ("v", merge_helper_->values()[0]);
|
||||
ASSERT_EQ(Key("a", 50, kTypeMerge), merge_helper_->keys()[1]);
|
||||
ASSERT_EQ("v2", merge_helper_->values()[1]);
|
||||
}
|
||||
|
||||
// Merging with a deletion turns the deletion into a value
|
||||
TEST_F(MergeHelperTest, MergeDeletion) {
|
||||
AddKeyVal("a", 30, kTypeMerge, EncodeInt(3U));
|
||||
AddKeyVal("a", 20, kTypeDeletion, "");
|
||||
|
||||
RunUInt64MergeHelper(15, false);
|
||||
CheckState(true, -1);
|
||||
ASSERT_EQ(Key("a", 30, kTypeValue), merge_helper_->key());
|
||||
ASSERT_EQ(EncodeInt(3U), merge_helper_->value());
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
@@ -87,6 +87,8 @@ std::shared_ptr<DB> OpenDb(const string& dbname, const bool ttl = false,
|
||||
options.min_partial_merge_operands = min_partial_merge_operands;
|
||||
Status s;
|
||||
DestroyDB(dbname, Options());
|
||||
// DBWithTTL is not supported in ROCKSDB_LITE
|
||||
#ifndef ROCKSDB_LITE
|
||||
if (ttl) {
|
||||
cout << "Opening database with TTL\n";
|
||||
DBWithTTL* db_with_ttl;
|
||||
@@ -95,6 +97,10 @@ std::shared_ptr<DB> OpenDb(const string& dbname, const bool ttl = false,
|
||||
} else {
|
||||
s = DB::Open(options, dbname, &db);
|
||||
}
|
||||
#else
|
||||
assert(!ttl);
|
||||
s = DB::Open(options, dbname, &db);
|
||||
#endif // !ROCKSDB_LITE
|
||||
if (!s.ok()) {
|
||||
cerr << s.ToString() << endl;
|
||||
assert(false);
|
||||
@@ -503,7 +509,10 @@ 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");
|
||||
// DBWithTTL is not supported in ROCKSDB_LITE
|
||||
#ifndef ROCKSDB_LITE
|
||||
runTest(argc, test::TmpDir() + "/merge_testdbttl", true); // Run test on TTL database
|
||||
#endif // !ROCKSDB_LITE
|
||||
printf("Passed all tests!\n");
|
||||
return 0;
|
||||
}
|
||||
@@ -8,6 +8,9 @@
|
||||
// Copyright (c) 2011 The LevelDB Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
|
||||
#include <algorithm>
|
||||
#include <set>
|
||||
|
||||
@@ -198,10 +201,9 @@ class TestPlainTableReader : public PlainTableReader {
|
||||
int bloom_bits_per_key, double hash_table_ratio,
|
||||
size_t index_sparseness,
|
||||
const TableProperties* table_properties,
|
||||
unique_ptr<RandomAccessFile>&& file,
|
||||
unique_ptr<RandomAccessFileReader>&& file,
|
||||
const ImmutableCFOptions& ioptions,
|
||||
bool* expect_bloom_not_match,
|
||||
bool store_index_in_file)
|
||||
bool* expect_bloom_not_match, bool store_index_in_file)
|
||||
: PlainTableReader(ioptions, std::move(file), env_options, icomparator,
|
||||
encoding_type, file_size, table_properties),
|
||||
expect_bloom_not_match_(expect_bloom_not_match) {
|
||||
@@ -257,7 +259,8 @@ class TestPlainTableFactory : public PlainTableFactory {
|
||||
Status NewTableReader(const ImmutableCFOptions& ioptions,
|
||||
const EnvOptions& env_options,
|
||||
const InternalKeyComparator& internal_comparator,
|
||||
unique_ptr<RandomAccessFile>&& file, uint64_t file_size,
|
||||
unique_ptr<RandomAccessFileReader>&& file,
|
||||
uint64_t file_size,
|
||||
unique_ptr<TableReader>* table) const override {
|
||||
TableProperties* props = nullptr;
|
||||
auto s = ReadTableProperties(file.get(), file_size, kPlainTableMagicNumber,
|
||||
@@ -1011,7 +1014,6 @@ TEST_F(PlainTableDBTest, CompactionTrigger) {
|
||||
Options options = CurrentOptions();
|
||||
options.write_buffer_size = 100 << 10; //100KB
|
||||
options.num_levels = 3;
|
||||
options.max_mem_compaction_level = 0;
|
||||
options.level0_file_num_compaction_trigger = 3;
|
||||
Reopen(&options);
|
||||
|
||||
@@ -1090,3 +1092,13 @@ int main(int argc, char** argv) {
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
|
||||
#else
|
||||
#include <stdio.h>
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
fprintf(stderr, "SKIPPED as plain table is not supported in ROCKSDB_LITE\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif // !ROCKSDB_LITE
|
||||
@@ -3,6 +3,8 @@
|
||||
// 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
|
||||
|
||||
#ifndef GFLAGS
|
||||
#include <cstdio>
|
||||
int main() {
|
||||
@@ -501,3 +503,15 @@ int main(int argc, char** argv) {
|
||||
}
|
||||
|
||||
#endif // GFLAGS
|
||||
|
||||
#else
|
||||
#include <stdio.h>
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
fprintf(stderr,
|
||||
"SKIPPED as HashSkipList and HashLinkList are not supported in "
|
||||
"ROCKSDB_LITE\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif // !ROCKSDB_LITE
|
||||
+11
-6
@@ -81,6 +81,7 @@
|
||||
#include "rocksdb/env.h"
|
||||
#include "rocksdb/options.h"
|
||||
#include "rocksdb/immutable_options.h"
|
||||
#include "util/file_reader_writer.h"
|
||||
#include "util/scoped_arena_iterator.h"
|
||||
|
||||
namespace rocksdb {
|
||||
@@ -127,7 +128,7 @@ class Repairer {
|
||||
}
|
||||
Log(InfoLogLevel::WARN_LEVEL, options_.info_log,
|
||||
"**** Repaired rocksdb %s; "
|
||||
"recovered %zu files; %" PRIu64
|
||||
"recovered %" ROCKSDB_PRIszt " files; %" PRIu64
|
||||
"bytes. "
|
||||
"Some data may have been lost. "
|
||||
"****",
|
||||
@@ -236,6 +237,8 @@ class Repairer {
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
unique_ptr<SequentialFileReader> lfile_reader(
|
||||
new SequentialFileReader(std::move(lfile)));
|
||||
|
||||
// Create the log reader.
|
||||
LogReporter reporter;
|
||||
@@ -246,8 +249,8 @@ class Repairer {
|
||||
// corruptions cause entire commits to be skipped instead of
|
||||
// propagating bad information (like overly large sequence
|
||||
// numbers).
|
||||
log::Reader reader(std::move(lfile), &reporter, true /*enable checksum*/,
|
||||
0/*initial_offset*/);
|
||||
log::Reader reader(std::move(lfile_reader), &reporter,
|
||||
true /*enable checksum*/, 0 /*initial_offset*/);
|
||||
|
||||
// Read all the records and add to a memtable
|
||||
std::string scratch;
|
||||
@@ -378,8 +381,8 @@ class Repairer {
|
||||
Status WriteDescriptor() {
|
||||
std::string tmp = TempFileName(dbname_, 1);
|
||||
unique_ptr<WritableFile> file;
|
||||
Status status = env_->NewWritableFile(
|
||||
tmp, &file, env_->OptimizeForManifestWrite(env_options_));
|
||||
EnvOptions env_options = env_->OptimizeForManifestWrite(env_options_);
|
||||
Status status = env_->NewWritableFile(tmp, &file, env_options);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
@@ -407,7 +410,9 @@ class Repairer {
|
||||
|
||||
//fprintf(stderr, "NewDescriptor:\n%s\n", edit_.DebugString().c_str());
|
||||
{
|
||||
log::Writer log(std::move(file));
|
||||
unique_ptr<WritableFileWriter> file_writer(
|
||||
new WritableFileWriter(std::move(file), env_options));
|
||||
log::Writer log(std::move(file_writer));
|
||||
std::string record;
|
||||
edit_->EncodeTo(&record);
|
||||
status = log.AddRecord(record);
|
||||
|
||||
+13
-3
@@ -18,6 +18,8 @@
|
||||
#include "table/table_reader.h"
|
||||
#include "table/get_context.h"
|
||||
#include "util/coding.h"
|
||||
#include "util/file_reader_writer.h"
|
||||
#include "util/perf_context_imp.h"
|
||||
#include "util/stop_watch.h"
|
||||
|
||||
namespace rocksdb {
|
||||
@@ -77,7 +79,8 @@ void TableCache::ReleaseHandle(Cache::Handle* handle) {
|
||||
Status TableCache::FindTable(const EnvOptions& env_options,
|
||||
const InternalKeyComparator& internal_comparator,
|
||||
const FileDescriptor& fd, Cache::Handle** handle,
|
||||
const bool no_io) {
|
||||
const bool no_io, bool record_read_stats) {
|
||||
PERF_TIMER_GUARD(find_table_nanos);
|
||||
Status s;
|
||||
uint64_t number = fd.GetNumber();
|
||||
Slice key = GetSliceForFileNumber(&number);
|
||||
@@ -97,8 +100,13 @@ Status TableCache::FindTable(const EnvOptions& env_options,
|
||||
file->Hint(RandomAccessFile::RANDOM);
|
||||
}
|
||||
StopWatch sw(ioptions_.env, ioptions_.statistics, TABLE_OPEN_IO_MICROS);
|
||||
std::unique_ptr<RandomAccessFileReader> file_reader(
|
||||
new RandomAccessFileReader(
|
||||
std::move(file), ioptions_.env,
|
||||
record_read_stats ? ioptions_.statistics : nullptr,
|
||||
SST_READ_MICROS));
|
||||
s = ioptions_.table_factory->NewTableReader(
|
||||
ioptions_, env_options, internal_comparator, std::move(file),
|
||||
ioptions_, env_options, internal_comparator, std::move(file_reader),
|
||||
fd.GetFileSize(), &table_reader);
|
||||
}
|
||||
|
||||
@@ -121,6 +129,8 @@ Iterator* TableCache::NewIterator(const ReadOptions& options,
|
||||
const FileDescriptor& fd,
|
||||
TableReader** table_reader_ptr,
|
||||
bool for_compaction, Arena* arena) {
|
||||
PERF_TIMER_GUARD(new_table_iterator_nanos);
|
||||
|
||||
if (table_reader_ptr != nullptr) {
|
||||
*table_reader_ptr = nullptr;
|
||||
}
|
||||
@@ -129,7 +139,7 @@ Iterator* TableCache::NewIterator(const ReadOptions& options,
|
||||
Status s;
|
||||
if (table_reader == nullptr) {
|
||||
s = FindTable(env_options, icomparator, fd, &handle,
|
||||
options.read_tier == kBlockCacheTier);
|
||||
options.read_tier == kBlockCacheTier, !for_compaction);
|
||||
if (!s.ok()) {
|
||||
return NewErrorIterator(s, arena);
|
||||
}
|
||||
|
||||
+1
-1
@@ -63,7 +63,7 @@ class TableCache {
|
||||
Status FindTable(const EnvOptions& toptions,
|
||||
const InternalKeyComparator& internal_comparator,
|
||||
const FileDescriptor& file_fd, Cache::Handle**,
|
||||
const bool no_io = false);
|
||||
const bool no_io = false, bool record_read_stats = true);
|
||||
|
||||
// Get TableReader from a cache handle.
|
||||
TableReader* GetTableReaderFromHandle(Cache::Handle* handle);
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#include "table/plain_table_factory.h"
|
||||
#include "table/table_builder.h"
|
||||
#include "util/coding.h"
|
||||
#include "util/file_reader_writer.h"
|
||||
#include "util/testharness.h"
|
||||
#include "util/testutil.h"
|
||||
|
||||
@@ -31,74 +32,17 @@ class TablePropertiesTest : public testing::Test,
|
||||
bool backward_mode_;
|
||||
};
|
||||
|
||||
// TODO(kailiu) the following classes should be moved to some more general
|
||||
// places, so that other tests can also make use of them.
|
||||
// `FakeWritableFile` and `FakeRandomeAccessFile` bypass the real file system
|
||||
// and therefore enable us to quickly setup the tests.
|
||||
class FakeWritableFile : public WritableFile {
|
||||
public:
|
||||
~FakeWritableFile() { }
|
||||
|
||||
const std::string& contents() const { return contents_; }
|
||||
|
||||
virtual Status Close() override { return Status::OK(); }
|
||||
virtual Status Flush() override { return Status::OK(); }
|
||||
virtual Status Sync() override { return Status::OK(); }
|
||||
|
||||
virtual Status Append(const Slice& data) override {
|
||||
contents_.append(data.data(), data.size());
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
private:
|
||||
std::string contents_;
|
||||
};
|
||||
|
||||
|
||||
class FakeRandomeAccessFile : public RandomAccessFile {
|
||||
public:
|
||||
explicit FakeRandomeAccessFile(const Slice& contents)
|
||||
: contents_(contents.data(), contents.size()) {
|
||||
}
|
||||
|
||||
virtual ~FakeRandomeAccessFile() { }
|
||||
|
||||
uint64_t Size() const { return contents_.size(); }
|
||||
|
||||
virtual Status Read(uint64_t offset, size_t n, Slice* result,
|
||||
char* scratch) const override {
|
||||
if (offset > contents_.size()) {
|
||||
return Status::InvalidArgument("invalid Read offset");
|
||||
}
|
||||
if (offset + n > contents_.size()) {
|
||||
n = contents_.size() - offset;
|
||||
}
|
||||
memcpy(scratch, &contents_[offset], n);
|
||||
*result = Slice(scratch, n);
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
private:
|
||||
std::string contents_;
|
||||
};
|
||||
|
||||
|
||||
class DumbLogger : public Logger {
|
||||
public:
|
||||
using Logger::Logv;
|
||||
virtual void Logv(const char* format, va_list ap) override {}
|
||||
virtual size_t GetLogFileSize() const override { return 0; }
|
||||
};
|
||||
|
||||
// Utilities test functions
|
||||
namespace {
|
||||
void MakeBuilder(const Options& options, const ImmutableCFOptions& ioptions,
|
||||
const InternalKeyComparator& internal_comparator,
|
||||
const std::vector<std::unique_ptr<IntTblPropCollectorFactory>>*
|
||||
int_tbl_prop_collector_factories,
|
||||
std::unique_ptr<FakeWritableFile>* writable,
|
||||
std::unique_ptr<WritableFileWriter>* writable,
|
||||
std::unique_ptr<TableBuilder>* builder) {
|
||||
writable->reset(new FakeWritableFile);
|
||||
unique_ptr<WritableFile> wf(new test::StringSink);
|
||||
writable->reset(new WritableFileWriter(std::move(wf), EnvOptions()));
|
||||
|
||||
builder->reset(NewTableBuilder(
|
||||
ioptions, internal_comparator, int_tbl_prop_collector_factories,
|
||||
writable->get(), options.compression, options.compression_opts));
|
||||
@@ -267,8 +211,8 @@ class FlushBlockEveryThreePolicyFactory : public FlushBlockPolicyFactory {
|
||||
}
|
||||
};
|
||||
|
||||
extern uint64_t kBlockBasedTableMagicNumber;
|
||||
extern uint64_t kPlainTableMagicNumber;
|
||||
extern const uint64_t kBlockBasedTableMagicNumber;
|
||||
extern const uint64_t kPlainTableMagicNumber;
|
||||
namespace {
|
||||
void TestCustomizedTablePropertiesCollector(
|
||||
bool backward_mode, uint64_t magic_number, bool test_int_tbl_prop_collector,
|
||||
@@ -289,7 +233,7 @@ void TestCustomizedTablePropertiesCollector(
|
||||
|
||||
// -- Step 1: build table
|
||||
std::unique_ptr<TableBuilder> builder;
|
||||
std::unique_ptr<FakeWritableFile> writable;
|
||||
std::unique_ptr<WritableFileWriter> writer;
|
||||
const ImmutableCFOptions ioptions(options);
|
||||
std::vector<std::unique_ptr<IntTblPropCollectorFactory>>
|
||||
int_tbl_prop_collector_factories;
|
||||
@@ -300,7 +244,7 @@ void TestCustomizedTablePropertiesCollector(
|
||||
GetIntTblPropCollectorFactory(options, &int_tbl_prop_collector_factories);
|
||||
}
|
||||
MakeBuilder(options, ioptions, internal_comparator,
|
||||
&int_tbl_prop_collector_factories, &writable, &builder);
|
||||
&int_tbl_prop_collector_factories, &writer, &builder);
|
||||
|
||||
SequenceNumber seqNum = 0U;
|
||||
for (const auto& kv : kvs) {
|
||||
@@ -310,18 +254,17 @@ void TestCustomizedTablePropertiesCollector(
|
||||
builder->Add(ikey.Encode(), kv.second);
|
||||
}
|
||||
ASSERT_OK(builder->Finish());
|
||||
writer->Flush();
|
||||
|
||||
// -- Step 2: Read properties
|
||||
FakeRandomeAccessFile readable(writable->contents());
|
||||
test::StringSink* fwf =
|
||||
static_cast<test::StringSink*>(writer->writable_file());
|
||||
std::unique_ptr<RandomAccessFileReader> fake_file_reader(
|
||||
test::GetRandomAccessFileReader(
|
||||
new test::StringSource(fwf->contents())));
|
||||
TableProperties* props;
|
||||
Status s = ReadTableProperties(
|
||||
&readable,
|
||||
writable->contents().size(),
|
||||
magic_number,
|
||||
Env::Default(),
|
||||
nullptr,
|
||||
&props
|
||||
);
|
||||
Status s = ReadTableProperties(fake_file_reader.get(), fwf->contents().size(),
|
||||
magic_number, Env::Default(), nullptr, &props);
|
||||
std::unique_ptr<TableProperties> props_guard(props);
|
||||
ASSERT_OK(s);
|
||||
|
||||
@@ -363,10 +306,6 @@ TEST_P(TablePropertiesTest, CustomizedTablePropertiesCollector) {
|
||||
// Test properties collectors with internal keys or regular keys
|
||||
// for block based table
|
||||
for (bool encode_as_internal : { true, false }) {
|
||||
if (!backward_mode_ && !encode_as_internal) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Options options;
|
||||
BlockBasedTableOptions table_options;
|
||||
table_options.flush_block_policy_factory =
|
||||
@@ -414,7 +353,7 @@ void TestInternalKeyPropertiesCollector(
|
||||
};
|
||||
|
||||
std::unique_ptr<TableBuilder> builder;
|
||||
std::unique_ptr<FakeWritableFile> writable;
|
||||
std::unique_ptr<WritableFileWriter> writable;
|
||||
Options options;
|
||||
test::PlainInternalKeyComparator pikc(options.comparator);
|
||||
|
||||
@@ -429,7 +368,7 @@ void TestInternalKeyPropertiesCollector(
|
||||
auto comparator = options.comparator;
|
||||
// HACK: Set options.info_log to avoid writing log in
|
||||
// SanitizeOptions().
|
||||
options.info_log = std::make_shared<DumbLogger>();
|
||||
options.info_log = std::make_shared<test::NullLogger>();
|
||||
options = SanitizeOptions("db", // just a place holder
|
||||
&pikc,
|
||||
options);
|
||||
@@ -449,12 +388,16 @@ void TestInternalKeyPropertiesCollector(
|
||||
}
|
||||
|
||||
ASSERT_OK(builder->Finish());
|
||||
writable->Flush();
|
||||
|
||||
FakeRandomeAccessFile readable(writable->contents());
|
||||
test::StringSink* fwf =
|
||||
static_cast<test::StringSink*>(writable->writable_file());
|
||||
unique_ptr<RandomAccessFileReader> reader(test::GetRandomAccessFileReader(
|
||||
new test::StringSource(fwf->contents())));
|
||||
TableProperties* props;
|
||||
Status s =
|
||||
ReadTableProperties(&readable, writable->contents().size(),
|
||||
magic_number, Env::Default(), nullptr, &props);
|
||||
ReadTableProperties(reader.get(), fwf->contents().size(), magic_number,
|
||||
Env::Default(), nullptr, &props);
|
||||
ASSERT_OK(s);
|
||||
|
||||
std::unique_ptr<TableProperties> props_guard(props);
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include <inttypes.h>
|
||||
#include "db/transaction_log_impl.h"
|
||||
#include "db/write_batch_internal.h"
|
||||
#include "util/file_reader_writer.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
@@ -40,23 +41,27 @@ TransactionLogIteratorImpl::TransactionLogIteratorImpl(
|
||||
}
|
||||
|
||||
Status TransactionLogIteratorImpl::OpenLogFile(
|
||||
const LogFile* logFile,
|
||||
unique_ptr<SequentialFile>* file) {
|
||||
const LogFile* logFile, unique_ptr<SequentialFileReader>* file_reader) {
|
||||
Env* env = options_->env;
|
||||
unique_ptr<SequentialFile> file;
|
||||
Status s;
|
||||
if (logFile->Type() == kArchivedLogFile) {
|
||||
std::string fname = ArchivedLogFileName(dir_, logFile->LogNumber());
|
||||
return env->NewSequentialFile(fname, file, soptions_);
|
||||
s = env->NewSequentialFile(fname, &file, soptions_);
|
||||
} else {
|
||||
std::string fname = LogFileName(dir_, logFile->LogNumber());
|
||||
Status s = env->NewSequentialFile(fname, file, soptions_);
|
||||
s = env->NewSequentialFile(fname, &file, soptions_);
|
||||
if (!s.ok()) {
|
||||
// If cannot open file in DB directory.
|
||||
// Try the archive dir, as it could have moved in the meanwhile.
|
||||
fname = ArchivedLogFileName(dir_, logFile->LogNumber());
|
||||
s = env->NewSequentialFile(fname, file, soptions_);
|
||||
s = env->NewSequentialFile(fname, &file, soptions_);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
if (s.ok()) {
|
||||
file_reader->reset(new SequentialFileReader(std::move(file)));
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
BatchResult TransactionLogIteratorImpl::GetBatch() {
|
||||
@@ -251,7 +256,7 @@ void TransactionLogIteratorImpl::UpdateCurrentWriteBatch(const Slice& record) {
|
||||
}
|
||||
|
||||
Status TransactionLogIteratorImpl::OpenLogReader(const LogFile* logFile) {
|
||||
unique_ptr<SequentialFile> file;
|
||||
unique_ptr<SequentialFileReader> file;
|
||||
Status s = OpenLogFile(logFile, &file);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#include "db/version_set.h"
|
||||
#include "db/log_reader.h"
|
||||
#include "db/filename.h"
|
||||
#include "port/port.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
@@ -83,13 +84,15 @@ class TransactionLogIteratorImpl : public TransactionLogIterator {
|
||||
size_t currentFileIndex_;
|
||||
std::unique_ptr<WriteBatch> currentBatch_;
|
||||
unique_ptr<log::Reader> currentLogReader_;
|
||||
Status OpenLogFile(const LogFile* logFile, unique_ptr<SequentialFile>* file);
|
||||
Status OpenLogFile(const LogFile* logFile,
|
||||
unique_ptr<SequentialFileReader>* file);
|
||||
|
||||
struct LogReporter : public log::Reader::Reporter {
|
||||
Env* env;
|
||||
Logger* info_log;
|
||||
virtual void Corruption(size_t bytes, const Status& s) override {
|
||||
Log(InfoLogLevel::ERROR_LEVEL, info_log, "dropping %zu bytes; %s", bytes,
|
||||
Log(InfoLogLevel::ERROR_LEVEL, info_log,
|
||||
"dropping %" ROCKSDB_PRIszt " bytes; %s", bytes,
|
||||
s.ToString().c_str());
|
||||
}
|
||||
virtual void Info(const char* s) {
|
||||
|
||||
+73
-1
@@ -11,6 +11,7 @@
|
||||
|
||||
#include "db/version_set.h"
|
||||
#include "util/coding.h"
|
||||
#include "util/event_logger.h"
|
||||
#include "rocksdb/slice.h"
|
||||
|
||||
namespace rocksdb {
|
||||
@@ -359,7 +360,7 @@ std::string VersionEdit::DebugString(bool hex_key) const {
|
||||
AppendNumberTo(&r, prev_log_number_);
|
||||
}
|
||||
if (has_next_file_number_) {
|
||||
r.append("\n NextFile: ");
|
||||
r.append("\n NextFileNumber: ");
|
||||
AppendNumberTo(&r, next_file_number_);
|
||||
}
|
||||
if (has_last_sequence_) {
|
||||
@@ -404,4 +405,75 @@ std::string VersionEdit::DebugString(bool hex_key) const {
|
||||
return r;
|
||||
}
|
||||
|
||||
std::string VersionEdit::DebugJSON(int edit_num, bool hex_key) const {
|
||||
JSONWriter jw;
|
||||
jw << "EditNumber" << edit_num;
|
||||
|
||||
if (has_comparator_) {
|
||||
jw << "Comparator" << comparator_;
|
||||
}
|
||||
if (has_log_number_) {
|
||||
jw << "LogNumber" << log_number_;
|
||||
}
|
||||
if (has_prev_log_number_) {
|
||||
jw << "PrevLogNumber" << prev_log_number_;
|
||||
}
|
||||
if (has_next_file_number_) {
|
||||
jw << "NextFileNumber" << next_file_number_;
|
||||
}
|
||||
if (has_last_sequence_) {
|
||||
jw << "LastSeq" << last_sequence_;
|
||||
}
|
||||
|
||||
if (!deleted_files_.empty()) {
|
||||
jw << "DeletedFiles";
|
||||
jw.StartArray();
|
||||
|
||||
for (DeletedFileSet::const_iterator iter = deleted_files_.begin();
|
||||
iter != deleted_files_.end();
|
||||
++iter) {
|
||||
jw.StartArrayedObject();
|
||||
jw << "Level" << iter->first;
|
||||
jw << "FileNumber" << iter->second;
|
||||
jw.EndArrayedObject();
|
||||
}
|
||||
|
||||
jw.EndArray();
|
||||
}
|
||||
|
||||
if (!new_files_.empty()) {
|
||||
jw << "AddedFiles";
|
||||
jw.StartArray();
|
||||
|
||||
for (size_t i = 0; i < new_files_.size(); i++) {
|
||||
jw.StartArrayedObject();
|
||||
jw << "Level" << new_files_[i].first;
|
||||
const FileMetaData& f = new_files_[i].second;
|
||||
jw << "FileNumber" << f.fd.GetNumber();
|
||||
jw << "FileSize" << f.fd.GetFileSize();
|
||||
jw << "SmallestIKey" << f.smallest.DebugString(hex_key);
|
||||
jw << "LargestIKey" << f.largest.DebugString(hex_key);
|
||||
jw.EndArrayedObject();
|
||||
}
|
||||
|
||||
jw.EndArray();
|
||||
}
|
||||
|
||||
jw << "ColumnFamily" << column_family_;
|
||||
|
||||
if (is_column_family_add_) {
|
||||
jw << "ColumnFamilyAdd" << column_family_name_;
|
||||
}
|
||||
if (is_column_family_drop_) {
|
||||
jw << "ColumnFamilyDrop" << column_family_name_;
|
||||
}
|
||||
if (has_max_column_family_) {
|
||||
jw << "MaxColumnFamily" << max_column_family_;
|
||||
}
|
||||
|
||||
jw.EndObject();
|
||||
|
||||
return jw.Get();
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
@@ -227,6 +227,7 @@ class VersionEdit {
|
||||
}
|
||||
|
||||
std::string DebugString(bool hex_key = false) const;
|
||||
std::string DebugJSON(int edit_num, bool hex_key = false) const;
|
||||
|
||||
private:
|
||||
friend class VersionSet;
|
||||
|
||||
+146
-105
@@ -42,6 +42,7 @@
|
||||
#include "table/meta_blocks.h"
|
||||
#include "table/get_context.h"
|
||||
#include "util/coding.h"
|
||||
#include "util/file_reader_writer.h"
|
||||
#include "util/logging.h"
|
||||
#include "util/stop_watch.h"
|
||||
#include "util/sync_point.h"
|
||||
@@ -567,10 +568,12 @@ Status Version::GetTableProperties(std::shared_ptr<const TableProperties>* tp,
|
||||
TableProperties* raw_table_properties;
|
||||
// By setting the magic number to kInvalidTableMagicNumber, we can by
|
||||
// pass the magic number check in the footer.
|
||||
std::unique_ptr<RandomAccessFileReader> file_reader(
|
||||
new RandomAccessFileReader(std::move(file)));
|
||||
s = ReadTableProperties(
|
||||
file.get(), file_meta->fd.GetFileSize(),
|
||||
Footer::kInvalidTableMagicNumber /* table's magic number */,
|
||||
vset_->env_, ioptions->info_log, &raw_table_properties);
|
||||
file_reader.get(), file_meta->fd.GetFileSize(),
|
||||
Footer::kInvalidTableMagicNumber /* table's magic number */, vset_->env_,
|
||||
ioptions->info_log, &raw_table_properties);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
@@ -870,8 +873,10 @@ void VersionStorageInfo::GenerateLevelFilesBrief() {
|
||||
}
|
||||
}
|
||||
|
||||
void Version::PrepareApply(const MutableCFOptions& mutable_cf_options) {
|
||||
UpdateAccumulatedStats();
|
||||
void Version::PrepareApply(
|
||||
const MutableCFOptions& mutable_cf_options,
|
||||
bool update_stats) {
|
||||
UpdateAccumulatedStats(update_stats);
|
||||
storage_info_.UpdateNumNonEmptyLevels();
|
||||
storage_info_.CalculateBaseBytes(*cfd_->ioptions(), mutable_cf_options);
|
||||
storage_info_.UpdateFilesBySize();
|
||||
@@ -914,42 +919,45 @@ void VersionStorageInfo::UpdateAccumulatedStats(FileMetaData* file_meta) {
|
||||
num_samples_++;
|
||||
}
|
||||
|
||||
void Version::UpdateAccumulatedStats() {
|
||||
// maximum number of table properties loaded from files.
|
||||
const int kMaxInitCount = 20;
|
||||
int init_count = 0;
|
||||
// here only the first kMaxInitCount files which haven't been
|
||||
// initialized from file will be updated with num_deletions.
|
||||
// The motivation here is to cap the maximum I/O per Version creation.
|
||||
// The reason for choosing files from lower-level instead of higher-level
|
||||
// is that such design is able to propagate the initialization from
|
||||
// lower-level to higher-level: When the num_deletions of lower-level
|
||||
// files are updated, it will make the lower-level files have accurate
|
||||
// compensated_file_size, making lower-level to higher-level compaction
|
||||
// will be triggered, which creates higher-level files whose num_deletions
|
||||
// will be updated here.
|
||||
for (int level = 0;
|
||||
level < storage_info_.num_levels_ && init_count < kMaxInitCount;
|
||||
++level) {
|
||||
for (auto* file_meta : storage_info_.files_[level]) {
|
||||
if (MaybeInitializeFileMetaData(file_meta)) {
|
||||
// each FileMeta will be initialized only once.
|
||||
storage_info_.UpdateAccumulatedStats(file_meta);
|
||||
if (++init_count >= kMaxInitCount) {
|
||||
break;
|
||||
void Version::UpdateAccumulatedStats(bool update_stats) {
|
||||
if (update_stats) {
|
||||
// maximum number of table properties loaded from files.
|
||||
const int kMaxInitCount = 20;
|
||||
int init_count = 0;
|
||||
// here only the first kMaxInitCount files which haven't been
|
||||
// initialized from file will be updated with num_deletions.
|
||||
// The motivation here is to cap the maximum I/O per Version creation.
|
||||
// The reason for choosing files from lower-level instead of higher-level
|
||||
// is that such design is able to propagate the initialization from
|
||||
// lower-level to higher-level: When the num_deletions of lower-level
|
||||
// files are updated, it will make the lower-level files have accurate
|
||||
// compensated_file_size, making lower-level to higher-level compaction
|
||||
// will be triggered, which creates higher-level files whose num_deletions
|
||||
// will be updated here.
|
||||
for (int level = 0;
|
||||
level < storage_info_.num_levels_ && init_count < kMaxInitCount;
|
||||
++level) {
|
||||
for (auto* file_meta : storage_info_.files_[level]) {
|
||||
if (MaybeInitializeFileMetaData(file_meta)) {
|
||||
// each FileMeta will be initialized only once.
|
||||
storage_info_.UpdateAccumulatedStats(file_meta);
|
||||
if (++init_count >= kMaxInitCount) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// In case all sampled-files contain only deletion entries, then we
|
||||
// load the table-property of a file in higher-level to initialize
|
||||
// that value.
|
||||
for (int level = storage_info_.num_levels_ - 1;
|
||||
storage_info_.accumulated_raw_value_size_ == 0 && level >= 0; --level) {
|
||||
for (int i = static_cast<int>(storage_info_.files_[level].size()) - 1;
|
||||
storage_info_.accumulated_raw_value_size_ == 0 && i >= 0; --i) {
|
||||
if (MaybeInitializeFileMetaData(storage_info_.files_[level][i])) {
|
||||
storage_info_.UpdateAccumulatedStats(storage_info_.files_[level][i]);
|
||||
// In case all sampled-files contain only deletion entries, then we
|
||||
// load the table-property of a file in higher-level to initialize
|
||||
// that value.
|
||||
for (int level = storage_info_.num_levels_ - 1;
|
||||
storage_info_.accumulated_raw_value_size_ == 0 && level >= 0;
|
||||
--level) {
|
||||
for (int i = static_cast<int>(storage_info_.files_[level].size()) - 1;
|
||||
storage_info_.accumulated_raw_value_size_ == 0 && i >= 0; --i) {
|
||||
if (MaybeInitializeFileMetaData(storage_info_.files_[level][i])) {
|
||||
storage_info_.UpdateAccumulatedStats(storage_info_.files_[level][i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1279,38 +1287,6 @@ bool VersionStorageInfo::OverlapInLevel(int level,
|
||||
largest_user_key);
|
||||
}
|
||||
|
||||
int VersionStorageInfo::PickLevelForMemTableOutput(
|
||||
const MutableCFOptions& mutable_cf_options, const Slice& smallest_user_key,
|
||||
const Slice& largest_user_key) {
|
||||
int level = 0;
|
||||
if (!OverlapInLevel(0, &smallest_user_key, &largest_user_key)) {
|
||||
// Push to next level if there is no overlap in next level,
|
||||
// and the #bytes overlapping in the level after that are limited.
|
||||
InternalKey start;
|
||||
start.SetMaxPossibleForUserKey(smallest_user_key);
|
||||
InternalKey limit(largest_user_key, 0, static_cast<ValueType>(0));
|
||||
std::vector<FileMetaData*> overlaps;
|
||||
while (mutable_cf_options.max_mem_compaction_level > 0 &&
|
||||
level < mutable_cf_options.max_mem_compaction_level) {
|
||||
if (OverlapInLevel(level + 1, &smallest_user_key, &largest_user_key)) {
|
||||
break;
|
||||
}
|
||||
if (level + 2 >= num_levels_) {
|
||||
level++;
|
||||
break;
|
||||
}
|
||||
GetOverlappingInputs(level + 2, &start, &limit, &overlaps);
|
||||
const uint64_t sum = TotalFileSize(overlaps);
|
||||
if (sum > mutable_cf_options.MaxGrandParentOverlapBytes(level)) {
|
||||
break;
|
||||
}
|
||||
level++;
|
||||
}
|
||||
}
|
||||
|
||||
return level;
|
||||
}
|
||||
|
||||
// Store in "*inputs" all files in "level" that overlap [begin,end]
|
||||
// If hint_index is specified, then it points to a file in the
|
||||
// overlapping range.
|
||||
@@ -1561,7 +1537,7 @@ const char* VersionStorageInfo::LevelSummary(
|
||||
|
||||
if (!files_marked_for_compaction_.empty()) {
|
||||
snprintf(scratch->buffer + len, sizeof(scratch->buffer) - len,
|
||||
" (%zu files need compaction)",
|
||||
" (%" ROCKSDB_PRIszt " files need compaction)",
|
||||
files_marked_for_compaction_.size());
|
||||
}
|
||||
|
||||
@@ -1729,6 +1705,41 @@ void VersionStorageInfo::CalculateBaseBytes(const ImmutableCFOptions& ioptions,
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t VersionStorageInfo::EstimateLiveDataSize() const {
|
||||
// Estimate the live data size by adding up the size of the last level for all
|
||||
// key ranges. Note: Estimate depends on the ordering of files in level 0
|
||||
// because files in level 0 can be overlapping.
|
||||
uint64_t size = 0;
|
||||
|
||||
auto ikey_lt = [this](InternalKey* x, InternalKey* y) {
|
||||
return internal_comparator_->Compare(*x, *y) < 0;
|
||||
};
|
||||
// (Ordered) map of largest keys in non-overlapping files
|
||||
std::map<InternalKey*, FileMetaData*, decltype(ikey_lt)> ranges(ikey_lt);
|
||||
|
||||
for (int l = num_levels_ - 1; l >= 0; l--) {
|
||||
bool found_end = false;
|
||||
for (auto file : files_[l]) {
|
||||
// Find the first file where the largest key is larger than the smallest
|
||||
// key of the current file. If this file does not overlap with the
|
||||
// current file, none of the files in the map does. If there is
|
||||
// no potential overlap, we can safely insert the rest of this level
|
||||
// (if the level is not 0) into the map without checking again because
|
||||
// the elements in the level are sorted and non-overlapping.
|
||||
auto lb = (found_end && l != 0) ?
|
||||
ranges.end() : ranges.lower_bound(&file->smallest);
|
||||
found_end = (lb == ranges.end());
|
||||
if (found_end || internal_comparator_->Compare(
|
||||
file->largest, (*lb).second->smallest) < 0) {
|
||||
ranges.emplace_hint(lb, &file->largest, file);
|
||||
size += file->fd.file_size;
|
||||
}
|
||||
}
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
|
||||
void Version::AddLiveFiles(std::vector<FileDescriptor>* live) {
|
||||
for (int level = 0; level < storage_info_.num_levels(); level++) {
|
||||
const std::vector<FileMetaData*>& files = storage_info_.files_[level];
|
||||
@@ -1944,20 +1955,24 @@ Status VersionSet::LogAndApply(ColumnFamilyData* column_family_data,
|
||||
Log(InfoLogLevel::INFO_LEVEL, db_options_->info_log,
|
||||
"Creating manifest %" PRIu64 "\n", pending_manifest_file_number_);
|
||||
unique_ptr<WritableFile> descriptor_file;
|
||||
EnvOptions opt_env_opts = env_->OptimizeForManifestWrite(env_options_);
|
||||
s = env_->NewWritableFile(
|
||||
DescriptorFileName(dbname_, pending_manifest_file_number_),
|
||||
&descriptor_file, env_->OptimizeForManifestWrite(env_options_));
|
||||
&descriptor_file, opt_env_opts);
|
||||
if (s.ok()) {
|
||||
descriptor_file->SetPreallocationBlockSize(
|
||||
db_options_->manifest_preallocation_size);
|
||||
descriptor_log_.reset(new log::Writer(std::move(descriptor_file)));
|
||||
|
||||
unique_ptr<WritableFileWriter> file_writer(
|
||||
new WritableFileWriter(std::move(descriptor_file), opt_env_opts));
|
||||
descriptor_log_.reset(new log::Writer(std::move(file_writer)));
|
||||
s = WriteSnapshot(descriptor_log_.get());
|
||||
}
|
||||
}
|
||||
|
||||
if (!edit->IsColumnFamilyManipulation()) {
|
||||
// This is cpu-heavy operations, which should be called outside mutex.
|
||||
v->PrepareApply(mutable_cf_options);
|
||||
v->PrepareApply(mutable_cf_options, true);
|
||||
}
|
||||
|
||||
// Write new record to MANIFEST log
|
||||
@@ -2164,11 +2179,16 @@ Status VersionSet::Recover(
|
||||
manifest_filename.c_str());
|
||||
|
||||
manifest_filename = dbname_ + "/" + manifest_filename;
|
||||
unique_ptr<SequentialFile> manifest_file;
|
||||
s = env_->NewSequentialFile(manifest_filename, &manifest_file,
|
||||
env_options_);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
unique_ptr<SequentialFileReader> manifest_file_reader;
|
||||
{
|
||||
unique_ptr<SequentialFile> manifest_file;
|
||||
s = env_->NewSequentialFile(manifest_filename, &manifest_file,
|
||||
env_options_);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
manifest_file_reader.reset(
|
||||
new SequentialFileReader(std::move(manifest_file)));
|
||||
}
|
||||
uint64_t current_manifest_file_size;
|
||||
s = env_->GetFileSize(manifest_filename, ¤t_manifest_file_size);
|
||||
@@ -2202,8 +2222,8 @@ Status VersionSet::Recover(
|
||||
{
|
||||
VersionSet::LogReporter reporter;
|
||||
reporter.status = &s;
|
||||
log::Reader reader(std::move(manifest_file), &reporter, true /*checksum*/,
|
||||
0 /*initial_offset*/);
|
||||
log::Reader reader(std::move(manifest_file_reader), &reporter,
|
||||
true /*checksum*/, 0 /*initial_offset*/);
|
||||
Slice record;
|
||||
std::string scratch;
|
||||
while (reader.ReadRecord(&record, &scratch) && s.ok()) {
|
||||
@@ -2383,7 +2403,8 @@ Status VersionSet::Recover(
|
||||
builder->SaveTo(v->storage_info());
|
||||
|
||||
// Install recovered version
|
||||
v->PrepareApply(*cfd->GetLatestMutableCFOptions());
|
||||
v->PrepareApply(*cfd->GetLatestMutableCFOptions(),
|
||||
!(db_options_->skip_stats_update_on_db_open));
|
||||
AppendVersion(cfd, v);
|
||||
}
|
||||
|
||||
@@ -2437,18 +2458,23 @@ Status VersionSet::ListColumnFamilies(std::vector<std::string>* column_families,
|
||||
current.resize(current.size() - 1);
|
||||
|
||||
std::string dscname = dbname + "/" + current;
|
||||
|
||||
unique_ptr<SequentialFileReader> file_reader;
|
||||
{
|
||||
unique_ptr<SequentialFile> file;
|
||||
s = env->NewSequentialFile(dscname, &file, soptions);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
file_reader.reset(new SequentialFileReader(std::move(file)));
|
||||
}
|
||||
|
||||
std::map<uint32_t, std::string> column_family_names;
|
||||
// default column family is always implicitly there
|
||||
column_family_names.insert({0, kDefaultColumnFamilyName});
|
||||
VersionSet::LogReporter reporter;
|
||||
reporter.status = &s;
|
||||
log::Reader reader(std::move(file), &reporter, true /*checksum*/,
|
||||
log::Reader reader(std::move(file_reader), &reporter, true /*checksum*/,
|
||||
0 /*initial_offset*/);
|
||||
Slice record;
|
||||
std::string scratch;
|
||||
@@ -2572,12 +2598,17 @@ Status VersionSet::ReduceNumberOfLevels(const std::string& dbname,
|
||||
}
|
||||
|
||||
Status VersionSet::DumpManifest(Options& options, std::string& dscname,
|
||||
bool verbose, bool hex) {
|
||||
bool verbose, bool hex, bool json) {
|
||||
// Open the specified manifest file.
|
||||
unique_ptr<SequentialFile> file;
|
||||
Status s = options.env->NewSequentialFile(dscname, &file, env_options_);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
unique_ptr<SequentialFileReader> file_reader;
|
||||
Status s;
|
||||
{
|
||||
unique_ptr<SequentialFile> file;
|
||||
s = options.env->NewSequentialFile(dscname, &file, env_options_);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
file_reader.reset(new SequentialFileReader(std::move(file)));
|
||||
}
|
||||
|
||||
bool have_prev_log_number = false;
|
||||
@@ -2601,8 +2632,8 @@ Status VersionSet::DumpManifest(Options& options, std::string& dscname,
|
||||
{
|
||||
VersionSet::LogReporter reporter;
|
||||
reporter.status = &s;
|
||||
log::Reader reader(std::move(file), &reporter, true/*checksum*/,
|
||||
0/*initial_offset*/);
|
||||
log::Reader reader(std::move(file_reader), &reporter, true /*checksum*/,
|
||||
0 /*initial_offset*/);
|
||||
Slice record;
|
||||
std::string scratch;
|
||||
while (reader.ReadRecord(&record, &scratch) && s.ok()) {
|
||||
@@ -2613,9 +2644,10 @@ Status VersionSet::DumpManifest(Options& options, std::string& dscname,
|
||||
}
|
||||
|
||||
// Write out each individual edit
|
||||
if (verbose) {
|
||||
printf("*************************Edit[%d] = %s\n",
|
||||
count, edit.DebugString(hex).c_str());
|
||||
if (verbose && !json) {
|
||||
printf("%s\n", edit.DebugString(hex).c_str());
|
||||
} else if (json) {
|
||||
printf("%s\n", edit.DebugJSON(count, hex).c_str());
|
||||
}
|
||||
count++;
|
||||
|
||||
@@ -2695,7 +2727,7 @@ Status VersionSet::DumpManifest(Options& options, std::string& dscname,
|
||||
}
|
||||
}
|
||||
}
|
||||
file.reset();
|
||||
file_reader.reset();
|
||||
|
||||
if (s.ok()) {
|
||||
if (!have_next_file) {
|
||||
@@ -2722,7 +2754,7 @@ Status VersionSet::DumpManifest(Options& options, std::string& dscname,
|
||||
|
||||
Version* v = new Version(cfd, this, current_version_number_++);
|
||||
builder->SaveTo(v->storage_info());
|
||||
v->PrepareApply(*cfd->GetLatestMutableCFOptions());
|
||||
v->PrepareApply(*cfd->GetLatestMutableCFOptions(), false);
|
||||
|
||||
printf("--------------- Column family \"%s\" (ID %u) --------------\n",
|
||||
cfd->GetName().c_str(), (unsigned int)cfd->GetID());
|
||||
@@ -2837,17 +2869,23 @@ bool VersionSet::ManifestContains(uint64_t manifest_file_num,
|
||||
std::string fname = DescriptorFileName(dbname_, manifest_file_num);
|
||||
Log(InfoLogLevel::INFO_LEVEL, db_options_->info_log,
|
||||
"ManifestContains: checking %s\n", fname.c_str());
|
||||
unique_ptr<SequentialFile> file;
|
||||
Status s = env_->NewSequentialFile(fname, &file, env_options_);
|
||||
if (!s.ok()) {
|
||||
Log(InfoLogLevel::INFO_LEVEL, db_options_->info_log,
|
||||
"ManifestContains: %s\n", s.ToString().c_str());
|
||||
Log(InfoLogLevel::INFO_LEVEL, db_options_->info_log,
|
||||
"ManifestContains: is unable to reopen the manifest file %s",
|
||||
fname.c_str());
|
||||
return false;
|
||||
|
||||
unique_ptr<SequentialFileReader> file_reader;
|
||||
Status s;
|
||||
{
|
||||
unique_ptr<SequentialFile> file;
|
||||
s = env_->NewSequentialFile(fname, &file, env_options_);
|
||||
if (!s.ok()) {
|
||||
Log(InfoLogLevel::INFO_LEVEL, db_options_->info_log,
|
||||
"ManifestContains: %s\n", s.ToString().c_str());
|
||||
Log(InfoLogLevel::INFO_LEVEL, db_options_->info_log,
|
||||
"ManifestContains: is unable to reopen the manifest file %s",
|
||||
fname.c_str());
|
||||
return false;
|
||||
}
|
||||
file_reader.reset(new SequentialFileReader(std::move(file)));
|
||||
}
|
||||
log::Reader reader(std::move(file), nullptr, true/*checksum*/, 0);
|
||||
log::Reader reader(std::move(file_reader), nullptr, true /*checksum*/, 0);
|
||||
Slice r;
|
||||
std::string scratch;
|
||||
bool result = false;
|
||||
@@ -3002,6 +3040,9 @@ Iterator* VersionSet::MakeInputIterator(Compaction* c) {
|
||||
read_options.verify_checksums =
|
||||
c->mutable_cf_options()->verify_checksums_in_compaction;
|
||||
read_options.fill_cache = false;
|
||||
if (c->IsSubCompaction()) {
|
||||
read_options.total_order_seek = true;
|
||||
}
|
||||
|
||||
// Level-0 files have to be merged together. For other levels,
|
||||
// we will make a concatenating iterator per level.
|
||||
|
||||
+10
-10
@@ -186,12 +186,6 @@ class VersionStorageInfo {
|
||||
bool HasOverlappingUserKey(const std::vector<FileMetaData*>* inputs,
|
||||
int level);
|
||||
|
||||
// Return the level at which we should place a new memtable compaction
|
||||
// result that covers the range [smallest_user_key,largest_user_key].
|
||||
int PickLevelForMemTableOutput(const MutableCFOptions& mutable_cf_options,
|
||||
const Slice& smallest_user_key,
|
||||
const Slice& largest_user_key);
|
||||
|
||||
int num_levels() const { return num_levels_; }
|
||||
|
||||
// REQUIRES: This version has been saved (see VersionSet::SaveTo)
|
||||
@@ -318,6 +312,9 @@ class VersionStorageInfo {
|
||||
void CalculateBaseBytes(const ImmutableCFOptions& ioptions,
|
||||
const MutableCFOptions& options);
|
||||
|
||||
// Returns an estimate of the amount of live data in bytes.
|
||||
uint64_t EstimateLiveDataSize() const;
|
||||
|
||||
private:
|
||||
const InternalKeyComparator* internal_comparator_;
|
||||
const Comparator* user_comparator_;
|
||||
@@ -420,7 +417,8 @@ class Version {
|
||||
|
||||
// Loads some stats information from files. Call without mutex held. It needs
|
||||
// to be called before applying the version to the version set.
|
||||
void PrepareApply(const MutableCFOptions& mutable_cf_options);
|
||||
void PrepareApply(const MutableCFOptions& mutable_cf_options,
|
||||
bool update_stats);
|
||||
|
||||
// Reference count management (so Versions do not disappear out from
|
||||
// under live iterators)
|
||||
@@ -493,7 +491,7 @@ class Version {
|
||||
|
||||
// Update the accumulated stats associated with the current version.
|
||||
// This accumulated stats will be used in compaction.
|
||||
void UpdateAccumulatedStats();
|
||||
void UpdateAccumulatedStats(bool update_stats);
|
||||
|
||||
// Sort all files for this version based on their file size and
|
||||
// record results in files_by_size_. The largest files are listed first.
|
||||
@@ -572,7 +570,7 @@ class VersionSet {
|
||||
|
||||
// printf contents (for debugging)
|
||||
Status DumpManifest(Options& options, std::string& manifestFileName,
|
||||
bool verbose, bool hex = false);
|
||||
bool verbose, bool hex = false, bool json = false);
|
||||
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
@@ -612,7 +610,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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -234,6 +234,29 @@ TEST_F(VersionStorageInfoTest, MaxBytesForLevelDynamicLargeLevel) {
|
||||
ASSERT_EQ(0, logger_->log_count);
|
||||
}
|
||||
|
||||
TEST_F(VersionStorageInfoTest, EstimateLiveDataSize) {
|
||||
// Test whether the overlaps are detected as expected
|
||||
Add(1, 1U, "4", "7", 1U); // Perfect overlap with last level
|
||||
Add(2, 2U, "3", "5", 1U); // Partial overlap with last level
|
||||
Add(2, 3U, "6", "8", 1U); // Partial overlap with last level
|
||||
Add(3, 4U, "1", "9", 1U); // Contains range of last level
|
||||
Add(4, 5U, "4", "5", 1U); // Inside range of last level
|
||||
Add(4, 5U, "6", "7", 1U); // Inside range of last level
|
||||
Add(5, 6U, "4", "7", 10U);
|
||||
ASSERT_EQ(10U, vstorage_.EstimateLiveDataSize());
|
||||
}
|
||||
|
||||
TEST_F(VersionStorageInfoTest, EstimateLiveDataSize2) {
|
||||
Add(0, 1U, "9", "9", 1U); // Level 0 is not ordered
|
||||
Add(0, 1U, "5", "6", 1U); // Ignored because of [5,6] in l1
|
||||
Add(1, 1U, "1", "2", 1U); // Ignored because of [2,3] in l2
|
||||
Add(1, 2U, "3", "4", 1U); // Ignored because of [2,3] in l2
|
||||
Add(1, 3U, "5", "6", 1U);
|
||||
Add(2, 4U, "2", "3", 1U);
|
||||
Add(3, 5U, "7", "8", 1U);
|
||||
ASSERT_EQ(4U, vstorage_.EstimateLiveDataSize());
|
||||
}
|
||||
|
||||
class FindLevelFileTest : public testing::Test {
|
||||
public:
|
||||
LevelFilesBrief file_level_;
|
||||
|
||||
+13
-6
@@ -28,6 +28,7 @@
|
||||
#include "rocksdb/options.h"
|
||||
#include "rocksdb/write_batch.h"
|
||||
#include "util/coding.h"
|
||||
#include "util/file_reader_writer.h"
|
||||
#include "util/logging.h"
|
||||
#include "util/mutexlock.h"
|
||||
#include "util/sync_point.h"
|
||||
@@ -58,11 +59,15 @@ Status WalManager::GetSortedWalFiles(VectorLogPtr& files) {
|
||||
files.clear();
|
||||
// list wal files in archive dir.
|
||||
std::string archivedir = ArchivalDirectory(db_options_.wal_dir);
|
||||
if (env_->FileExists(archivedir)) {
|
||||
Status exists = env_->FileExists(archivedir);
|
||||
if (exists.ok()) {
|
||||
s = GetSortedWalsOfType(archivedir, files, kArchivedLogFile);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
} else if (!exists.IsNotFound()) {
|
||||
assert(s.IsIOError());
|
||||
return s;
|
||||
}
|
||||
|
||||
uint64_t latest_archived_log_number = 0;
|
||||
@@ -312,9 +317,9 @@ Status WalManager::GetSortedWalsOfType(const std::string& path,
|
||||
// re-try in case the alive log file has been moved to archive.
|
||||
std::string archived_file = ArchivedLogFileName(path, number);
|
||||
if (!s.ok() && log_type == kAliveLogFile &&
|
||||
env_->FileExists(archived_file)) {
|
||||
env_->FileExists(archived_file).ok()) {
|
||||
s = env_->GetFileSize(archived_file, &size_bytes);
|
||||
if (!s.ok() && !env_->FileExists(archived_file)) {
|
||||
if (!s.ok() && env_->FileExists(archived_file).IsNotFound()) {
|
||||
// oops, the file just got deleted from archived dir! move on
|
||||
s = Status::OK();
|
||||
continue;
|
||||
@@ -379,7 +384,7 @@ Status WalManager::ReadFirstRecord(const WalFileType type,
|
||||
if (type == kAliveLogFile) {
|
||||
std::string fname = LogFileName(db_options_.wal_dir, number);
|
||||
s = ReadFirstLine(fname, sequence);
|
||||
if (env_->FileExists(fname) && !s.ok()) {
|
||||
if (env_->FileExists(fname).ok() && !s.ok()) {
|
||||
// return any error that is not caused by non-existing file
|
||||
return s;
|
||||
}
|
||||
@@ -393,7 +398,7 @@ Status WalManager::ReadFirstRecord(const WalFileType type,
|
||||
// maybe the file was deleted from archive dir. If that's the case, return
|
||||
// Status::OK(). The caller with identify this as empty file because
|
||||
// *sequence == 0
|
||||
if (!s.ok() && !env_->FileExists(archived_file)) {
|
||||
if (!s.ok() && env_->FileExists(archived_file).IsNotFound()) {
|
||||
return Status::OK();
|
||||
}
|
||||
}
|
||||
@@ -430,6 +435,8 @@ Status WalManager::ReadFirstLine(const std::string& fname,
|
||||
|
||||
std::unique_ptr<SequentialFile> file;
|
||||
Status status = env_->NewSequentialFile(fname, &file, env_options_);
|
||||
unique_ptr<SequentialFileReader> file_reader(
|
||||
new SequentialFileReader(std::move(file)));
|
||||
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
@@ -441,7 +448,7 @@ Status WalManager::ReadFirstLine(const std::string& fname,
|
||||
reporter.fname = fname.c_str();
|
||||
reporter.status = &status;
|
||||
reporter.ignore_error = !db_options_.paranoid_checks;
|
||||
log::Reader reader(std::move(file), &reporter, true /*checksum*/,
|
||||
log::Reader reader(std::move(file_reader), &reporter, true /*checksum*/,
|
||||
0 /*initial_offset*/);
|
||||
std::string scratch;
|
||||
Slice record;
|
||||
|
||||
+19
-2
@@ -3,6 +3,8 @@
|
||||
// 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 <map>
|
||||
#include <string>
|
||||
|
||||
@@ -14,6 +16,7 @@
|
||||
#include "db/column_family.h"
|
||||
#include "db/version_set.h"
|
||||
#include "db/writebuffer.h"
|
||||
#include "util/file_reader_writer.h"
|
||||
#include "util/mock_env.h"
|
||||
#include "util/string_util.h"
|
||||
#include "util/testharness.h"
|
||||
@@ -72,7 +75,9 @@ class WalManagerTest : public testing::Test {
|
||||
std::string fname = ArchivedLogFileName(dbname_, current_log_number_);
|
||||
unique_ptr<WritableFile> file;
|
||||
ASSERT_OK(env_->NewWritableFile(fname, &file, env_options_));
|
||||
current_log_writer_.reset(new log::Writer(std::move(file)));
|
||||
unique_ptr<WritableFileWriter> file_writer(
|
||||
new WritableFileWriter(std::move(file), env_options_));
|
||||
current_log_writer_.reset(new log::Writer(std::move(file_writer)));
|
||||
}
|
||||
|
||||
void CreateArchiveLogs(int num_logs, int entries_per_log) {
|
||||
@@ -120,7 +125,9 @@ TEST_F(WalManagerTest, ReadFirstRecordCache) {
|
||||
ASSERT_OK(wal_manager_->TEST_ReadFirstRecord(kAliveLogFile, 1, &s));
|
||||
ASSERT_EQ(s, 0U);
|
||||
|
||||
log::Writer writer(std::move(file));
|
||||
unique_ptr<WritableFileWriter> file_writer(
|
||||
new WritableFileWriter(std::move(file), EnvOptions()));
|
||||
log::Writer writer(std::move(file_writer));
|
||||
WriteBatch batch;
|
||||
batch.Put("foo", "bar");
|
||||
WriteBatchInternal::SetSequence(&batch, 10);
|
||||
@@ -287,3 +294,13 @@ int main(int argc, char** argv) {
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
|
||||
#else
|
||||
#include <stdio.h>
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
fprintf(stderr, "SKIPPED as WalManager is not supported in ROCKSDB_LITE\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif // !ROCKSDB_LITE
|
||||
+60
-3
@@ -23,6 +23,10 @@
|
||||
// data: uint8[len]
|
||||
|
||||
#include "rocksdb/write_batch.h"
|
||||
|
||||
#include <stack>
|
||||
#include <stdexcept>
|
||||
|
||||
#include "rocksdb/merge_operator.h"
|
||||
#include "db/dbformat.h"
|
||||
#include "db/db_impl.h"
|
||||
@@ -32,7 +36,6 @@
|
||||
#include "db/write_batch_internal.h"
|
||||
#include "util/coding.h"
|
||||
#include "util/statistics.h"
|
||||
#include <stdexcept>
|
||||
#include "util/perf_context_imp.h"
|
||||
|
||||
namespace rocksdb {
|
||||
@@ -40,12 +43,26 @@ namespace rocksdb {
|
||||
// WriteBatch header has an 8-byte sequence number followed by a 4-byte count.
|
||||
static const size_t kHeader = 12;
|
||||
|
||||
WriteBatch::WriteBatch(size_t reserved_bytes) {
|
||||
struct SavePoint {
|
||||
size_t size; // size of rep_
|
||||
int count; // count of elements in rep_
|
||||
SavePoint(size_t s, int c) : size(s), count(c) {}
|
||||
};
|
||||
|
||||
struct SavePoints {
|
||||
std::stack<SavePoint> stack;
|
||||
};
|
||||
|
||||
WriteBatch::WriteBatch(size_t reserved_bytes) : save_points_(nullptr) {
|
||||
rep_.reserve((reserved_bytes > kHeader) ? reserved_bytes : kHeader);
|
||||
Clear();
|
||||
}
|
||||
|
||||
WriteBatch::~WriteBatch() { }
|
||||
WriteBatch::~WriteBatch() {
|
||||
if (save_points_ != nullptr) {
|
||||
delete save_points_;
|
||||
}
|
||||
}
|
||||
|
||||
WriteBatch::Handler::~Handler() { }
|
||||
|
||||
@@ -61,6 +78,12 @@ bool WriteBatch::Handler::Continue() {
|
||||
void WriteBatch::Clear() {
|
||||
rep_.clear();
|
||||
rep_.resize(kHeader);
|
||||
|
||||
if (save_points_ != nullptr) {
|
||||
while (!save_points_->stack.empty()) {
|
||||
save_points_->stack.pop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int WriteBatch::Count() const {
|
||||
@@ -188,6 +211,8 @@ void WriteBatchInternal::SetSequence(WriteBatch* b, SequenceNumber seq) {
|
||||
EncodeFixed64(&b->rep_[0], seq);
|
||||
}
|
||||
|
||||
size_t WriteBatchInternal::GetFirstOffset(WriteBatch* b) { return kHeader; }
|
||||
|
||||
void WriteBatchInternal::Put(WriteBatch* b, uint32_t column_family_id,
|
||||
const Slice& key, const Slice& value) {
|
||||
WriteBatchInternal::SetCount(b, WriteBatchInternal::Count(b) + 1);
|
||||
@@ -301,6 +326,38 @@ void WriteBatch::PutLogData(const Slice& blob) {
|
||||
PutLengthPrefixedSlice(&rep_, blob);
|
||||
}
|
||||
|
||||
void WriteBatch::SetSavePoint() {
|
||||
if (save_points_ == nullptr) {
|
||||
save_points_ = new SavePoints();
|
||||
}
|
||||
// Record length and count of current batch of writes.
|
||||
save_points_->stack.push(SavePoint(GetDataSize(), Count()));
|
||||
}
|
||||
|
||||
Status WriteBatch::RollbackToSavePoint() {
|
||||
if (save_points_ == nullptr || save_points_->stack.size() == 0) {
|
||||
return Status::NotFound();
|
||||
}
|
||||
|
||||
// Pop the most recent savepoint off the stack
|
||||
SavePoint savepoint = save_points_->stack.top();
|
||||
save_points_->stack.pop();
|
||||
|
||||
assert(savepoint.size <= rep_.size());
|
||||
|
||||
if (savepoint.size == rep_.size()) {
|
||||
// No changes to rollback
|
||||
} else if (savepoint.size == 0) {
|
||||
// Rollback everything
|
||||
Clear();
|
||||
} else {
|
||||
rep_.resize(savepoint.size);
|
||||
WriteBatchInternal::SetCount(this, savepoint.count);
|
||||
}
|
||||
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
namespace {
|
||||
// This class can *only* be used from a single-threaded write thread, because it
|
||||
// calls ColumnFamilyMemTablesImpl::Seek()
|
||||
|
||||
@@ -92,6 +92,10 @@ class WriteBatchInternal {
|
||||
// this batch.
|
||||
static void SetSequence(WriteBatch* batch, SequenceNumber seq);
|
||||
|
||||
// Returns the offset of the first entry in the batch.
|
||||
// This offset is only valid if the batch is not empty.
|
||||
static size_t GetFirstOffset(WriteBatch* batch);
|
||||
|
||||
static Slice Contents(const WriteBatch* batch) {
|
||||
return Slice(batch->rep_);
|
||||
}
|
||||
|
||||
@@ -362,6 +362,7 @@ TEST_F(WriteBatchTest, ColumnFamiliesBatchTest) {
|
||||
handler.seen);
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
TEST_F(WriteBatchTest, ColumnFamiliesBatchWithIndexTest) {
|
||||
WriteBatchWithIndex batch;
|
||||
ColumnFamilyHandleImplDummy zero(0), two(2), three(3), eight(8);
|
||||
@@ -443,6 +444,112 @@ TEST_F(WriteBatchTest, ColumnFamiliesBatchWithIndexTest) {
|
||||
"Merge(omom, nom)",
|
||||
handler.seen);
|
||||
}
|
||||
#endif // !ROCKSDB_LITE
|
||||
|
||||
TEST_F(WriteBatchTest, SavePointTest) {
|
||||
Status s;
|
||||
WriteBatch batch;
|
||||
batch.SetSavePoint();
|
||||
|
||||
batch.Put("A", "a");
|
||||
batch.Put("B", "b");
|
||||
batch.SetSavePoint();
|
||||
|
||||
batch.Put("C", "c");
|
||||
batch.Delete("A");
|
||||
batch.SetSavePoint();
|
||||
batch.SetSavePoint();
|
||||
|
||||
ASSERT_OK(batch.RollbackToSavePoint());
|
||||
ASSERT_EQ(
|
||||
"Delete(A)@3"
|
||||
"Put(A, a)@0"
|
||||
"Put(B, b)@1"
|
||||
"Put(C, c)@2",
|
||||
PrintContents(&batch));
|
||||
|
||||
ASSERT_OK(batch.RollbackToSavePoint());
|
||||
ASSERT_OK(batch.RollbackToSavePoint());
|
||||
ASSERT_EQ(
|
||||
"Put(A, a)@0"
|
||||
"Put(B, b)@1",
|
||||
PrintContents(&batch));
|
||||
|
||||
batch.Delete("A");
|
||||
batch.Put("B", "bb");
|
||||
|
||||
ASSERT_OK(batch.RollbackToSavePoint());
|
||||
ASSERT_EQ("", PrintContents(&batch));
|
||||
|
||||
s = batch.RollbackToSavePoint();
|
||||
ASSERT_TRUE(s.IsNotFound());
|
||||
ASSERT_EQ("", PrintContents(&batch));
|
||||
|
||||
batch.Put("D", "d");
|
||||
batch.Delete("A");
|
||||
|
||||
batch.SetSavePoint();
|
||||
|
||||
batch.Put("A", "aaa");
|
||||
|
||||
ASSERT_OK(batch.RollbackToSavePoint());
|
||||
ASSERT_EQ(
|
||||
"Delete(A)@1"
|
||||
"Put(D, d)@0",
|
||||
PrintContents(&batch));
|
||||
|
||||
batch.SetSavePoint();
|
||||
|
||||
batch.Put("D", "d");
|
||||
batch.Delete("A");
|
||||
|
||||
ASSERT_OK(batch.RollbackToSavePoint());
|
||||
ASSERT_EQ(
|
||||
"Delete(A)@1"
|
||||
"Put(D, d)@0",
|
||||
PrintContents(&batch));
|
||||
|
||||
s = batch.RollbackToSavePoint();
|
||||
ASSERT_TRUE(s.IsNotFound());
|
||||
ASSERT_EQ(
|
||||
"Delete(A)@1"
|
||||
"Put(D, d)@0",
|
||||
PrintContents(&batch));
|
||||
|
||||
WriteBatch batch2;
|
||||
|
||||
s = batch2.RollbackToSavePoint();
|
||||
ASSERT_TRUE(s.IsNotFound());
|
||||
ASSERT_EQ("", PrintContents(&batch2));
|
||||
|
||||
batch2.Delete("A");
|
||||
batch2.SetSavePoint();
|
||||
|
||||
s = batch2.RollbackToSavePoint();
|
||||
ASSERT_OK(s);
|
||||
ASSERT_EQ("Delete(A)@0", PrintContents(&batch2));
|
||||
|
||||
batch2.Clear();
|
||||
ASSERT_EQ("", PrintContents(&batch2));
|
||||
|
||||
batch2.SetSavePoint();
|
||||
|
||||
batch2.Delete("B");
|
||||
ASSERT_EQ("Delete(B)@0", PrintContents(&batch2));
|
||||
|
||||
batch2.SetSavePoint();
|
||||
s = batch2.RollbackToSavePoint();
|
||||
ASSERT_OK(s);
|
||||
ASSERT_EQ("Delete(B)@0", PrintContents(&batch2));
|
||||
|
||||
s = batch2.RollbackToSavePoint();
|
||||
ASSERT_OK(s);
|
||||
ASSERT_EQ("", PrintContents(&batch2));
|
||||
|
||||
s = batch2.RollbackToSavePoint();
|
||||
ASSERT_TRUE(s.IsNotFound());
|
||||
ASSERT_EQ("", PrintContents(&batch2));
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
|
||||
@@ -117,4 +117,13 @@ int main(int argc, char** argv) {
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
|
||||
#endif // ROCKSDB_LITE
|
||||
#else
|
||||
#include <stdio.h>
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
fprintf(stderr,
|
||||
"SKIPPED as WriteWithCallback is not supported in ROCKSDB_LITE\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif // !ROCKSDB_LITE
|
||||
+2
-48
@@ -7,8 +7,7 @@
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
Status WriteThread::EnterWriteThread(WriteThread::Writer* w,
|
||||
uint64_t expiration_time) {
|
||||
void WriteThread::EnterWriteThread(WriteThread::Writer* w) {
|
||||
// the following code block pushes the current writer "w" into the writer
|
||||
// queue "writers_" and wait until one of the following conditions met:
|
||||
// 1. the job of "w" has been done by some other writers.
|
||||
@@ -16,48 +15,9 @@ Status WriteThread::EnterWriteThread(WriteThread::Writer* w,
|
||||
// 3. "w" timed-out.
|
||||
writers_.push_back(w);
|
||||
|
||||
bool timed_out = false;
|
||||
while (!w->done && w != writers_.front()) {
|
||||
if (expiration_time == 0) {
|
||||
w->cv.Wait();
|
||||
} else if (w->cv.TimedWait(expiration_time)) {
|
||||
if (w->in_batch_group) {
|
||||
// then it means the front writer is currently doing the
|
||||
// write on behalf of this "timed-out" writer. Then it
|
||||
// should wait until the write completes.
|
||||
expiration_time = 0;
|
||||
} else {
|
||||
timed_out = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
w->cv.Wait();
|
||||
}
|
||||
|
||||
if (timed_out) {
|
||||
#ifndef NDEBUG
|
||||
bool found = false;
|
||||
#endif
|
||||
for (auto iter = writers_.begin(); iter != writers_.end(); iter++) {
|
||||
if (*iter == w) {
|
||||
writers_.erase(iter);
|
||||
#ifndef NDEBUG
|
||||
found = true;
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
}
|
||||
#ifndef NDEBUG
|
||||
assert(found);
|
||||
#endif
|
||||
// writers_.front() might still be in cond_wait without a time-out.
|
||||
// As a result, we need to signal it to wake it up. Otherwise no
|
||||
// one else will wake him up, and RocksDB will hang.
|
||||
if (!writers_.empty()) {
|
||||
writers_.front()->cv.Signal();
|
||||
}
|
||||
return Status::TimedOut();
|
||||
}
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
void WriteThread::ExitWriteThread(WriteThread::Writer* w,
|
||||
@@ -128,12 +88,6 @@ size_t WriteThread::BuildBatchGroup(
|
||||
break;
|
||||
}
|
||||
|
||||
if (w->timeout_hint_us < first->timeout_hint_us) {
|
||||
// Do not include those writes with shorter timeout. Otherwise, we might
|
||||
// execute a write that should instead be aborted because of timeout.
|
||||
break;
|
||||
}
|
||||
|
||||
if (w->has_callback) {
|
||||
// Do not include writes which may be aborted if the callback does not
|
||||
// succeed.
|
||||
|
||||
+1
-5
@@ -18,7 +18,6 @@ namespace rocksdb {
|
||||
|
||||
class WriteThread {
|
||||
public:
|
||||
static const uint64_t kNoTimeOut = std::numeric_limits<uint64_t>::max();
|
||||
// Information kept for every waiting writer
|
||||
struct Writer {
|
||||
Status status;
|
||||
@@ -28,7 +27,6 @@ class WriteThread {
|
||||
bool in_batch_group;
|
||||
bool done;
|
||||
bool has_callback;
|
||||
uint64_t timeout_hint_us;
|
||||
InstrumentedCondVar cv;
|
||||
|
||||
explicit Writer(InstrumentedMutex* mu)
|
||||
@@ -38,7 +36,6 @@ class WriteThread {
|
||||
in_batch_group(false),
|
||||
done(false),
|
||||
has_callback(false),
|
||||
timeout_hint_us(kNoTimeOut),
|
||||
cv(mu) {}
|
||||
};
|
||||
|
||||
@@ -52,10 +49,9 @@ class WriteThread {
|
||||
// for examples), so check it via w.done before applying changes.
|
||||
//
|
||||
// Writer* w: writer to be placed in the queue
|
||||
// uint64_t expiration_time: maximum time to be in the queue
|
||||
// See also: ExitWriteThread
|
||||
// REQUIRES: db mutex held
|
||||
Status EnterWriteThread(Writer* w, uint64_t expiration_time);
|
||||
void EnterWriteThread(Writer* w);
|
||||
|
||||
// After doing write job, we need to remove already used writers from
|
||||
// writers_ queue and notify head of the queue about it.
|
||||
|
||||
+5
-3
@@ -7,9 +7,9 @@
|
||||
#pragma once
|
||||
#include <algorithm>
|
||||
#include <stdio.h>
|
||||
#include <sys/time.h>
|
||||
#include <time.h>
|
||||
#include <iostream>
|
||||
#include "port/sys_time.h"
|
||||
#include "rocksdb/env.h"
|
||||
#include "rocksdb/status.h"
|
||||
|
||||
@@ -73,7 +73,7 @@ class HdfsEnv : public Env {
|
||||
virtual Status NewDirectory(const std::string& name,
|
||||
std::unique_ptr<Directory>* result);
|
||||
|
||||
virtual bool FileExists(const std::string& fname);
|
||||
virtual Status FileExists(const std::string& fname);
|
||||
|
||||
virtual Status GetChildren(const std::string& path,
|
||||
std::vector<std::string>* result);
|
||||
@@ -279,7 +279,9 @@ class HdfsEnv : public Env {
|
||||
return notsup;
|
||||
}
|
||||
|
||||
virtual bool FileExists(const std::string& fname) override { return false; }
|
||||
virtual Status FileExists(const std::string& fname) override {
|
||||
return notsup;
|
||||
}
|
||||
|
||||
virtual Status GetChildren(const std::string& path,
|
||||
std::vector<std::string>* result) override {
|
||||
|
||||
+577
-621
File diff suppressed because it is too large.
Load diff
@@ -36,7 +36,9 @@ struct CompactionJobStats {
|
||||
// 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
|
||||
// number of records being replaced by newer record associated with same key.
|
||||
// this could be a new value or a deletion entry for that key so this field
|
||||
// sums up all updated and deleted keys
|
||||
uint64_t num_records_replaced;
|
||||
|
||||
// the sum of the uncompressed input keys in bytes.
|
||||
@@ -44,6 +46,18 @@ struct CompactionJobStats {
|
||||
// the sum of the uncompressed input values in bytes.
|
||||
uint64_t total_input_raw_value_bytes;
|
||||
|
||||
// the number of deletion entries before compaction. Deletion entries
|
||||
// can disappear after compaction because they expired
|
||||
uint64_t num_input_deletion_records;
|
||||
// number of deletion records that were found obsolete and discarded
|
||||
// because it is not possible to delete any more keys with this entry
|
||||
// (i.e. all possible deletions resulting from it have been completed)
|
||||
uint64_t num_expired_deletion_records;
|
||||
|
||||
// number of corrupt keys (ParseInternalKey returned false when applied to
|
||||
// the key) encountered and written out.
|
||||
uint64_t num_corrupt_keys;
|
||||
|
||||
// 0-terminated strings storing the first 8 bytes of the smallest and
|
||||
// largest key in the output.
|
||||
static const size_t kMaxPrefixLength = 8;
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
// Copyright (c) 2014, 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 <unordered_map>
|
||||
#include <string>
|
||||
#include "rocksdb/options.h"
|
||||
#include "rocksdb/table.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
// Take a map of option name and option value, apply them into the
|
||||
// base_options, and return the new options as a result
|
||||
Status GetColumnFamilyOptionsFromMap(
|
||||
const ColumnFamilyOptions& base_options,
|
||||
const std::unordered_map<std::string, std::string>& opts_map,
|
||||
ColumnFamilyOptions* new_options);
|
||||
|
||||
Status GetDBOptionsFromMap(
|
||||
const DBOptions& base_options,
|
||||
const std::unordered_map<std::string, std::string>& opts_map,
|
||||
DBOptions* new_options);
|
||||
|
||||
Status GetBlockBasedTableOptionsFromMap(
|
||||
const BlockBasedTableOptions& table_options,
|
||||
const std::unordered_map<std::string, std::string>& opts_map,
|
||||
BlockBasedTableOptions* new_table_options);
|
||||
|
||||
// Take a string representation of option names and values, apply them into the
|
||||
// base_options, and return the new options as a result. The string has the
|
||||
// following format:
|
||||
// "write_buffer_size=1024;max_write_buffer_number=2"
|
||||
// Nested options config is also possible. For example, you can define
|
||||
// BlockBasedTableOptions as part of the string for block-based table factory:
|
||||
// "write_buffer_size=1024;block_based_table_factory={block_size=4k};"
|
||||
// "max_write_buffer_num=2"
|
||||
Status GetColumnFamilyOptionsFromString(
|
||||
const ColumnFamilyOptions& base_options,
|
||||
const std::string& opts_str,
|
||||
ColumnFamilyOptions* new_options);
|
||||
|
||||
Status GetDBOptionsFromString(
|
||||
const DBOptions& base_options,
|
||||
const std::string& opts_str,
|
||||
DBOptions* new_options);
|
||||
|
||||
Status GetBlockBasedTableOptionsFromString(
|
||||
const BlockBasedTableOptions& table_options,
|
||||
const std::string& opts_str,
|
||||
BlockBasedTableOptions* new_table_options);
|
||||
|
||||
Status GetOptionsFromString(const Options& base_options,
|
||||
const std::string& opts_str, Options* new_options);
|
||||
|
||||
/// Request stopping background work, if wait is true wait until it's done
|
||||
void CancelAllBackgroundWork(DB* db, bool wait = false);
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
} // namespace rocksdb
|
||||
+32
-3
@@ -24,6 +24,12 @@
|
||||
#include "rocksdb/listener.h"
|
||||
#include "rocksdb/thread_status.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
// Windows API macro interference
|
||||
#undef DeleteFile
|
||||
#endif
|
||||
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
struct Options;
|
||||
@@ -332,6 +338,7 @@ class DB {
|
||||
// See version_set.h for details. More live versions often mean more SST
|
||||
// files are held from being deleted, by iterators or unfinished
|
||||
// compactions.
|
||||
// "rocksdb.estimate-live-data-size"
|
||||
#ifndef ROCKSDB_LITE
|
||||
struct Properties {
|
||||
static const std::string kNumFilesAtLevelPrefix;
|
||||
@@ -355,6 +362,7 @@ class DB {
|
||||
static const std::string kNumSnapshots;
|
||||
static const std::string kOldestSnapshotTime;
|
||||
static const std::string kNumLiveVersions;
|
||||
static const std::string kEstimateLiveDataSize;
|
||||
};
|
||||
#endif /* ROCKSDB_LITE */
|
||||
|
||||
@@ -383,6 +391,7 @@ class DB {
|
||||
// "rocksdb.num-snapshots"
|
||||
// "rocksdb.oldest-snapshot-time"
|
||||
// "rocksdb.num-live-versions"
|
||||
// "rocksdb.estimate-live-data-size"
|
||||
virtual bool GetIntProperty(ColumnFamilyHandle* column_family,
|
||||
const Slice& property, uint64_t* value) = 0;
|
||||
virtual bool GetIntProperty(const Slice& property, uint64_t* value) {
|
||||
@@ -396,7 +405,9 @@ class DB {
|
||||
// if the user data compresses by a factor of ten, the returned
|
||||
// sizes will be one-tenth the size of the corresponding user data size.
|
||||
//
|
||||
// The results may not include the sizes of recently written data.
|
||||
// If include_memtable is set to true, then the result will also
|
||||
// include those recently written data in the mem-tables if
|
||||
// the mem-table type supports it.
|
||||
virtual void GetApproximateSizes(ColumnFamilyHandle* column_family,
|
||||
const Range* range, int n, uint64_t* sizes,
|
||||
bool include_memtable = false) = 0;
|
||||
@@ -431,7 +442,12 @@ class DB {
|
||||
return CompactRange(options, DefaultColumnFamily(), begin, end);
|
||||
}
|
||||
|
||||
__attribute__((deprecated)) virtual Status
|
||||
#if defined(__GNUC__) || defined(__clang__)
|
||||
__attribute__((deprecated))
|
||||
#elif _WIN32
|
||||
__declspec(deprecated)
|
||||
#endif
|
||||
virtual Status
|
||||
CompactRange(ColumnFamilyHandle* column_family, const Slice* begin,
|
||||
const Slice* end, bool change_level = false,
|
||||
int target_level = -1, uint32_t target_path_id = 0) {
|
||||
@@ -441,7 +457,12 @@ class DB {
|
||||
options.target_path_id = target_path_id;
|
||||
return CompactRange(options, column_family, begin, end);
|
||||
}
|
||||
__attribute__((deprecated)) virtual Status
|
||||
#if defined(__GNUC__) || defined(__clang__)
|
||||
__attribute__((deprecated))
|
||||
#elif _WIN32
|
||||
__declspec(deprecated)
|
||||
#endif
|
||||
virtual Status
|
||||
CompactRange(const Slice* begin, const Slice* end,
|
||||
bool change_level = false, int target_level = -1,
|
||||
uint32_t target_path_id = 0) {
|
||||
@@ -524,6 +545,12 @@ class DB {
|
||||
return Flush(options, DefaultColumnFamily());
|
||||
}
|
||||
|
||||
// Sync the wal. Note that Write() followed by SyncWAL() is not exactly the
|
||||
// same as Write() with sync=true: in the latter case the changes won't be
|
||||
// visible until the sync is done.
|
||||
// Currently only works if allow_mmap_writes = false in Options.
|
||||
virtual Status SyncWAL() = 0;
|
||||
|
||||
// The sequence number of the most recent transaction.
|
||||
virtual SequenceNumber GetLatestSequenceNumber() const = 0;
|
||||
|
||||
@@ -581,6 +608,8 @@ class DB {
|
||||
const TransactionLogIterator::ReadOptions&
|
||||
read_options = TransactionLogIterator::ReadOptions()) = 0;
|
||||
|
||||
// Windows API macro interference
|
||||
#undef DeleteFile
|
||||
// Delete the file name from the db directory and update the internal state to
|
||||
// reflect that. Supports deletion of sst and log files only. 'name' must be
|
||||
// path relative to the db directory. eg. 000001.sst, /archive/000003.log
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
// 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 <map>
|
||||
#include <string>
|
||||
|
||||
#include "rocksdb/status.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
class Env;
|
||||
class Logger;
|
||||
|
||||
// DeleteScheduler allow the DB to enforce a rate limit on file deletion,
|
||||
// Instead of deleteing files immediately, files are moved to trash_dir
|
||||
// and deleted in a background thread that apply sleep penlty between deletes
|
||||
// if they are happening in a rate faster than rate_bytes_per_sec,
|
||||
//
|
||||
// Rate limiting can be turned off by setting rate_bytes_per_sec = 0, In this
|
||||
// case DeleteScheduler will delete files immediately.
|
||||
class DeleteScheduler {
|
||||
public:
|
||||
virtual ~DeleteScheduler() {}
|
||||
|
||||
// Return delete rate limit in bytes per second
|
||||
virtual int64_t GetRateBytesPerSecond() = 0;
|
||||
|
||||
// Move file to trash directory and schedule it's deletion
|
||||
virtual Status DeleteFile(const std::string& fname) = 0;
|
||||
|
||||
// Return a map containing errors that happened in the background thread
|
||||
// file_path => error status
|
||||
virtual std::map<std::string, Status> GetBackgroundErrors() = 0;
|
||||
};
|
||||
|
||||
// Create a new DeleteScheduler that can be shared among multiple RocksDB
|
||||
// instances to control the file deletion rate.
|
||||
//
|
||||
// @env: Pointer to Env object, please see "rocksdb/env.h".
|
||||
// @trash_dir: Path to the directory where deleted files will be moved into
|
||||
// to be deleted in a background thread while applying rate limiting. If this
|
||||
// directory dont exist, it will be created. This directory should not be
|
||||
// used by any other process or any other DeleteScheduler.
|
||||
// @rate_bytes_per_sec: How many bytes should be deleted per second, If this
|
||||
// value is set to 1024 (1 Kb / sec) and we deleted a file of size 4 Kb
|
||||
// in 1 second, we will wait for another 3 seconds before we delete other
|
||||
// files, Set to 0 to disable rate limiting.
|
||||
// @info_log: If not nullptr, info_log will be used to log errors.
|
||||
// @delete_exisitng_trash: If set to true, the newly created DeleteScheduler
|
||||
// will delete files that already exist in trash_dir.
|
||||
// @status: If not nullptr, status will contain any errors that happened during
|
||||
// creating the missing trash_dir or deleting existing files in trash.
|
||||
extern DeleteScheduler* NewDeleteScheduler(
|
||||
Env* env, const std::string& trash_dir, int64_t rate_bytes_per_sec,
|
||||
std::shared_ptr<Logger> info_log = nullptr,
|
||||
bool delete_exisitng_trash = true, Status* status = nullptr);
|
||||
|
||||
} // namespace rocksdb
|
||||
Loaded 100 of 325 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user