mirror of
https://github.com/facebook/rocksdb.git
synced 2026-11-08 03:52:33 -05:00
Compare commits
43
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d9ed290bfa | ||
|
|
9cc25190e1 | ||
|
|
50d6969816 | ||
|
|
83a4dd1a67 | ||
|
|
8efb5cfb6f | ||
|
|
9f21d08660 | ||
|
|
5b2bbacb6f | ||
|
|
1092f19d95 | ||
|
|
d739318ba7 | ||
|
|
096beb787e | ||
|
|
dea4063b13 | ||
|
|
25a0d0ca30 | ||
|
|
d76eed4839 | ||
|
|
88b4210701 | ||
|
|
a607f3efaa | ||
|
|
f4583f7480 | ||
|
|
552fd765b3 | ||
|
|
d6b7b7712f | ||
|
|
569b87e8c7 | ||
|
|
775dc623ad | ||
|
|
312f23c92d | ||
|
|
223b57eeb8 | ||
|
|
94d04529de | ||
|
|
742b452863 | ||
|
|
b7bab48099 | ||
|
|
9bfd46d0d8 | ||
|
|
aa8f1331af | ||
|
|
88db97b06d | ||
|
|
9c24a5cb4d | ||
|
|
f7613e2a9e | ||
|
|
15d9f28da5 | ||
|
|
bacd6edcbe | ||
|
|
7e2ac0c3a0 | ||
|
|
d63f86e506 | ||
|
|
9810f40075 | ||
|
|
af58d92760 | ||
|
|
83833637c1 | ||
|
|
717749f4c0 | ||
|
|
d854abad78 | ||
|
|
722ebba834 | ||
|
|
b3585a11b4 | ||
|
|
fbe2d259cb | ||
|
|
e6be168aa5 |
No files matched your search
+18
-1
@@ -53,7 +53,21 @@ jobs:
|
||||
resource_class: 2xlarge
|
||||
steps:
|
||||
- checkout # check out the code in the project directory
|
||||
- run: USE_CLANG=1 make all -j32
|
||||
- run: sudo apt-get update -y
|
||||
- run: sudo apt-get install -y clang
|
||||
- run: CC=clang CXX=clang++ USE_CLANG=1 PORTABLE=1 make all -j32
|
||||
|
||||
build-linux-clang10-no-test:
|
||||
machine:
|
||||
image: ubuntu-1604:201903-01
|
||||
resource_class: 2xlarge
|
||||
steps:
|
||||
- checkout # check out the code in the project directory
|
||||
- run: sudo sh -c 'echo "deb http://apt.llvm.org/xenial/ llvm-toolchain-xenial-10 main" >> /etc/apt/sources.list'
|
||||
- run: sudo sh -c 'echo "deb-src http://apt.llvm.org/xenial/ llvm-toolchain-xenial-10 main" >> /etc/apt/sources.list'
|
||||
- run: sudo apt-get update -y
|
||||
- run: sudo apt-get install -y clang-10
|
||||
- run: CC=clang-10 CXX=clang++-10 ROCKSDB_DISABLE_ALIGNED_NEW=1 USE_CLANG=1 make all -j32 # aligned new doesn't work for reason we haven't figured out
|
||||
|
||||
build-linux-cmake:
|
||||
machine:
|
||||
@@ -123,6 +137,9 @@ workflows:
|
||||
build-linux-clang-no-test:
|
||||
jobs:
|
||||
- build-linux-clang-no-test
|
||||
build-linux-clang10-no-test:
|
||||
jobs:
|
||||
- build-linux-clang10-no-test
|
||||
build-linux-cmake:
|
||||
jobs:
|
||||
- build-linux-cmake
|
||||
|
||||
+17
-4
@@ -95,7 +95,7 @@ if(MSVC)
|
||||
option(WITH_XPRESS "build with windows built in compression" OFF)
|
||||
include(${CMAKE_CURRENT_SOURCE_DIR}/thirdparty.inc)
|
||||
else()
|
||||
if(CMAKE_SYSTEM_NAME MATCHES "FreeBSD")
|
||||
if(CMAKE_SYSTEM_NAME MATCHES "FreeBSD" AND NOT CMAKE_SYSTEM_NAME MATCHES "kFreeBSD")
|
||||
# FreeBSD has jemalloc as default malloc
|
||||
# but it does not have all the jemalloc files in include/...
|
||||
set(WITH_JEMALLOC ON)
|
||||
@@ -448,6 +448,8 @@ elseif(CMAKE_SYSTEM_NAME MATCHES "Linux")
|
||||
add_definitions(-DOS_LINUX)
|
||||
elseif(CMAKE_SYSTEM_NAME MATCHES "SunOS")
|
||||
add_definitions(-DOS_SOLARIS)
|
||||
elseif(CMAKE_SYSTEM_NAME MATCHES "kFreeBSD")
|
||||
add_definitions(-DOS_GNU_KFREEBSD)
|
||||
elseif(CMAKE_SYSTEM_NAME MATCHES "FreeBSD")
|
||||
add_definitions(-DOS_FREEBSD)
|
||||
elseif(CMAKE_SYSTEM_NAME MATCHES "NetBSD")
|
||||
@@ -974,12 +976,19 @@ if(NOT WIN32 OR ROCKSDB_INSTALL_ON_WINDOWS)
|
||||
)
|
||||
endif()
|
||||
|
||||
option(WITH_ALL_TESTS "Build all test, rather than a small subset" ON)
|
||||
|
||||
if(WITH_TESTS)
|
||||
add_subdirectory(third-party/gtest-1.8.1/fused-src/gtest)
|
||||
add_library(testharness STATIC
|
||||
test_util/testharness.cc)
|
||||
target_link_libraries(testharness gtest)
|
||||
set(TESTS
|
||||
db/db_basic_test.cc
|
||||
env/env_basic_test.cc
|
||||
)
|
||||
if(WITH_ALL_TESTS)
|
||||
list(APPEND TESTS
|
||||
cache/cache_test.cc
|
||||
cache/lru_cache_test.cc
|
||||
db/blob/blob_file_addition_test.cc
|
||||
@@ -994,7 +1003,6 @@ if(WITH_TESTS)
|
||||
db/comparator_db_test.cc
|
||||
db/corruption_test.cc
|
||||
db/cuckoo_table_db_test.cc
|
||||
db/db_basic_test.cc
|
||||
db/db_with_timestamp_basic_test.cc
|
||||
db/db_block_cache_test.cc
|
||||
db/db_bloom_filter_test.cc
|
||||
@@ -1057,7 +1065,6 @@ if(WITH_TESTS)
|
||||
db/write_batch_test.cc
|
||||
db/write_callback_test.cc
|
||||
db/write_controller_test.cc
|
||||
env/env_basic_test.cc
|
||||
env/env_test.cc
|
||||
env/io_posix_test.cc
|
||||
env/mock_env_test.cc
|
||||
@@ -1140,7 +1147,8 @@ if(WITH_TESTS)
|
||||
utilities/transactions/write_unprepared_transaction_test.cc
|
||||
utilities/ttl/ttl_test.cc
|
||||
utilities/write_batch_with_index/write_batch_with_index_test.cc
|
||||
)
|
||||
)
|
||||
endif()
|
||||
if(WITH_LIBRADOS)
|
||||
list(APPEND TESTS utilities/env_librados_test.cc)
|
||||
endif()
|
||||
@@ -1261,3 +1269,8 @@ if(WITH_TOOLS)
|
||||
add_custom_target(tools
|
||||
DEPENDS ${tool_deps})
|
||||
endif()
|
||||
|
||||
option(WITH_EXAMPLES "build with examples" OFF)
|
||||
if(WITH_EXAMPLES)
|
||||
add_subdirectory(examples)
|
||||
endif()
|
||||
+28
-4
@@ -1,9 +1,25 @@
|
||||
# Rocksdb Change Log
|
||||
## Unreleased
|
||||
### Behavior Changes
|
||||
* Disable delete triggered compaction (NewCompactOnDeletionCollectorFactory) in universal compaction mode and num_levels = 1 in order to avoid a corruption bug.
|
||||
* `pin_l0_filter_and_index_blocks_in_cache` no longer applies to L0 files larger than `1.5 * write_buffer_size` to give more predictable memory usage. Such L0 files may exist due to intra-L0 compaction, external file ingestion, or user dynamically changing `write_buffer_size` (note, however, that files that are already pinned will continue being pinned, even after such a dynamic change).
|
||||
### Public API Change
|
||||
* Encryption file classes now exposed for inheritance in env_encryption.h
|
||||
|
||||
### Behavior Changes
|
||||
* Best-efforts recovery ignores CURRENT file completely. If CURRENT file is missing during recovery, best-efforts recovery still proceeds with MANIFEST file(s).
|
||||
* In best-efforts recovery, an error that is not Corruption or IOError::kNotFound or IOError::kPathNotFound will be overwritten silently. Fix this by checking all non-ok cases and return early.
|
||||
* Compressed block cache was automatically disabled with read-only DBs by mistake. Now it is fixed: compressed block cache will be in effective with read-only DB too.
|
||||
* Fix a bug of wrong iterator result if another thread finishes an update and a DB flush between two statement.
|
||||
|
||||
### Public API Change
|
||||
* `DB::GetDbSessionId(std::string& session_id)` is added. `session_id` stores a unique identifier that gets reset every time the DB is opened. This DB session ID should be unique among all open DB instances on all hosts, and should be unique among re-openings of the same or other DBs. This identifier is recorded in the LOG file on the line starting with "DB Session ID:".
|
||||
|
||||
### New Features
|
||||
* DB identity (`db_id`) and DB session identity (`db_session_id`) are added to table properties and stored in SST files. SST files generated from SstFileWriter and Repairer have DB identity “SST Writer” and “DB Repairer”, respectively. Their DB session IDs are generated in the same way as `DB::GetDbSessionId`. The session ID for SstFileWriter (resp., Repairer) resets every time `SstFileWriter::Open` (resp., `Repairer::Run`) is called.
|
||||
* Added experimental option BlockBasedTableOptions::optimize_filters_for_memory for reducing allocated memory size of Bloom filters (~10% savings with Jemalloc) while preserving the same general accuracy. To have an effect, the option requires format_version=5 and malloc_usable_size. Enabling this option is forward and backward compatible with existing format_version=5.
|
||||
|
||||
### Bug Fixes
|
||||
* Fail recovery and report once hitting a physical log record checksum mismatch, while reading MANIFEST. RocksDB should not continue processing the MANIFEST any further.
|
||||
|
||||
## 6.11 (6/12/2020)
|
||||
### Bug Fixes
|
||||
* Fix consistency checking error swallowing in some cases when options.force_consistency_checks = true.
|
||||
* Fix possible false NotFound status from batched MultiGet using index type kHashSearch.
|
||||
@@ -24,11 +40,19 @@
|
||||
* ldb now uses options.force_consistency_checks = true by default and "--disable_consistency_checks" is added to disable it.
|
||||
* DB::OpenForReadOnly no longer creates files or directories if the named DB does not exist, unless create_if_missing is set to true.
|
||||
* The consistency checks that validate LSM state changes (table file additions/deletions during flushes and compactions) are now stricter, more efficient, and no longer optional, i.e. they are performed even if `force_consistency_checks` is `false`.
|
||||
* Disable delete triggered compaction (NewCompactOnDeletionCollectorFactory) in universal compaction mode and num_levels = 1 in order to avoid a corruption bug.
|
||||
* `pin_l0_filter_and_index_blocks_in_cache` no longer applies to L0 files larger than `1.5 * write_buffer_size` to give more predictable memory usage. Such L0 files may exist due to intra-L0 compaction, external file ingestion, or user dynamically changing `write_buffer_size` (note, however, that files that are already pinned will continue being pinned, even after such a dynamic change).
|
||||
* In point-in-time wal recovery mode, fail database recovery in case of IOError while reading the WAL to avoid data loss.
|
||||
* A new method `Env::LowerThreadPoolCPUPriority(Priority, CpuPriority)` is added to `Env` to be able to lower to a specific priority such as `CpuPriority::kIdle`.
|
||||
|
||||
### New Feature
|
||||
### New Features
|
||||
* sst_dump to add a new --readahead_size argument. Users can specify read size when scanning the data. Sst_dump also tries to prefetch tail part of the SST files so usually some number of I/Os are saved there too.
|
||||
* Generate file checksum in SstFileWriter if Options.file_checksum_gen_factory is set. The checksum and checksum function name are stored in ExternalSstFileInfo after the sst file write is finished.
|
||||
* Add a value_size_soft_limit in read options which limits the cumulative value size of keys read in batches in MultiGet. Once the cumulative value size of found keys exceeds read_options.value_size_soft_limit, all the remaining keys are returned with status Abort without further finding their values. By default the value_size_soft_limit is std::numeric_limits<uint64_t>::max().
|
||||
* Enable SST file ingestion with file checksum information when calling IngestExternalFiles(const std::vector<IngestExternalFileArg>& args). Added files_checksums and files_checksum_func_names to IngestExternalFileArg such that user can ingest the sst files with their file checksum information. Added verify_file_checksum to IngestExternalFileOptions (default is True). To be backward compatible, if DB does not enable file checksum or user does not provide checksum information (vectors of files_checksums and files_checksum_func_names are both empty), verification of file checksum is always sucessful. If DB enables file checksum, DB will always generate the checksum for each ingested SST file during Prepare stage of ingestion and store the checksum in Manifest, unless verify_file_checksum is False and checksum information is provided by the application. In this case, we only verify the checksum function name and directly store the ingested checksum in Manifest. If verify_file_checksum is set to True, DB will verify the ingested checksum and function name with the genrated ones. Any mismatch will fail the ingestion. Note that, if IngestExternalFileOptions::write_global_seqno is True, the seqno will be changed in the ingested file. Therefore, the checksum of the file will be changed. In this case, a new checksum will be generated after the seqno is updated and be stored in the Manifest.
|
||||
|
||||
### Performance Improvements
|
||||
* Eliminate redundant key comparisons during random access in block-based tables.
|
||||
|
||||
## 6.10 (5/2/2020)
|
||||
### Bug Fixes
|
||||
|
||||
@@ -640,6 +640,7 @@ TESTS = \
|
||||
timer_test \
|
||||
db_with_timestamp_compaction_test \
|
||||
testutil_test \
|
||||
io_tracer_test \
|
||||
|
||||
PARALLEL_TEST = \
|
||||
backupable_db_test \
|
||||
@@ -674,9 +675,9 @@ ifdef COMPILE_WITH_UBSAN
|
||||
TESTS := $(shell echo $(TESTS) | sed 's/\boptions_settable_test\b//g')
|
||||
endif
|
||||
ifdef ASSERT_STATUS_CHECKED
|
||||
# This is a new check for which we will add support incrementally. The
|
||||
# whitelist can be removed once support is fully added.
|
||||
TESTS_WHITELIST = \
|
||||
# This is a new check for which we will add support incrementally. This
|
||||
# list can be removed once support is fully added.
|
||||
TESTS_PASSING_ASC = \
|
||||
arena_test \
|
||||
autovector_test \
|
||||
blob_file_addition_test \
|
||||
@@ -722,8 +723,8 @@ ifdef ASSERT_STATUS_CHECKED
|
||||
work_queue_test \
|
||||
write_controller_test \
|
||||
|
||||
TESTS := $(filter $(TESTS_WHITELIST),$(TESTS))
|
||||
PARALLEL_TEST := $(filter $(TESTS_WHITELIST),$(PARALLEL_TEST))
|
||||
TESTS := $(filter $(TESTS_PASSING_ASC),$(TESTS))
|
||||
PARALLEL_TEST := $(filter $(TESTS_PASSING_ASC),$(PARALLEL_TEST))
|
||||
endif
|
||||
SUBSET := $(TESTS)
|
||||
ifdef ROCKSDBTESTS_START
|
||||
@@ -844,7 +845,8 @@ endif # PLATFORM_SHARED_EXT
|
||||
dbg rocksdbjavastatic rocksdbjava install install-static install-shared uninstall \
|
||||
analyze tools tools_lib \
|
||||
blackbox_crash_test_with_atomic_flush whitebox_crash_test_with_atomic_flush \
|
||||
blackbox_crash_test_with_txn whitebox_crash_test_with_txn
|
||||
blackbox_crash_test_with_txn whitebox_crash_test_with_txn \
|
||||
blackbox_crash_test_with_best_efforts_recovery
|
||||
|
||||
|
||||
all: $(LIBRARY) $(BENCHMARKS) tools tools_lib test_libs $(TESTS)
|
||||
@@ -976,7 +978,7 @@ J ?= 100%
|
||||
# Use this regexp to select the subset of tests whose names match.
|
||||
tests-regexp = .
|
||||
|
||||
ifeq ($(PRINT_PARALLEL_OUTPUTS), 1)
|
||||
ifeq ($(PRINT_PARALLEL_OUTPUTS), 1)
|
||||
parallel_com = '{}'
|
||||
else
|
||||
parallel_com = '{} >& t/log-{/}'
|
||||
@@ -1000,7 +1002,7 @@ check_0:
|
||||
awk '{ if ($$7 != 0 || $$8 != 0) { if ($$7 == "Exitval") { h = $$0; } else { if (!f) print h; print; f = 1 } } } END { if(f) exit 1; }' < LOG ; \
|
||||
if [ $$parallel_retcode -ne 0 ] ; then exit 1 ; fi
|
||||
|
||||
valgrind-blacklist-regexp = InlineSkipTest.ConcurrentInsert|TransactionStressTest.DeadlockStress|DBCompactionTest.SuggestCompactRangeNoTwoLevel0Compactions|BackupableDBTest.RateLimiting|DBTest.CloseSpeedup|DBTest.ThreadStatusFlush|DBTest.RateLimitingTest|DBTest.EncodeDecompressedBlockSizeTest|FaultInjectionTest.UninstalledCompaction|HarnessTest.Randomized|ExternalSSTFileTest.CompactDuringAddFileRandom|ExternalSSTFileTest.IngestFileWithGlobalSeqnoRandomized|MySQLStyleTransactionTest.TransactionStressTest
|
||||
valgrind-exclude-regexp = InlineSkipTest.ConcurrentInsert|TransactionStressTest.DeadlockStress|DBCompactionTest.SuggestCompactRangeNoTwoLevel0Compactions|BackupableDBTest.RateLimiting|DBTest.CloseSpeedup|DBTest.ThreadStatusFlush|DBTest.RateLimitingTest|DBTest.EncodeDecompressedBlockSizeTest|FaultInjectionTest.UninstalledCompaction|HarnessTest.Randomized|ExternalSSTFileTest.CompactDuringAddFileRandom|ExternalSSTFileTest.IngestFileWithGlobalSeqnoRandomized|MySQLStyleTransactionTest.TransactionStressTest
|
||||
|
||||
.PHONY: valgrind_check_0
|
||||
valgrind_check_0:
|
||||
@@ -1015,7 +1017,7 @@ valgrind_check_0:
|
||||
} \
|
||||
| $(prioritize_long_running_tests) \
|
||||
| grep -E '$(tests-regexp)' \
|
||||
| grep -E -v '$(valgrind-blacklist-regexp)' \
|
||||
| grep -E -v '$(valgrind-exclude-regexp)' \
|
||||
| build_tools/gnu_parallel -j$(J) --plain --joblog=LOG $$eta --gnu \
|
||||
'(if [[ "{}" == "./"* ]] ; then $(DRIVER) {}; else {}; fi) ' \
|
||||
'>& t/valgrind_log-{/}'
|
||||
@@ -1057,7 +1059,7 @@ ifndef ASSERT_STATUS_CHECKED # not yet working with these tests
|
||||
sh tools/rocksdb_dump_test.sh
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
ifndef SKIP_FORMAT_BUCK_CHECKS
|
||||
$(MAKE) check-format
|
||||
$(MAKE) check-buck-targets
|
||||
@@ -1077,6 +1079,8 @@ crash_test_with_atomic_flush: whitebox_crash_test_with_atomic_flush blackbox_cra
|
||||
|
||||
crash_test_with_txn: whitebox_crash_test_with_txn blackbox_crash_test_with_txn
|
||||
|
||||
crash_test_with_best_efforts_recovery: blackbox_crash_test_with_best_efforts_recovery
|
||||
|
||||
blackbox_crash_test: db_stress
|
||||
$(PYTHON) -u tools/db_crashtest.py --simple blackbox $(CRASH_TEST_EXT_ARGS)
|
||||
$(PYTHON) -u tools/db_crashtest.py blackbox $(CRASH_TEST_EXT_ARGS)
|
||||
@@ -1087,6 +1091,9 @@ blackbox_crash_test_with_atomic_flush: db_stress
|
||||
blackbox_crash_test_with_txn: db_stress
|
||||
$(PYTHON) -u tools/db_crashtest.py --txn blackbox $(CRASH_TEST_EXT_ARGS)
|
||||
|
||||
blackbox_crash_test_with_best_efforts_recovery: db_stress
|
||||
$(PYTHON) -u tools/db_crashtest.py --test_best_efforts_recovery blackbox $(CRASH_TEST_EXT_ARGS)
|
||||
|
||||
ifeq ($(CRASH_TEST_KILL_ODD),)
|
||||
CRASH_TEST_KILL_ODD=888887
|
||||
endif
|
||||
@@ -1125,6 +1132,11 @@ asan_crash_test_with_txn:
|
||||
COMPILE_WITH_ASAN=1 $(MAKE) crash_test_with_txn
|
||||
$(MAKE) clean
|
||||
|
||||
asan_crash_test_with_best_efforts_recovery:
|
||||
$(MAKE) clean
|
||||
COMPILE_WITH_ASAN=1 $(MAKE) crash_test_with_best_efforts_recovery
|
||||
$(MAKE) clean
|
||||
|
||||
ubsan_check:
|
||||
$(MAKE) clean
|
||||
COMPILE_WITH_UBSAN=1 $(MAKE) check -j32
|
||||
@@ -1145,6 +1157,11 @@ ubsan_crash_test_with_txn:
|
||||
COMPILE_WITH_UBSAN=1 $(MAKE) crash_test_with_txn
|
||||
$(MAKE) clean
|
||||
|
||||
ubsan_crash_test_with_best_efforts_recovery:
|
||||
$(MAKE) clean
|
||||
COMPILE_WITH_UBSAN=1 $(MAKE) crash_test_with_best_efforts_recovery
|
||||
$(MAKE) clean
|
||||
|
||||
valgrind_test:
|
||||
ROCKSDB_VALGRIND_RUN=1 DISABLE_JEMALLOC=1 $(MAKE) valgrind_check
|
||||
|
||||
@@ -1880,6 +1897,8 @@ timer_test: util/timer_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
|
||||
testutil_test: test_util/testutil_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
io_tracer_test: trace_replay/io_tracer_test.o trace_replay/io_tracer.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
#-------------------------------------------------
|
||||
# make install related stuff
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
## RocksDB: A Persistent Key-Value Store for Flash and RAM Storage
|
||||
|
||||
[](https://travis-ci.org/facebook/rocksdb)
|
||||
[](https://ci.appveyor.com/project/Facebook/rocksdb/branch/master)
|
||||
[](https://circleci.com/gh/facebook/rocksdb)
|
||||
[](https://travis-ci.org/facebook/rocksdb)
|
||||
[](https://ci.appveyor.com/project/Facebook/rocksdb/branch/master)
|
||||
[](http://140.211.168.68:8080/job/Rocksdb)
|
||||
|
||||
RocksDB is developed and maintained by Facebook Database Engineering Team.
|
||||
|
||||
@@ -294,6 +294,7 @@ cpp_library(
|
||||
"tools/ldb_tool.cc",
|
||||
"tools/sst_dump_tool.cc",
|
||||
"trace_replay/block_cache_tracer.cc",
|
||||
"trace_replay/io_tracer.cc",
|
||||
"trace_replay/trace_replay.cc",
|
||||
"util/build_version.cc",
|
||||
"util/coding.cc",
|
||||
@@ -1155,6 +1156,13 @@ ROCKS_TESTS = [
|
||||
[],
|
||||
[],
|
||||
],
|
||||
[
|
||||
"io_tracer_test",
|
||||
"trace_replay/io_tracer_test.cc",
|
||||
"serial",
|
||||
[],
|
||||
[],
|
||||
],
|
||||
[
|
||||
"iostats_context_test",
|
||||
"monitoring/iostats_context_test.cc",
|
||||
|
||||
+2
-2
@@ -68,7 +68,7 @@ before_build:
|
||||
- md %APPVEYOR_BUILD_FOLDER%\build
|
||||
- cd %APPVEYOR_BUILD_FOLDER%\build
|
||||
- if DEFINED CMAKE_PLATEFORM_NAME (set "PLATEFORM_OPT=-A %CMAKE_PLATEFORM_NAME%")
|
||||
- cmake .. -G "%CMAKE_GENERATOR%" %PLATEFORM_OPT% %CMAKE_OPT% -DCMAKE_BUILD_TYPE=Debug -DOPTDBG=1 -DPORTABLE=1 -DSNAPPY=1 -DLZ4=1 -DZSTD=1 -DXPRESS=1 -DJNI=1
|
||||
- cmake .. -G "%CMAKE_GENERATOR%" %PLATEFORM_OPT% %CMAKE_OPT% -DCMAKE_BUILD_TYPE=Debug -DOPTDBG=1 -DPORTABLE=1 -DSNAPPY=1 -DLZ4=1 -DZSTD=1 -DXPRESS=1 -DJNI=1 -DWITH_ALL_TESTS=0
|
||||
- cd ..
|
||||
|
||||
build:
|
||||
@@ -79,7 +79,7 @@ build:
|
||||
test:
|
||||
|
||||
test_script:
|
||||
- ps: build_tools\run_ci_db_test.ps1 -SuiteRun db_basic_test,db_with_timestamp_basic_test,db_test2,db_test,env_basic_test,env_test,db_merge_operand_test -Concurrency 8
|
||||
- ps: build_tools\run_ci_db_test.ps1 -SuiteRun db_basic_test,env_basic_test -Concurrency 8
|
||||
|
||||
on_failure:
|
||||
- cmd: 7z a build-failed.zip %APPVEYOR_BUILD_FOLDER%\build\ && appveyor PushArtifact build-failed.zip
|
||||
|
||||
@@ -192,6 +192,17 @@ EOF
|
||||
PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -lpthread"
|
||||
# PORT_FILES=port/freebsd/freebsd_specific.cc
|
||||
;;
|
||||
GNU/kFreeBSD)
|
||||
PLATFORM=OS_GNU_KFREEBSD
|
||||
COMMON_FLAGS="$COMMON_FLAGS -DOS_GNU_KFREEBSD"
|
||||
if [ -z "$USE_CLANG" ]; then
|
||||
COMMON_FLAGS="$COMMON_FLAGS -fno-builtin-memcmp"
|
||||
else
|
||||
PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -latomic"
|
||||
fi
|
||||
PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -lpthread -lrt"
|
||||
# PORT_FILES=port/gnu_kfreebsd/gnu_kfreebsd_specific.cc
|
||||
;;
|
||||
NetBSD)
|
||||
PLATFORM=OS_NETBSD
|
||||
COMMON_FLAGS="$COMMON_FLAGS -fno-builtin-memcmp -D_REENTRANT -DOS_NETBSD"
|
||||
|
||||
@@ -56,8 +56,9 @@ Status ArenaWrappedDBIter::Refresh() {
|
||||
// TODO(yiwu): For last_seq_same_as_publish_seq_==false, this is not the
|
||||
// correct behavior. Will be corrected automatically when we take a snapshot
|
||||
// here for the case of WritePreparedTxnDB.
|
||||
SequenceNumber latest_seq = db_impl_->GetLatestSequenceNumber();
|
||||
uint64_t cur_sv_number = cfd_->GetSuperVersionNumber();
|
||||
TEST_SYNC_POINT("ArenaWrappedDBIter::Refresh:1");
|
||||
TEST_SYNC_POINT("ArenaWrappedDBIter::Refresh:2");
|
||||
if (sv_number_ != cur_sv_number) {
|
||||
Env* env = db_iter_->env();
|
||||
db_iter_->~DBIter();
|
||||
@@ -65,6 +66,7 @@ Status ArenaWrappedDBIter::Refresh() {
|
||||
new (&arena_) Arena();
|
||||
|
||||
SuperVersion* sv = cfd_->GetReferencedSuperVersion(db_impl_);
|
||||
SequenceNumber latest_seq = db_impl_->GetLatestSequenceNumber();
|
||||
if (read_callback_) {
|
||||
read_callback_->Refresh(latest_seq);
|
||||
}
|
||||
@@ -78,7 +80,7 @@ Status ArenaWrappedDBIter::Refresh() {
|
||||
latest_seq, /* allow_unprepared_value */ true);
|
||||
SetIterUnderDBIter(internal_iter);
|
||||
} else {
|
||||
db_iter_->set_sequence(latest_seq);
|
||||
db_iter_->set_sequence(db_impl_->GetLatestSequenceNumber());
|
||||
db_iter_->set_valid(false);
|
||||
}
|
||||
return Status::OK();
|
||||
|
||||
@@ -38,8 +38,15 @@ std::string BlobFileMetaData::DebugString() const {
|
||||
std::ostream& operator<<(std::ostream& os, const BlobFileMetaData& meta) {
|
||||
const auto& shared_meta = meta.GetSharedMeta();
|
||||
assert(shared_meta);
|
||||
os << (*shared_meta);
|
||||
|
||||
os << (*shared_meta) << " garbage_blob_count: " << meta.GetGarbageBlobCount()
|
||||
os << " linked_ssts: {";
|
||||
for (uint64_t file_number : meta.GetLinkedSsts()) {
|
||||
os << ' ' << file_number;
|
||||
}
|
||||
os << " }";
|
||||
|
||||
os << " garbage_blob_count: " << meta.GetGarbageBlobCount()
|
||||
<< " garbage_blob_bytes: " << meta.GetGarbageBlobBytes();
|
||||
|
||||
return os;
|
||||
|
||||
@@ -5,12 +5,13 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "rocksdb/rocksdb_namespace.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <iosfwd>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <unordered_set>
|
||||
|
||||
#include "rocksdb/rocksdb_namespace.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
@@ -90,11 +91,15 @@ std::ostream& operator<<(std::ostream& os,
|
||||
|
||||
class BlobFileMetaData {
|
||||
public:
|
||||
using LinkedSsts = std::unordered_set<uint64_t>;
|
||||
|
||||
static std::shared_ptr<BlobFileMetaData> Create(
|
||||
std::shared_ptr<SharedBlobFileMetaData> shared_meta,
|
||||
uint64_t garbage_blob_count, uint64_t garbage_blob_bytes) {
|
||||
return std::shared_ptr<BlobFileMetaData>(new BlobFileMetaData(
|
||||
std::move(shared_meta), garbage_blob_count, garbage_blob_bytes));
|
||||
LinkedSsts linked_ssts, uint64_t garbage_blob_count,
|
||||
uint64_t garbage_blob_bytes) {
|
||||
return std::shared_ptr<BlobFileMetaData>(
|
||||
new BlobFileMetaData(std::move(shared_meta), std::move(linked_ssts),
|
||||
garbage_blob_count, garbage_blob_bytes));
|
||||
}
|
||||
|
||||
BlobFileMetaData(const BlobFileMetaData&) = delete;
|
||||
@@ -128,6 +133,8 @@ class BlobFileMetaData {
|
||||
return shared_meta_->GetChecksumValue();
|
||||
}
|
||||
|
||||
const LinkedSsts& GetLinkedSsts() const { return linked_ssts_; }
|
||||
|
||||
uint64_t GetGarbageBlobCount() const { return garbage_blob_count_; }
|
||||
uint64_t GetGarbageBlobBytes() const { return garbage_blob_bytes_; }
|
||||
|
||||
@@ -135,8 +142,10 @@ class BlobFileMetaData {
|
||||
|
||||
private:
|
||||
BlobFileMetaData(std::shared_ptr<SharedBlobFileMetaData> shared_meta,
|
||||
uint64_t garbage_blob_count, uint64_t garbage_blob_bytes)
|
||||
LinkedSsts linked_ssts, uint64_t garbage_blob_count,
|
||||
uint64_t garbage_blob_bytes)
|
||||
: shared_meta_(std::move(shared_meta)),
|
||||
linked_ssts_(std::move(linked_ssts)),
|
||||
garbage_blob_count_(garbage_blob_count),
|
||||
garbage_blob_bytes_(garbage_blob_bytes) {
|
||||
assert(shared_meta_);
|
||||
@@ -145,6 +154,7 @@ class BlobFileMetaData {
|
||||
}
|
||||
|
||||
std::shared_ptr<SharedBlobFileMetaData> shared_meta_;
|
||||
LinkedSsts linked_ssts_;
|
||||
uint64_t garbage_blob_count_;
|
||||
uint64_t garbage_blob_bytes_;
|
||||
};
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
@@ -23,7 +22,6 @@ namespace blob_db {
|
||||
|
||||
constexpr uint32_t kMagicNumber = 2395959; // 0x00248f37
|
||||
constexpr uint32_t kVersion1 = 1;
|
||||
constexpr uint64_t kNoExpiration = std::numeric_limits<uint64_t>::max();
|
||||
|
||||
using ExpirationRange = std::pair<uint64_t, uint64_t>;
|
||||
|
||||
|
||||
+6
-4
@@ -51,7 +51,8 @@ TableBuilder* NewTableBuilder(
|
||||
uint64_t sample_for_compression, const CompressionOptions& compression_opts,
|
||||
int level, const bool skip_filters, const uint64_t creation_time,
|
||||
const uint64_t oldest_key_time, const uint64_t target_file_size,
|
||||
const uint64_t file_creation_time) {
|
||||
const uint64_t file_creation_time, const std::string& db_id,
|
||||
const std::string& db_session_id) {
|
||||
assert((column_family_id ==
|
||||
TablePropertiesCollectorFactory::Context::kUnknownColumnFamily) ==
|
||||
column_family_name.empty());
|
||||
@@ -61,7 +62,7 @@ TableBuilder* NewTableBuilder(
|
||||
sample_for_compression, compression_opts,
|
||||
skip_filters, column_family_name, level,
|
||||
creation_time, oldest_key_time, target_file_size,
|
||||
file_creation_time),
|
||||
file_creation_time, db_id, db_session_id),
|
||||
column_family_id, file);
|
||||
}
|
||||
|
||||
@@ -85,7 +86,8 @@ Status BuildTable(
|
||||
EventLogger* event_logger, int job_id, const Env::IOPriority io_priority,
|
||||
TableProperties* table_properties, int level, const uint64_t creation_time,
|
||||
const uint64_t oldest_key_time, Env::WriteLifeTimeHint write_hint,
|
||||
const uint64_t file_creation_time) {
|
||||
const uint64_t file_creation_time, const std::string& db_id,
|
||||
const std::string& db_session_id) {
|
||||
assert((column_family_id ==
|
||||
TablePropertiesCollectorFactory::Context::kUnknownColumnFamily) ==
|
||||
column_family_name.empty());
|
||||
@@ -142,7 +144,7 @@ Status BuildTable(
|
||||
column_family_name, file_writer.get(), compression,
|
||||
sample_for_compression, compression_opts_for_flush, level,
|
||||
false /* skip_filters */, creation_time, oldest_key_time,
|
||||
0 /*target_file_size*/, file_creation_time);
|
||||
0 /*target_file_size*/, file_creation_time, db_id, db_session_id);
|
||||
}
|
||||
|
||||
MergeHelper merge(env, internal_comparator.user_comparator(),
|
||||
|
||||
+4
-2
@@ -51,7 +51,8 @@ TableBuilder* NewTableBuilder(
|
||||
const CompressionOptions& compression_opts, int level,
|
||||
const bool skip_filters = false, const uint64_t creation_time = 0,
|
||||
const uint64_t oldest_key_time = 0, const uint64_t target_file_size = 0,
|
||||
const uint64_t file_creation_time = 0);
|
||||
const uint64_t file_creation_time = 0, const std::string& db_id = "",
|
||||
const std::string& db_session_id = "");
|
||||
|
||||
// Build a Table file from the contents of *iter. The generated file
|
||||
// will be named according to number specified in meta. On success, the rest of
|
||||
@@ -83,6 +84,7 @@ extern Status BuildTable(
|
||||
TableProperties* table_properties = nullptr, int level = -1,
|
||||
const uint64_t creation_time = 0, const uint64_t oldest_key_time = 0,
|
||||
Env::WriteLifeTimeHint write_hint = Env::WLTH_NOT_SET,
|
||||
const uint64_t file_creation_time = 0);
|
||||
const uint64_t file_creation_time = 0, const std::string& db_id = "",
|
||||
const std::string& db_session_id = "");
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
@@ -305,12 +305,15 @@ CompactionJob::CompactionJob(
|
||||
const SnapshotChecker* snapshot_checker, std::shared_ptr<Cache> table_cache,
|
||||
EventLogger* event_logger, bool paranoid_file_checks, bool measure_io_stats,
|
||||
const std::string& dbname, CompactionJobStats* compaction_job_stats,
|
||||
Env::Priority thread_pri, const std::atomic<bool>* manual_compaction_paused)
|
||||
Env::Priority thread_pri, const std::atomic<bool>* manual_compaction_paused,
|
||||
const std::string& db_id, const std::string& db_session_id)
|
||||
: job_id_(job_id),
|
||||
compact_(new CompactionState(compaction)),
|
||||
compaction_job_stats_(compaction_job_stats),
|
||||
compaction_stats_(compaction->compaction_reason(), 1),
|
||||
dbname_(dbname),
|
||||
db_id_(db_id),
|
||||
db_session_id_(db_session_id),
|
||||
db_options_(db_options),
|
||||
file_options_(file_options),
|
||||
env_(db_options.env),
|
||||
@@ -1554,7 +1557,8 @@ Status CompactionJob::OpenCompactionOutputFile(
|
||||
sub_compact->compaction->output_compression_opts(),
|
||||
sub_compact->compaction->output_level(), skip_filters,
|
||||
oldest_ancester_time, 0 /* oldest_key_time */,
|
||||
sub_compact->compaction->max_output_file_size(), current_time));
|
||||
sub_compact->compaction->max_output_file_size(), current_time, db_id_,
|
||||
db_session_id_));
|
||||
LogFlush(db_options_.info_log);
|
||||
return s;
|
||||
}
|
||||
|
||||
@@ -78,7 +78,9 @@ class CompactionJob {
|
||||
const std::string& dbname,
|
||||
CompactionJobStats* compaction_job_stats,
|
||||
Env::Priority thread_pri,
|
||||
const std::atomic<bool>* manual_compaction_paused = nullptr);
|
||||
const std::atomic<bool>* manual_compaction_paused = nullptr,
|
||||
const std::string& db_id = "",
|
||||
const std::string& db_session_id = "");
|
||||
|
||||
~CompactionJob();
|
||||
|
||||
@@ -152,6 +154,8 @@ class CompactionJob {
|
||||
|
||||
// DBImpl state
|
||||
const std::string& dbname_;
|
||||
const std::string db_id_;
|
||||
const std::string db_session_id_;
|
||||
const ImmutableDBOptions& db_options_;
|
||||
const FileOptions file_options_;
|
||||
|
||||
|
||||
@@ -41,6 +41,56 @@ TEST_F(DBBasicTest, OpenWhenOpen) {
|
||||
delete db2;
|
||||
}
|
||||
|
||||
TEST_F(DBBasicTest, UniqueSession) {
|
||||
Options options = CurrentOptions();
|
||||
std::string sid1, sid2, sid3, sid4;
|
||||
|
||||
db_->GetDbSessionId(sid1);
|
||||
Reopen(options);
|
||||
db_->GetDbSessionId(sid2);
|
||||
ASSERT_OK(Put("foo", "v1"));
|
||||
db_->GetDbSessionId(sid4);
|
||||
Reopen(options);
|
||||
db_->GetDbSessionId(sid3);
|
||||
|
||||
ASSERT_NE(sid1, sid2);
|
||||
ASSERT_NE(sid1, sid3);
|
||||
ASSERT_NE(sid2, sid3);
|
||||
|
||||
ASSERT_EQ(sid2, sid4);
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
Close();
|
||||
ASSERT_OK(ReadOnlyReopen(options));
|
||||
db_->GetDbSessionId(sid1);
|
||||
// Test uniqueness between readonly open (sid1) and regular open (sid3)
|
||||
ASSERT_NE(sid1, sid3);
|
||||
Close();
|
||||
ASSERT_OK(ReadOnlyReopen(options));
|
||||
db_->GetDbSessionId(sid2);
|
||||
ASSERT_EQ("v1", Get("foo"));
|
||||
db_->GetDbSessionId(sid3);
|
||||
|
||||
ASSERT_NE(sid1, sid2);
|
||||
|
||||
ASSERT_EQ(sid2, sid3);
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
CreateAndReopenWithCF({"goku"}, options);
|
||||
db_->GetDbSessionId(sid1);
|
||||
ASSERT_OK(Put("bar", "e1"));
|
||||
db_->GetDbSessionId(sid2);
|
||||
ASSERT_EQ("e1", Get("bar"));
|
||||
db_->GetDbSessionId(sid3);
|
||||
ReopenWithColumnFamilies({"default", "goku"}, options);
|
||||
db_->GetDbSessionId(sid4);
|
||||
|
||||
ASSERT_EQ(sid1, sid2);
|
||||
ASSERT_EQ(sid2, sid3);
|
||||
|
||||
ASSERT_NE(sid1, sid4);
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
TEST_F(DBBasicTest, ReadOnlyDB) {
|
||||
ASSERT_OK(Put("foo", "v1"));
|
||||
@@ -2199,6 +2249,35 @@ TEST_F(DBBasicTest, RecoverWithNoCurrentFile) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBBasicTest, RecoverWithNoManifest) {
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
DestroyAndReopen(options);
|
||||
ASSERT_OK(Put("foo", "value"));
|
||||
ASSERT_OK(Flush());
|
||||
Close();
|
||||
{
|
||||
// Delete all MANIFEST.
|
||||
std::vector<std::string> files;
|
||||
ASSERT_OK(env_->GetChildren(dbname_, &files));
|
||||
for (const auto& file : files) {
|
||||
uint64_t number = 0;
|
||||
FileType type = kLogFile;
|
||||
if (ParseFileName(file, &number, &type) && type == kDescriptorFile) {
|
||||
ASSERT_OK(env_->DeleteFile(dbname_ + "/" + file));
|
||||
}
|
||||
}
|
||||
}
|
||||
options.best_efforts_recovery = true;
|
||||
options.create_if_missing = false;
|
||||
Status s = TryReopen(options);
|
||||
ASSERT_TRUE(s.IsInvalidArgument());
|
||||
options.create_if_missing = true;
|
||||
Reopen(options);
|
||||
// Since no MANIFEST exists, best-efforts recovery creates a new, empty db.
|
||||
ASSERT_EQ("NOT_FOUND", Get("foo"));
|
||||
}
|
||||
|
||||
TEST_F(DBBasicTest, SkipWALIfMissingTableFiles) {
|
||||
Options options = CurrentOptions();
|
||||
DestroyAndReopen(options);
|
||||
@@ -2239,6 +2318,33 @@ TEST_F(DBBasicTest, SkipWALIfMissingTableFiles) {
|
||||
}
|
||||
#endif // !ROCKSDB_LITE
|
||||
|
||||
TEST_F(DBBasicTest, ManifestChecksumMismatch) {
|
||||
Options options = CurrentOptions();
|
||||
DestroyAndReopen(options);
|
||||
ASSERT_OK(Put("bar", "value"));
|
||||
ASSERT_OK(Flush());
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"LogWriter::EmitPhysicalRecord:BeforeEncodeChecksum", [&](void* arg) {
|
||||
auto* crc = reinterpret_cast<uint32_t*>(arg);
|
||||
*crc = *crc + 1;
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
WriteOptions write_opts;
|
||||
write_opts.disableWAL = true;
|
||||
Status s = db_->Put(write_opts, "foo", "value");
|
||||
ASSERT_OK(s);
|
||||
ASSERT_OK(Flush());
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
ASSERT_OK(Put("foo", "value1"));
|
||||
ASSERT_OK(Flush());
|
||||
s = TryReopen(options);
|
||||
ASSERT_TRUE(s.IsCorruption());
|
||||
}
|
||||
|
||||
class DBBasicTestMultiGet : public DBTestBase {
|
||||
public:
|
||||
DBBasicTestMultiGet(std::string test_dir, int num_cfs, bool compressed_cache,
|
||||
|
||||
@@ -5183,7 +5183,6 @@ TEST_P(DBCompactionTestWithParam,
|
||||
// Put one key, to make biggest log sequence number in this memtable is bigger
|
||||
// than sst which would be ingested in next step.
|
||||
ASSERT_OK(Put(Key(2), "b"));
|
||||
ASSERT_EQ(10, NumTableFilesAtLevel(0));
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
|
||||
std::vector<std::vector<FileMetaData>> level_to_files;
|
||||
|
||||
+46
-8
@@ -249,7 +249,8 @@ DBImpl::DBImpl(const DBOptions& options, const std::string& dbname,
|
||||
new ColumnFamilyMemTablesImpl(versions_->GetColumnFamilySet()));
|
||||
|
||||
DumpRocksDBBuildVersion(immutable_db_options_.info_log.get());
|
||||
DumpDBFileSummary(immutable_db_options_, dbname_);
|
||||
SetDbSessionId();
|
||||
DumpDBFileSummary(immutable_db_options_, dbname_, db_session_id_);
|
||||
immutable_db_options_.Dump(immutable_db_options_.info_log.get());
|
||||
mutable_db_options_.Dump(immutable_db_options_.info_log.get());
|
||||
DumpSupportInfo(immutable_db_options_.info_log.get());
|
||||
@@ -2670,7 +2671,8 @@ Iterator* DBImpl::NewIterator(const ReadOptions& read_options,
|
||||
" guaranteed to be preserved, try larger iter_start_seqnum opt."));
|
||||
}
|
||||
auto cfh = reinterpret_cast<ColumnFamilyHandleImpl*>(column_family);
|
||||
auto cfd = cfh->cfd();
|
||||
ColumnFamilyData* cfd = cfh->cfd();
|
||||
assert(cfd != nullptr);
|
||||
ReadCallback* read_callback = nullptr; // No read callback provided.
|
||||
if (read_options.tailing) {
|
||||
#ifdef ROCKSDB_LITE
|
||||
@@ -2691,10 +2693,11 @@ Iterator* DBImpl::NewIterator(const ReadOptions& read_options,
|
||||
// Note: no need to consider the special case of
|
||||
// last_seq_same_as_publish_seq_==false since NewIterator is overridden in
|
||||
// WritePreparedTxnDB
|
||||
auto snapshot = read_options.snapshot != nullptr
|
||||
? read_options.snapshot->GetSequenceNumber()
|
||||
: versions_->LastSequence();
|
||||
result = NewIteratorImpl(read_options, cfd, snapshot, read_callback);
|
||||
result = NewIteratorImpl(read_options, cfd,
|
||||
(read_options.snapshot != nullptr)
|
||||
? read_options.snapshot->GetSequenceNumber()
|
||||
: kMaxSequenceNumber,
|
||||
read_callback);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -2707,6 +2710,24 @@ ArenaWrappedDBIter* DBImpl::NewIteratorImpl(const ReadOptions& read_options,
|
||||
bool allow_refresh) {
|
||||
SuperVersion* sv = cfd->GetReferencedSuperVersion(this);
|
||||
|
||||
TEST_SYNC_POINT("DBImpl::NewIterator:1");
|
||||
TEST_SYNC_POINT("DBImpl::NewIterator:2");
|
||||
|
||||
if (snapshot == kMaxSequenceNumber) {
|
||||
// Note that the snapshot is assigned AFTER referencing the super
|
||||
// version because otherwise a flush happening in between may compact away
|
||||
// data for the snapshot, so the reader would see neither data that was be
|
||||
// visible to the snapshot before compaction nor the newer data inserted
|
||||
// afterwards.
|
||||
// Note that the super version might not contain all the data available
|
||||
// to this snapshot, but in that case it can see all the data in the
|
||||
// super version, which is a valid consistent state after the user
|
||||
// calls NewIterator().
|
||||
snapshot = versions_->LastSequence();
|
||||
TEST_SYNC_POINT("DBImpl::NewIterator:3");
|
||||
TEST_SYNC_POINT("DBImpl::NewIterator:4");
|
||||
}
|
||||
|
||||
// Try to generate a DB iterator tree in continuous memory area to be
|
||||
// cache friendly. Here is an example of result:
|
||||
// +-------------------------------+
|
||||
@@ -3601,6 +3622,21 @@ Status DBImpl::GetDbIdentityFromIdentityFile(std::string* identity) const {
|
||||
return s;
|
||||
}
|
||||
|
||||
Status DBImpl::GetDbSessionId(std::string& session_id) const {
|
||||
session_id.assign(db_session_id_);
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
void DBImpl::SetDbSessionId() {
|
||||
// GenerateUniqueId() generates an identifier
|
||||
// that has a negligible probability of being duplicated
|
||||
db_session_id_ = env_->GenerateUniqueId();
|
||||
// Remove the extra '\n' at the end if there is one
|
||||
if (!db_session_id_.empty() && db_session_id_.back() == '\n') {
|
||||
db_session_id_.pop_back();
|
||||
}
|
||||
}
|
||||
|
||||
// Default implementation -- returns not supported status
|
||||
Status DB::CreateColumnFamily(const ColumnFamilyOptions& /*cf_options*/,
|
||||
const std::string& /*column_family_name*/,
|
||||
@@ -4180,7 +4216,8 @@ Status DBImpl::IngestExternalFiles(
|
||||
static_cast<ColumnFamilyHandleImpl*>(args[i].column_family)->cfd();
|
||||
SuperVersion* super_version = cfd->GetReferencedSuperVersion(this);
|
||||
exec_results[i].second = ingestion_jobs[i].Prepare(
|
||||
args[i].external_files, start_file_number, super_version);
|
||||
args[i].external_files, args[i].files_checksums,
|
||||
args[i].files_checksum_func_names, start_file_number, super_version);
|
||||
exec_results[i].first = true;
|
||||
CleanupSuperVersion(super_version);
|
||||
}
|
||||
@@ -4191,7 +4228,8 @@ Status DBImpl::IngestExternalFiles(
|
||||
static_cast<ColumnFamilyHandleImpl*>(args[0].column_family)->cfd();
|
||||
SuperVersion* super_version = cfd->GetReferencedSuperVersion(this);
|
||||
exec_results[0].second = ingestion_jobs[0].Prepare(
|
||||
args[0].external_files, next_file_number, super_version);
|
||||
args[0].external_files, args[0].files_checksums,
|
||||
args[0].files_checksum_func_names, next_file_number, super_version);
|
||||
exec_results[0].first = true;
|
||||
CleanupSuperVersion(super_version);
|
||||
}
|
||||
|
||||
@@ -350,6 +350,8 @@ class DBImpl : public DB {
|
||||
|
||||
virtual Status GetDbIdentityFromIdentityFile(std::string* identity) const;
|
||||
|
||||
virtual Status GetDbSessionId(std::string& session_id) const override;
|
||||
|
||||
ColumnFamilyHandle* DefaultColumnFamily() const override;
|
||||
|
||||
ColumnFamilyHandle* PersistentStatsColumnFamily() const;
|
||||
@@ -477,6 +479,7 @@ class DBImpl : public DB {
|
||||
Status GetImpl(const ReadOptions& options, const Slice& key,
|
||||
GetImplOptions& get_impl_options);
|
||||
|
||||
// If `snapshot` == kMaxSequenceNumber, set a recent one inside the file.
|
||||
ArenaWrappedDBIter* NewIteratorImpl(const ReadOptions& options,
|
||||
ColumnFamilyData* cfd,
|
||||
SequenceNumber snapshot,
|
||||
@@ -980,6 +983,9 @@ class DBImpl : public DB {
|
||||
protected:
|
||||
const std::string dbname_;
|
||||
std::string db_id_;
|
||||
// db_session_id_ is an identifier that gets reset
|
||||
// every time the DB is opened
|
||||
std::string db_session_id_;
|
||||
std::unique_ptr<VersionSet> versions_;
|
||||
// Flag to check whether we allocated and own the info log file
|
||||
bool own_info_log_;
|
||||
@@ -1161,6 +1167,10 @@ class DBImpl : public DB {
|
||||
// bump up the version set's next_file_number_ to be 1 + largest_file_number.
|
||||
Status FinishBestEffortsRecovery();
|
||||
|
||||
// SetDbSessionId() should be called in the constuctor DBImpl()
|
||||
// to ensure that db_session_id_ gets updated every time the DB is opened
|
||||
void SetDbSessionId();
|
||||
|
||||
private:
|
||||
friend class DB;
|
||||
friend class ErrorHandler;
|
||||
|
||||
@@ -154,7 +154,8 @@ Status DBImpl::FlushMemTableToOutputFile(
|
||||
GetDataDir(cfd, 0U),
|
||||
GetCompressionFlush(*cfd->ioptions(), mutable_cf_options), stats_,
|
||||
&event_logger_, mutable_cf_options.report_bg_io_stats,
|
||||
true /* sync_output_directory */, true /* write_manifest */, thread_pri);
|
||||
true /* sync_output_directory */, true /* write_manifest */, thread_pri,
|
||||
db_id_, db_session_id_);
|
||||
FileMetaData file_meta;
|
||||
|
||||
TEST_SYNC_POINT("DBImpl::FlushMemTableToOutputFile:BeforePickMemtables");
|
||||
@@ -345,7 +346,7 @@ Status DBImpl::AtomicFlushMemTablesToOutputFiles(
|
||||
data_dir, GetCompressionFlush(*cfd->ioptions(), mutable_cf_options),
|
||||
stats_, &event_logger_, mutable_cf_options.report_bg_io_stats,
|
||||
false /* sync_output_directory */, false /* write_manifest */,
|
||||
thread_pri));
|
||||
thread_pri, db_id_, db_session_id_));
|
||||
jobs.back()->PickMemTable();
|
||||
}
|
||||
|
||||
@@ -1031,7 +1032,8 @@ Status DBImpl::CompactFilesImpl(
|
||||
snapshot_checker, table_cache_, &event_logger_,
|
||||
c->mutable_cf_options()->paranoid_file_checks,
|
||||
c->mutable_cf_options()->report_bg_io_stats, dbname_,
|
||||
&compaction_job_stats, Env::Priority::USER, &manual_compaction_paused_);
|
||||
&compaction_job_stats, Env::Priority::USER, &manual_compaction_paused_,
|
||||
db_id_, db_session_id_);
|
||||
|
||||
// Creating a compaction influences the compaction score because the score
|
||||
// takes running compactions into account (by skipping files that are already
|
||||
@@ -2822,7 +2824,8 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
|
||||
&event_logger_, c->mutable_cf_options()->paranoid_file_checks,
|
||||
c->mutable_cf_options()->report_bg_io_stats, dbname_,
|
||||
&compaction_job_stats, thread_pri,
|
||||
is_manual ? &manual_compaction_paused_ : nullptr);
|
||||
is_manual ? &manual_compaction_paused_ : nullptr, db_id_,
|
||||
db_session_id_);
|
||||
compaction_job.Prepare();
|
||||
|
||||
NotifyOnCompactionBegin(c->column_family_data(), c.get(), status,
|
||||
|
||||
+46
-10
@@ -370,7 +370,30 @@ Status DBImpl::Recover(
|
||||
}
|
||||
|
||||
std::string current_fname = CurrentFileName(dbname_);
|
||||
s = env_->FileExists(current_fname);
|
||||
// Path to any MANIFEST file in the db dir. It does not matter which one.
|
||||
// Since best-efforts recovery ignores CURRENT file, existence of a
|
||||
// MANIFEST indicates the recovery to recover existing db. If no MANIFEST
|
||||
// can be found, a new db will be created.
|
||||
std::string manifest_path;
|
||||
if (!immutable_db_options_.best_efforts_recovery) {
|
||||
s = env_->FileExists(current_fname);
|
||||
} else {
|
||||
s = Status::NotFound();
|
||||
std::vector<std::string> files;
|
||||
// No need to check return value
|
||||
env_->GetChildren(dbname_, &files);
|
||||
for (const std::string& file : files) {
|
||||
uint64_t number = 0;
|
||||
FileType type = kLogFile; // initialize
|
||||
if (ParseFileName(file, &number, &type) && type == kDescriptorFile) {
|
||||
// Found MANIFEST (descriptor log), thus best-efforts recovery does
|
||||
// not have to treat the db as empty.
|
||||
s = Status::OK();
|
||||
manifest_path = dbname_ + "/" + file;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (s.IsNotFound()) {
|
||||
if (immutable_db_options_.create_if_missing) {
|
||||
s = NewDB();
|
||||
@@ -398,14 +421,14 @@ Status DBImpl::Recover(
|
||||
FileOptions customized_fs(file_options_);
|
||||
customized_fs.use_direct_reads |=
|
||||
immutable_db_options_.use_direct_io_for_flush_and_compaction;
|
||||
s = fs_->NewRandomAccessFile(current_fname, customized_fs, &idfile,
|
||||
nullptr);
|
||||
const std::string& fname =
|
||||
manifest_path.empty() ? current_fname : manifest_path;
|
||||
s = fs_->NewRandomAccessFile(fname, customized_fs, &idfile, nullptr);
|
||||
if (!s.ok()) {
|
||||
std::string error_str = s.ToString();
|
||||
// Check if unsupported Direct I/O is the root cause
|
||||
customized_fs.use_direct_reads = false;
|
||||
s = fs_->NewRandomAccessFile(current_fname, customized_fs, &idfile,
|
||||
nullptr);
|
||||
s = fs_->NewRandomAccessFile(fname, customized_fs, &idfile, nullptr);
|
||||
if (s.ok()) {
|
||||
return Status::InvalidArgument(
|
||||
"Direct I/O is not supported by the specified DB.");
|
||||
@@ -704,10 +727,10 @@ Status DBImpl::RecoverLogFiles(const std::vector<uint64_t>& log_numbers,
|
||||
Status* status; // nullptr if immutable_db_options_.paranoid_checks==false
|
||||
void Corruption(size_t bytes, const Status& s) override {
|
||||
ROCKS_LOG_WARN(info_log, "%s%s: dropping %d bytes; %s",
|
||||
(this->status == nullptr ? "(ignoring error) " : ""),
|
||||
fname, static_cast<int>(bytes), s.ToString().c_str());
|
||||
if (this->status != nullptr && this->status->ok()) {
|
||||
*this->status = s;
|
||||
(status == nullptr ? "(ignoring error) " : ""), fname,
|
||||
static_cast<int>(bytes), s.ToString().c_str());
|
||||
if (status != nullptr && status->ok()) {
|
||||
*status = s;
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -830,6 +853,8 @@ Status DBImpl::RecoverLogFiles(const std::vector<uint64_t>& log_numbers,
|
||||
Slice record;
|
||||
WriteBatch batch;
|
||||
|
||||
TEST_SYNC_POINT_CALLBACK("DBImpl::RecoverLogFiles:BeforeReadWal",
|
||||
/*arg=*/nullptr);
|
||||
while (!stop_replay_by_wal_filter &&
|
||||
reader.ReadRecord(&record, &scratch,
|
||||
immutable_db_options_.wal_recovery_mode) &&
|
||||
@@ -994,6 +1019,16 @@ Status DBImpl::RecoverLogFiles(const std::vector<uint64_t>& log_numbers,
|
||||
status = Status::OK();
|
||||
} else if (immutable_db_options_.wal_recovery_mode ==
|
||||
WALRecoveryMode::kPointInTimeRecovery) {
|
||||
if (status.IsIOError()) {
|
||||
ROCKS_LOG_ERROR(immutable_db_options_.info_log,
|
||||
"IOError during point-in-time reading log #%" PRIu64
|
||||
" seq #%" PRIu64
|
||||
". %s. This likely mean loss of synced WAL, "
|
||||
"thus recovery fails.",
|
||||
log_number, *next_sequence,
|
||||
status.ToString().c_str());
|
||||
return status;
|
||||
}
|
||||
// We should ignore the error but not continue replaying
|
||||
status = Status::OK();
|
||||
stop_replay_for_corruption = true;
|
||||
@@ -1254,7 +1289,8 @@ Status DBImpl::WriteLevel0TableForRecovery(int job_id, ColumnFamilyData* cfd,
|
||||
mutable_cf_options.compression_opts, paranoid_file_checks,
|
||||
cfd->internal_stats(), TableFileCreationReason::kRecovery, &io_s,
|
||||
&event_logger_, job_id, Env::IO_HIGH, nullptr /* table_properties */,
|
||||
-1 /* level */, current_time, write_hint);
|
||||
-1 /* level */, current_time, 0 /* oldest_key_time */, write_hint,
|
||||
0 /* file_creation_time */, db_id_, db_session_id_);
|
||||
LogFlush(immutable_db_options_.info_log);
|
||||
ROCKS_LOG_DEBUG(immutable_db_options_.info_log,
|
||||
"[%s] [WriteLevel0TableForRecovery]"
|
||||
|
||||
@@ -17,7 +17,8 @@
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
void DumpDBFileSummary(const ImmutableDBOptions& options,
|
||||
const std::string& dbname) {
|
||||
const std::string& dbname,
|
||||
const std::string& session_id) {
|
||||
if (options.info_log == nullptr) {
|
||||
return;
|
||||
}
|
||||
@@ -32,6 +33,8 @@ void DumpDBFileSummary(const ImmutableDBOptions& options,
|
||||
std::string file_info, wal_info;
|
||||
|
||||
Header(options.info_log, "DB SUMMARY\n");
|
||||
Header(options.info_log, "DB Session ID: %s\n", session_id.c_str());
|
||||
|
||||
// Get files in dbname dir
|
||||
if (!env->GetChildren(dbname, &files).ok()) {
|
||||
Error(options.info_log,
|
||||
|
||||
+2
-1
@@ -10,5 +10,6 @@
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
void DumpDBFileSummary(const ImmutableDBOptions& options,
|
||||
const std::string& dbname);
|
||||
const std::string& dbname,
|
||||
const std::string& session_id = "");
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
@@ -97,7 +97,8 @@ struct StressTestIterator : public InternalIterator {
|
||||
|
||||
bool MaybeFail() {
|
||||
if (rnd->Next() >=
|
||||
std::numeric_limits<uint64_t>::max() * error_probability) {
|
||||
static_cast<double>(std::numeric_limits<uint64_t>::max()) *
|
||||
error_probability) {
|
||||
return false;
|
||||
}
|
||||
if (rnd->Next() % 2) {
|
||||
@@ -114,7 +115,8 @@ struct StressTestIterator : public InternalIterator {
|
||||
|
||||
void MaybeMutate() {
|
||||
if (rnd->Next() >=
|
||||
std::numeric_limits<uint64_t>::max() * mutation_probability) {
|
||||
static_cast<double>(std::numeric_limits<uint64_t>::max()) *
|
||||
mutation_probability) {
|
||||
return;
|
||||
}
|
||||
do {
|
||||
@@ -126,8 +128,9 @@ struct StressTestIterator : public InternalIterator {
|
||||
if (data->hidden.empty()) {
|
||||
hide_probability = 1;
|
||||
}
|
||||
bool do_hide =
|
||||
rnd->Next() < std::numeric_limits<uint64_t>::max() * hide_probability;
|
||||
bool do_hide = rnd->Next() <
|
||||
static_cast<double>(std::numeric_limits<uint64_t>::max()) *
|
||||
hide_probability;
|
||||
if (do_hide) {
|
||||
// Hide a random entry.
|
||||
size_t idx = rnd->Next() % data->entries.size();
|
||||
|
||||
@@ -303,14 +303,14 @@ TEST_F(DBMemTableTest, InsertWithHint) {
|
||||
ASSERT_EQ(hint_bar, rep->last_hint_in());
|
||||
ASSERT_EQ(hint_bar, rep->last_hint_out());
|
||||
ASSERT_EQ(5, rep->num_insert_with_hint());
|
||||
ASSERT_OK(Put("whitelisted", "vvv"));
|
||||
ASSERT_OK(Put("NotInPrefixDomain", "vvv"));
|
||||
ASSERT_EQ(5, rep->num_insert_with_hint());
|
||||
ASSERT_EQ("foo_v1", Get("foo_k1"));
|
||||
ASSERT_EQ("foo_v2", Get("foo_k2"));
|
||||
ASSERT_EQ("foo_v3", Get("foo_k3"));
|
||||
ASSERT_EQ("bar_v1", Get("bar_k1"));
|
||||
ASSERT_EQ("bar_v2", Get("bar_k2"));
|
||||
ASSERT_EQ("vvv", Get("whitelisted"));
|
||||
ASSERT_EQ("vvv", Get("NotInPrefixDomain"));
|
||||
}
|
||||
|
||||
TEST_F(DBMemTableTest, ColumnFamilyId) {
|
||||
|
||||
@@ -39,7 +39,7 @@ class DBOptionsTest : public DBTestBase {
|
||||
std::unordered_map<std::string, std::string> options_map;
|
||||
StringToMap(options_str, &options_map);
|
||||
std::unordered_map<std::string, std::string> mutable_map;
|
||||
for (const auto opt : db_options_type_info) {
|
||||
for (const auto& opt : db_options_type_info) {
|
||||
if (opt.second.IsMutable() && opt.second.ShouldSerialize()) {
|
||||
mutable_map[opt.first] = options_map[opt.first];
|
||||
}
|
||||
@@ -57,7 +57,7 @@ class DBOptionsTest : public DBTestBase {
|
||||
std::unordered_map<std::string, std::string> options_map;
|
||||
StringToMap(options_str, &options_map);
|
||||
std::unordered_map<std::string, std::string> mutable_map;
|
||||
for (const auto opt : cf_options_type_info) {
|
||||
for (const auto& opt : cf_options_type_info) {
|
||||
if (opt.second.IsMutable() && opt.second.ShouldSerialize()) {
|
||||
mutable_map[opt.first] = options_map[opt.first];
|
||||
}
|
||||
|
||||
@@ -252,6 +252,26 @@ TEST_F(DBTablePropertiesTest, GetColumnFamilyNameProperty) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBTablePropertiesTest, GetDbIdentifiersProperty) {
|
||||
CreateAndReopenWithCF({"goku"}, CurrentOptions());
|
||||
|
||||
for (uint32_t cf = 0; cf < 2; ++cf) {
|
||||
Put(cf, "key", "val");
|
||||
Put(cf, "foo", "bar");
|
||||
Flush(cf);
|
||||
|
||||
TablePropertiesCollection fname_to_props;
|
||||
ASSERT_OK(db_->GetPropertiesOfAllTables(handles_[cf], &fname_to_props));
|
||||
ASSERT_EQ(1U, fname_to_props.size());
|
||||
|
||||
std::string id, sid;
|
||||
db_->GetDbIdentity(id);
|
||||
db_->GetDbSessionId(sid);
|
||||
ASSERT_EQ(id, fname_to_props.begin()->second->db_id);
|
||||
ASSERT_EQ(sid, fname_to_props.begin()->second->db_session_id);
|
||||
}
|
||||
}
|
||||
|
||||
class DeletionTriggeredCompactionTestListener : public EventListener {
|
||||
public:
|
||||
void OnCompactionBegin(DB* , const CompactionJobInfo& ci) override {
|
||||
|
||||
@@ -3031,6 +3031,10 @@ class ModelDB : public DB {
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status GetDbSessionId(std::string& /*session_id*/) const override {
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
SequenceNumber GetLatestSequenceNumber() const override { return 0; }
|
||||
|
||||
bool SetPreserveDeletesSequenceNumber(SequenceNumber /*seqnum*/) override {
|
||||
|
||||
+288
-107
@@ -86,6 +86,64 @@ TEST_F(DBTest2, OpenForReadOnlyWithColumnFamilies) {
|
||||
// With create_if_missing false, there should not be a dir in the file system
|
||||
ASSERT_NOK(env_->FileExists(dbname));
|
||||
}
|
||||
|
||||
class TestReadOnlyWithCompressedCache
|
||||
: public DBTestBase,
|
||||
public testing::WithParamInterface<std::tuple<int, bool>> {
|
||||
public:
|
||||
TestReadOnlyWithCompressedCache()
|
||||
: DBTestBase("/test_readonly_with_compressed_cache") {
|
||||
max_open_files_ = std::get<0>(GetParam());
|
||||
use_mmap_ = std::get<1>(GetParam());
|
||||
}
|
||||
int max_open_files_;
|
||||
bool use_mmap_;
|
||||
};
|
||||
|
||||
TEST_P(TestReadOnlyWithCompressedCache, ReadOnlyWithCompressedCache) {
|
||||
if (use_mmap_ && !IsMemoryMappedAccessSupported()) {
|
||||
return;
|
||||
}
|
||||
ASSERT_OK(Put("foo", "bar"));
|
||||
ASSERT_OK(Put("foo2", "barbarbarbarbarbarbarbar"));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
DB* db_ptr = nullptr;
|
||||
Options options = CurrentOptions();
|
||||
options.allow_mmap_reads = use_mmap_;
|
||||
options.max_open_files = max_open_files_;
|
||||
options.compression = kSnappyCompression;
|
||||
BlockBasedTableOptions table_options;
|
||||
table_options.block_cache_compressed = NewLRUCache(8 * 1024 * 1024);
|
||||
table_options.no_block_cache = true;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
options.statistics = CreateDBStatistics();
|
||||
|
||||
ASSERT_OK(DB::OpenForReadOnly(options, dbname_, &db_ptr));
|
||||
|
||||
std::string v;
|
||||
ASSERT_OK(db_ptr->Get(ReadOptions(), "foo", &v));
|
||||
ASSERT_EQ("bar", v);
|
||||
ASSERT_EQ(0, options.statistics->getTickerCount(BLOCK_CACHE_COMPRESSED_HIT));
|
||||
ASSERT_OK(db_ptr->Get(ReadOptions(), "foo", &v));
|
||||
ASSERT_EQ("bar", v);
|
||||
if (Snappy_Supported()) {
|
||||
if (use_mmap_) {
|
||||
ASSERT_EQ(0,
|
||||
options.statistics->getTickerCount(BLOCK_CACHE_COMPRESSED_HIT));
|
||||
} else {
|
||||
ASSERT_EQ(1,
|
||||
options.statistics->getTickerCount(BLOCK_CACHE_COMPRESSED_HIT));
|
||||
}
|
||||
}
|
||||
|
||||
delete db_ptr;
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(TestReadOnlyWithCompressedCache,
|
||||
TestReadOnlyWithCompressedCache,
|
||||
::testing::Combine(::testing::Values(-1, 100),
|
||||
::testing::Bool()));
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
class PrefixFullBloomWithReverseComparator
|
||||
@@ -1331,7 +1389,42 @@ class CompactionCompressionListener : public EventListener {
|
||||
const Options* db_options_;
|
||||
};
|
||||
|
||||
TEST_F(DBTest2, CompressionFailures) {
|
||||
enum CompressionFailureType {
|
||||
kTestCompressionFail,
|
||||
kTestDecompressionFail,
|
||||
kTestDecompressionCorruption
|
||||
};
|
||||
|
||||
class CompressionFailuresTest
|
||||
: public DBTest2,
|
||||
public testing::WithParamInterface<std::tuple<
|
||||
CompressionFailureType, CompressionType, uint32_t, uint32_t>> {
|
||||
public:
|
||||
CompressionFailuresTest() {
|
||||
std::tie(compression_failure_type_, compression_type_,
|
||||
compression_max_dict_bytes_, compression_parallel_threads_) =
|
||||
GetParam();
|
||||
}
|
||||
|
||||
CompressionFailureType compression_failure_type_ = kTestCompressionFail;
|
||||
CompressionType compression_type_ = kNoCompression;
|
||||
uint32_t compression_max_dict_bytes_ = 0;
|
||||
uint32_t compression_parallel_threads_ = 0;
|
||||
};
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(
|
||||
DBTest2, CompressionFailuresTest,
|
||||
::testing::Combine(::testing::Values(kTestCompressionFail,
|
||||
kTestDecompressionFail,
|
||||
kTestDecompressionCorruption),
|
||||
::testing::ValuesIn(GetSupportedCompressions()),
|
||||
::testing::Values(0, 10), ::testing::Values(1, 4)));
|
||||
|
||||
TEST_P(CompressionFailuresTest, CompressionFailures) {
|
||||
if (compression_type_ == kNoCompression) {
|
||||
return;
|
||||
}
|
||||
|
||||
Options options = CurrentOptions();
|
||||
options.level0_file_num_compaction_trigger = 2;
|
||||
options.max_bytes_for_level_base = 1024;
|
||||
@@ -1345,50 +1438,41 @@ TEST_F(DBTest2, CompressionFailures) {
|
||||
table_options.verify_compression = true;
|
||||
options.table_factory.reset(new BlockBasedTableFactory(table_options));
|
||||
|
||||
enum CompressionFailureType {
|
||||
kTestCompressionFail,
|
||||
kTestDecompressionFail,
|
||||
kTestDecompressionCorruption
|
||||
} curr_compression_failure_type;
|
||||
std::vector<CompressionFailureType> compression_failure_types = {
|
||||
kTestCompressionFail, kTestDecompressionFail,
|
||||
kTestDecompressionCorruption};
|
||||
options.compression = compression_type_;
|
||||
options.compression_opts.parallel_threads = compression_parallel_threads_;
|
||||
options.compression_opts.max_dict_bytes = compression_max_dict_bytes_;
|
||||
options.bottommost_compression_opts.parallel_threads =
|
||||
compression_parallel_threads_;
|
||||
options.bottommost_compression_opts.max_dict_bytes =
|
||||
compression_max_dict_bytes_;
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"BlockBasedTableBuilder::CompressBlockInternal:TamperWithReturnValue",
|
||||
[&curr_compression_failure_type](void* arg) {
|
||||
bool* ret = static_cast<bool*>(arg);
|
||||
if (curr_compression_failure_type == kTestCompressionFail) {
|
||||
if (compression_failure_type_ == kTestCompressionFail) {
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"BlockBasedTableBuilder::CompressBlockInternal:TamperWithReturnValue",
|
||||
[](void* arg) {
|
||||
bool* ret = static_cast<bool*>(arg);
|
||||
*ret = false;
|
||||
}
|
||||
});
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"UncompressBlockContentsForCompressionType:TamperWithReturnValue",
|
||||
[&curr_compression_failure_type](void* arg) {
|
||||
Status* ret = static_cast<Status*>(arg);
|
||||
ASSERT_OK(*ret);
|
||||
if (curr_compression_failure_type == kTestDecompressionFail) {
|
||||
});
|
||||
} else if (compression_failure_type_ == kTestDecompressionFail) {
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"UncompressBlockContentsForCompressionType:TamperWithReturnValue",
|
||||
[](void* arg) {
|
||||
Status* ret = static_cast<Status*>(arg);
|
||||
ASSERT_OK(*ret);
|
||||
*ret = Status::Corruption("kTestDecompressionFail");
|
||||
}
|
||||
});
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"UncompressBlockContentsForCompressionType:"
|
||||
"TamperWithDecompressionOutput",
|
||||
[&curr_compression_failure_type](void* arg) {
|
||||
if (curr_compression_failure_type == kTestDecompressionCorruption) {
|
||||
});
|
||||
} else if (compression_failure_type_ == kTestDecompressionCorruption) {
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"UncompressBlockContentsForCompressionType:"
|
||||
"TamperWithDecompressionOutput",
|
||||
[](void* arg) {
|
||||
BlockContents* contents = static_cast<BlockContents*>(arg);
|
||||
// Ensure uncompressed data != original data
|
||||
const size_t len = contents->data.size() + 1;
|
||||
std::unique_ptr<char[]> fake_data(new char[len]());
|
||||
*contents = BlockContents(std::move(fake_data), len);
|
||||
}
|
||||
});
|
||||
|
||||
std::vector<CompressionType> compression_types = GetSupportedCompressions();
|
||||
std::vector<uint32_t> compression_max_dict_bytes = {0, 10};
|
||||
std::vector<uint32_t> compression_parallel_threads = {1, 4};
|
||||
});
|
||||
}
|
||||
|
||||
std::map<std::string, std::string> key_value_written;
|
||||
|
||||
@@ -1399,74 +1483,55 @@ TEST_F(DBTest2, CompressionFailures) {
|
||||
|
||||
Status s = Status::OK();
|
||||
|
||||
for (auto compression_failure_type : compression_failure_types) {
|
||||
curr_compression_failure_type = compression_failure_type;
|
||||
for (auto compression_type : compression_types) {
|
||||
if (compression_type == kNoCompression) {
|
||||
continue;
|
||||
DestroyAndReopen(options);
|
||||
// Write 10 random files
|
||||
for (int i = 0; i < 10; i++) {
|
||||
for (int j = 0; j < 5; j++) {
|
||||
std::string key = RandomString(&rnd, kKeySize);
|
||||
// Ensure good compression ratio
|
||||
std::string valueUnit = RandomString(&rnd, kValUnitSize);
|
||||
std::string value;
|
||||
for (int k = 0; k < kValSize; k += kValUnitSize) {
|
||||
value += valueUnit;
|
||||
}
|
||||
for (auto parallel_threads : compression_parallel_threads) {
|
||||
for (auto max_dict_bytes : compression_max_dict_bytes) {
|
||||
options.compression = compression_type;
|
||||
options.compression_opts.parallel_threads = parallel_threads;
|
||||
options.compression_opts.max_dict_bytes = max_dict_bytes;
|
||||
options.bottommost_compression_opts.parallel_threads =
|
||||
parallel_threads;
|
||||
options.bottommost_compression_opts.max_dict_bytes = max_dict_bytes;
|
||||
|
||||
DestroyAndReopen(options);
|
||||
// Write 10 random files
|
||||
for (int i = 0; i < 10; i++) {
|
||||
for (int j = 0; j < 5; j++) {
|
||||
std::string key = RandomString(&rnd, kKeySize);
|
||||
// Ensure good compression ratio
|
||||
std::string valueUnit = RandomString(&rnd, kValUnitSize);
|
||||
std::string value;
|
||||
for (int k = 0; k < kValSize; k += kValUnitSize) {
|
||||
value += valueUnit;
|
||||
}
|
||||
s = Put(key, value);
|
||||
if (compression_failure_type == kTestCompressionFail) {
|
||||
key_value_written[key] = value;
|
||||
ASSERT_OK(s);
|
||||
}
|
||||
}
|
||||
s = Flush();
|
||||
if (compression_failure_type == kTestCompressionFail) {
|
||||
ASSERT_OK(s);
|
||||
}
|
||||
s = dbfull()->TEST_WaitForCompact();
|
||||
if (compression_failure_type == kTestCompressionFail) {
|
||||
ASSERT_OK(s);
|
||||
}
|
||||
if (i == 4) {
|
||||
// Make compression fail at the mid of table building
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
|
||||
}
|
||||
}
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
|
||||
|
||||
if (compression_failure_type == kTestCompressionFail) {
|
||||
// Should be kNoCompression, check content consistency
|
||||
std::unique_ptr<Iterator> db_iter(db_->NewIterator(ReadOptions()));
|
||||
for (db_iter->SeekToFirst(); db_iter->Valid(); db_iter->Next()) {
|
||||
std::string key = db_iter->key().ToString();
|
||||
std::string value = db_iter->value().ToString();
|
||||
ASSERT_NE(key_value_written.find(key), key_value_written.end());
|
||||
ASSERT_EQ(key_value_written[key], value);
|
||||
key_value_written.erase(key);
|
||||
}
|
||||
ASSERT_EQ(0, key_value_written.size());
|
||||
} else if (compression_failure_type == kTestDecompressionFail) {
|
||||
ASSERT_EQ(std::string(s.getState()),
|
||||
"Could not decompress: kTestDecompressionFail");
|
||||
} else if (compression_failure_type == kTestDecompressionCorruption) {
|
||||
ASSERT_EQ(std::string(s.getState()),
|
||||
"Decompressed block did not match raw block");
|
||||
}
|
||||
}
|
||||
s = Put(key, value);
|
||||
if (compression_failure_type_ == kTestCompressionFail) {
|
||||
key_value_written[key] = value;
|
||||
ASSERT_OK(s);
|
||||
}
|
||||
}
|
||||
s = Flush();
|
||||
if (compression_failure_type_ == kTestCompressionFail) {
|
||||
ASSERT_OK(s);
|
||||
}
|
||||
s = dbfull()->TEST_WaitForCompact();
|
||||
if (compression_failure_type_ == kTestCompressionFail) {
|
||||
ASSERT_OK(s);
|
||||
}
|
||||
if (i == 4) {
|
||||
// Make compression fail at the mid of table building
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
|
||||
}
|
||||
}
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
|
||||
|
||||
if (compression_failure_type_ == kTestCompressionFail) {
|
||||
// Should be kNoCompression, check content consistency
|
||||
std::unique_ptr<Iterator> db_iter(db_->NewIterator(ReadOptions()));
|
||||
for (db_iter->SeekToFirst(); db_iter->Valid(); db_iter->Next()) {
|
||||
std::string key = db_iter->key().ToString();
|
||||
std::string value = db_iter->value().ToString();
|
||||
ASSERT_NE(key_value_written.find(key), key_value_written.end());
|
||||
ASSERT_EQ(key_value_written[key], value);
|
||||
key_value_written.erase(key);
|
||||
}
|
||||
ASSERT_EQ(0, key_value_written.size());
|
||||
} else if (compression_failure_type_ == kTestDecompressionFail) {
|
||||
ASSERT_EQ(std::string(s.getState()),
|
||||
"Could not decompress: kTestDecompressionFail");
|
||||
} else if (compression_failure_type_ == kTestDecompressionCorruption) {
|
||||
ASSERT_EQ(std::string(s.getState()),
|
||||
"Decompressed block did not match raw block");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2003,6 +2068,10 @@ class MockPersistentCache : public PersistentCache {
|
||||
return PersistentCache::StatsType();
|
||||
}
|
||||
|
||||
uint64_t NewId() override {
|
||||
return last_id_.fetch_add(1, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
Status Insert(const Slice& page_key, const char* data,
|
||||
const size_t size) override {
|
||||
MutexLock _(&lock_);
|
||||
@@ -2043,6 +2112,7 @@ class MockPersistentCache : public PersistentCache {
|
||||
const bool is_compressed_ = true;
|
||||
size_t size_ = 0;
|
||||
const size_t max_size_ = 10 * 1024; // 10KiB
|
||||
std::atomic<uint64_t> last_id_{1};
|
||||
};
|
||||
|
||||
#ifdef OS_LINUX
|
||||
@@ -2166,10 +2236,7 @@ TEST_F(DBTest2, TestPerfContextIterCpuTime) {
|
||||
}
|
||||
#endif // OS_LINUX
|
||||
|
||||
// GetUniqueIdFromFile is not implemented on these platforms. Persistent cache
|
||||
// breaks when that function is not implemented and no regular block cache is
|
||||
// provided.
|
||||
#if !defined(OS_SOLARIS) && !defined(OS_WIN)
|
||||
#if !defined OS_SOLARIS
|
||||
TEST_F(DBTest2, PersistentCache) {
|
||||
int num_iter = 80;
|
||||
|
||||
@@ -2233,7 +2300,7 @@ TEST_F(DBTest2, PersistentCache) {
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif // !defined(OS_SOLARIS) && !defined(OS_WIN)
|
||||
#endif // !defined OS_SOLARIS
|
||||
|
||||
namespace {
|
||||
void CountSyncPoint() {
|
||||
@@ -2890,6 +2957,94 @@ TEST_F(DBTest2, OptimizeForSmallDB) {
|
||||
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
TEST_F(DBTest2, IterRaceFlush1) {
|
||||
ASSERT_OK(Put("foo", "v1"));
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency(
|
||||
{{"DBImpl::NewIterator:1", "DBTest2::IterRaceFlush:1"},
|
||||
{"DBTest2::IterRaceFlush:2", "DBImpl::NewIterator:2"}});
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
ROCKSDB_NAMESPACE::port::Thread t1([&] {
|
||||
TEST_SYNC_POINT("DBTest2::IterRaceFlush:1");
|
||||
ASSERT_OK(Put("foo", "v2"));
|
||||
Flush();
|
||||
TEST_SYNC_POINT("DBTest2::IterRaceFlush:2");
|
||||
});
|
||||
|
||||
// iterator is created after the first Put(), so it should see either
|
||||
// "v1" or "v2".
|
||||
{
|
||||
std::unique_ptr<Iterator> it(db_->NewIterator(ReadOptions()));
|
||||
it->Seek("foo");
|
||||
ASSERT_TRUE(it->Valid());
|
||||
ASSERT_EQ("foo", it->key().ToString());
|
||||
}
|
||||
|
||||
t1.join();
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
|
||||
TEST_F(DBTest2, IterRaceFlush2) {
|
||||
ASSERT_OK(Put("foo", "v1"));
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency(
|
||||
{{"DBImpl::NewIterator:3", "DBTest2::IterRaceFlush2:1"},
|
||||
{"DBTest2::IterRaceFlush2:2", "DBImpl::NewIterator:4"}});
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
ROCKSDB_NAMESPACE::port::Thread t1([&] {
|
||||
TEST_SYNC_POINT("DBTest2::IterRaceFlush2:1");
|
||||
ASSERT_OK(Put("foo", "v2"));
|
||||
Flush();
|
||||
TEST_SYNC_POINT("DBTest2::IterRaceFlush2:2");
|
||||
});
|
||||
|
||||
// iterator is created after the first Put(), so it should see either
|
||||
// "v1" or "v2".
|
||||
{
|
||||
std::unique_ptr<Iterator> it(db_->NewIterator(ReadOptions()));
|
||||
it->Seek("foo");
|
||||
ASSERT_TRUE(it->Valid());
|
||||
ASSERT_EQ("foo", it->key().ToString());
|
||||
}
|
||||
|
||||
t1.join();
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
|
||||
TEST_F(DBTest2, IterRefreshRaceFlush) {
|
||||
ASSERT_OK(Put("foo", "v1"));
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency(
|
||||
{{"ArenaWrappedDBIter::Refresh:1", "DBTest2::IterRefreshRaceFlush:1"},
|
||||
{"DBTest2::IterRefreshRaceFlush:2", "ArenaWrappedDBIter::Refresh:2"}});
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
ROCKSDB_NAMESPACE::port::Thread t1([&] {
|
||||
TEST_SYNC_POINT("DBTest2::IterRefreshRaceFlush:1");
|
||||
ASSERT_OK(Put("foo", "v2"));
|
||||
Flush();
|
||||
TEST_SYNC_POINT("DBTest2::IterRefreshRaceFlush:2");
|
||||
});
|
||||
|
||||
// iterator is created after the first Put(), so it should see either
|
||||
// "v1" or "v2".
|
||||
{
|
||||
std::unique_ptr<Iterator> it(db_->NewIterator(ReadOptions()));
|
||||
it->Refresh();
|
||||
it->Seek("foo");
|
||||
ASSERT_TRUE(it->Valid());
|
||||
ASSERT_EQ("foo", it->key().ToString());
|
||||
}
|
||||
|
||||
t1.join();
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
|
||||
TEST_F(DBTest2, GetRaceFlush1) {
|
||||
ASSERT_OK(Put("foo", "v1"));
|
||||
|
||||
@@ -4919,6 +5074,32 @@ TEST_F(DBTest2, AutoPrefixMode1) {
|
||||
}
|
||||
}
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
// WAL recovery mode is WALRecoveryMode::kPointInTimeRecovery.
|
||||
TEST_F(DBTest2, PointInTimeRecoveryWithIOErrorWhileReadingWal) {
|
||||
Options options = CurrentOptions();
|
||||
DestroyAndReopen(options);
|
||||
ASSERT_OK(Put("foo", "value0"));
|
||||
Close();
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
bool should_inject_error = false;
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::RecoverLogFiles:BeforeReadWal",
|
||||
[&](void* /*arg*/) { should_inject_error = true; });
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"LogReader::ReadMore:AfterReadFile", [&](void* arg) {
|
||||
if (should_inject_error) {
|
||||
ASSERT_NE(nullptr, arg);
|
||||
*reinterpret_cast<Status*>(arg) = Status::IOError("Injected IOError");
|
||||
}
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
options.avoid_flush_during_recovery = true;
|
||||
options.wal_recovery_mode = WALRecoveryMode::kPointInTimeRecovery;
|
||||
Status s = TryReopen(options);
|
||||
ASSERT_TRUE(s.IsIOError());
|
||||
}
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
#ifdef ROCKSDB_UNITTESTS_WITH_CUSTOM_OBJECTS_FROM_STATIC_LIBS
|
||||
|
||||
+3
-1
@@ -121,7 +121,9 @@ void EventHelpers::LogAndNotifyTableFileCreationFinished(
|
||||
<< table_properties.compression_options << "creation_time"
|
||||
<< table_properties.creation_time << "oldest_key_time"
|
||||
<< table_properties.oldest_key_time << "file_creation_time"
|
||||
<< table_properties.file_creation_time;
|
||||
<< table_properties.file_creation_time << "db_id"
|
||||
<< table_properties.db_id << "db_session_id"
|
||||
<< table_properties.db_session_id;
|
||||
|
||||
// user collected properties
|
||||
for (const auto& prop : table_properties.readable_properties) {
|
||||
|
||||
@@ -42,6 +42,29 @@ class ExternalSSTFileBasicTest
|
||||
return db_->IngestExternalFile(files, opts);
|
||||
}
|
||||
|
||||
Status AddFileWithFileChecksum(
|
||||
const std::vector<std::string>& files,
|
||||
const std::vector<std::string>& files_checksums,
|
||||
const std::vector<std::string>& files_checksum_func_names,
|
||||
bool verify_file_checksum = true, bool move_files = false,
|
||||
bool skip_snapshot_check = false, bool write_global_seqno = true) {
|
||||
IngestExternalFileOptions opts;
|
||||
opts.move_files = move_files;
|
||||
opts.snapshot_consistency = !skip_snapshot_check;
|
||||
opts.allow_global_seqno = false;
|
||||
opts.allow_blocking_flush = false;
|
||||
opts.write_global_seqno = write_global_seqno;
|
||||
opts.verify_file_checksum = verify_file_checksum;
|
||||
|
||||
IngestExternalFileArg arg;
|
||||
arg.column_family = db_->DefaultColumnFamily();
|
||||
arg.external_files = files;
|
||||
arg.options = opts;
|
||||
arg.files_checksums = files_checksums;
|
||||
arg.files_checksum_func_names = files_checksum_func_names;
|
||||
return db_->IngestExternalFiles({arg});
|
||||
}
|
||||
|
||||
Status GenerateAndAddExternalFile(
|
||||
const Options options, std::vector<int> keys,
|
||||
const std::vector<ValueType>& value_types,
|
||||
@@ -298,6 +321,266 @@ TEST_F(ExternalSSTFileBasicTest, BasicWithFileChecksumCrc32c) {
|
||||
DestroyAndRecreateExternalSSTFilesDir();
|
||||
}
|
||||
|
||||
TEST_F(ExternalSSTFileBasicTest, IngestFileWithFileChecksum) {
|
||||
Options old_options = CurrentOptions();
|
||||
Options options = CurrentOptions();
|
||||
options.file_checksum_gen_factory = GetFileChecksumGenCrc32cFactory();
|
||||
const ImmutableCFOptions ioptions(options);
|
||||
ChecksumVerifyHelper checksum_helper(options);
|
||||
|
||||
SstFileWriter sst_file_writer(EnvOptions(), options);
|
||||
|
||||
// file01.sst (1000 => 1099)
|
||||
std::string file1 = sst_files_dir_ + "file01.sst";
|
||||
ASSERT_OK(sst_file_writer.Open(file1));
|
||||
for (int k = 1000; k < 1100; k++) {
|
||||
ASSERT_OK(sst_file_writer.Put(Key(k), Key(k) + "_val"));
|
||||
}
|
||||
ExternalSstFileInfo file1_info;
|
||||
Status s = sst_file_writer.Finish(&file1_info);
|
||||
ASSERT_TRUE(s.ok()) << s.ToString();
|
||||
ASSERT_EQ(file1_info.file_path, file1);
|
||||
ASSERT_EQ(file1_info.num_entries, 100);
|
||||
ASSERT_EQ(file1_info.smallest_key, Key(1000));
|
||||
ASSERT_EQ(file1_info.largest_key, Key(1099));
|
||||
std::string file_checksum1, file_checksum_func_name1;
|
||||
ASSERT_OK(checksum_helper.GetSingleFileChecksumAndFuncName(
|
||||
file1, &file_checksum1, &file_checksum_func_name1));
|
||||
ASSERT_EQ(file1_info.file_checksum, file_checksum1);
|
||||
ASSERT_EQ(file1_info.file_checksum_func_name, file_checksum_func_name1);
|
||||
|
||||
// file02.sst (1100 => 1299)
|
||||
std::string file2 = sst_files_dir_ + "file02.sst";
|
||||
ASSERT_OK(sst_file_writer.Open(file2));
|
||||
for (int k = 1100; k < 1300; k++) {
|
||||
ASSERT_OK(sst_file_writer.Put(Key(k), Key(k) + "_val"));
|
||||
}
|
||||
ExternalSstFileInfo file2_info;
|
||||
s = sst_file_writer.Finish(&file2_info);
|
||||
ASSERT_TRUE(s.ok()) << s.ToString();
|
||||
ASSERT_EQ(file2_info.file_path, file2);
|
||||
ASSERT_EQ(file2_info.num_entries, 200);
|
||||
ASSERT_EQ(file2_info.smallest_key, Key(1100));
|
||||
ASSERT_EQ(file2_info.largest_key, Key(1299));
|
||||
std::string file_checksum2, file_checksum_func_name2;
|
||||
ASSERT_OK(checksum_helper.GetSingleFileChecksumAndFuncName(
|
||||
file2, &file_checksum2, &file_checksum_func_name2));
|
||||
ASSERT_EQ(file2_info.file_checksum, file_checksum2);
|
||||
ASSERT_EQ(file2_info.file_checksum_func_name, file_checksum_func_name2);
|
||||
|
||||
// file03.sst (1300 => 1499)
|
||||
std::string file3 = sst_files_dir_ + "file03.sst";
|
||||
ASSERT_OK(sst_file_writer.Open(file3));
|
||||
for (int k = 1300; k < 1500; k++) {
|
||||
ASSERT_OK(sst_file_writer.Put(Key(k), Key(k) + "_val_overlap"));
|
||||
}
|
||||
ExternalSstFileInfo file3_info;
|
||||
s = sst_file_writer.Finish(&file3_info);
|
||||
ASSERT_TRUE(s.ok()) << s.ToString();
|
||||
ASSERT_EQ(file3_info.file_path, file3);
|
||||
ASSERT_EQ(file3_info.num_entries, 200);
|
||||
ASSERT_EQ(file3_info.smallest_key, Key(1300));
|
||||
ASSERT_EQ(file3_info.largest_key, Key(1499));
|
||||
std::string file_checksum3, file_checksum_func_name3;
|
||||
ASSERT_OK(checksum_helper.GetSingleFileChecksumAndFuncName(
|
||||
file3, &file_checksum3, &file_checksum_func_name3));
|
||||
ASSERT_EQ(file3_info.file_checksum, file_checksum3);
|
||||
ASSERT_EQ(file3_info.file_checksum_func_name, file_checksum_func_name3);
|
||||
|
||||
// file04.sst (1500 => 1799)
|
||||
std::string file4 = sst_files_dir_ + "file04.sst";
|
||||
ASSERT_OK(sst_file_writer.Open(file4));
|
||||
for (int k = 1500; k < 1800; k++) {
|
||||
ASSERT_OK(sst_file_writer.Put(Key(k), Key(k) + "_val_overlap"));
|
||||
}
|
||||
ExternalSstFileInfo file4_info;
|
||||
s = sst_file_writer.Finish(&file4_info);
|
||||
ASSERT_TRUE(s.ok()) << s.ToString();
|
||||
ASSERT_EQ(file4_info.file_path, file4);
|
||||
ASSERT_EQ(file4_info.num_entries, 300);
|
||||
ASSERT_EQ(file4_info.smallest_key, Key(1500));
|
||||
ASSERT_EQ(file4_info.largest_key, Key(1799));
|
||||
std::string file_checksum4, file_checksum_func_name4;
|
||||
ASSERT_OK(checksum_helper.GetSingleFileChecksumAndFuncName(
|
||||
file4, &file_checksum4, &file_checksum_func_name4));
|
||||
ASSERT_EQ(file4_info.file_checksum, file_checksum4);
|
||||
ASSERT_EQ(file4_info.file_checksum_func_name, file_checksum_func_name4);
|
||||
|
||||
// file05.sst (1800 => 1899)
|
||||
std::string file5 = sst_files_dir_ + "file05.sst";
|
||||
ASSERT_OK(sst_file_writer.Open(file5));
|
||||
for (int k = 1800; k < 2000; k++) {
|
||||
ASSERT_OK(sst_file_writer.Put(Key(k), Key(k) + "_val_overlap"));
|
||||
}
|
||||
ExternalSstFileInfo file5_info;
|
||||
s = sst_file_writer.Finish(&file5_info);
|
||||
ASSERT_TRUE(s.ok()) << s.ToString();
|
||||
ASSERT_EQ(file5_info.file_path, file5);
|
||||
ASSERT_EQ(file5_info.num_entries, 200);
|
||||
ASSERT_EQ(file5_info.smallest_key, Key(1800));
|
||||
ASSERT_EQ(file5_info.largest_key, Key(1999));
|
||||
std::string file_checksum5, file_checksum_func_name5;
|
||||
ASSERT_OK(checksum_helper.GetSingleFileChecksumAndFuncName(
|
||||
file5, &file_checksum5, &file_checksum_func_name5));
|
||||
ASSERT_EQ(file5_info.file_checksum, file_checksum5);
|
||||
ASSERT_EQ(file5_info.file_checksum_func_name, file_checksum_func_name5);
|
||||
|
||||
// file06.sst (2000 => 2199)
|
||||
std::string file6 = sst_files_dir_ + "file06.sst";
|
||||
ASSERT_OK(sst_file_writer.Open(file6));
|
||||
for (int k = 2000; k < 2200; k++) {
|
||||
ASSERT_OK(sst_file_writer.Put(Key(k), Key(k) + "_val_overlap"));
|
||||
}
|
||||
ExternalSstFileInfo file6_info;
|
||||
s = sst_file_writer.Finish(&file6_info);
|
||||
ASSERT_TRUE(s.ok()) << s.ToString();
|
||||
ASSERT_EQ(file6_info.file_path, file6);
|
||||
ASSERT_EQ(file6_info.num_entries, 200);
|
||||
ASSERT_EQ(file6_info.smallest_key, Key(2000));
|
||||
ASSERT_EQ(file6_info.largest_key, Key(2199));
|
||||
std::string file_checksum6, file_checksum_func_name6;
|
||||
ASSERT_OK(checksum_helper.GetSingleFileChecksumAndFuncName(
|
||||
file6, &file_checksum6, &file_checksum_func_name6));
|
||||
ASSERT_EQ(file6_info.file_checksum, file_checksum6);
|
||||
ASSERT_EQ(file6_info.file_checksum_func_name, file_checksum_func_name6);
|
||||
|
||||
s = AddFileWithFileChecksum({file1}, {file_checksum1, "xyz"},
|
||||
{file_checksum1}, true, false, false, false);
|
||||
// does not care the checksum input since db does not enable file checksum
|
||||
ASSERT_TRUE(s.ok()) << s.ToString();
|
||||
ASSERT_OK(env_->FileExists(file1));
|
||||
std::vector<LiveFileMetaData> live_files;
|
||||
dbfull()->GetLiveFilesMetaData(&live_files);
|
||||
std::set<std::string> set1;
|
||||
for (auto f : live_files) {
|
||||
set1.insert(f.name);
|
||||
ASSERT_EQ(f.file_checksum, kUnknownFileChecksum);
|
||||
ASSERT_EQ(f.file_checksum_func_name, kUnknownFileChecksumFuncName);
|
||||
}
|
||||
|
||||
// Reopen Db with checksum enabled
|
||||
Reopen(options);
|
||||
// Enable verify_file_checksum option
|
||||
// The checksum vector does not match, fail the ingestion
|
||||
s = AddFileWithFileChecksum({file2}, {file_checksum2, "xyz"},
|
||||
{file_checksum_func_name2}, true, false, false,
|
||||
false);
|
||||
ASSERT_FALSE(s.ok()) << s.ToString();
|
||||
|
||||
// Enable verify_file_checksum option
|
||||
// The checksum name does not match, fail the ingestion
|
||||
s = AddFileWithFileChecksum({file2}, {file_checksum2}, {"xyz"}, true, false,
|
||||
false, false);
|
||||
ASSERT_FALSE(s.ok()) << s.ToString();
|
||||
|
||||
// Enable verify_file_checksum option
|
||||
// The checksum itself does not match, fail the ingestion
|
||||
s = AddFileWithFileChecksum({file2}, {"xyz"}, {file_checksum_func_name2},
|
||||
true, false, false, false);
|
||||
ASSERT_FALSE(s.ok()) << s.ToString();
|
||||
|
||||
// Enable verify_file_checksum option
|
||||
// All matches, ingestion is successful
|
||||
s = AddFileWithFileChecksum({file2}, {file_checksum2},
|
||||
{file_checksum_func_name2}, true, false, false,
|
||||
false);
|
||||
ASSERT_TRUE(s.ok()) << s.ToString();
|
||||
std::vector<LiveFileMetaData> live_files1;
|
||||
dbfull()->GetLiveFilesMetaData(&live_files1);
|
||||
for (auto f : live_files1) {
|
||||
if (set1.find(f.name) == set1.end()) {
|
||||
ASSERT_EQ(f.file_checksum, file_checksum2);
|
||||
ASSERT_EQ(f.file_checksum_func_name, file_checksum_func_name2);
|
||||
set1.insert(f.name);
|
||||
}
|
||||
}
|
||||
ASSERT_OK(env_->FileExists(file2));
|
||||
|
||||
// Enable verify_file_checksum option
|
||||
// No checksum information is provided, generate it when ingesting
|
||||
std::vector<std::string> checksum, checksum_func;
|
||||
s = AddFileWithFileChecksum({file3}, checksum, checksum_func, true, false,
|
||||
false, false);
|
||||
ASSERT_TRUE(s.ok()) << s.ToString();
|
||||
std::vector<LiveFileMetaData> live_files2;
|
||||
dbfull()->GetLiveFilesMetaData(&live_files2);
|
||||
for (auto f : live_files2) {
|
||||
if (set1.find(f.name) == set1.end()) {
|
||||
ASSERT_EQ(f.file_checksum, file_checksum3);
|
||||
ASSERT_EQ(f.file_checksum_func_name, file_checksum_func_name3);
|
||||
set1.insert(f.name);
|
||||
}
|
||||
}
|
||||
ASSERT_TRUE(s.ok()) << s.ToString();
|
||||
ASSERT_OK(env_->FileExists(file3));
|
||||
|
||||
// Does not enable verify_file_checksum options
|
||||
// The checksum name does not match, fail the ingestion
|
||||
s = AddFileWithFileChecksum({file4}, {file_checksum4}, {"xyz"}, false, false,
|
||||
false, false);
|
||||
ASSERT_FALSE(s.ok()) << s.ToString();
|
||||
|
||||
// Does not enable verify_file_checksum options
|
||||
// Checksum function name matches, store the checksum being ingested.
|
||||
s = AddFileWithFileChecksum({file4}, {"asd"}, {file_checksum_func_name4},
|
||||
false, false, false, false);
|
||||
ASSERT_TRUE(s.ok()) << s.ToString();
|
||||
std::vector<LiveFileMetaData> live_files3;
|
||||
dbfull()->GetLiveFilesMetaData(&live_files3);
|
||||
for (auto f : live_files3) {
|
||||
if (set1.find(f.name) == set1.end()) {
|
||||
ASSERT_FALSE(f.file_checksum == file_checksum4);
|
||||
ASSERT_EQ(f.file_checksum, "asd");
|
||||
ASSERT_EQ(f.file_checksum_func_name, file_checksum_func_name4);
|
||||
set1.insert(f.name);
|
||||
}
|
||||
}
|
||||
ASSERT_TRUE(s.ok()) << s.ToString();
|
||||
ASSERT_OK(env_->FileExists(file4));
|
||||
|
||||
// enable verify_file_checksum options, DB enable checksum, and enable
|
||||
// write_global_seq. So the checksum stored is different from the one
|
||||
// ingested due to the sequence number changes.
|
||||
s = AddFileWithFileChecksum({file5}, {file_checksum5},
|
||||
{file_checksum_func_name5}, true, false, false,
|
||||
true);
|
||||
ASSERT_OK(s);
|
||||
ASSERT_TRUE(s.ok()) << s.ToString();
|
||||
std::vector<LiveFileMetaData> live_files4;
|
||||
dbfull()->GetLiveFilesMetaData(&live_files4);
|
||||
for (auto f : live_files4) {
|
||||
if (set1.find(f.name) == set1.end()) {
|
||||
std::string cur_checksum5, cur_checksum_func_name5;
|
||||
ASSERT_OK(checksum_helper.GetSingleFileChecksumAndFuncName(
|
||||
dbname_ + f.name, &cur_checksum5, &cur_checksum_func_name5));
|
||||
ASSERT_EQ(f.file_checksum, cur_checksum5);
|
||||
ASSERT_EQ(f.file_checksum_func_name, file_checksum_func_name5);
|
||||
set1.insert(f.name);
|
||||
}
|
||||
}
|
||||
ASSERT_TRUE(s.ok()) << s.ToString();
|
||||
ASSERT_OK(env_->FileExists(file5));
|
||||
|
||||
// Does not enable verify_file_checksum options and also the ingested file
|
||||
// checksum information is empty. DB will generate and store the checksum
|
||||
// in Manifest.
|
||||
std::vector<std::string> files_c6, files_name6;
|
||||
s = AddFileWithFileChecksum({file6}, files_c6, files_name6, false, false,
|
||||
false, false);
|
||||
ASSERT_TRUE(s.ok()) << s.ToString();
|
||||
std::vector<LiveFileMetaData> live_files6;
|
||||
dbfull()->GetLiveFilesMetaData(&live_files6);
|
||||
for (auto f : live_files6) {
|
||||
if (set1.find(f.name) == set1.end()) {
|
||||
ASSERT_EQ(f.file_checksum, file_checksum6);
|
||||
ASSERT_EQ(f.file_checksum_func_name, file_checksum_func_name6);
|
||||
set1.insert(f.name);
|
||||
}
|
||||
}
|
||||
ASSERT_TRUE(s.ok()) << s.ToString();
|
||||
ASSERT_OK(env_->FileExists(file6));
|
||||
}
|
||||
|
||||
TEST_F(ExternalSSTFileBasicTest, NoCopy) {
|
||||
Options options = CurrentOptions();
|
||||
const ImmutableCFOptions ioptions(options);
|
||||
|
||||
@@ -28,6 +28,8 @@ namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
Status ExternalSstFileIngestionJob::Prepare(
|
||||
const std::vector<std::string>& external_files_paths,
|
||||
const std::vector<std::string>& files_checksums,
|
||||
const std::vector<std::string>& files_checksum_func_names,
|
||||
uint64_t next_file_number, SuperVersion* sv) {
|
||||
Status status;
|
||||
|
||||
@@ -142,6 +144,9 @@ Status ExternalSstFileIngestionJob::Prepare(
|
||||
break;
|
||||
}
|
||||
f.internal_file_path = path_inside_db;
|
||||
// Initialize the checksum information of ingested files.
|
||||
f.file_checksum = kUnknownFileChecksum;
|
||||
f.file_checksum_func_name = kUnknownFileChecksumFuncName;
|
||||
ingestion_path_ids.insert(f.fd.GetPathId());
|
||||
}
|
||||
|
||||
@@ -160,6 +165,128 @@ Status ExternalSstFileIngestionJob::Prepare(
|
||||
}
|
||||
TEST_SYNC_POINT("ExternalSstFileIngestionJob::AfterSyncDir");
|
||||
|
||||
// Generate and check the sst file checksum. Note that, if
|
||||
// IngestExternalFileOptions::write_global_seqno is true, we will not update
|
||||
// the checksum information in the files_to_ingests_ here, since the file is
|
||||
// upadted with the new global_seqno. After global_seqno is updated, DB will
|
||||
// generate the new checksum and store it in the Manifest. In all other cases
|
||||
// if ingestion_options_.write_global_seqno == true and
|
||||
// verify_file_checksum is false, we only check the checksum function name.
|
||||
if (status.ok() && db_options_.file_checksum_gen_factory != nullptr) {
|
||||
if (ingestion_options_.verify_file_checksum == false &&
|
||||
files_checksums.size() == files_to_ingest_.size() &&
|
||||
files_checksum_func_names.size() == files_to_ingest_.size()) {
|
||||
// Only when verify_file_checksum == false and the checksum for ingested
|
||||
// files are provided, DB will use the provided checksum and does not
|
||||
// generate the checksum for ingested files.
|
||||
need_generate_file_checksum_ = false;
|
||||
} else {
|
||||
need_generate_file_checksum_ = true;
|
||||
}
|
||||
FileChecksumGenContext gen_context;
|
||||
std::unique_ptr<FileChecksumGenerator> file_checksum_gen =
|
||||
db_options_.file_checksum_gen_factory->CreateFileChecksumGenerator(
|
||||
gen_context);
|
||||
std::vector<std::string> generated_checksums;
|
||||
std::vector<std::string> generated_checksum_func_names;
|
||||
// Step 1: generate the checksum for ingested sst file.
|
||||
if (need_generate_file_checksum_) {
|
||||
for (size_t i = 0; i < files_to_ingest_.size(); i++) {
|
||||
std::string generated_checksum, generated_checksum_func_name;
|
||||
IOStatus io_s = GenerateOneFileChecksum(
|
||||
fs_, files_to_ingest_[i].internal_file_path,
|
||||
db_options_.file_checksum_gen_factory.get(), &generated_checksum,
|
||||
&generated_checksum_func_name,
|
||||
ingestion_options_.verify_checksums_readahead_size,
|
||||
db_options_.allow_mmap_reads);
|
||||
if (!io_s.ok()) {
|
||||
status = io_s;
|
||||
ROCKS_LOG_WARN(db_options_.info_log,
|
||||
"Sst file checksum generation of file: %s failed: %s",
|
||||
files_to_ingest_[i].internal_file_path.c_str(),
|
||||
status.ToString().c_str());
|
||||
break;
|
||||
}
|
||||
if (ingestion_options_.write_global_seqno == false) {
|
||||
files_to_ingest_[i].file_checksum = generated_checksum;
|
||||
files_to_ingest_[i].file_checksum_func_name =
|
||||
generated_checksum_func_name;
|
||||
}
|
||||
generated_checksums.push_back(generated_checksum);
|
||||
generated_checksum_func_names.push_back(generated_checksum_func_name);
|
||||
}
|
||||
}
|
||||
|
||||
// Step 2: based on the verify_file_checksum and ingested checksum
|
||||
// information, do the verification.
|
||||
if (status.ok()) {
|
||||
if (files_checksums.size() == files_to_ingest_.size() &&
|
||||
files_checksum_func_names.size() == files_to_ingest_.size()) {
|
||||
// Verify the checksum and checksum function name.
|
||||
if (ingestion_options_.verify_file_checksum) {
|
||||
for (size_t i = 0; i < files_to_ingest_.size(); i++) {
|
||||
if (files_checksum_func_names[i] !=
|
||||
generated_checksum_func_names[i]) {
|
||||
status = Status::InvalidArgument(
|
||||
"Checksum function name does not match with the checksum "
|
||||
"function name of this DB");
|
||||
ROCKS_LOG_WARN(
|
||||
db_options_.info_log,
|
||||
"Sst file checksum verification of file: %s failed: %s",
|
||||
external_files_paths[i].c_str(), status.ToString().c_str());
|
||||
break;
|
||||
}
|
||||
if (files_checksums[i] != generated_checksums[i]) {
|
||||
status = Status::Corruption(
|
||||
"Ingested checksum does not match with the generated "
|
||||
"checksum");
|
||||
ROCKS_LOG_WARN(
|
||||
db_options_.info_log,
|
||||
"Sst file checksum verification of file: %s failed: %s",
|
||||
files_to_ingest_[i].internal_file_path.c_str(),
|
||||
status.ToString().c_str());
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// If verify_file_checksum is not enabled, we only verify the
|
||||
// checksum function name. If it does not match, fail the ingestion.
|
||||
// If matches, we trust the ingested checksum information and store
|
||||
// in the Manifest.
|
||||
for (size_t i = 0; i < files_to_ingest_.size(); i++) {
|
||||
if (files_checksum_func_names[i] != file_checksum_gen->Name()) {
|
||||
status = Status::InvalidArgument(
|
||||
"Checksum function name does not match with the checksum "
|
||||
"function name of this DB");
|
||||
ROCKS_LOG_WARN(
|
||||
db_options_.info_log,
|
||||
"Sst file checksum verification of file: %s failed: %s",
|
||||
external_files_paths[i].c_str(), status.ToString().c_str());
|
||||
break;
|
||||
}
|
||||
files_to_ingest_[i].file_checksum = files_checksums[i];
|
||||
files_to_ingest_[i].file_checksum_func_name =
|
||||
files_checksum_func_names[i];
|
||||
}
|
||||
}
|
||||
} else if (files_checksums.size() != files_checksum_func_names.size() ||
|
||||
(files_checksums.size() == files_checksum_func_names.size() &&
|
||||
files_checksums.size() != 0)) {
|
||||
// The checksum or checksum function name vector are not both empty
|
||||
// and they are incomplete.
|
||||
status = Status::InvalidArgument(
|
||||
"The checksum information of ingested sst files are nonempty and "
|
||||
"the size of checksums or the size of the checksum function "
|
||||
"names "
|
||||
"does not match with the number of ingested sst files");
|
||||
ROCKS_LOG_WARN(
|
||||
db_options_.info_log,
|
||||
"The ingested sst files checksum information is incomplete: %s",
|
||||
status.ToString().c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: The following is duplicated with Cleanup().
|
||||
if (!status.ok()) {
|
||||
// We failed, remove all files that we copied into the db
|
||||
@@ -245,6 +372,11 @@ Status ExternalSstFileIngestionJob::Run() {
|
||||
return status;
|
||||
}
|
||||
|
||||
status = GenerateChecksumForIngestedFile(&f);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
|
||||
// We use the import time as the ancester time. This is the time the data
|
||||
// is written to the database.
|
||||
int64_t temp_current_time = 0;
|
||||
@@ -255,11 +387,11 @@ Status ExternalSstFileIngestionJob::Run() {
|
||||
static_cast<uint64_t>(temp_current_time);
|
||||
}
|
||||
|
||||
edit_.AddFile(
|
||||
f.picked_level, f.fd.GetNumber(), f.fd.GetPathId(), f.fd.GetFileSize(),
|
||||
f.smallest_internal_key, f.largest_internal_key, f.assigned_seqno,
|
||||
f.assigned_seqno, false, kInvalidBlobFileNumber, oldest_ancester_time,
|
||||
current_time, kUnknownFileChecksum, kUnknownFileChecksumFuncName);
|
||||
edit_.AddFile(f.picked_level, f.fd.GetNumber(), f.fd.GetPathId(),
|
||||
f.fd.GetFileSize(), f.smallest_internal_key,
|
||||
f.largest_internal_key, f.assigned_seqno, f.assigned_seqno,
|
||||
false, kInvalidBlobFileNumber, oldest_ancester_time,
|
||||
current_time, f.file_checksum, f.file_checksum_func_name);
|
||||
}
|
||||
return status;
|
||||
}
|
||||
@@ -687,6 +819,31 @@ Status ExternalSstFileIngestionJob::AssignGlobalSeqnoForIngestedFile(
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
IOStatus ExternalSstFileIngestionJob::GenerateChecksumForIngestedFile(
|
||||
IngestedFileInfo* file_to_ingest) {
|
||||
if (db_options_.file_checksum_gen_factory == nullptr ||
|
||||
need_generate_file_checksum_ == false ||
|
||||
ingestion_options_.write_global_seqno == false) {
|
||||
// If file_checksum_gen_factory is not set, we are not able to generate
|
||||
// the checksum. if write_global_seqno is false, it means we will use
|
||||
// file checksum generated during Prepare(). This step will be skipped.
|
||||
return IOStatus::OK();
|
||||
}
|
||||
std::string file_checksum, file_checksum_func_name;
|
||||
IOStatus io_s = GenerateOneFileChecksum(
|
||||
fs_, file_to_ingest->internal_file_path,
|
||||
db_options_.file_checksum_gen_factory.get(), &file_checksum,
|
||||
&file_checksum_func_name,
|
||||
ingestion_options_.verify_checksums_readahead_size,
|
||||
db_options_.allow_mmap_reads);
|
||||
if (!io_s.ok()) {
|
||||
return io_s;
|
||||
}
|
||||
file_to_ingest->file_checksum = file_checksum;
|
||||
file_to_ingest->file_checksum_func_name = file_checksum_func_name;
|
||||
return IOStatus::OK();
|
||||
}
|
||||
|
||||
bool ExternalSstFileIngestionJob::IngestedFileFitInLevel(
|
||||
const IngestedFileInfo* file_to_ingest, int level) {
|
||||
if (level == 0) {
|
||||
|
||||
@@ -63,6 +63,10 @@ struct IngestedFileInfo {
|
||||
// ingestion_options.move_files is false by default, thus copy_file is true
|
||||
// by default.
|
||||
bool copy_file = true;
|
||||
// The checksum of ingested file
|
||||
std::string file_checksum;
|
||||
// The name of checksum function that generate the checksum
|
||||
std::string file_checksum_func_name;
|
||||
};
|
||||
|
||||
class ExternalSstFileIngestionJob {
|
||||
@@ -90,6 +94,8 @@ class ExternalSstFileIngestionJob {
|
||||
|
||||
// Prepare the job by copying external files into the DB.
|
||||
Status Prepare(const std::vector<std::string>& external_files_paths,
|
||||
const std::vector<std::string>& files_checksums,
|
||||
const std::vector<std::string>& files_checksum_func_names,
|
||||
uint64_t next_file_number, SuperVersion* sv);
|
||||
|
||||
// Check if we need to flush the memtable before running the ingestion job
|
||||
@@ -148,6 +154,8 @@ class ExternalSstFileIngestionJob {
|
||||
// Set the file global sequence number to `seqno`
|
||||
Status AssignGlobalSeqnoForIngestedFile(IngestedFileInfo* file_to_ingest,
|
||||
SequenceNumber seqno);
|
||||
// Generate the file checksum and store in the IngestedFileInfo
|
||||
IOStatus GenerateChecksumForIngestedFile(IngestedFileInfo* file_to_ingest);
|
||||
|
||||
// Check if `file_to_ingest` can fit in level `level`
|
||||
// REQUIRES: Mutex held
|
||||
@@ -175,6 +183,9 @@ class ExternalSstFileIngestionJob {
|
||||
// Set in ExternalSstFileIngestionJob::Prepare(), if true all files are
|
||||
// ingested in L0
|
||||
bool files_overlap_{false};
|
||||
// Set in ExternalSstFileIngestionJob::Prepare(), if true and DB
|
||||
// file_checksum_gen_factory is set, DB will generate checksum each file.
|
||||
bool need_generate_file_checksum_{true};
|
||||
};
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
@@ -12,6 +12,7 @@
|
||||
#include "file/filename.h"
|
||||
#include "port/port.h"
|
||||
#include "port/stack_trace.h"
|
||||
#include "rocksdb/sst_file_reader.h"
|
||||
#include "rocksdb/sst_file_writer.h"
|
||||
#include "test_util/fault_injection_test_env.h"
|
||||
#include "test_util/testutil.h"
|
||||
@@ -2393,6 +2394,41 @@ TEST_F(ExternalSSTFileTest, IngestFileWrittenWithCompressionDictionary) {
|
||||
ASSERT_EQ(1, num_compression_dicts);
|
||||
}
|
||||
|
||||
// Very slow, not worth the cost to run regularly
|
||||
TEST_F(ExternalSSTFileTest, DISABLED_HugeBlockChecksum) {
|
||||
int max_checksum = static_cast<int>(kxxHash64);
|
||||
for (int i = 0; i <= max_checksum; ++i) {
|
||||
BlockBasedTableOptions table_options;
|
||||
table_options.checksum = static_cast<ChecksumType>(i);
|
||||
Options options = CurrentOptions();
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
|
||||
SstFileWriter sst_file_writer(EnvOptions(), options);
|
||||
|
||||
// 2^32 - 1, will lead to data block with more than 2^32 bytes
|
||||
size_t huge_size = port::kMaxUint32;
|
||||
|
||||
std::string f = sst_files_dir_ + "f.sst";
|
||||
ASSERT_OK(sst_file_writer.Open(f));
|
||||
{
|
||||
Random64 r(123);
|
||||
std::string huge(huge_size, 0);
|
||||
for (size_t j = 0; j + 7 < huge_size; j += 8) {
|
||||
EncodeFixed64(&huge[j], r.Next());
|
||||
}
|
||||
ASSERT_OK(sst_file_writer.Put("Huge", huge));
|
||||
}
|
||||
|
||||
ExternalSstFileInfo f_info;
|
||||
ASSERT_OK(sst_file_writer.Finish(&f_info));
|
||||
ASSERT_GT(f_info.file_size, uint64_t{huge_size} + 10);
|
||||
|
||||
SstFileReader sst_file_reader(options);
|
||||
ASSERT_OK(sst_file_reader.Open(f));
|
||||
ASSERT_OK(sst_file_reader.VerifyChecksum());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(ExternalSSTFileTest, IngestFilesIntoMultipleColumnFamilies_Success) {
|
||||
std::unique_ptr<FaultInjectionTestEnv> fault_injection_env(
|
||||
new FaultInjectionTestEnv(env_));
|
||||
|
||||
+6
-2
@@ -97,8 +97,11 @@ FlushJob::FlushJob(const std::string& dbname, ColumnFamilyData* cfd,
|
||||
CompressionType output_compression, Statistics* stats,
|
||||
EventLogger* event_logger, bool measure_io_stats,
|
||||
const bool sync_output_directory, const bool write_manifest,
|
||||
Env::Priority thread_pri)
|
||||
Env::Priority thread_pri, const std::string& db_id,
|
||||
const std::string& db_session_id)
|
||||
: dbname_(dbname),
|
||||
db_id_(db_id),
|
||||
db_session_id_(db_session_id),
|
||||
cfd_(cfd),
|
||||
db_options_(db_options),
|
||||
mutable_cf_options_(mutable_cf_options),
|
||||
@@ -393,7 +396,8 @@ Status FlushJob::WriteLevel0Table() {
|
||||
mutable_cf_options_.paranoid_file_checks, cfd_->internal_stats(),
|
||||
TableFileCreationReason::kFlush, &io_s, event_logger_,
|
||||
job_context_->job_id, Env::IO_HIGH, &table_properties_, 0 /* level */,
|
||||
creation_time, oldest_key_time, write_hint, current_time);
|
||||
creation_time, oldest_key_time, write_hint, current_time, db_id_,
|
||||
db_session_id_);
|
||||
if (!io_s.ok()) {
|
||||
io_status_ = io_s;
|
||||
}
|
||||
|
||||
+4
-1
@@ -72,7 +72,8 @@ class FlushJob {
|
||||
CompressionType output_compression, Statistics* stats,
|
||||
EventLogger* event_logger, bool measure_io_stats,
|
||||
const bool sync_output_directory, const bool write_manifest,
|
||||
Env::Priority thread_pri);
|
||||
Env::Priority thread_pri, const std::string& db_id = "",
|
||||
const std::string& db_session_id = "");
|
||||
|
||||
~FlushJob();
|
||||
|
||||
@@ -103,6 +104,8 @@ class FlushJob {
|
||||
#endif // !ROCKSDB_LITE
|
||||
|
||||
const std::string& dbname_;
|
||||
const std::string db_id_;
|
||||
const std::string db_session_id_;
|
||||
ColumnFamilyData* cfd_;
|
||||
const ImmutableDBOptions& db_options_;
|
||||
const MutableCFOptions& mutable_cf_options_;
|
||||
|
||||
@@ -282,7 +282,6 @@ TEST_F(FlushJobTest, FlushMemTablesSingleColumnFamily) {
|
||||
assert(memtable_ids.size() == num_mems);
|
||||
uint64_t smallest_memtable_id = memtable_ids.front();
|
||||
uint64_t flush_memtable_id = smallest_memtable_id + num_mems_to_flush - 1;
|
||||
|
||||
FlushJob flush_job(dbname_, versions_->GetColumnFamilySet()->GetDefault(),
|
||||
db_options_, *cfd->GetLatestMutableCFOptions(),
|
||||
&flush_memtable_id, env_options_, versions_.get(), &mutex_,
|
||||
|
||||
@@ -14,7 +14,7 @@ class ImportColumnFamilyTest : public DBTestBase {
|
||||
ImportColumnFamilyTest() : DBTestBase("/import_column_family_test") {
|
||||
sst_files_dir_ = dbname_ + "/sst_files/";
|
||||
DestroyAndRecreateExternalSSTFilesDir();
|
||||
export_files_dir_ = test::TmpDir(env_) + "/export";
|
||||
export_files_dir_ = test::PerThreadDBPath(env_, "export");
|
||||
import_cfh_ = nullptr;
|
||||
import_cfh2_ = nullptr;
|
||||
metadata_ptr_ = nullptr;
|
||||
|
||||
@@ -281,6 +281,7 @@ bool Reader::ReadMore(size_t* drop_size, int *error) {
|
||||
// Last read was a full read, so this is a trailer to skip
|
||||
buffer_.clear();
|
||||
Status status = file_->Read(kBlockSize, &buffer_, backing_store_);
|
||||
TEST_SYNC_POINT_CALLBACK("LogReader::ReadMore:AfterReadFile", &status);
|
||||
end_of_buffer_offset_ += buffer_.size();
|
||||
if (!status.ok()) {
|
||||
buffer_.clear();
|
||||
|
||||
@@ -147,6 +147,8 @@ IOStatus Writer::EmitPhysicalRecord(RecordType t, const char* ptr, size_t n) {
|
||||
// Compute the crc of the record type and the payload.
|
||||
crc = crc32c::Extend(crc, ptr, n);
|
||||
crc = crc32c::Mask(crc); // Adjust for storage
|
||||
TEST_SYNC_POINT_CALLBACK("LogWriter::EmitPhysicalRecord:BeforeEncodeChecksum",
|
||||
&crc);
|
||||
EncodeFixed32(buf, crc);
|
||||
|
||||
// Write the header and the payload
|
||||
|
||||
+6
-1
@@ -183,6 +183,8 @@ class Repairer {
|
||||
}
|
||||
// Just create a DBImpl temporarily so we can reuse NewDB()
|
||||
DBImpl* db_impl = new DBImpl(db_options_, dbname_);
|
||||
// Also use this temp DBImpl to get a session id
|
||||
db_impl->GetDbSessionId(db_session_id_);
|
||||
status = db_impl->NewDB();
|
||||
delete db_impl;
|
||||
}
|
||||
@@ -229,6 +231,7 @@ class Repairer {
|
||||
};
|
||||
|
||||
std::string const dbname_;
|
||||
std::string db_session_id_;
|
||||
Env* const env_;
|
||||
const EnvOptions env_options_;
|
||||
const DBOptions db_options_;
|
||||
@@ -435,7 +438,9 @@ class Repairer {
|
||||
CompressionOptions(), false, nullptr /* internal_stats */,
|
||||
TableFileCreationReason::kRecovery, &io_s, nullptr /* event_logger */,
|
||||
0 /* job_id */, Env::IO_HIGH, nullptr /* table_properties */,
|
||||
-1 /* level */, current_time, write_hint);
|
||||
-1 /* level */, current_time, 0 /* oldest_key_time */, write_hint,
|
||||
0 /* file_creation_time */, "DB Repairer" /* db_id */,
|
||||
db_session_id_);
|
||||
ROCKS_LOG_INFO(db_options_.info_log,
|
||||
"Log #%" PRIu64 ": %d ops saved to Table #%" PRIu64 " %s",
|
||||
log, counter, meta.fd.GetNumber(),
|
||||
|
||||
+200
-73
@@ -90,7 +90,8 @@ class VersionBuilder::Rep {
|
||||
public:
|
||||
bool IsEmpty() const {
|
||||
return !shared_meta_ && !additional_garbage_count_ &&
|
||||
!additional_garbage_bytes_;
|
||||
!additional_garbage_bytes_ && newly_linked_ssts_.empty() &&
|
||||
newly_unlinked_ssts_.empty();
|
||||
}
|
||||
|
||||
std::shared_ptr<SharedBlobFileMetaData> GetSharedMeta() const {
|
||||
@@ -105,6 +106,14 @@ class VersionBuilder::Rep {
|
||||
return additional_garbage_bytes_;
|
||||
}
|
||||
|
||||
const std::unordered_set<uint64_t>& GetNewlyLinkedSsts() const {
|
||||
return newly_linked_ssts_;
|
||||
}
|
||||
|
||||
const std::unordered_set<uint64_t>& GetNewlyUnlinkedSsts() const {
|
||||
return newly_unlinked_ssts_;
|
||||
}
|
||||
|
||||
void SetSharedMeta(std::shared_ptr<SharedBlobFileMetaData> shared_meta) {
|
||||
assert(!shared_meta_);
|
||||
assert(shared_meta);
|
||||
@@ -117,10 +126,44 @@ class VersionBuilder::Rep {
|
||||
additional_garbage_bytes_ += bytes;
|
||||
}
|
||||
|
||||
void LinkSst(uint64_t sst_file_number) {
|
||||
assert(newly_linked_ssts_.find(sst_file_number) ==
|
||||
newly_linked_ssts_.end());
|
||||
|
||||
// Reconcile with newly unlinked SSTs on the fly. (Note: an SST can be
|
||||
// linked to and unlinked from the same blob file in the case of a trivial
|
||||
// move.)
|
||||
auto it = newly_unlinked_ssts_.find(sst_file_number);
|
||||
|
||||
if (it != newly_unlinked_ssts_.end()) {
|
||||
newly_unlinked_ssts_.erase(it);
|
||||
} else {
|
||||
newly_linked_ssts_.emplace(sst_file_number);
|
||||
}
|
||||
}
|
||||
|
||||
void UnlinkSst(uint64_t sst_file_number) {
|
||||
assert(newly_unlinked_ssts_.find(sst_file_number) ==
|
||||
newly_unlinked_ssts_.end());
|
||||
|
||||
// Reconcile with newly linked SSTs on the fly. (Note: an SST can be
|
||||
// linked to and unlinked from the same blob file in the case of a trivial
|
||||
// move.)
|
||||
auto it = newly_linked_ssts_.find(sst_file_number);
|
||||
|
||||
if (it != newly_linked_ssts_.end()) {
|
||||
newly_linked_ssts_.erase(it);
|
||||
} else {
|
||||
newly_unlinked_ssts_.emplace(sst_file_number);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr<SharedBlobFileMetaData> shared_meta_;
|
||||
uint64_t additional_garbage_count_ = 0;
|
||||
uint64_t additional_garbage_bytes_ = 0;
|
||||
std::unordered_set<uint64_t> newly_linked_ssts_;
|
||||
std::unordered_set<uint64_t> newly_unlinked_ssts_;
|
||||
};
|
||||
|
||||
const FileOptions& file_options_;
|
||||
@@ -212,29 +255,19 @@ class VersionBuilder::Rep {
|
||||
return false;
|
||||
}
|
||||
|
||||
Status CheckConsistencyOfOldestBlobFileReference(
|
||||
const VersionStorageInfo* vstorage, uint64_t blob_file_number) const {
|
||||
assert(vstorage);
|
||||
using ExpectedLinkedSsts =
|
||||
std::unordered_map<uint64_t, BlobFileMetaData::LinkedSsts>;
|
||||
|
||||
// TODO: remove this check once we actually start recoding metadata for
|
||||
// blob files in the MANIFEST.
|
||||
if (vstorage->GetBlobFiles().empty()) {
|
||||
return Status::OK();
|
||||
}
|
||||
static void UpdateExpectedLinkedSsts(
|
||||
uint64_t table_file_number, uint64_t blob_file_number,
|
||||
ExpectedLinkedSsts* expected_linked_ssts) {
|
||||
assert(expected_linked_ssts);
|
||||
|
||||
if (blob_file_number == kInvalidBlobFileNumber) {
|
||||
return Status::OK();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!IsBlobFileInVersion(blob_file_number)) {
|
||||
std::ostringstream oss;
|
||||
oss << "Blob file #" << blob_file_number
|
||||
<< " is not part of this version";
|
||||
|
||||
return Status::Corruption("VersionBuilder", oss.str());
|
||||
}
|
||||
|
||||
return Status::OK();
|
||||
(*expected_linked_ssts)[blob_file_number].emplace(table_file_number);
|
||||
}
|
||||
|
||||
Status CheckConsistency(VersionStorageInfo* vstorage) {
|
||||
@@ -245,9 +278,13 @@ class VersionBuilder::Rep {
|
||||
return Status::OK();
|
||||
}
|
||||
#endif
|
||||
// Make sure the files are sorted correctly and that the oldest blob file
|
||||
// reference for each table file points to a valid blob file in this
|
||||
// version.
|
||||
// Make sure the files are sorted correctly and that the links between
|
||||
// table files and blob files are consistent. The latter is checked using
|
||||
// the following mapping, which is built using the forward links
|
||||
// (table file -> blob file), and is subsequently compared with the inverse
|
||||
// mapping stored in the BlobFileMetaData objects.
|
||||
ExpectedLinkedSsts expected_linked_ssts;
|
||||
|
||||
for (int level = 0; level < num_levels_; level++) {
|
||||
auto& level_files = vstorage->LevelFiles(level);
|
||||
|
||||
@@ -256,19 +293,14 @@ class VersionBuilder::Rep {
|
||||
}
|
||||
|
||||
assert(level_files[0]);
|
||||
Status s = CheckConsistencyOfOldestBlobFileReference(
|
||||
vstorage, level_files[0]->oldest_blob_file_number);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
|
||||
UpdateExpectedLinkedSsts(level_files[0]->fd.GetNumber(),
|
||||
level_files[0]->oldest_blob_file_number,
|
||||
&expected_linked_ssts);
|
||||
for (size_t i = 1; i < level_files.size(); i++) {
|
||||
assert(level_files[i]);
|
||||
s = CheckConsistencyOfOldestBlobFileReference(
|
||||
vstorage, level_files[i]->oldest_blob_file_number);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
UpdateExpectedLinkedSsts(level_files[i]->fd.GetNumber(),
|
||||
level_files[i]->oldest_blob_file_number,
|
||||
&expected_linked_ssts);
|
||||
|
||||
auto f1 = level_files[i - 1];
|
||||
auto f2 = level_files[i];
|
||||
@@ -328,17 +360,27 @@ class VersionBuilder::Rep {
|
||||
// Make sure that all blob files in the version have non-garbage data.
|
||||
const auto& blob_files = vstorage->GetBlobFiles();
|
||||
for (const auto& pair : blob_files) {
|
||||
const uint64_t blob_file_number = pair.first;
|
||||
const auto& blob_file_meta = pair.second;
|
||||
assert(blob_file_meta);
|
||||
|
||||
if (blob_file_meta->GetGarbageBlobCount() >=
|
||||
blob_file_meta->GetTotalBlobCount()) {
|
||||
std::ostringstream oss;
|
||||
oss << "Blob file #" << blob_file_meta->GetBlobFileNumber()
|
||||
oss << "Blob file #" << blob_file_number
|
||||
<< " consists entirely of garbage";
|
||||
|
||||
return Status::Corruption("VersionBuilder", oss.str());
|
||||
}
|
||||
|
||||
if (blob_file_meta->GetLinkedSsts() !=
|
||||
expected_linked_ssts[blob_file_number]) {
|
||||
std::ostringstream oss;
|
||||
oss << "Links are inconsistent between table files and blob file #"
|
||||
<< blob_file_number;
|
||||
|
||||
return Status::Corruption("VersionBuilder", oss.str());
|
||||
}
|
||||
}
|
||||
|
||||
Status ret_s;
|
||||
@@ -430,6 +472,28 @@ class VersionBuilder::Rep {
|
||||
return base_vstorage_->GetFileLocation(file_number).GetLevel();
|
||||
}
|
||||
|
||||
uint64_t GetOldestBlobFileNumberForTableFile(int level,
|
||||
uint64_t file_number) const {
|
||||
assert(level < num_levels_);
|
||||
|
||||
const auto& added_files = levels_[level].added_files;
|
||||
|
||||
auto it = added_files.find(file_number);
|
||||
if (it != added_files.end()) {
|
||||
const FileMetaData* const meta = it->second;
|
||||
assert(meta);
|
||||
|
||||
return meta->oldest_blob_file_number;
|
||||
}
|
||||
|
||||
assert(base_vstorage_);
|
||||
const FileMetaData* const meta =
|
||||
base_vstorage_->GetFileMetaDataByNumber(file_number);
|
||||
assert(meta);
|
||||
|
||||
return meta->oldest_blob_file_number;
|
||||
}
|
||||
|
||||
Status ApplyFileDeletion(int level, uint64_t file_number) {
|
||||
assert(level != VersionStorageInfo::FileLocation::Invalid().GetLevel());
|
||||
|
||||
@@ -463,6 +527,14 @@ class VersionBuilder::Rep {
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
const uint64_t blob_file_number =
|
||||
GetOldestBlobFileNumberForTableFile(level, file_number);
|
||||
|
||||
if (blob_file_number != kInvalidBlobFileNumber &&
|
||||
IsBlobFileInVersion(blob_file_number)) {
|
||||
blob_file_meta_deltas_[blob_file_number].UnlinkSst(file_number);
|
||||
}
|
||||
|
||||
auto& level_state = levels_[level];
|
||||
|
||||
auto& add_files = level_state.added_files;
|
||||
@@ -470,12 +542,12 @@ class VersionBuilder::Rep {
|
||||
if (add_it != add_files.end()) {
|
||||
UnrefFile(add_it->second);
|
||||
add_files.erase(add_it);
|
||||
} else {
|
||||
auto& del_files = level_state.deleted_files;
|
||||
assert(del_files.find(file_number) == del_files.end());
|
||||
del_files.emplace(file_number);
|
||||
}
|
||||
|
||||
auto& del_files = level_state.deleted_files;
|
||||
assert(del_files.find(file_number) == del_files.end());
|
||||
del_files.emplace(file_number);
|
||||
|
||||
table_file_levels_[file_number] =
|
||||
VersionStorageInfo::FileLocation::Invalid().GetLevel();
|
||||
|
||||
@@ -514,13 +586,20 @@ class VersionBuilder::Rep {
|
||||
auto del_it = del_files.find(file_number);
|
||||
if (del_it != del_files.end()) {
|
||||
del_files.erase(del_it);
|
||||
} else {
|
||||
FileMetaData* const f = new FileMetaData(meta);
|
||||
f->refs = 1;
|
||||
}
|
||||
|
||||
auto& add_files = level_state.added_files;
|
||||
assert(add_files.find(file_number) == add_files.end());
|
||||
add_files.emplace(file_number, f);
|
||||
FileMetaData* const f = new FileMetaData(meta);
|
||||
f->refs = 1;
|
||||
|
||||
auto& add_files = level_state.added_files;
|
||||
assert(add_files.find(file_number) == add_files.end());
|
||||
add_files.emplace(file_number, f);
|
||||
|
||||
const uint64_t blob_file_number = f->oldest_blob_file_number;
|
||||
|
||||
if (blob_file_number != kInvalidBlobFileNumber &&
|
||||
IsBlobFileInVersion(blob_file_number)) {
|
||||
blob_file_meta_deltas_[blob_file_number].LinkSst(file_number);
|
||||
}
|
||||
|
||||
table_file_levels_[file_number] = level;
|
||||
@@ -537,27 +616,9 @@ class VersionBuilder::Rep {
|
||||
}
|
||||
}
|
||||
|
||||
// Delete files
|
||||
for (const auto& deleted_file : edit->GetDeletedFiles()) {
|
||||
const int level = deleted_file.first;
|
||||
const uint64_t file_number = deleted_file.second;
|
||||
|
||||
const Status s = ApplyFileDeletion(level, file_number);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
// Add new files
|
||||
for (const auto& new_file : edit->GetNewFiles()) {
|
||||
const int level = new_file.first;
|
||||
const FileMetaData& meta = new_file.second;
|
||||
|
||||
const Status s = ApplyFileAddition(level, meta);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
}
|
||||
// Note: we process the blob file related changes first because the
|
||||
// table file addition/deletion logic depends on the blob files
|
||||
// already being there.
|
||||
|
||||
// Add new blob files
|
||||
for (const auto& blob_file_addition : edit->GetBlobFileAdditions()) {
|
||||
@@ -575,17 +636,62 @@ class VersionBuilder::Rep {
|
||||
}
|
||||
}
|
||||
|
||||
// Delete table files
|
||||
for (const auto& deleted_file : edit->GetDeletedFiles()) {
|
||||
const int level = deleted_file.first;
|
||||
const uint64_t file_number = deleted_file.second;
|
||||
|
||||
const Status s = ApplyFileDeletion(level, file_number);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
// Add new table files
|
||||
for (const auto& new_file : edit->GetNewFiles()) {
|
||||
const int level = new_file.first;
|
||||
const FileMetaData& meta = new_file.second;
|
||||
|
||||
const Status s = ApplyFileAddition(level, meta);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
static BlobFileMetaData::LinkedSsts ApplyLinkedSstChanges(
|
||||
const BlobFileMetaData::LinkedSsts& base,
|
||||
const std::unordered_set<uint64_t>& newly_linked,
|
||||
const std::unordered_set<uint64_t>& newly_unlinked) {
|
||||
BlobFileMetaData::LinkedSsts result(base);
|
||||
|
||||
for (uint64_t sst_file_number : newly_unlinked) {
|
||||
assert(result.find(sst_file_number) != result.end());
|
||||
|
||||
result.erase(sst_file_number);
|
||||
}
|
||||
|
||||
for (uint64_t sst_file_number : newly_linked) {
|
||||
assert(result.find(sst_file_number) == result.end());
|
||||
|
||||
result.emplace(sst_file_number);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static std::shared_ptr<BlobFileMetaData> CreateMetaDataForNewBlobFile(
|
||||
const BlobFileMetaDataDelta& delta) {
|
||||
auto shared_meta = delta.GetSharedMeta();
|
||||
assert(shared_meta);
|
||||
|
||||
auto meta = BlobFileMetaData::Create(std::move(shared_meta),
|
||||
delta.GetAdditionalGarbageCount(),
|
||||
delta.GetAdditionalGarbageBytes());
|
||||
assert(delta.GetNewlyUnlinkedSsts().empty());
|
||||
|
||||
auto meta = BlobFileMetaData::Create(
|
||||
std::move(shared_meta), delta.GetNewlyLinkedSsts(),
|
||||
delta.GetAdditionalGarbageCount(), delta.GetAdditionalGarbageBytes());
|
||||
|
||||
return meta;
|
||||
}
|
||||
@@ -604,8 +710,12 @@ class VersionBuilder::Rep {
|
||||
auto shared_meta = base_meta->GetSharedMeta();
|
||||
assert(shared_meta);
|
||||
|
||||
auto linked_ssts = ApplyLinkedSstChanges(base_meta->GetLinkedSsts(),
|
||||
delta.GetNewlyLinkedSsts(),
|
||||
delta.GetNewlyUnlinkedSsts());
|
||||
|
||||
auto meta = BlobFileMetaData::Create(
|
||||
std::move(shared_meta),
|
||||
std::move(shared_meta), std::move(linked_ssts),
|
||||
base_meta->GetGarbageBlobCount() + delta.GetAdditionalGarbageCount(),
|
||||
base_meta->GetGarbageBlobBytes() + delta.GetAdditionalGarbageBytes());
|
||||
|
||||
@@ -618,7 +728,8 @@ class VersionBuilder::Rep {
|
||||
assert(vstorage);
|
||||
assert(meta);
|
||||
|
||||
if (meta->GetGarbageBlobCount() < meta->GetTotalBlobCount()) {
|
||||
if (meta->GetGarbageBlobCount() < meta->GetTotalBlobCount() ||
|
||||
!meta->GetLinkedSsts().empty()) {
|
||||
vstorage->AddBlobFile(meta);
|
||||
}
|
||||
}
|
||||
@@ -850,12 +961,28 @@ class VersionBuilder::Rep {
|
||||
}
|
||||
|
||||
void MaybeAddFile(VersionStorageInfo* vstorage, int level, FileMetaData* f) {
|
||||
if (levels_[level].deleted_files.count(f->fd.GetNumber()) > 0) {
|
||||
const uint64_t file_number = f->fd.GetNumber();
|
||||
|
||||
const auto& level_state = levels_[level];
|
||||
|
||||
const auto& del_files = level_state.deleted_files;
|
||||
const auto del_it = del_files.find(file_number);
|
||||
|
||||
if (del_it != del_files.end()) {
|
||||
// f is to-be-deleted table file
|
||||
vstorage->RemoveCurrentStats(f);
|
||||
} else {
|
||||
assert(ioptions_);
|
||||
vstorage->AddFile(level, f, ioptions_->info_log);
|
||||
const auto& add_files = level_state.added_files;
|
||||
const auto add_it = add_files.find(file_number);
|
||||
|
||||
// Note: if the file appears both in the base version and in the added
|
||||
// list, the added FileMetaData supersedes the one in the base version.
|
||||
if (add_it != add_files.end() && add_it->second != f) {
|
||||
vstorage->RemoveCurrentStats(f);
|
||||
} else {
|
||||
assert(ioptions_);
|
||||
vstorage->AddFile(level, f, ioptions_->info_log);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
+245
-16
@@ -4,8 +4,11 @@
|
||||
// (found in the LICENSE.Apache file in the root directory).
|
||||
|
||||
#include <cstring>
|
||||
#include <iomanip>
|
||||
#include <memory>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
#include "db/version_edit.h"
|
||||
#include "db/version_set.h"
|
||||
#include "logging/logging.h"
|
||||
@@ -80,13 +83,15 @@ class VersionBuilderTest : public testing::Test {
|
||||
|
||||
void AddBlob(uint64_t blob_file_number, uint64_t total_blob_count,
|
||||
uint64_t total_blob_bytes, std::string checksum_method,
|
||||
std::string checksum_value, uint64_t garbage_blob_count,
|
||||
uint64_t garbage_blob_bytes) {
|
||||
std::string checksum_value,
|
||||
BlobFileMetaData::LinkedSsts linked_ssts,
|
||||
uint64_t garbage_blob_count, uint64_t garbage_blob_bytes) {
|
||||
auto shared_meta = SharedBlobFileMetaData::Create(
|
||||
blob_file_number, total_blob_count, total_blob_bytes,
|
||||
std::move(checksum_method), std::move(checksum_value));
|
||||
auto meta = BlobFileMetaData::Create(
|
||||
std::move(shared_meta), garbage_blob_count, garbage_blob_bytes);
|
||||
auto meta =
|
||||
BlobFileMetaData::Create(std::move(shared_meta), std::move(linked_ssts),
|
||||
garbage_blob_count, garbage_blob_bytes);
|
||||
|
||||
vstorage_.AddBlobFile(std::move(meta));
|
||||
}
|
||||
@@ -642,6 +647,7 @@ TEST_F(VersionBuilderTest, ApplyBlobFileAddition) {
|
||||
ASSERT_EQ(new_meta->GetTotalBlobBytes(), total_blob_bytes);
|
||||
ASSERT_EQ(new_meta->GetChecksumMethod(), checksum_method);
|
||||
ASSERT_EQ(new_meta->GetChecksumValue(), checksum_value);
|
||||
ASSERT_TRUE(new_meta->GetLinkedSsts().empty());
|
||||
ASSERT_EQ(new_meta->GetGarbageBlobCount(), 0);
|
||||
ASSERT_EQ(new_meta->GetGarbageBlobBytes(), 0);
|
||||
}
|
||||
@@ -658,7 +664,8 @@ TEST_F(VersionBuilderTest, ApplyBlobFileAdditionAlreadyInBase) {
|
||||
constexpr uint64_t garbage_blob_bytes = 456789;
|
||||
|
||||
AddBlob(blob_file_number, total_blob_count, total_blob_bytes, checksum_method,
|
||||
checksum_value, garbage_blob_count, garbage_blob_bytes);
|
||||
checksum_value, BlobFileMetaData::LinkedSsts(), garbage_blob_count,
|
||||
garbage_blob_bytes);
|
||||
|
||||
EnvOptions env_options;
|
||||
constexpr TableCache* table_cache = nullptr;
|
||||
@@ -717,7 +724,8 @@ TEST_F(VersionBuilderTest, ApplyBlobFileGarbageFileInBase) {
|
||||
constexpr uint64_t garbage_blob_bytes = 456789;
|
||||
|
||||
AddBlob(blob_file_number, total_blob_count, total_blob_bytes, checksum_method,
|
||||
checksum_value, garbage_blob_count, garbage_blob_bytes);
|
||||
checksum_value, BlobFileMetaData::LinkedSsts(), garbage_blob_count,
|
||||
garbage_blob_bytes);
|
||||
|
||||
const auto meta =
|
||||
GetBlobFileMetaData(vstorage_.GetBlobFiles(), blob_file_number);
|
||||
@@ -759,6 +767,7 @@ TEST_F(VersionBuilderTest, ApplyBlobFileGarbageFileInBase) {
|
||||
ASSERT_EQ(new_meta->GetTotalBlobBytes(), total_blob_bytes);
|
||||
ASSERT_EQ(new_meta->GetChecksumMethod(), checksum_method);
|
||||
ASSERT_EQ(new_meta->GetChecksumValue(), checksum_value);
|
||||
ASSERT_TRUE(new_meta->GetLinkedSsts().empty());
|
||||
ASSERT_EQ(new_meta->GetGarbageBlobCount(),
|
||||
garbage_blob_count + new_garbage_blob_count);
|
||||
ASSERT_EQ(new_meta->GetGarbageBlobBytes(),
|
||||
@@ -816,6 +825,7 @@ TEST_F(VersionBuilderTest, ApplyBlobFileGarbageFileAdditionApplied) {
|
||||
ASSERT_EQ(new_meta->GetTotalBlobBytes(), total_blob_bytes);
|
||||
ASSERT_EQ(new_meta->GetChecksumMethod(), checksum_method);
|
||||
ASSERT_EQ(new_meta->GetChecksumValue(), checksum_value);
|
||||
ASSERT_TRUE(new_meta->GetLinkedSsts().empty());
|
||||
ASSERT_EQ(new_meta->GetGarbageBlobCount(), garbage_blob_count);
|
||||
ASSERT_EQ(new_meta->GetGarbageBlobBytes(), garbage_blob_bytes);
|
||||
}
|
||||
@@ -856,8 +866,8 @@ TEST_F(VersionBuilderTest, SaveBlobFilesTo) {
|
||||
|
||||
AddBlob(blob_file_number, total_blob_count, total_blob_bytes,
|
||||
/* checksum_method */ std::string(),
|
||||
/* checksum_value */ std::string(), garbage_blob_count,
|
||||
garbage_blob_bytes);
|
||||
/* checksum_value */ std::string(), BlobFileMetaData::LinkedSsts(),
|
||||
garbage_blob_count, garbage_blob_bytes);
|
||||
}
|
||||
|
||||
EnvOptions env_options;
|
||||
@@ -946,7 +956,7 @@ TEST_F(VersionBuilderTest, CheckConsistencyForBlobFiles) {
|
||||
AddBlob(/* blob_file_number */ 16, /* total_blob_count */ 1000,
|
||||
/* total_blob_bytes */ 1000000,
|
||||
/* checksum_method */ std::string(),
|
||||
/* checksum_value */ std::string(),
|
||||
/* checksum_value */ std::string(), BlobFileMetaData::LinkedSsts{1},
|
||||
/* garbage_blob_count */ 500, /* garbage_blob_bytes */ 300000);
|
||||
|
||||
UpdateVersionStorageInfo();
|
||||
@@ -995,9 +1005,9 @@ TEST_F(VersionBuilderTest, CheckConsistencyForBlobFiles) {
|
||||
UnrefFilesInVersion(&new_vstorage);
|
||||
}
|
||||
|
||||
TEST_F(VersionBuilderTest, CheckConsistencyForBlobFilesNotInVersion) {
|
||||
// Initialize base version. The table file points to a blob file that is
|
||||
// not in this version.
|
||||
TEST_F(VersionBuilderTest, CheckConsistencyForBlobFilesInconsistentLinks) {
|
||||
// Initialize base version. Links between the table file and the blob file
|
||||
// are inconsistent.
|
||||
|
||||
Add(/* level */ 1, /* file_number */ 1, /* smallest */ "150",
|
||||
/* largest */ "200", /* file_size */ 100,
|
||||
@@ -1009,7 +1019,7 @@ TEST_F(VersionBuilderTest, CheckConsistencyForBlobFilesNotInVersion) {
|
||||
AddBlob(/* blob_file_number */ 16, /* total_blob_count */ 1000,
|
||||
/* total_blob_bytes */ 1000000,
|
||||
/* checksum_method */ std::string(),
|
||||
/* checksum_value */ std::string(),
|
||||
/* checksum_value */ std::string(), BlobFileMetaData::LinkedSsts{1},
|
||||
/* garbage_blob_count */ 500, /* garbage_blob_bytes */ 300000);
|
||||
|
||||
UpdateVersionStorageInfo();
|
||||
@@ -1029,8 +1039,9 @@ TEST_F(VersionBuilderTest, CheckConsistencyForBlobFilesNotInVersion) {
|
||||
|
||||
const Status s = builder.SaveTo(&new_vstorage);
|
||||
ASSERT_TRUE(s.IsCorruption());
|
||||
ASSERT_TRUE(
|
||||
std::strstr(s.getState(), "Blob file #256 is not part of this version"));
|
||||
ASSERT_TRUE(std::strstr(
|
||||
s.getState(),
|
||||
"Links are inconsistent between table files and blob file #16"));
|
||||
|
||||
UnrefFilesInVersion(&new_vstorage);
|
||||
}
|
||||
@@ -1049,7 +1060,7 @@ TEST_F(VersionBuilderTest, CheckConsistencyForBlobFilesAllGarbage) {
|
||||
AddBlob(/* blob_file_number */ 16, /* total_blob_count */ 1000,
|
||||
/* total_blob_bytes */ 1000000,
|
||||
/* checksum_method */ std::string(),
|
||||
/* checksum_value */ std::string(),
|
||||
/* checksum_value */ std::string(), BlobFileMetaData::LinkedSsts{1},
|
||||
/* garbage_blob_count */ 1000, /* garbage_blob_bytes */ 1000000);
|
||||
|
||||
UpdateVersionStorageInfo();
|
||||
@@ -1075,6 +1086,224 @@ TEST_F(VersionBuilderTest, CheckConsistencyForBlobFilesAllGarbage) {
|
||||
UnrefFilesInVersion(&new_vstorage);
|
||||
}
|
||||
|
||||
TEST_F(VersionBuilderTest, CheckConsistencyForBlobFilesAllGarbageLinkedSsts) {
|
||||
// Initialize base version, with a table file pointing to a blob file
|
||||
// that has no garbage at this point.
|
||||
|
||||
Add(/* level */ 1, /* file_number */ 1, /* smallest */ "150",
|
||||
/* largest */ "200", /* file_size */ 100,
|
||||
/* path_id */ 0, /* smallest_seq */ 100, /* largest_seq */ 100,
|
||||
/* num_entries */ 0, /* num_deletions */ 0,
|
||||
/* sampled */ false, /* smallest_seqno */ 100, /* largest_seqno */ 100,
|
||||
/* oldest_blob_file_number */ 16);
|
||||
|
||||
AddBlob(/* blob_file_number */ 16, /* total_blob_count */ 1000,
|
||||
/* total_blob_bytes */ 1000000,
|
||||
/* checksum_method */ std::string(),
|
||||
/* checksum_value */ std::string(), BlobFileMetaData::LinkedSsts{1},
|
||||
/* garbage_blob_count */ 0, /* garbage_blob_bytes */ 0);
|
||||
|
||||
UpdateVersionStorageInfo();
|
||||
|
||||
// Mark the entire blob file garbage but do not remove the linked SST.
|
||||
EnvOptions env_options;
|
||||
constexpr TableCache* table_cache = nullptr;
|
||||
constexpr VersionSet* version_set = nullptr;
|
||||
|
||||
VersionBuilder builder(env_options, &ioptions_, table_cache, &vstorage_,
|
||||
version_set);
|
||||
|
||||
VersionEdit edit;
|
||||
|
||||
edit.AddBlobFileGarbage(/* blob_file_number */ 16,
|
||||
/* garbage_blob_count */ 1000,
|
||||
/* garbage_blob_bytes */ 1000000);
|
||||
|
||||
ASSERT_OK(builder.Apply(&edit));
|
||||
|
||||
// Save to a new version in order to trigger consistency checks.
|
||||
constexpr bool force_consistency_checks = true;
|
||||
VersionStorageInfo new_vstorage(&icmp_, ucmp_, options_.num_levels,
|
||||
kCompactionStyleLevel, &vstorage_,
|
||||
force_consistency_checks);
|
||||
|
||||
const Status s = builder.SaveTo(&new_vstorage);
|
||||
ASSERT_TRUE(s.IsCorruption());
|
||||
ASSERT_TRUE(
|
||||
std::strstr(s.getState(), "Blob file #16 consists entirely of garbage"));
|
||||
|
||||
UnrefFilesInVersion(&new_vstorage);
|
||||
}
|
||||
|
||||
TEST_F(VersionBuilderTest, MaintainLinkedSstsForBlobFiles) {
|
||||
// Initialize base version. Table files 1..10 are linked to blob files 1..5,
|
||||
// while table files 11..20 are not linked to any blob files.
|
||||
|
||||
for (uint64_t i = 1; i <= 10; ++i) {
|
||||
std::ostringstream oss;
|
||||
oss << std::setw(2) << std::setfill('0') << i;
|
||||
|
||||
const std::string key = oss.str();
|
||||
|
||||
Add(/* level */ 1, /* file_number */ i, /* smallest */ key.c_str(),
|
||||
/* largest */ key.c_str(), /* file_size */ 100,
|
||||
/* path_id */ 0, /* smallest_seq */ i * 100, /* largest_seq */ i * 100,
|
||||
/* num_entries */ 0, /* num_deletions */ 0,
|
||||
/* sampled */ false, /* smallest_seqno */ i * 100,
|
||||
/* largest_seqno */ i * 100,
|
||||
/* oldest_blob_file_number */ ((i - 1) % 5) + 1);
|
||||
}
|
||||
|
||||
for (uint64_t i = 1; i <= 5; ++i) {
|
||||
AddBlob(/* blob_file_number */ i, /* total_blob_count */ 2000,
|
||||
/* total_blob_bytes */ 2000000,
|
||||
/* checksum_method */ std::string(),
|
||||
/* checksum_value */ std::string(),
|
||||
BlobFileMetaData::LinkedSsts{i, i + 5},
|
||||
/* garbage_blob_count */ 1000, /* garbage_blob_bytes */ 1000000);
|
||||
}
|
||||
|
||||
for (uint64_t i = 11; i <= 20; ++i) {
|
||||
std::ostringstream oss;
|
||||
oss << std::setw(2) << std::setfill('0') << i;
|
||||
|
||||
const std::string key = oss.str();
|
||||
|
||||
Add(/* level */ 1, /* file_number */ i, /* smallest */ key.c_str(),
|
||||
/* largest */ key.c_str(), /* file_size */ 100,
|
||||
/* path_id */ 0, /* smallest_seq */ i * 100, /* largest_seq */ i * 100,
|
||||
/* num_entries */ 0, /* num_deletions */ 0,
|
||||
/* sampled */ false, /* smallest_seqno */ i * 100,
|
||||
/* largest_seqno */ i * 100, kInvalidBlobFileNumber);
|
||||
}
|
||||
|
||||
UpdateVersionStorageInfo();
|
||||
|
||||
{
|
||||
const auto& blob_files = vstorage_.GetBlobFiles();
|
||||
ASSERT_EQ(blob_files.size(), 5);
|
||||
|
||||
const std::vector<BlobFileMetaData::LinkedSsts> expected_linked_ssts{
|
||||
{1, 6}, {2, 7}, {3, 8}, {4, 9}, {5, 10}};
|
||||
|
||||
for (size_t i = 0; i < 5; ++i) {
|
||||
const auto meta =
|
||||
GetBlobFileMetaData(blob_files, /* blob_file_number */ i + 1);
|
||||
ASSERT_NE(meta, nullptr);
|
||||
ASSERT_EQ(meta->GetLinkedSsts(), expected_linked_ssts[i]);
|
||||
}
|
||||
}
|
||||
|
||||
VersionEdit edit;
|
||||
|
||||
// Add an SST that references a blob file.
|
||||
edit.AddFile(
|
||||
/* level */ 1, /* file_number */ 21, /* path_id */ 0,
|
||||
/* file_size */ 100, /* smallest */ GetInternalKey("21", 2100),
|
||||
/* largest */ GetInternalKey("21", 2100), /* smallest_seqno */ 2100,
|
||||
/* largest_seqno */ 2100, /* marked_for_compaction */ false,
|
||||
/* oldest_blob_file_number */ 1, kUnknownOldestAncesterTime,
|
||||
kUnknownFileCreationTime, kUnknownFileChecksum,
|
||||
kUnknownFileChecksumFuncName);
|
||||
|
||||
// Add an SST that does not reference any blob files.
|
||||
edit.AddFile(
|
||||
/* level */ 1, /* file_number */ 22, /* path_id */ 0,
|
||||
/* file_size */ 100, /* smallest */ GetInternalKey("22", 2200),
|
||||
/* largest */ GetInternalKey("22", 2200), /* smallest_seqno */ 2200,
|
||||
/* largest_seqno */ 2200, /* marked_for_compaction */ false,
|
||||
kInvalidBlobFileNumber, kUnknownOldestAncesterTime,
|
||||
kUnknownFileCreationTime, kUnknownFileChecksum,
|
||||
kUnknownFileChecksumFuncName);
|
||||
|
||||
// Delete a file that references a blob file.
|
||||
edit.DeleteFile(/* level */ 1, /* file_number */ 6);
|
||||
|
||||
// Delete a file that does not reference any blob files.
|
||||
edit.DeleteFile(/* level */ 1, /* file_number */ 16);
|
||||
|
||||
// Trivially move a file that references a blob file. Note that we save
|
||||
// the original BlobFileMetaData object so we can check that no new object
|
||||
// gets created.
|
||||
auto meta3 =
|
||||
GetBlobFileMetaData(vstorage_.GetBlobFiles(), /* blob_file_number */ 3);
|
||||
|
||||
edit.DeleteFile(/* level */ 1, /* file_number */ 3);
|
||||
edit.AddFile(/* level */ 2, /* file_number */ 3, /* path_id */ 0,
|
||||
/* file_size */ 100, /* smallest */ GetInternalKey("03", 300),
|
||||
/* largest */ GetInternalKey("03", 300),
|
||||
/* smallest_seqno */ 300,
|
||||
/* largest_seqno */ 300, /* marked_for_compaction */ false,
|
||||
/* oldest_blob_file_number */ 3, kUnknownOldestAncesterTime,
|
||||
kUnknownFileCreationTime, kUnknownFileChecksum,
|
||||
kUnknownFileChecksumFuncName);
|
||||
|
||||
// Trivially move a file that does not reference any blob files.
|
||||
edit.DeleteFile(/* level */ 1, /* file_number */ 13);
|
||||
edit.AddFile(/* level */ 2, /* file_number */ 13, /* path_id */ 0,
|
||||
/* file_size */ 100, /* smallest */ GetInternalKey("13", 1300),
|
||||
/* largest */ GetInternalKey("13", 1300),
|
||||
/* smallest_seqno */ 1300,
|
||||
/* largest_seqno */ 1300, /* marked_for_compaction */ false,
|
||||
kInvalidBlobFileNumber, kUnknownOldestAncesterTime,
|
||||
kUnknownFileCreationTime, kUnknownFileChecksum,
|
||||
kUnknownFileChecksumFuncName);
|
||||
|
||||
// Add one more SST file that references a blob file, then promptly
|
||||
// delete it in a second version edit before the new version gets saved.
|
||||
// This file should not show up as linked to the blob file in the new version.
|
||||
edit.AddFile(/* level */ 1, /* file_number */ 23, /* path_id */ 0,
|
||||
/* file_size */ 100, /* smallest */ GetInternalKey("23", 2300),
|
||||
/* largest */ GetInternalKey("23", 2300),
|
||||
/* smallest_seqno */ 2300,
|
||||
/* largest_seqno */ 2300, /* marked_for_compaction */ false,
|
||||
/* oldest_blob_file_number */ 5, kUnknownOldestAncesterTime,
|
||||
kUnknownFileCreationTime, kUnknownFileChecksum,
|
||||
kUnknownFileChecksumFuncName);
|
||||
|
||||
VersionEdit edit2;
|
||||
|
||||
edit2.DeleteFile(/* level */ 1, /* file_number */ 23);
|
||||
|
||||
EnvOptions env_options;
|
||||
constexpr TableCache* table_cache = nullptr;
|
||||
constexpr VersionSet* version_set = nullptr;
|
||||
|
||||
VersionBuilder builder(env_options, &ioptions_, table_cache, &vstorage_,
|
||||
version_set);
|
||||
|
||||
ASSERT_OK(builder.Apply(&edit));
|
||||
ASSERT_OK(builder.Apply(&edit2));
|
||||
|
||||
constexpr bool force_consistency_checks = true;
|
||||
VersionStorageInfo new_vstorage(&icmp_, ucmp_, options_.num_levels,
|
||||
kCompactionStyleLevel, &vstorage_,
|
||||
force_consistency_checks);
|
||||
|
||||
ASSERT_OK(builder.SaveTo(&new_vstorage));
|
||||
|
||||
{
|
||||
const auto& blob_files = new_vstorage.GetBlobFiles();
|
||||
ASSERT_EQ(blob_files.size(), 5);
|
||||
|
||||
const std::vector<BlobFileMetaData::LinkedSsts> expected_linked_ssts{
|
||||
{1, 21}, {2, 7}, {3, 8}, {4, 9}, {5, 10}};
|
||||
|
||||
for (size_t i = 0; i < 5; ++i) {
|
||||
const auto meta =
|
||||
GetBlobFileMetaData(blob_files, /* blob_file_number */ i + 1);
|
||||
ASSERT_NE(meta, nullptr);
|
||||
ASSERT_EQ(meta->GetLinkedSsts(), expected_linked_ssts[i]);
|
||||
}
|
||||
|
||||
// Make sure that no new BlobFileMetaData got created for the blob file
|
||||
// affected by the trivial move.
|
||||
ASSERT_EQ(GetBlobFileMetaData(blob_files, /* blob_file_number */ 3), meta3);
|
||||
}
|
||||
|
||||
UnrefFilesInVersion(&new_vstorage);
|
||||
}
|
||||
|
||||
TEST_F(VersionBuilderTest, CheckConsistencyForFileDeletedTwice) {
|
||||
Add(0, 1U, "150", "200", 100U);
|
||||
UpdateVersionStorageInfo();
|
||||
|
||||
@@ -27,12 +27,17 @@ VersionEditHandler::VersionEditHandler(
|
||||
assert(version_set_ != nullptr);
|
||||
}
|
||||
|
||||
Status VersionEditHandler::Iterate(log::Reader& reader, std::string* db_id) {
|
||||
void VersionEditHandler::Iterate(log::Reader& reader, Status* log_read_status,
|
||||
std::string* db_id) {
|
||||
Slice record;
|
||||
std::string scratch;
|
||||
assert(log_read_status);
|
||||
assert(log_read_status->ok());
|
||||
|
||||
size_t recovered_edits = 0;
|
||||
Status s = Initialize();
|
||||
while (reader.ReadRecord(&record, &scratch) && s.ok()) {
|
||||
while (s.ok() && reader.ReadRecord(&record, &scratch) &&
|
||||
log_read_status->ok()) {
|
||||
VersionEdit edit;
|
||||
s = edit.DecodeFrom(record);
|
||||
if (!s.ok()) {
|
||||
@@ -70,13 +75,15 @@ Status VersionEditHandler::Iterate(log::Reader& reader, std::string* db_id) {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!log_read_status->ok()) {
|
||||
s = *log_read_status;
|
||||
}
|
||||
|
||||
CheckIterationResult(reader, &s);
|
||||
|
||||
if (!s.ok()) {
|
||||
status_ = s;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
Status VersionEditHandler::Initialize() {
|
||||
@@ -404,13 +411,12 @@ Status VersionEditHandler::LoadTables(ColumnFamilyData* cfd,
|
||||
bool is_initial_load) {
|
||||
assert(cfd != nullptr);
|
||||
assert(!cfd->IsDropped());
|
||||
Status s;
|
||||
auto builder_iter = builders_.find(cfd->GetID());
|
||||
assert(builder_iter != builders_.end());
|
||||
assert(builder_iter->second != nullptr);
|
||||
VersionBuilder* builder = builder_iter->second->version_builder();
|
||||
assert(builder);
|
||||
s = builder->LoadTableHandlers(
|
||||
Status s = builder->LoadTableHandlers(
|
||||
cfd->internal_stats(),
|
||||
version_set_->db_options_->max_file_opening_threads,
|
||||
prefetch_index_and_filter_in_cache, is_initial_load,
|
||||
@@ -551,6 +557,8 @@ Status VersionEditHandlerPointInTime::MaybeCreateVersion(
|
||||
if (s.IsPathNotFound() || s.IsNotFound() || s.IsCorruption()) {
|
||||
missing_files.insert(file_num);
|
||||
s = Status::OK();
|
||||
} else if (!s.ok()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
bool missing_info = !version_edit_params_.has_log_number_ ||
|
||||
@@ -558,8 +566,9 @@ Status VersionEditHandlerPointInTime::MaybeCreateVersion(
|
||||
!version_edit_params_.has_last_sequence_;
|
||||
|
||||
// Create version before apply edit
|
||||
if (!missing_info && ((!missing_files.empty() && !prev_has_missing_files) ||
|
||||
(missing_files.empty() && force_create_version))) {
|
||||
if (s.ok() && !missing_info &&
|
||||
((!missing_files.empty() && !prev_has_missing_files) ||
|
||||
(missing_files.empty() && force_create_version))) {
|
||||
auto builder_iter = builders_.find(cfd->GetID());
|
||||
assert(builder_iter != builders_.end());
|
||||
auto* builder = builder_iter->second->version_builder();
|
||||
|
||||
@@ -40,7 +40,8 @@ class VersionEditHandler {
|
||||
|
||||
virtual ~VersionEditHandler() {}
|
||||
|
||||
Status Iterate(log::Reader& reader, std::string* db_id);
|
||||
void Iterate(log::Reader& reader, Status* log_read_status,
|
||||
std::string* db_id);
|
||||
|
||||
const Status& status() const { return status_; }
|
||||
|
||||
|
||||
+30
-25
@@ -10,6 +10,7 @@
|
||||
#include "db/version_set.h"
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cinttypes>
|
||||
@@ -19,6 +20,7 @@
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "compaction/compaction.h"
|
||||
#include "db/internal_stats.h"
|
||||
#include "db/log_reader.h"
|
||||
@@ -50,6 +52,7 @@
|
||||
#include "table/table_reader.h"
|
||||
#include "table/two_level_iterator.h"
|
||||
#include "test_util/sync_point.h"
|
||||
#include "util/cast_util.h"
|
||||
#include "util/coding.h"
|
||||
#include "util/stop_watch.h"
|
||||
#include "util/string_util.h"
|
||||
@@ -4444,24 +4447,26 @@ Status VersionSet::GetCurrentManifestPath(const std::string& dbname,
|
||||
if (dbname.back() != '/') {
|
||||
manifest_path->push_back('/');
|
||||
}
|
||||
*manifest_path += fname;
|
||||
manifest_path->append(fname);
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status VersionSet::ReadAndRecover(
|
||||
log::Reader* reader, AtomicGroupReadBuffer* read_buffer,
|
||||
log::Reader& reader, AtomicGroupReadBuffer* read_buffer,
|
||||
const std::unordered_map<std::string, ColumnFamilyOptions>& name_to_options,
|
||||
std::unordered_map<int, std::string>& column_families_not_found,
|
||||
std::unordered_map<uint32_t, std::unique_ptr<BaseReferencedVersionBuilder>>&
|
||||
builders,
|
||||
VersionEditParams* version_edit_params, std::string* db_id) {
|
||||
assert(reader != nullptr);
|
||||
Status* log_read_status, VersionEditParams* version_edit_params,
|
||||
std::string* db_id) {
|
||||
assert(read_buffer != nullptr);
|
||||
assert(log_read_status != nullptr);
|
||||
Status s;
|
||||
Slice record;
|
||||
std::string scratch;
|
||||
size_t recovered_edits = 0;
|
||||
while (reader->ReadRecord(&record, &scratch) && s.ok()) {
|
||||
while (s.ok() && reader.ReadRecord(&record, &scratch) &&
|
||||
log_read_status->ok()) {
|
||||
VersionEdit edit;
|
||||
s = edit.DecodeFrom(record);
|
||||
if (!s.ok()) {
|
||||
@@ -4505,6 +4510,9 @@ Status VersionSet::ReadAndRecover(
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!log_read_status->ok()) {
|
||||
s = *log_read_status;
|
||||
}
|
||||
if (!s.ok()) {
|
||||
// Clear the buffer if we fail to decode/apply an edit.
|
||||
read_buffer->Clear();
|
||||
@@ -4551,8 +4559,7 @@ Status VersionSet::Recover(
|
||||
db_options_->log_readahead_size));
|
||||
}
|
||||
|
||||
std::unordered_map<uint32_t, std::unique_ptr<BaseReferencedVersionBuilder>>
|
||||
builders;
|
||||
VersionBuilderMap builders;
|
||||
|
||||
// add default column family
|
||||
auto default_cf_iter = cf_name_to_options.find(kDefaultColumnFamilyName);
|
||||
@@ -4574,12 +4581,13 @@ Status VersionSet::Recover(
|
||||
VersionEditParams version_edit_params;
|
||||
{
|
||||
VersionSet::LogReporter reporter;
|
||||
reporter.status = &s;
|
||||
Status log_read_status;
|
||||
reporter.status = &log_read_status;
|
||||
log::Reader reader(nullptr, std::move(manifest_file_reader), &reporter,
|
||||
true /* checksum */, 0 /* log_number */);
|
||||
AtomicGroupReadBuffer read_buffer;
|
||||
s = ReadAndRecover(&reader, &read_buffer, cf_name_to_options,
|
||||
column_families_not_found, builders,
|
||||
s = ReadAndRecover(reader, &read_buffer, cf_name_to_options,
|
||||
column_families_not_found, builders, &log_read_status,
|
||||
&version_edit_params, db_id);
|
||||
current_manifest_file_size = reader.GetReadOffset();
|
||||
assert(current_manifest_file_size != 0);
|
||||
@@ -4845,21 +4853,20 @@ Status VersionSet::TryRecoverFromOneManifest(
|
||||
db_options_->log_readahead_size));
|
||||
}
|
||||
|
||||
assert(s.ok());
|
||||
VersionSet::LogReporter reporter;
|
||||
reporter.status = &s;
|
||||
log::Reader reader(nullptr, std::move(manifest_file_reader), &reporter,
|
||||
/*checksum=*/true, /*log_num=*/0);
|
||||
{
|
||||
VersionEditHandlerPointInTime handler_pit(read_only, column_families,
|
||||
const_cast<VersionSet*>(this));
|
||||
VersionEditHandlerPointInTime handler_pit(read_only, column_families,
|
||||
const_cast<VersionSet*>(this));
|
||||
|
||||
s = handler_pit.Iterate(reader, db_id);
|
||||
handler_pit.Iterate(reader, &s, db_id);
|
||||
|
||||
assert(nullptr != has_missing_table_file);
|
||||
*has_missing_table_file = handler_pit.HasMissingFiles();
|
||||
}
|
||||
assert(nullptr != has_missing_table_file);
|
||||
*has_missing_table_file = handler_pit.HasMissingFiles();
|
||||
|
||||
return s;
|
||||
return handler_pit.status();
|
||||
}
|
||||
|
||||
Status VersionSet::ListColumnFamilies(std::vector<std::string>* column_families,
|
||||
@@ -5980,8 +5987,7 @@ Status ReactiveVersionSet::Recover(
|
||||
// In recovery, nobody else can access it, so it's fine to set it to be
|
||||
// initialized earlier.
|
||||
default_cfd->set_initialized();
|
||||
std::unordered_map<uint32_t, std::unique_ptr<BaseReferencedVersionBuilder>>
|
||||
builders;
|
||||
VersionBuilderMap builders;
|
||||
std::unordered_map<int, std::string> column_families_not_found;
|
||||
builders.insert(
|
||||
std::make_pair(0, std::unique_ptr<BaseReferencedVersionBuilder>(
|
||||
@@ -5989,7 +5995,7 @@ Status ReactiveVersionSet::Recover(
|
||||
|
||||
manifest_reader_status->reset(new Status());
|
||||
manifest_reporter->reset(new LogReporter());
|
||||
static_cast<LogReporter*>(manifest_reporter->get())->status =
|
||||
static_cast_with_check<LogReporter>(manifest_reporter->get())->status =
|
||||
manifest_reader_status->get();
|
||||
Status s = MaybeSwitchManifest(manifest_reporter->get(), manifest_reader);
|
||||
log::Reader* reader = manifest_reader->get();
|
||||
@@ -5998,10 +6004,9 @@ Status ReactiveVersionSet::Recover(
|
||||
VersionEdit version_edit;
|
||||
while (s.ok() && retry < 1) {
|
||||
assert(reader != nullptr);
|
||||
Slice record;
|
||||
std::string scratch;
|
||||
s = ReadAndRecover(reader, &read_buffer_, cf_name_to_options,
|
||||
column_families_not_found, builders, &version_edit);
|
||||
s = ReadAndRecover(*reader, &read_buffer_, cf_name_to_options,
|
||||
column_families_not_found, builders,
|
||||
manifest_reader_status->get(), &version_edit);
|
||||
if (s.ok()) {
|
||||
bool enough = version_edit.has_next_file_number_ &&
|
||||
version_edit.has_log_number_ &&
|
||||
|
||||
+11
-5
@@ -1165,6 +1165,10 @@ class VersionSet {
|
||||
void SetIOStatusOK() { io_status_ = IOStatus::OK(); }
|
||||
|
||||
protected:
|
||||
using VersionBuilderMap =
|
||||
std::unordered_map<uint32_t,
|
||||
std::unique_ptr<BaseReferencedVersionBuilder>>;
|
||||
|
||||
struct ManifestWriter;
|
||||
|
||||
friend class Version;
|
||||
@@ -1176,7 +1180,9 @@ class VersionSet {
|
||||
struct LogReporter : public log::Reader::Reporter {
|
||||
Status* status;
|
||||
virtual void Corruption(size_t /*bytes*/, const Status& s) override {
|
||||
if (this->status->ok()) *this->status = s;
|
||||
if (status->ok()) {
|
||||
*status = s;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1207,13 +1213,14 @@ class VersionSet {
|
||||
const VersionEdit* edit);
|
||||
|
||||
Status ReadAndRecover(
|
||||
log::Reader* reader, AtomicGroupReadBuffer* read_buffer,
|
||||
log::Reader& reader, AtomicGroupReadBuffer* read_buffer,
|
||||
const std::unordered_map<std::string, ColumnFamilyOptions>&
|
||||
name_to_options,
|
||||
std::unordered_map<int, std::string>& column_families_not_found,
|
||||
std::unordered_map<
|
||||
uint32_t, std::unique_ptr<BaseReferencedVersionBuilder>>& builders,
|
||||
VersionEditParams* version_edit, std::string* db_id = nullptr);
|
||||
Status* log_read_status, VersionEditParams* version_edit,
|
||||
std::string* db_id = nullptr);
|
||||
|
||||
// REQUIRES db mutex
|
||||
Status ApplyOneVersionEditToBuilder(
|
||||
@@ -1342,8 +1349,7 @@ class ReactiveVersionSet : public VersionSet {
|
||||
std::unique_ptr<log::FragmentBufferedReader>* manifest_reader);
|
||||
|
||||
private:
|
||||
std::unordered_map<uint32_t, std::unique_ptr<BaseReferencedVersionBuilder>>
|
||||
active_version_builders_;
|
||||
VersionBuilderMap active_version_builders_;
|
||||
AtomicGroupReadBuffer read_buffer_;
|
||||
// Number of version edits to skip by ReadAndApply at the beginning of a new
|
||||
// MANIFEST created by primary.
|
||||
|
||||
@@ -146,6 +146,7 @@ DECLARE_int32(reopen);
|
||||
DECLARE_double(bloom_bits);
|
||||
DECLARE_bool(use_block_based_filter);
|
||||
DECLARE_bool(partition_filters);
|
||||
DECLARE_bool(optimize_filters_for_memory);
|
||||
DECLARE_int32(index_type);
|
||||
DECLARE_string(db);
|
||||
DECLARE_string(secondaries_base);
|
||||
@@ -161,7 +162,7 @@ DECLARE_bool(statistics);
|
||||
DECLARE_bool(sync);
|
||||
DECLARE_bool(use_fsync);
|
||||
DECLARE_int32(kill_random_test);
|
||||
DECLARE_string(kill_prefix_blacklist);
|
||||
DECLARE_string(kill_exclude_prefixes);
|
||||
DECLARE_bool(disable_wal);
|
||||
DECLARE_uint64(recycle_log_file_num);
|
||||
DECLARE_int64(target_file_size_base);
|
||||
@@ -236,6 +237,10 @@ DECLARE_double(blob_db_gc_cutoff);
|
||||
DECLARE_int32(approximate_size_one_in);
|
||||
DECLARE_bool(sync_fault_injection);
|
||||
|
||||
DECLARE_bool(best_efforts_recovery);
|
||||
DECLARE_bool(skip_verifydb);
|
||||
DECLARE_bool(enable_compaction_filter);
|
||||
|
||||
const long KB = 1024;
|
||||
const int kRandomValueMaxFactor = 3;
|
||||
const int kValueMaxLen = 100;
|
||||
@@ -436,19 +441,10 @@ extern inline bool GetIntVal(std::string big_endian_key, uint64_t* key_p) {
|
||||
|
||||
assert(size_key <= key_gen_ctx.weights.size() * sizeof(uint64_t));
|
||||
|
||||
// Pad with zeros to make it a multiple of 8. This function may be called
|
||||
// with a prefix, in which case we return the first index that falls
|
||||
// inside or outside that prefix, dependeing on whether the prefix is
|
||||
// the start of upper bound of a scan
|
||||
unsigned int pad = sizeof(uint64_t) - (size_key % sizeof(uint64_t));
|
||||
if (pad < sizeof(uint64_t)) {
|
||||
big_endian_key.append(pad, '\0');
|
||||
size_key += pad;
|
||||
}
|
||||
|
||||
std::string little_endian_key;
|
||||
little_endian_key.resize(size_key);
|
||||
for (size_t start = 0; start < size_key; start += sizeof(uint64_t)) {
|
||||
for (size_t start = 0; start + sizeof(uint64_t) <= size_key;
|
||||
start += sizeof(uint64_t)) {
|
||||
size_t end = start + sizeof(uint64_t);
|
||||
for (size_t i = 0; i < sizeof(uint64_t); ++i) {
|
||||
little_endian_key[start + i] = big_endian_key[end - 1 - i];
|
||||
@@ -467,17 +463,40 @@ extern inline bool GetIntVal(std::string big_endian_key, uint64_t* key_p) {
|
||||
uint64_t pfx = prefixes[i];
|
||||
key += (pfx / key_gen_ctx.weights[i]) * key_gen_ctx.window +
|
||||
pfx % key_gen_ctx.weights[i];
|
||||
if (i < prefixes.size() - 1) {
|
||||
// The encoding writes a `key_gen_ctx.weights[i] - 1` that counts for
|
||||
// `key_gen_ctx.weights[i]` when there are more prefixes to come. So we
|
||||
// need to add back the one here as we're at a non-last prefix.
|
||||
++key;
|
||||
}
|
||||
}
|
||||
*key_p = key;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Given a string prefix, map it to the first corresponding index in the
|
||||
// expected values buffer.
|
||||
inline bool GetFirstIntValInPrefix(std::string big_endian_prefix,
|
||||
uint64_t* key_p) {
|
||||
size_t size_key = big_endian_prefix.size();
|
||||
// Pad with zeros to make it a multiple of 8. This function may be called
|
||||
// with a prefix, in which case we return the first index that falls
|
||||
// inside or outside that prefix, dependeing on whether the prefix is
|
||||
// the start of upper bound of a scan
|
||||
unsigned int pad = sizeof(uint64_t) - (size_key % sizeof(uint64_t));
|
||||
if (pad < sizeof(uint64_t)) {
|
||||
big_endian_prefix.append(pad, '\0');
|
||||
}
|
||||
return GetIntVal(std::move(big_endian_prefix), key_p);
|
||||
}
|
||||
|
||||
extern inline uint64_t GetPrefixKeyCount(const std::string& prefix,
|
||||
const std::string& ub) {
|
||||
uint64_t start = 0;
|
||||
uint64_t end = 0;
|
||||
|
||||
if (!GetIntVal(prefix, &start) || !GetIntVal(ub, &end)) {
|
||||
if (!GetFirstIntValInPrefix(prefix, &start) ||
|
||||
!GetFirstIntValInPrefix(ub, &end)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under both the GPLv2 (found in the
|
||||
// COPYING file in the root directory) and Apache 2.0 License
|
||||
// (found in the LICENSE.Apache file in the root directory).
|
||||
//
|
||||
// Copyright (c) 2011 The LevelDB Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "rocksdb/compaction_filter.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
// DbStressCompactionFilter is safe to use with db_stress as it does not perform
|
||||
// any mutation. It only makes `kRemove` decisions for keys that are already
|
||||
// non-existent according to the `SharedState`.
|
||||
class DbStressCompactionFilter : public CompactionFilter {
|
||||
public:
|
||||
DbStressCompactionFilter(SharedState* state, int cf_id)
|
||||
: state_(state), cf_id_(cf_id) {}
|
||||
|
||||
Decision FilterV2(int /*level*/, const Slice& key, ValueType /*value_type*/,
|
||||
const Slice& /*existing_value*/, std::string* /*new_value*/,
|
||||
std::string* /*skip_until*/) const override {
|
||||
if (state_ == nullptr) {
|
||||
return Decision::kKeep;
|
||||
}
|
||||
if (key.empty() || ('0' <= key[0] && key[0] <= '9')) {
|
||||
// It is likely leftover from a test_batches_snapshots run. Below this
|
||||
// conditional, the test_batches_snapshots key format is not handled
|
||||
// properly. Just keep it to be safe.
|
||||
return Decision::kKeep;
|
||||
}
|
||||
uint64_t key_num = 0;
|
||||
bool ok = GetIntVal(key.ToString(), &key_num);
|
||||
assert(ok);
|
||||
(void)ok;
|
||||
MutexLock key_lock(state_->GetMutexForKey(cf_id_, key_num));
|
||||
if (!state_->Exists(cf_id_, key_num)) {
|
||||
return Decision::kRemove;
|
||||
}
|
||||
return Decision::kKeep;
|
||||
}
|
||||
|
||||
const char* Name() const override { return "DbStressCompactionFilter"; }
|
||||
|
||||
private:
|
||||
SharedState* const state_;
|
||||
const int cf_id_;
|
||||
};
|
||||
|
||||
class DbStressCompactionFilterFactory : public CompactionFilterFactory {
|
||||
public:
|
||||
DbStressCompactionFilterFactory() : state_(nullptr) {}
|
||||
|
||||
void SetSharedState(SharedState* state) {
|
||||
MutexLock state_mutex_guard(&state_mutex_);
|
||||
state_ = state;
|
||||
}
|
||||
|
||||
std::unique_ptr<CompactionFilter> CreateCompactionFilter(
|
||||
const CompactionFilter::Context& context) override {
|
||||
MutexLock state_mutex_guard(&state_mutex_);
|
||||
return std::unique_ptr<CompactionFilter>(
|
||||
new DbStressCompactionFilter(state_, context.column_family_id));
|
||||
}
|
||||
|
||||
const char* Name() const override {
|
||||
return "DbStressCompactionFilterFactory";
|
||||
}
|
||||
|
||||
private:
|
||||
port::Mutex state_mutex_;
|
||||
SharedState* state_;
|
||||
};
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
@@ -16,7 +16,7 @@ void ThreadBody(void* v) {
|
||||
ThreadState* thread = reinterpret_cast<ThreadState*>(v);
|
||||
SharedState* shared = thread->shared;
|
||||
|
||||
if (shared->ShouldVerifyAtBeginning()) {
|
||||
if (!FLAGS_skip_verifydb && shared->ShouldVerifyAtBeginning()) {
|
||||
thread->shared->GetStressTest()->VerifyDb(thread);
|
||||
}
|
||||
{
|
||||
@@ -42,7 +42,9 @@ void ThreadBody(void* v) {
|
||||
}
|
||||
}
|
||||
|
||||
thread->shared->GetStressTest()->VerifyDb(thread);
|
||||
if (!FLAGS_skip_verifydb) {
|
||||
thread->shared->GetStressTest()->VerifyDb(thread);
|
||||
}
|
||||
|
||||
{
|
||||
MutexLock l(shared->GetMutex());
|
||||
@@ -55,11 +57,8 @@ void ThreadBody(void* v) {
|
||||
|
||||
bool RunStressTest(StressTest* stress) {
|
||||
stress->InitDb();
|
||||
|
||||
SharedState shared(db_stress_env, stress);
|
||||
if (FLAGS_read_only) {
|
||||
stress->InitReadonlyDb(&shared);
|
||||
}
|
||||
stress->FinishInitDb(&shared);
|
||||
|
||||
#ifndef NDEBUG
|
||||
if (FLAGS_sync_fault_injection) {
|
||||
@@ -118,6 +117,9 @@ bool RunStressTest(StressTest* stress) {
|
||||
if (FLAGS_test_batches_snapshots) {
|
||||
fprintf(stdout, "%s Limited verification already done during gets\n",
|
||||
db_stress_env->TimeToString((uint64_t)now / 1000000).c_str());
|
||||
} else if (FLAGS_skip_verifydb) {
|
||||
fprintf(stdout, "%s Verification skipped\n",
|
||||
db_stress_env->TimeToString((uint64_t)now / 1000000).c_str());
|
||||
} else {
|
||||
fprintf(stdout, "%s Starting verification\n",
|
||||
db_stress_env->TimeToString((uint64_t)now / 1000000).c_str());
|
||||
@@ -140,7 +142,8 @@ bool RunStressTest(StressTest* stress) {
|
||||
threads[i] = nullptr;
|
||||
}
|
||||
now = db_stress_env->NowMicros();
|
||||
if (!FLAGS_test_batches_snapshots && !shared.HasVerificationFailedYet()) {
|
||||
if (!FLAGS_skip_verifydb && !FLAGS_test_batches_snapshots &&
|
||||
!shared.HasVerificationFailedYet()) {
|
||||
fprintf(stdout, "%s Verification successful\n",
|
||||
db_stress_env->TimeToString(now / 1000000).c_str());
|
||||
}
|
||||
|
||||
@@ -361,6 +361,11 @@ DEFINE_bool(partition_filters, false,
|
||||
"use partitioned filters "
|
||||
"for block-based table");
|
||||
|
||||
DEFINE_bool(
|
||||
optimize_filters_for_memory,
|
||||
ROCKSDB_NAMESPACE::BlockBasedTableOptions().optimize_filters_for_memory,
|
||||
"Minimize memory footprint of filters");
|
||||
|
||||
DEFINE_int32(
|
||||
index_type,
|
||||
static_cast<int32_t>(
|
||||
@@ -414,10 +419,10 @@ static const bool FLAGS_kill_random_test_dummy __attribute__((__unused__)) =
|
||||
RegisterFlagValidator(&FLAGS_kill_random_test, &ValidateInt32Positive);
|
||||
extern int rocksdb_kill_odds;
|
||||
|
||||
DEFINE_string(kill_prefix_blacklist, "",
|
||||
DEFINE_string(kill_exclude_prefixes, "",
|
||||
"If non-empty, kill points with prefix in the list given will be"
|
||||
" skipped. Items are comma-separated.");
|
||||
extern std::vector<std::string> rocksdb_kill_prefix_blacklist;
|
||||
extern std::vector<std::string> rocksdb_kill_exclude_prefixes;
|
||||
|
||||
DEFINE_bool(disable_wal, false, "If true, do not write WAL for write.");
|
||||
|
||||
@@ -691,4 +696,13 @@ DEFINE_int32(read_fault_one_in, 1000,
|
||||
DEFINE_bool(sync_fault_injection, false,
|
||||
"If true, FaultInjectionTestFS will be used for write operations, "
|
||||
" and unsynced data in DB will lost after crash.");
|
||||
|
||||
DEFINE_bool(best_efforts_recovery, false,
|
||||
"If true, use best efforts recovery.");
|
||||
DEFINE_bool(skip_verifydb, false, "If true, skip VerifyDb() calls.");
|
||||
|
||||
DEFINE_bool(enable_compaction_filter, false,
|
||||
"If true, configures a compaction filter that returns a kRemove "
|
||||
"decision for deleted keys.");
|
||||
|
||||
#endif // GFLAGS
|
||||
@@ -10,6 +10,7 @@
|
||||
|
||||
#ifdef GFLAGS
|
||||
#include "db_stress_tool/db_stress_common.h"
|
||||
#include "db_stress_tool/db_stress_compaction_filter.h"
|
||||
#include "db_stress_tool/db_stress_driver.h"
|
||||
#include "rocksdb/convenience.h"
|
||||
#include "rocksdb/sst_file_manager.h"
|
||||
@@ -195,11 +196,18 @@ void StressTest::InitDb() {
|
||||
BuildOptionsTable();
|
||||
}
|
||||
|
||||
void StressTest::InitReadonlyDb(SharedState* shared) {
|
||||
uint64_t now = db_stress_env->NowMicros();
|
||||
fprintf(stdout, "%s Preloading db with %" PRIu64 " KVs\n",
|
||||
db_stress_env->TimeToString(now / 1000000).c_str(), FLAGS_max_key);
|
||||
PreloadDbAndReopenAsReadOnly(FLAGS_max_key, shared);
|
||||
void StressTest::FinishInitDb(SharedState* shared) {
|
||||
if (FLAGS_read_only) {
|
||||
uint64_t now = db_stress_env->NowMicros();
|
||||
fprintf(stdout, "%s Preloading db with %" PRIu64 " KVs\n",
|
||||
db_stress_env->TimeToString(now / 1000000).c_str(), FLAGS_max_key);
|
||||
PreloadDbAndReopenAsReadOnly(FLAGS_max_key, shared);
|
||||
}
|
||||
if (FLAGS_enable_compaction_filter) {
|
||||
reinterpret_cast<DbStressCompactionFilterFactory*>(
|
||||
options_.compaction_filter_factory.get())
|
||||
->SetSharedState(shared);
|
||||
}
|
||||
}
|
||||
|
||||
bool StressTest::VerifySecondaries() {
|
||||
@@ -1709,9 +1717,9 @@ void StressTest::PrintEnv() const {
|
||||
fprintf(stdout, "Memtablerep : %s\n", memtablerep);
|
||||
|
||||
fprintf(stdout, "Test kill odd : %d\n", rocksdb_kill_odds);
|
||||
if (!rocksdb_kill_prefix_blacklist.empty()) {
|
||||
if (!rocksdb_kill_exclude_prefixes.empty()) {
|
||||
fprintf(stdout, "Skipping kill points prefixes:\n");
|
||||
for (auto& p : rocksdb_kill_prefix_blacklist) {
|
||||
for (auto& p : rocksdb_kill_exclude_prefixes) {
|
||||
fprintf(stdout, " %s\n", p.c_str());
|
||||
}
|
||||
}
|
||||
@@ -1729,6 +1737,8 @@ void StressTest::PrintEnv() const {
|
||||
static_cast<int>(FLAGS_level_compaction_dynamic_level_bytes));
|
||||
fprintf(stdout, "Read fault one in : %d\n", FLAGS_read_fault_one_in);
|
||||
fprintf(stdout, "Sync fault injection : %d\n", FLAGS_sync_fault_injection);
|
||||
fprintf(stdout, "Best efforts recovery : %d\n",
|
||||
static_cast<int>(FLAGS_best_efforts_recovery));
|
||||
|
||||
fprintf(stdout, "------------------------------------------------\n");
|
||||
}
|
||||
@@ -1752,6 +1762,8 @@ void StressTest::Open() {
|
||||
static_cast<int32_t>(FLAGS_index_block_restart_interval);
|
||||
block_based_options.filter_policy = filter_policy_;
|
||||
block_based_options.partition_filters = FLAGS_partition_filters;
|
||||
block_based_options.optimize_filters_for_memory =
|
||||
FLAGS_optimize_filters_for_memory;
|
||||
block_based_options.index_type =
|
||||
static_cast<BlockBasedTableOptions::IndexType>(FLAGS_index_type);
|
||||
options_.table_factory.reset(
|
||||
@@ -1912,6 +1924,12 @@ void StressTest::Open() {
|
||||
} else {
|
||||
options_.merge_operator = MergeOperators::CreatePutOperator();
|
||||
}
|
||||
if (FLAGS_enable_compaction_filter) {
|
||||
options_.compaction_filter_factory =
|
||||
std::make_shared<DbStressCompactionFilterFactory>();
|
||||
}
|
||||
|
||||
options_.best_efforts_recovery = FLAGS_best_efforts_recovery;
|
||||
|
||||
fprintf(stdout, "DB path: [%s]\n", FLAGS_db.c_str());
|
||||
|
||||
|
||||
@@ -27,7 +27,9 @@ class StressTest {
|
||||
bool BuildOptionsTable();
|
||||
|
||||
void InitDb();
|
||||
void InitReadonlyDb(SharedState*);
|
||||
// The initialization work is split into two parts to avoid a circular
|
||||
// dependency with `SharedState`.
|
||||
void FinishInitDb(SharedState*);
|
||||
|
||||
// Return false if verification fails.
|
||||
bool VerifySecondaries();
|
||||
|
||||
@@ -216,9 +216,39 @@ int db_stress_tool(int argc, char** argv) {
|
||||
"Must set -test_secondary=true if secondary_catch_up_one_in > 0.\n");
|
||||
exit(1);
|
||||
}
|
||||
if (FLAGS_best_efforts_recovery && !FLAGS_skip_verifydb &&
|
||||
!FLAGS_disable_wal) {
|
||||
fprintf(stderr,
|
||||
"With best-efforts recovery, either skip_verifydb or disable_wal "
|
||||
"should be set to true.\n");
|
||||
exit(1);
|
||||
}
|
||||
if (FLAGS_skip_verifydb) {
|
||||
if (FLAGS_verify_db_one_in > 0) {
|
||||
fprintf(stderr,
|
||||
"Must set -verify_db_one_in=0 if skip_verifydb is true.\n");
|
||||
exit(1);
|
||||
}
|
||||
if (FLAGS_continuous_verification_interval > 0) {
|
||||
fprintf(stderr,
|
||||
"Must set -continuous_verification_interval=0 if skip_verifydb "
|
||||
"is true.\n");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
if (FLAGS_enable_compaction_filter &&
|
||||
(FLAGS_acquire_snapshot_one_in > 0 || FLAGS_compact_range_one_in > 0 ||
|
||||
FLAGS_iterpercent > 0 || FLAGS_test_batches_snapshots ||
|
||||
FLAGS_test_cf_consistency)) {
|
||||
fprintf(
|
||||
stderr,
|
||||
"Error: acquire_snapshot_one_in, compact_range_one_in, iterpercent, "
|
||||
"test_batches_snapshots must all be 0 when using compaction filter\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
rocksdb_kill_odds = FLAGS_kill_random_test;
|
||||
rocksdb_kill_prefix_blacklist = SplitString(FLAGS_kill_prefix_blacklist);
|
||||
rocksdb_kill_exclude_prefixes = SplitString(FLAGS_kill_exclude_prefixes);
|
||||
|
||||
unsigned int levels = FLAGS_max_key_len;
|
||||
std::vector<std::string> weights;
|
||||
|
||||
Vendored
+6
-2
@@ -555,14 +555,18 @@ class CompositeEnvWrapper : public Env {
|
||||
return env_target_->IncBackgroundThreadsIfNeeded(num, pri);
|
||||
}
|
||||
|
||||
void LowerThreadPoolIOPriority(Priority pool = LOW) override {
|
||||
void LowerThreadPoolIOPriority(Priority pool) override {
|
||||
env_target_->LowerThreadPoolIOPriority(pool);
|
||||
}
|
||||
|
||||
void LowerThreadPoolCPUPriority(Priority pool = LOW) override {
|
||||
void LowerThreadPoolCPUPriority(Priority pool) override {
|
||||
env_target_->LowerThreadPoolCPUPriority(pool);
|
||||
}
|
||||
|
||||
Status LowerThreadPoolCPUPriority(Priority pool, CpuPriority pri) override {
|
||||
return env_target_->LowerThreadPoolCPUPriority(pool, pri);
|
||||
}
|
||||
|
||||
std::string TimeToString(uint64_t time) override {
|
||||
return env_target_->TimeToString(time);
|
||||
}
|
||||
|
||||
Vendored
+20
-1
@@ -4,13 +4,14 @@
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
|
||||
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
|
||||
#include "env/mock_env.h"
|
||||
#include "rocksdb/env.h"
|
||||
#include "rocksdb/env_encryption.h"
|
||||
#include "test_util/testharness.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
@@ -89,6 +90,24 @@ INSTANTIATE_TEST_CASE_P(EnvDefault, EnvMoreTestWithParam,
|
||||
static std::unique_ptr<Env> mock_env(new MockEnv(Env::Default()));
|
||||
INSTANTIATE_TEST_CASE_P(MockEnv, EnvBasicTestWithParam,
|
||||
::testing::Values(mock_env.get()));
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
// next statements run env test against default encryption code.
|
||||
static ROT13BlockCipher encrypt_block_rot13(32);
|
||||
|
||||
static CTREncryptionProvider encrypt_provider_ctr(encrypt_block_rot13);
|
||||
|
||||
static std::unique_ptr<Env> ecpt_env(NewEncryptedEnv(Env::Default(),
|
||||
&encrypt_provider_ctr));
|
||||
|
||||
static std::unique_ptr<Env> encrypt_env(
|
||||
new NormalizingEnvWrapper(ecpt_env.get()));
|
||||
INSTANTIATE_TEST_CASE_P(EncryptedEnv, EnvBasicTestWithParam,
|
||||
::testing::Values(encrypt_env.get()));
|
||||
INSTANTIATE_TEST_CASE_P(EncryptedEnv, EnvMoreTestWithParam,
|
||||
::testing::Values(encrypt_env.get()));
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
static std::unique_ptr<Env> mem_env(NewMemEnv(Env::Default()));
|
||||
INSTANTIATE_TEST_CASE_P(MemEnv, EnvBasicTestWithParam,
|
||||
|
||||
Vendored
+272
-281
@@ -23,19 +23,6 @@ namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
|
||||
class EncryptedSequentialFile : public SequentialFile {
|
||||
private:
|
||||
std::unique_ptr<SequentialFile> file_;
|
||||
std::unique_ptr<BlockAccessCipherStream> stream_;
|
||||
uint64_t offset_;
|
||||
size_t prefixLength_;
|
||||
|
||||
public:
|
||||
// Default ctor. Given underlying sequential file is supposed to be at
|
||||
// offset == prefixLength.
|
||||
EncryptedSequentialFile(SequentialFile* f, BlockAccessCipherStream* s, size_t prefixLength)
|
||||
: file_(f), stream_(s), offset_(prefixLength), prefixLength_(prefixLength) {
|
||||
}
|
||||
|
||||
// Read up to "n" bytes from the file. "scratch[0..n-1]" may be
|
||||
// written by this routine. Sets "*result" to the data that was
|
||||
@@ -45,83 +32,73 @@ class EncryptedSequentialFile : public SequentialFile {
|
||||
// If an error was encountered, returns a non-OK status.
|
||||
//
|
||||
// REQUIRES: External synchronization
|
||||
Status Read(size_t n, Slice* result, char* scratch) override {
|
||||
assert(scratch);
|
||||
Status status = file_->Read(n, result, scratch);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
{
|
||||
PERF_TIMER_GUARD(decrypt_data_nanos);
|
||||
status = stream_->Decrypt(offset_, (char*)result->data(), result->size());
|
||||
}
|
||||
offset_ += result->size(); // We've already ready data from disk, so update
|
||||
// offset_ even if decryption fails.
|
||||
Status EncryptedSequentialFile::Read(size_t n, Slice* result, char* scratch) {
|
||||
assert(scratch);
|
||||
Status status = file_->Read(n, result, scratch);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
{
|
||||
PERF_TIMER_GUARD(decrypt_data_nanos);
|
||||
status = stream_->Decrypt(offset_, (char*)result->data(), result->size());
|
||||
}
|
||||
offset_ += result->size(); // We've already ready data from disk, so update
|
||||
// offset_ even if decryption fails.
|
||||
return status;
|
||||
}
|
||||
|
||||
// Skip "n" bytes from the file. This is guaranteed to be no
|
||||
// slower that reading the same data, but may be faster.
|
||||
//
|
||||
// If end of file is reached, skipping will stop at the end of the
|
||||
// file, and Skip will return OK.
|
||||
//
|
||||
// REQUIRES: External synchronization
|
||||
Status Skip(uint64_t n) override {
|
||||
auto status = file_->Skip(n);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
offset_ += n;
|
||||
// Skip "n" bytes from the file. This is guaranteed to be no
|
||||
// slower that reading the same data, but may be faster.
|
||||
//
|
||||
// If end of file is reached, skipping will stop at the end of the
|
||||
// file, and Skip will return OK.
|
||||
//
|
||||
// REQUIRES: External synchronization
|
||||
Status EncryptedSequentialFile::Skip(uint64_t n) {
|
||||
auto status = file_->Skip(n);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
offset_ += n;
|
||||
return status;
|
||||
}
|
||||
|
||||
// Indicates the upper layers if the current SequentialFile implementation
|
||||
// uses direct IO.
|
||||
bool use_direct_io() const override { return file_->use_direct_io(); }
|
||||
// Indicates the upper layers if the current SequentialFile implementation
|
||||
// uses direct IO.
|
||||
bool EncryptedSequentialFile::use_direct_io() const {
|
||||
return file_->use_direct_io();
|
||||
}
|
||||
|
||||
// Use the returned alignment value to allocate
|
||||
// aligned buffer for Direct I/O
|
||||
size_t GetRequiredBufferAlignment() const override {
|
||||
return file_->GetRequiredBufferAlignment();
|
||||
}
|
||||
// Use the returned alignment value to allocate
|
||||
// aligned buffer for Direct I/O
|
||||
size_t EncryptedSequentialFile::GetRequiredBufferAlignment() const {
|
||||
return file_->GetRequiredBufferAlignment();
|
||||
}
|
||||
|
||||
// Remove any kind of caching of data from the offset to offset+length
|
||||
// of this file. If the length is 0, then it refers to the end of file.
|
||||
// If the system is not caching the file contents, then this is a noop.
|
||||
Status InvalidateCache(size_t offset, size_t length) override {
|
||||
return file_->InvalidateCache(offset + prefixLength_, length);
|
||||
}
|
||||
Status EncryptedSequentialFile::InvalidateCache(size_t offset, size_t length) {
|
||||
return file_->InvalidateCache(offset + prefixLength_, length);
|
||||
}
|
||||
|
||||
// Positioned Read for direct I/O
|
||||
// If Direct I/O enabled, offset, n, and scratch should be properly aligned
|
||||
Status PositionedRead(uint64_t offset, size_t n, Slice* result,
|
||||
char* scratch) override {
|
||||
assert(scratch);
|
||||
offset += prefixLength_; // Skip prefix
|
||||
auto status = file_->PositionedRead(offset, n, result, scratch);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
offset_ = offset + result->size();
|
||||
{
|
||||
PERF_TIMER_GUARD(decrypt_data_nanos);
|
||||
status = stream_->Decrypt(offset, (char*)result->data(), result->size());
|
||||
}
|
||||
Status EncryptedSequentialFile::PositionedRead(uint64_t offset, size_t n,
|
||||
Slice* result, char* scratch) {
|
||||
assert(scratch);
|
||||
offset += prefixLength_; // Skip prefix
|
||||
auto status = file_->PositionedRead(offset, n, result, scratch);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
};
|
||||
|
||||
// A file abstraction for randomly reading the contents of a file.
|
||||
class EncryptedRandomAccessFile : public RandomAccessFile {
|
||||
private:
|
||||
std::unique_ptr<RandomAccessFile> file_;
|
||||
std::unique_ptr<BlockAccessCipherStream> stream_;
|
||||
size_t prefixLength_;
|
||||
|
||||
public:
|
||||
EncryptedRandomAccessFile(RandomAccessFile* f, BlockAccessCipherStream* s, size_t prefixLength)
|
||||
: file_(f), stream_(s), prefixLength_(prefixLength) { }
|
||||
offset_ = offset + result->size();
|
||||
{
|
||||
PERF_TIMER_GUARD(decrypt_data_nanos);
|
||||
status = stream_->Decrypt(offset, (char*)result->data(), result->size());
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
// Read up to "n" bytes from the file starting at "offset".
|
||||
// "scratch[0..n-1]" may be written by this routine. Sets "*result"
|
||||
@@ -133,26 +110,26 @@ class EncryptedRandomAccessFile : public RandomAccessFile {
|
||||
//
|
||||
// Safe for concurrent use by multiple threads.
|
||||
// If Direct I/O enabled, offset, n, and scratch should be aligned properly.
|
||||
Status Read(uint64_t offset, size_t n, Slice* result,
|
||||
char* scratch) const override {
|
||||
assert(scratch);
|
||||
offset += prefixLength_;
|
||||
auto status = file_->Read(offset, n, result, scratch);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
{
|
||||
PERF_TIMER_GUARD(decrypt_data_nanos);
|
||||
status = stream_->Decrypt(offset, (char*)result->data(), result->size());
|
||||
}
|
||||
Status EncryptedRandomAccessFile::Read(uint64_t offset, size_t n, Slice* result,
|
||||
char* scratch) const {
|
||||
assert(scratch);
|
||||
offset += prefixLength_;
|
||||
auto status = file_->Read(offset, n, result, scratch);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
{
|
||||
PERF_TIMER_GUARD(decrypt_data_nanos);
|
||||
status = stream_->Decrypt(offset, (char*)result->data(), result->size());
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
// Readahead the file starting from offset by n bytes for caching.
|
||||
Status Prefetch(uint64_t offset, size_t n) override {
|
||||
//return Status::OK();
|
||||
return file_->Prefetch(offset + prefixLength_, n);
|
||||
}
|
||||
Status EncryptedRandomAccessFile::Prefetch(uint64_t offset, size_t n) {
|
||||
// return Status::OK();
|
||||
return file_->Prefetch(offset + prefixLength_, n);
|
||||
}
|
||||
|
||||
// Tries to get an unique ID for this file that will be the same each time
|
||||
// the file is opened (and will stay the same while the file is open).
|
||||
@@ -169,132 +146,128 @@ class EncryptedRandomAccessFile : public RandomAccessFile {
|
||||
// a single varint.
|
||||
//
|
||||
// Note: these IDs are only valid for the duration of the process.
|
||||
size_t GetUniqueId(char* id, size_t max_size) const override {
|
||||
return file_->GetUniqueId(id, max_size);
|
||||
};
|
||||
size_t EncryptedRandomAccessFile::GetUniqueId(char* id, size_t max_size) const {
|
||||
return file_->GetUniqueId(id, max_size);
|
||||
};
|
||||
|
||||
void Hint(AccessPattern pattern) override { file_->Hint(pattern); }
|
||||
void EncryptedRandomAccessFile::Hint(AccessPattern pattern) {
|
||||
file_->Hint(pattern);
|
||||
}
|
||||
|
||||
// Indicates the upper layers if the current RandomAccessFile implementation
|
||||
// uses direct IO.
|
||||
bool use_direct_io() const override { return file_->use_direct_io(); }
|
||||
bool EncryptedRandomAccessFile::use_direct_io() const {
|
||||
return file_->use_direct_io();
|
||||
}
|
||||
|
||||
// Use the returned alignment value to allocate
|
||||
// aligned buffer for Direct I/O
|
||||
size_t GetRequiredBufferAlignment() const override {
|
||||
return file_->GetRequiredBufferAlignment();
|
||||
}
|
||||
size_t EncryptedRandomAccessFile::GetRequiredBufferAlignment() const {
|
||||
return file_->GetRequiredBufferAlignment();
|
||||
}
|
||||
|
||||
// Remove any kind of caching of data from the offset to offset+length
|
||||
// of this file. If the length is 0, then it refers to the end of file.
|
||||
// If the system is not caching the file contents, then this is a noop.
|
||||
Status InvalidateCache(size_t offset, size_t length) override {
|
||||
return file_->InvalidateCache(offset + prefixLength_, length);
|
||||
}
|
||||
};
|
||||
Status EncryptedRandomAccessFile::InvalidateCache(size_t offset,
|
||||
size_t length) {
|
||||
return file_->InvalidateCache(offset + prefixLength_, length);
|
||||
}
|
||||
|
||||
// A file abstraction for sequential writing. The implementation
|
||||
// must provide buffering since callers may append small fragments
|
||||
// at a time to the file.
|
||||
class EncryptedWritableFile : public WritableFileWrapper {
|
||||
private:
|
||||
std::unique_ptr<WritableFile> file_;
|
||||
std::unique_ptr<BlockAccessCipherStream> stream_;
|
||||
size_t prefixLength_;
|
||||
|
||||
public:
|
||||
// Default ctor. Prefix is assumed to be written already.
|
||||
EncryptedWritableFile(WritableFile* f, BlockAccessCipherStream* s, size_t prefixLength)
|
||||
: WritableFileWrapper(f), file_(f), stream_(s), prefixLength_(prefixLength) { }
|
||||
|
||||
Status Append(const Slice& data) override {
|
||||
AlignedBuffer buf;
|
||||
Status status;
|
||||
Slice dataToAppend(data);
|
||||
if (data.size() > 0) {
|
||||
auto offset = file_->GetFileSize(); // size including prefix
|
||||
// Encrypt in cloned buffer
|
||||
buf.Alignment(GetRequiredBufferAlignment());
|
||||
buf.AllocateNewBuffer(data.size());
|
||||
// TODO (sagar0): Modify AlignedBuffer.Append to allow doing a memmove
|
||||
// so that the next two lines can be replaced with buf.Append().
|
||||
memmove(buf.BufferStart(), data.data(), data.size());
|
||||
buf.Size(data.size());
|
||||
{
|
||||
PERF_TIMER_GUARD(encrypt_data_nanos);
|
||||
status = stream_->Encrypt(offset, buf.BufferStart(), buf.CurrentSize());
|
||||
}
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
dataToAppend = Slice(buf.BufferStart(), buf.CurrentSize());
|
||||
Status EncryptedWritableFile::Append(const Slice& data) {
|
||||
AlignedBuffer buf;
|
||||
Status status;
|
||||
Slice dataToAppend(data);
|
||||
if (data.size() > 0) {
|
||||
auto offset = file_->GetFileSize(); // size including prefix
|
||||
// Encrypt in cloned buffer
|
||||
buf.Alignment(GetRequiredBufferAlignment());
|
||||
buf.AllocateNewBuffer(data.size());
|
||||
// TODO (sagar0): Modify AlignedBuffer.Append to allow doing a memmove
|
||||
// so that the next two lines can be replaced with buf.Append().
|
||||
memmove(buf.BufferStart(), data.data(), data.size());
|
||||
buf.Size(data.size());
|
||||
{
|
||||
PERF_TIMER_GUARD(encrypt_data_nanos);
|
||||
status = stream_->Encrypt(offset, buf.BufferStart(), buf.CurrentSize());
|
||||
}
|
||||
status = file_->Append(dataToAppend);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
dataToAppend = Slice(buf.BufferStart(), buf.CurrentSize());
|
||||
}
|
||||
status = file_->Append(dataToAppend);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
Status PositionedAppend(const Slice& data, uint64_t offset) override {
|
||||
AlignedBuffer buf;
|
||||
Status status;
|
||||
Slice dataToAppend(data);
|
||||
offset += prefixLength_;
|
||||
if (data.size() > 0) {
|
||||
// Encrypt in cloned buffer
|
||||
buf.Alignment(GetRequiredBufferAlignment());
|
||||
buf.AllocateNewBuffer(data.size());
|
||||
memmove(buf.BufferStart(), data.data(), data.size());
|
||||
buf.Size(data.size());
|
||||
{
|
||||
PERF_TIMER_GUARD(encrypt_data_nanos);
|
||||
status = stream_->Encrypt(offset, buf.BufferStart(), buf.CurrentSize());
|
||||
}
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
dataToAppend = Slice(buf.BufferStart(), buf.CurrentSize());
|
||||
Status EncryptedWritableFile::PositionedAppend(const Slice& data,
|
||||
uint64_t offset) {
|
||||
AlignedBuffer buf;
|
||||
Status status;
|
||||
Slice dataToAppend(data);
|
||||
offset += prefixLength_;
|
||||
if (data.size() > 0) {
|
||||
// Encrypt in cloned buffer
|
||||
buf.Alignment(GetRequiredBufferAlignment());
|
||||
buf.AllocateNewBuffer(data.size());
|
||||
memmove(buf.BufferStart(), data.data(), data.size());
|
||||
buf.Size(data.size());
|
||||
{
|
||||
PERF_TIMER_GUARD(encrypt_data_nanos);
|
||||
status = stream_->Encrypt(offset, buf.BufferStart(), buf.CurrentSize());
|
||||
}
|
||||
status = file_->PositionedAppend(dataToAppend, offset);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
dataToAppend = Slice(buf.BufferStart(), buf.CurrentSize());
|
||||
}
|
||||
status = file_->PositionedAppend(dataToAppend, offset);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
// Indicates the upper layers if the current WritableFile implementation
|
||||
// uses direct IO.
|
||||
bool use_direct_io() const override { return file_->use_direct_io(); }
|
||||
bool EncryptedWritableFile::use_direct_io() const {
|
||||
return file_->use_direct_io();
|
||||
}
|
||||
|
||||
// Use the returned alignment value to allocate
|
||||
// aligned buffer for Direct I/O
|
||||
size_t GetRequiredBufferAlignment() const override {
|
||||
return file_->GetRequiredBufferAlignment();
|
||||
}
|
||||
size_t EncryptedWritableFile::GetRequiredBufferAlignment() const {
|
||||
return file_->GetRequiredBufferAlignment();
|
||||
}
|
||||
|
||||
/*
|
||||
* Get the size of valid data in the file.
|
||||
*/
|
||||
uint64_t GetFileSize() override {
|
||||
return file_->GetFileSize() - prefixLength_;
|
||||
}
|
||||
/*
|
||||
* Get the size of valid data in the file.
|
||||
*/
|
||||
uint64_t EncryptedWritableFile::GetFileSize() {
|
||||
return file_->GetFileSize() - prefixLength_;
|
||||
}
|
||||
|
||||
// Truncate is necessary to trim the file to the correct size
|
||||
// before closing. It is not always possible to keep track of the file
|
||||
// size due to whole pages writes. The behavior is undefined if called
|
||||
// with other writes to follow.
|
||||
Status Truncate(uint64_t size) override {
|
||||
return file_->Truncate(size + prefixLength_);
|
||||
}
|
||||
Status EncryptedWritableFile::Truncate(uint64_t size) {
|
||||
return file_->Truncate(size + prefixLength_);
|
||||
}
|
||||
|
||||
// Remove any kind of caching of data from the offset to offset+length
|
||||
// of this file. If the length is 0, then it refers to the end of file.
|
||||
// If the system is not caching the file contents, then this is a noop.
|
||||
// This call has no effect on dirty pages in the cache.
|
||||
Status InvalidateCache(size_t offset, size_t length) override {
|
||||
return file_->InvalidateCache(offset + prefixLength_, length);
|
||||
}
|
||||
Status EncryptedWritableFile::InvalidateCache(size_t offset, size_t length) {
|
||||
return file_->InvalidateCache(offset + prefixLength_, length);
|
||||
}
|
||||
|
||||
// Sync a file range with disk.
|
||||
// offset is the starting byte of the file range to be synchronized.
|
||||
@@ -302,111 +275,103 @@ class EncryptedWritableFile : public WritableFileWrapper {
|
||||
// This asks the OS to initiate flushing the cached data to disk,
|
||||
// without waiting for completion.
|
||||
// Default implementation does nothing.
|
||||
Status RangeSync(uint64_t offset, uint64_t nbytes) override {
|
||||
return file_->RangeSync(offset + prefixLength_, nbytes);
|
||||
}
|
||||
Status EncryptedWritableFile::RangeSync(uint64_t offset, uint64_t nbytes) {
|
||||
return file_->RangeSync(offset + prefixLength_, nbytes);
|
||||
}
|
||||
|
||||
// PrepareWrite performs any necessary preparation for a write
|
||||
// before the write actually occurs. This allows for pre-allocation
|
||||
// of space on devices where it can result in less file
|
||||
// fragmentation and/or less waste from over-zealous filesystem
|
||||
// pre-allocation.
|
||||
void PrepareWrite(size_t offset, size_t len) override {
|
||||
file_->PrepareWrite(offset + prefixLength_, len);
|
||||
}
|
||||
void EncryptedWritableFile::PrepareWrite(size_t offset, size_t len) {
|
||||
file_->PrepareWrite(offset + prefixLength_, len);
|
||||
}
|
||||
|
||||
// Pre-allocates space for a file.
|
||||
Status Allocate(uint64_t offset, uint64_t len) override {
|
||||
return file_->Allocate(offset + prefixLength_, len);
|
||||
}
|
||||
};
|
||||
Status EncryptedWritableFile::Allocate(uint64_t offset, uint64_t len) {
|
||||
return file_->Allocate(offset + prefixLength_, len);
|
||||
}
|
||||
|
||||
// A file abstraction for random reading and writing.
|
||||
class EncryptedRandomRWFile : public RandomRWFile {
|
||||
private:
|
||||
std::unique_ptr<RandomRWFile> file_;
|
||||
std::unique_ptr<BlockAccessCipherStream> stream_;
|
||||
size_t prefixLength_;
|
||||
|
||||
public:
|
||||
EncryptedRandomRWFile(RandomRWFile* f, BlockAccessCipherStream* s, size_t prefixLength)
|
||||
: file_(f), stream_(s), prefixLength_(prefixLength) {}
|
||||
|
||||
// Indicates if the class makes use of direct I/O
|
||||
// If false you must pass aligned buffer to Write()
|
||||
bool use_direct_io() const override { return file_->use_direct_io(); }
|
||||
// Indicates if the class makes use of direct I/O
|
||||
// If false you must pass aligned buffer to Write()
|
||||
bool EncryptedRandomRWFile::use_direct_io() const {
|
||||
return file_->use_direct_io();
|
||||
}
|
||||
|
||||
// Use the returned alignment value to allocate
|
||||
// aligned buffer for Direct I/O
|
||||
size_t GetRequiredBufferAlignment() const override {
|
||||
return file_->GetRequiredBufferAlignment();
|
||||
}
|
||||
size_t EncryptedRandomRWFile::GetRequiredBufferAlignment() const {
|
||||
return file_->GetRequiredBufferAlignment();
|
||||
}
|
||||
|
||||
// Write bytes in `data` at offset `offset`, Returns Status::OK() on success.
|
||||
// Pass aligned buffer when use_direct_io() returns true.
|
||||
Status Write(uint64_t offset, const Slice& data) override {
|
||||
AlignedBuffer buf;
|
||||
Status status;
|
||||
Slice dataToWrite(data);
|
||||
offset += prefixLength_;
|
||||
if (data.size() > 0) {
|
||||
// Encrypt in cloned buffer
|
||||
buf.Alignment(GetRequiredBufferAlignment());
|
||||
buf.AllocateNewBuffer(data.size());
|
||||
memmove(buf.BufferStart(), data.data(), data.size());
|
||||
buf.Size(data.size());
|
||||
{
|
||||
PERF_TIMER_GUARD(encrypt_data_nanos);
|
||||
status = stream_->Encrypt(offset, buf.BufferStart(), buf.CurrentSize());
|
||||
}
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
dataToWrite = Slice(buf.BufferStart(), buf.CurrentSize());
|
||||
Status EncryptedRandomRWFile::Write(uint64_t offset, const Slice& data) {
|
||||
AlignedBuffer buf;
|
||||
Status status;
|
||||
Slice dataToWrite(data);
|
||||
offset += prefixLength_;
|
||||
if (data.size() > 0) {
|
||||
// Encrypt in cloned buffer
|
||||
buf.Alignment(GetRequiredBufferAlignment());
|
||||
buf.AllocateNewBuffer(data.size());
|
||||
memmove(buf.BufferStart(), data.data(), data.size());
|
||||
buf.Size(data.size());
|
||||
{
|
||||
PERF_TIMER_GUARD(encrypt_data_nanos);
|
||||
status = stream_->Encrypt(offset, buf.BufferStart(), buf.CurrentSize());
|
||||
}
|
||||
status = file_->Write(offset, dataToWrite);
|
||||
return status;
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
dataToWrite = Slice(buf.BufferStart(), buf.CurrentSize());
|
||||
}
|
||||
status = file_->Write(offset, dataToWrite);
|
||||
return status;
|
||||
}
|
||||
|
||||
// Read up to `n` bytes starting from offset `offset` and store them in
|
||||
// result, provided `scratch` size should be at least `n`.
|
||||
// Returns Status::OK() on success.
|
||||
Status Read(uint64_t offset, size_t n, Slice* result,
|
||||
char* scratch) const override {
|
||||
assert(scratch);
|
||||
offset += prefixLength_;
|
||||
auto status = file_->Read(offset, n, result, scratch);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
{
|
||||
PERF_TIMER_GUARD(decrypt_data_nanos);
|
||||
status = stream_->Decrypt(offset, (char*)result->data(), result->size());
|
||||
}
|
||||
Status EncryptedRandomRWFile::Read(uint64_t offset, size_t n, Slice* result,
|
||||
char* scratch) const {
|
||||
assert(scratch);
|
||||
offset += prefixLength_;
|
||||
auto status = file_->Read(offset, n, result, scratch);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
{
|
||||
PERF_TIMER_GUARD(decrypt_data_nanos);
|
||||
status = stream_->Decrypt(offset, (char*)result->data(), result->size());
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
Status Flush() override { return file_->Flush(); }
|
||||
Status EncryptedRandomRWFile::Flush() { return file_->Flush(); }
|
||||
|
||||
Status Sync() override { return file_->Sync(); }
|
||||
Status EncryptedRandomRWFile::Sync() { return file_->Sync(); }
|
||||
|
||||
Status Fsync() override { return file_->Fsync(); }
|
||||
Status EncryptedRandomRWFile::Fsync() { return file_->Fsync(); }
|
||||
|
||||
Status Close() override { return file_->Close(); }
|
||||
};
|
||||
Status EncryptedRandomRWFile::Close() { return file_->Close(); }
|
||||
|
||||
// EncryptedEnv implements an Env wrapper that adds encryption to files stored on disk.
|
||||
// EncryptedEnv implements an Env wrapper that adds encryption to files stored
|
||||
// on disk.
|
||||
class EncryptedEnv : public EnvWrapper {
|
||||
public:
|
||||
EncryptedEnv(Env* base_env, EncryptionProvider *provider)
|
||||
EncryptedEnv(Env* base_env, EncryptionProvider* provider)
|
||||
: EnvWrapper(base_env) {
|
||||
provider_ = provider;
|
||||
}
|
||||
|
||||
// NewSequentialFile opens a file for sequential reading.
|
||||
Status NewSequentialFile(const std::string& fname,
|
||||
std::unique_ptr<SequentialFile>* result,
|
||||
const EnvOptions& options) override {
|
||||
virtual Status NewSequentialFile(const std::string& fname,
|
||||
std::unique_ptr<SequentialFile>* result,
|
||||
const EnvOptions& options) override {
|
||||
result->reset();
|
||||
if (options.use_mmap_reads) {
|
||||
return Status::InvalidArgument();
|
||||
@@ -425,7 +390,8 @@ class EncryptedEnv : public EnvWrapper {
|
||||
// Read prefix
|
||||
prefixBuf.Alignment(underlying->GetRequiredBufferAlignment());
|
||||
prefixBuf.AllocateNewBuffer(prefixLength);
|
||||
status = underlying->Read(prefixLength, &prefixSlice, prefixBuf.BufferStart());
|
||||
status =
|
||||
underlying->Read(prefixLength, &prefixSlice, prefixBuf.BufferStart());
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
@@ -433,18 +399,20 @@ class EncryptedEnv : public EnvWrapper {
|
||||
}
|
||||
// Create cipher stream
|
||||
std::unique_ptr<BlockAccessCipherStream> stream;
|
||||
status = provider_->CreateCipherStream(fname, options, prefixSlice, &stream);
|
||||
status =
|
||||
provider_->CreateCipherStream(fname, options, prefixSlice, &stream);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
(*result) = std::unique_ptr<SequentialFile>(new EncryptedSequentialFile(underlying.release(), stream.release(), prefixLength));
|
||||
(*result) = std::unique_ptr<SequentialFile>(new EncryptedSequentialFile(
|
||||
std::move(underlying), std::move(stream), prefixLength));
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
// NewRandomAccessFile opens a file for random read access.
|
||||
Status NewRandomAccessFile(const std::string& fname,
|
||||
std::unique_ptr<RandomAccessFile>* result,
|
||||
const EnvOptions& options) override {
|
||||
virtual Status NewRandomAccessFile(const std::string& fname,
|
||||
std::unique_ptr<RandomAccessFile>* result,
|
||||
const EnvOptions& options) override {
|
||||
result->reset();
|
||||
if (options.use_mmap_reads) {
|
||||
return Status::InvalidArgument();
|
||||
@@ -463,7 +431,8 @@ class EncryptedEnv : public EnvWrapper {
|
||||
// Read prefix
|
||||
prefixBuf.Alignment(underlying->GetRequiredBufferAlignment());
|
||||
prefixBuf.AllocateNewBuffer(prefixLength);
|
||||
status = underlying->Read(0, prefixLength, &prefixSlice, prefixBuf.BufferStart());
|
||||
status = underlying->Read(0, prefixLength, &prefixSlice,
|
||||
prefixBuf.BufferStart());
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
@@ -471,18 +440,20 @@ class EncryptedEnv : public EnvWrapper {
|
||||
}
|
||||
// Create cipher stream
|
||||
std::unique_ptr<BlockAccessCipherStream> stream;
|
||||
status = provider_->CreateCipherStream(fname, options, prefixSlice, &stream);
|
||||
status =
|
||||
provider_->CreateCipherStream(fname, options, prefixSlice, &stream);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
(*result) = std::unique_ptr<RandomAccessFile>(new EncryptedRandomAccessFile(underlying.release(), stream.release(), prefixLength));
|
||||
(*result) = std::unique_ptr<RandomAccessFile>(new EncryptedRandomAccessFile(
|
||||
std::move(underlying), std::move(stream), prefixLength));
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
// NewWritableFile opens a file for sequential writing.
|
||||
Status NewWritableFile(const std::string& fname,
|
||||
std::unique_ptr<WritableFile>* result,
|
||||
const EnvOptions& options) override {
|
||||
virtual Status NewWritableFile(const std::string& fname,
|
||||
std::unique_ptr<WritableFile>* result,
|
||||
const EnvOptions& options) override {
|
||||
result->reset();
|
||||
if (options.use_mmap_writes) {
|
||||
return Status::InvalidArgument();
|
||||
@@ -512,11 +483,13 @@ class EncryptedEnv : public EnvWrapper {
|
||||
}
|
||||
// Create cipher stream
|
||||
std::unique_ptr<BlockAccessCipherStream> stream;
|
||||
status = provider_->CreateCipherStream(fname, options, prefixSlice, &stream);
|
||||
status =
|
||||
provider_->CreateCipherStream(fname, options, prefixSlice, &stream);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
(*result) = std::unique_ptr<WritableFile>(new EncryptedWritableFile(underlying.release(), stream.release(), prefixLength));
|
||||
(*result) = std::unique_ptr<WritableFile>(new EncryptedWritableFile(
|
||||
std::move(underlying), std::move(stream), prefixLength));
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
@@ -527,9 +500,9 @@ class EncryptedEnv : public EnvWrapper {
|
||||
// returns non-OK.
|
||||
//
|
||||
// The returned file will only be accessed by one thread at a time.
|
||||
Status ReopenWritableFile(const std::string& fname,
|
||||
std::unique_ptr<WritableFile>* result,
|
||||
const EnvOptions& options) override {
|
||||
virtual Status ReopenWritableFile(const std::string& fname,
|
||||
std::unique_ptr<WritableFile>* result,
|
||||
const EnvOptions& options) override {
|
||||
result->reset();
|
||||
if (options.use_mmap_writes) {
|
||||
return Status::InvalidArgument();
|
||||
@@ -559,26 +532,29 @@ class EncryptedEnv : public EnvWrapper {
|
||||
}
|
||||
// Create cipher stream
|
||||
std::unique_ptr<BlockAccessCipherStream> stream;
|
||||
status = provider_->CreateCipherStream(fname, options, prefixSlice, &stream);
|
||||
status =
|
||||
provider_->CreateCipherStream(fname, options, prefixSlice, &stream);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
(*result) = std::unique_ptr<WritableFile>(new EncryptedWritableFile(underlying.release(), stream.release(), prefixLength));
|
||||
(*result) = std::unique_ptr<WritableFile>(new EncryptedWritableFile(
|
||||
std::move(underlying), std::move(stream), prefixLength));
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
// Reuse an existing file by renaming it and opening it as writable.
|
||||
Status ReuseWritableFile(const std::string& fname,
|
||||
const std::string& old_fname,
|
||||
std::unique_ptr<WritableFile>* result,
|
||||
const EnvOptions& options) override {
|
||||
virtual Status ReuseWritableFile(const std::string& fname,
|
||||
const std::string& old_fname,
|
||||
std::unique_ptr<WritableFile>* result,
|
||||
const EnvOptions& options) override {
|
||||
result->reset();
|
||||
if (options.use_mmap_writes) {
|
||||
return Status::InvalidArgument();
|
||||
}
|
||||
// Open file using underlying Env implementation
|
||||
std::unique_ptr<WritableFile> underlying;
|
||||
Status status = EnvWrapper::ReuseWritableFile(fname, old_fname, &underlying, options);
|
||||
Status status =
|
||||
EnvWrapper::ReuseWritableFile(fname, old_fname, &underlying, options);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
@@ -601,11 +577,13 @@ class EncryptedEnv : public EnvWrapper {
|
||||
}
|
||||
// Create cipher stream
|
||||
std::unique_ptr<BlockAccessCipherStream> stream;
|
||||
status = provider_->CreateCipherStream(fname, options, prefixSlice, &stream);
|
||||
status =
|
||||
provider_->CreateCipherStream(fname, options, prefixSlice, &stream);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
(*result) = std::unique_ptr<WritableFile>(new EncryptedWritableFile(underlying.release(), stream.release(), prefixLength));
|
||||
(*result) = std::unique_ptr<WritableFile>(new EncryptedWritableFile(
|
||||
std::move(underlying), std::move(stream), prefixLength));
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
@@ -614,9 +592,9 @@ class EncryptedEnv : public EnvWrapper {
|
||||
// *result and returns OK. On failure returns non-OK.
|
||||
//
|
||||
// The returned file will only be accessed by one thread at a time.
|
||||
Status NewRandomRWFile(const std::string& fname,
|
||||
std::unique_ptr<RandomRWFile>* result,
|
||||
const EnvOptions& options) override {
|
||||
virtual Status NewRandomRWFile(const std::string& fname,
|
||||
std::unique_ptr<RandomRWFile>* result,
|
||||
const EnvOptions& options) override {
|
||||
result->reset();
|
||||
if (options.use_mmap_reads || options.use_mmap_writes) {
|
||||
return Status::InvalidArgument();
|
||||
@@ -639,14 +617,16 @@ class EncryptedEnv : public EnvWrapper {
|
||||
prefixBuf.AllocateNewBuffer(prefixLength);
|
||||
if (!isNewFile) {
|
||||
// File already exists, read prefix
|
||||
status = underlying->Read(0, prefixLength, &prefixSlice, prefixBuf.BufferStart());
|
||||
status = underlying->Read(0, prefixLength, &prefixSlice,
|
||||
prefixBuf.BufferStart());
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
prefixBuf.Size(prefixLength);
|
||||
} else {
|
||||
// File is new, initialize & write prefix
|
||||
provider_->CreateNewPrefix(fname, prefixBuf.BufferStart(), prefixLength);
|
||||
provider_->CreateNewPrefix(fname, prefixBuf.BufferStart(),
|
||||
prefixLength);
|
||||
prefixBuf.Size(prefixLength);
|
||||
prefixSlice = Slice(prefixBuf.BufferStart(), prefixBuf.CurrentSize());
|
||||
// Write prefix
|
||||
@@ -658,40 +638,51 @@ class EncryptedEnv : public EnvWrapper {
|
||||
}
|
||||
// Create cipher stream
|
||||
std::unique_ptr<BlockAccessCipherStream> stream;
|
||||
status = provider_->CreateCipherStream(fname, options, prefixSlice, &stream);
|
||||
status =
|
||||
provider_->CreateCipherStream(fname, options, prefixSlice, &stream);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
(*result) = std::unique_ptr<RandomRWFile>(new EncryptedRandomRWFile(underlying.release(), stream.release(), prefixLength));
|
||||
(*result) = std::unique_ptr<RandomRWFile>(new EncryptedRandomRWFile(
|
||||
std::move(underlying), std::move(stream), prefixLength));
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
// Store in *result the attributes of the children of the specified directory.
|
||||
// In case the implementation lists the directory prior to iterating the files
|
||||
// and files are concurrently deleted, the deleted files will be omitted from
|
||||
// Store in *result the attributes of the children of the specified
|
||||
// directory.
|
||||
// In case the implementation lists the directory prior to iterating the
|
||||
// files
|
||||
// and files are concurrently deleted, the deleted files will be omitted
|
||||
// from
|
||||
// result.
|
||||
// The name attributes are relative to "dir".
|
||||
// Original contents of *results are dropped.
|
||||
// Returns OK if "dir" exists and "*result" contains its children.
|
||||
// NotFound if "dir" does not exist, the calling process does not have
|
||||
// NotFound if "dir" does not exist, the calling process does not
|
||||
// have
|
||||
// permission to access "dir", or if "dir" is invalid.
|
||||
// IOError if an IO Error was encountered
|
||||
Status GetChildrenFileAttributes(
|
||||
virtual Status GetChildrenFileAttributes(
|
||||
const std::string& dir, std::vector<FileAttributes>* result) override {
|
||||
auto status = EnvWrapper::GetChildrenFileAttributes(dir, result);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
size_t prefixLength = provider_->GetPrefixLength();
|
||||
for (auto it = std::begin(*result); it!=std::end(*result); ++it) {
|
||||
assert(it->size_bytes >= prefixLength);
|
||||
for (auto it = std::begin(*result); it != std::end(*result); ++it) {
|
||||
// assert(it->size_bytes >= prefixLength);
|
||||
// breaks env_basic_test when called on directory containing
|
||||
// directories
|
||||
// which makes subtraction of prefixLength worrisome since
|
||||
// FileAttributes does not identify directories
|
||||
it->size_bytes -= prefixLength;
|
||||
}
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
// Store the size of fname in *file_size.
|
||||
Status GetFileSize(const std::string& fname, uint64_t* file_size) override {
|
||||
virtual Status GetFileSize(const std::string& fname,
|
||||
uint64_t* file_size) override {
|
||||
auto status = EnvWrapper::GetFileSize(fname, file_size);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
@@ -703,7 +694,7 @@ class EncryptedEnv : public EnvWrapper {
|
||||
}
|
||||
|
||||
private:
|
||||
EncryptionProvider *provider_;
|
||||
EncryptionProvider* provider_;
|
||||
};
|
||||
|
||||
// Returns an Env that encrypts data when stored on disk and decrypts data when
|
||||
|
||||
Vendored
+3
-3
@@ -12,7 +12,6 @@
|
||||
#define ROCKSDB_HDFS_FILE_C
|
||||
|
||||
#include <stdio.h>
|
||||
#include <sys/time.h>
|
||||
#include <time.h>
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
@@ -124,8 +123,9 @@ class HdfsReadableFile : virtual public SequentialFile,
|
||||
Status s;
|
||||
ROCKS_LOG_DEBUG(mylog, "[hdfs] HdfsReadableFile preading %s\n",
|
||||
filename_.c_str());
|
||||
ssize_t bytes_read = hdfsPread(fileSys_, hfile_, offset,
|
||||
(void*)scratch, (tSize)n);
|
||||
tSize bytes_read =
|
||||
hdfsPread(fileSys_, hfile_, offset, static_cast<void*>(scratch),
|
||||
static_cast<tSize>(n));
|
||||
ROCKS_LOG_DEBUG(mylog, "[hdfs] HdfsReadableFile pread %s\n",
|
||||
filename_.c_str());
|
||||
*result = Slice(scratch, (bytes_read < 0) ? 0 : bytes_read);
|
||||
|
||||
Vendored
+14
-11
@@ -41,7 +41,7 @@
|
||||
#include <time.h>
|
||||
#include <algorithm>
|
||||
// Get nano time includes
|
||||
#if defined(OS_LINUX) || defined(OS_FREEBSD)
|
||||
#if defined(OS_LINUX) || defined(OS_FREEBSD) || defined(OS_GNU_KFREEBSD)
|
||||
#elif defined(__MACH__)
|
||||
#include <Availability.h>
|
||||
#include <mach/clock.h>
|
||||
@@ -249,7 +249,8 @@ class PosixEnv : public CompositeEnvWrapper {
|
||||
}
|
||||
|
||||
uint64_t NowNanos() override {
|
||||
#if defined(OS_LINUX) || defined(OS_FREEBSD) || defined(OS_AIX)
|
||||
#if defined(OS_LINUX) || defined(OS_FREEBSD) || defined(OS_GNU_KFREEBSD) || \
|
||||
defined(OS_AIX)
|
||||
struct timespec ts;
|
||||
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||
return static_cast<uint64_t>(ts.tv_sec) * 1000000000 + ts.tv_nsec;
|
||||
@@ -269,8 +270,8 @@ class PosixEnv : public CompositeEnvWrapper {
|
||||
}
|
||||
|
||||
uint64_t NowCPUNanos() override {
|
||||
#if defined(OS_LINUX) || defined(OS_FREEBSD) || defined(OS_AIX) || \
|
||||
(defined(__MACH__) && defined(__MAC_10_12))
|
||||
#if defined(OS_LINUX) || defined(OS_FREEBSD) || defined(OS_GNU_KFREEBSD) || \
|
||||
defined(OS_AIX) || (defined(__MACH__) && defined(__MAC_10_12))
|
||||
struct timespec ts;
|
||||
clock_gettime(CLOCK_THREAD_CPUTIME_ID, &ts);
|
||||
return static_cast<uint64_t>(ts.tv_sec) * 1000000000 + ts.tv_nsec;
|
||||
@@ -329,7 +330,7 @@ class PosixEnv : public CompositeEnvWrapper {
|
||||
thread_pools_[pri].IncBackgroundThreadsIfNeeded(num);
|
||||
}
|
||||
|
||||
void LowerThreadPoolIOPriority(Priority pool = LOW) override {
|
||||
void LowerThreadPoolIOPriority(Priority pool) override {
|
||||
assert(pool >= Priority::BOTTOM && pool <= Priority::HIGH);
|
||||
#ifdef OS_LINUX
|
||||
thread_pools_[pool].LowerIOPriority();
|
||||
@@ -338,13 +339,15 @@ class PosixEnv : public CompositeEnvWrapper {
|
||||
#endif
|
||||
}
|
||||
|
||||
void LowerThreadPoolCPUPriority(Priority pool = LOW) override {
|
||||
void LowerThreadPoolCPUPriority(Priority pool) override {
|
||||
assert(pool >= Priority::BOTTOM && pool <= Priority::HIGH);
|
||||
#ifdef OS_LINUX
|
||||
thread_pools_[pool].LowerCPUPriority();
|
||||
#else
|
||||
(void)pool;
|
||||
#endif
|
||||
thread_pools_[pool].LowerCPUPriority(CpuPriority::kLow);
|
||||
}
|
||||
|
||||
Status LowerThreadPoolCPUPriority(Priority pool, CpuPriority pri) override {
|
||||
assert(pool >= Priority::BOTTOM && pool <= Priority::HIGH);
|
||||
thread_pools_[pool].LowerCPUPriority(pri);
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
std::string TimeToString(uint64_t secondsSince1970) override {
|
||||
|
||||
Vendored
+75
-2
@@ -202,6 +202,79 @@ TEST_F(EnvPosixTest, DISABLED_FilePermission) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(EnvPosixTest, LowerThreadPoolCpuPriority) {
|
||||
std::atomic<CpuPriority> from_priority(CpuPriority::kNormal);
|
||||
std::atomic<CpuPriority> to_priority(CpuPriority::kNormal);
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"ThreadPoolImpl::BGThread::BeforeSetCpuPriority", [&](void* pri) {
|
||||
from_priority.store(*reinterpret_cast<CpuPriority*>(pri));
|
||||
});
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"ThreadPoolImpl::BGThread::AfterSetCpuPriority", [&](void* pri) {
|
||||
to_priority.store(*reinterpret_cast<CpuPriority*>(pri));
|
||||
});
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
env_->SetBackgroundThreads(1, Env::BOTTOM);
|
||||
env_->SetBackgroundThreads(1, Env::HIGH);
|
||||
|
||||
auto RunTask = [&](Env::Priority pool) {
|
||||
std::atomic<bool> called(false);
|
||||
env_->Schedule(&SetBool, &called, pool);
|
||||
for (int i = 0; i < kDelayMicros; i++) {
|
||||
if (called.load()) {
|
||||
break;
|
||||
}
|
||||
Env::Default()->SleepForMicroseconds(1);
|
||||
}
|
||||
ASSERT_TRUE(called.load());
|
||||
};
|
||||
|
||||
{
|
||||
// Same priority, no-op.
|
||||
env_->LowerThreadPoolCPUPriority(Env::Priority::BOTTOM,
|
||||
CpuPriority::kNormal);
|
||||
RunTask(Env::Priority::BOTTOM);
|
||||
ASSERT_EQ(from_priority, CpuPriority::kNormal);
|
||||
ASSERT_EQ(to_priority, CpuPriority::kNormal);
|
||||
}
|
||||
|
||||
{
|
||||
// Higher priority, no-op.
|
||||
env_->LowerThreadPoolCPUPriority(Env::Priority::BOTTOM, CpuPriority::kHigh);
|
||||
RunTask(Env::Priority::BOTTOM);
|
||||
ASSERT_EQ(from_priority, CpuPriority::kNormal);
|
||||
ASSERT_EQ(to_priority, CpuPriority::kNormal);
|
||||
}
|
||||
|
||||
{
|
||||
// Lower priority from kNormal -> kLow.
|
||||
env_->LowerThreadPoolCPUPriority(Env::Priority::BOTTOM, CpuPriority::kLow);
|
||||
RunTask(Env::Priority::BOTTOM);
|
||||
ASSERT_EQ(from_priority, CpuPriority::kNormal);
|
||||
ASSERT_EQ(to_priority, CpuPriority::kLow);
|
||||
}
|
||||
|
||||
{
|
||||
// Lower priority from kLow -> kIdle.
|
||||
env_->LowerThreadPoolCPUPriority(Env::Priority::BOTTOM, CpuPriority::kIdle);
|
||||
RunTask(Env::Priority::BOTTOM);
|
||||
ASSERT_EQ(from_priority, CpuPriority::kLow);
|
||||
ASSERT_EQ(to_priority, CpuPriority::kIdle);
|
||||
}
|
||||
|
||||
{
|
||||
// Lower priority from kNormal -> kIdle for another pool.
|
||||
env_->LowerThreadPoolCPUPriority(Env::Priority::HIGH, CpuPriority::kIdle);
|
||||
RunTask(Env::Priority::HIGH);
|
||||
ASSERT_EQ(from_priority, CpuPriority::kNormal);
|
||||
ASSERT_EQ(to_priority, CpuPriority::kIdle);
|
||||
}
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST_F(EnvPosixTest, MemoryMappedFileBuffer) {
|
||||
@@ -1047,7 +1120,7 @@ TEST_P(EnvPosixTestWithParam, RandomAccessUniqueIDConcurrent) {
|
||||
|
||||
// Collect and check whether the IDs are unique.
|
||||
std::unordered_set<std::string> ids;
|
||||
for (const std::string fname : fnames) {
|
||||
for (const std::string& fname : fnames) {
|
||||
std::unique_ptr<RandomAccessFile> file;
|
||||
std::string unique_id;
|
||||
ASSERT_OK(env_->NewRandomAccessFile(fname, &file, soptions));
|
||||
@@ -1061,7 +1134,7 @@ TEST_P(EnvPosixTestWithParam, RandomAccessUniqueIDConcurrent) {
|
||||
}
|
||||
|
||||
// Delete the files
|
||||
for (const std::string fname : fnames) {
|
||||
for (const std::string& fname : fnames) {
|
||||
ASSERT_OK(env_->DeleteFile(fname));
|
||||
}
|
||||
|
||||
|
||||
Vendored
+6
-6
@@ -150,11 +150,11 @@ bool PosixPositionedWrite(int fd, const char* buf, size_t nbyte, off_t offset) {
|
||||
#endif
|
||||
|
||||
bool IsSyncFileRangeSupported(int fd) {
|
||||
// The approach taken in this function is to build a blacklist of cases where
|
||||
// we know `sync_file_range` definitely will not work properly despite passing
|
||||
// the compile-time check (`ROCKSDB_RANGESYNC_PRESENT`). If we are unsure, or
|
||||
// if any of the checks fail in unexpected ways, we allow `sync_file_range` to
|
||||
// be used. This way should minimize risk of impacting existing use cases.
|
||||
// This function tracks and checks for cases where we know `sync_file_range`
|
||||
// definitely will not work properly despite passing the compile-time check
|
||||
// (`ROCKSDB_RANGESYNC_PRESENT`). If we are unsure, or if any of the checks
|
||||
// fail in unexpected ways, we allow `sync_file_range` to be used. This way
|
||||
// should minimize risk of impacting existing use cases.
|
||||
struct statfs buf;
|
||||
int ret = fstatfs(fd, &buf);
|
||||
assert(ret == 0);
|
||||
@@ -176,7 +176,7 @@ bool IsSyncFileRangeSupported(int fd) {
|
||||
// ("Function not implemented").
|
||||
return false;
|
||||
}
|
||||
// None of the cases on the blacklist matched, so allow `sync_file_range` use.
|
||||
// None of the known cases matched, so allow `sync_file_range` use.
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
add_executable(simple_example
|
||||
simple_example.cc)
|
||||
target_link_libraries(simple_example
|
||||
${ROCKSDB_LIB})
|
||||
|
||||
add_executable(column_families_example
|
||||
column_families_example.cc)
|
||||
target_link_libraries(column_families_example
|
||||
${ROCKSDB_LIB})
|
||||
|
||||
add_executable(compact_files_example
|
||||
compact_files_example.cc)
|
||||
target_link_libraries(compact_files_example
|
||||
${ROCKSDB_LIB})
|
||||
|
||||
add_executable(c_simple_example
|
||||
c_simple_example.c)
|
||||
target_link_libraries(c_simple_example
|
||||
${ROCKSDB_LIB})
|
||||
|
||||
add_executable(optimistic_transaction_example
|
||||
optimistic_transaction_example.cc)
|
||||
target_link_libraries(optimistic_transaction_example
|
||||
${ROCKSDB_LIB})
|
||||
|
||||
add_executable(transaction_example
|
||||
transaction_example.cc)
|
||||
target_link_libraries(transaction_example
|
||||
${ROCKSDB_LIB})
|
||||
|
||||
add_executable(compaction_filter_example
|
||||
compaction_filter_example.cc)
|
||||
target_link_libraries(compaction_filter_example
|
||||
${ROCKSDB_LIB})
|
||||
|
||||
add_executable(options_file_example
|
||||
options_file_example.cc)
|
||||
target_link_libraries(options_file_example
|
||||
${ROCKSDB_LIB})
|
||||
|
||||
add_executable(multi_processes_example
|
||||
EXCLUDE_FROM_ALL
|
||||
multi_processes_example.cc)
|
||||
target_link_libraries(multi_processes_example
|
||||
${ROCKSDB_LIB})
|
||||
@@ -68,7 +68,7 @@ int main() {
|
||||
}
|
||||
|
||||
PinnableSlice pinnable_val;
|
||||
db->Get(ReadOptions(), db->DefaultColumnFamily(), "key1", &pinnable_val);
|
||||
s = db->Get(ReadOptions(), db->DefaultColumnFamily(), "key1", &pinnable_val);
|
||||
assert(s.IsNotFound());
|
||||
// Reset PinnableSlice after each use and before each reuse
|
||||
pinnable_val.Reset();
|
||||
|
||||
@@ -122,4 +122,68 @@ bool IsWalDirSameAsDBPath(const ImmutableDBOptions* db_options) {
|
||||
return same;
|
||||
}
|
||||
|
||||
IOStatus GenerateOneFileChecksum(FileSystem* fs, const std::string& file_path,
|
||||
FileChecksumGenFactory* checksum_factory,
|
||||
std::string* file_checksum,
|
||||
std::string* file_checksum_func_name,
|
||||
size_t verify_checksums_readahead_size,
|
||||
bool allow_mmap_reads) {
|
||||
if (checksum_factory == nullptr) {
|
||||
return IOStatus::InvalidArgument("Checksum factory is invalid");
|
||||
}
|
||||
assert(file_checksum != nullptr);
|
||||
assert(file_checksum_func_name != nullptr);
|
||||
|
||||
FileChecksumGenContext gen_context;
|
||||
std::unique_ptr<FileChecksumGenerator> checksum_generator =
|
||||
checksum_factory->CreateFileChecksumGenerator(gen_context);
|
||||
uint64_t size;
|
||||
IOStatus io_s;
|
||||
std::unique_ptr<RandomAccessFileReader> reader;
|
||||
{
|
||||
std::unique_ptr<FSRandomAccessFile> r_file;
|
||||
io_s = fs->NewRandomAccessFile(file_path, FileOptions(), &r_file, nullptr);
|
||||
if (!io_s.ok()) {
|
||||
return io_s;
|
||||
}
|
||||
io_s = fs->GetFileSize(file_path, IOOptions(), &size, nullptr);
|
||||
if (!io_s.ok()) {
|
||||
return io_s;
|
||||
}
|
||||
reader.reset(new RandomAccessFileReader(std::move(r_file), file_path));
|
||||
}
|
||||
|
||||
// Found that 256 KB readahead size provides the best performance, based on
|
||||
// experiments, for auto readahead. Experiment data is in PR #3282.
|
||||
size_t default_max_read_ahead_size = 256 * 1024;
|
||||
size_t readahead_size = (verify_checksums_readahead_size != 0)
|
||||
? verify_checksums_readahead_size
|
||||
: default_max_read_ahead_size;
|
||||
|
||||
FilePrefetchBuffer prefetch_buffer(
|
||||
reader.get(), readahead_size /* readadhead_size */,
|
||||
readahead_size /* max_readahead_size */, !allow_mmap_reads /* enable */);
|
||||
|
||||
Slice slice;
|
||||
uint64_t offset = 0;
|
||||
while (size > 0) {
|
||||
size_t bytes_to_read =
|
||||
static_cast<size_t>(std::min(uint64_t{readahead_size}, size));
|
||||
if (!prefetch_buffer.TryReadFromCache(offset, bytes_to_read, &slice,
|
||||
false)) {
|
||||
return IOStatus::Corruption("file read failed");
|
||||
}
|
||||
if (slice.size() == 0) {
|
||||
return IOStatus::Corruption("file too small");
|
||||
}
|
||||
checksum_generator->Update(slice.data(), slice.size());
|
||||
size -= slice.size();
|
||||
offset += slice.size();
|
||||
}
|
||||
checksum_generator->Finalize();
|
||||
*file_checksum = checksum_generator->GetChecksum();
|
||||
*file_checksum_func_name = checksum_generator->Name();
|
||||
return IOStatus::OK();
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
@@ -10,6 +10,7 @@
|
||||
#include "options/db_options.h"
|
||||
#include "rocksdb/env.h"
|
||||
#include "rocksdb/file_system.h"
|
||||
#include "rocksdb/sst_file_writer.h"
|
||||
#include "rocksdb/status.h"
|
||||
#include "rocksdb/types.h"
|
||||
|
||||
@@ -30,6 +31,12 @@ extern Status DeleteDBFile(const ImmutableDBOptions* db_options,
|
||||
|
||||
extern bool IsWalDirSameAsDBPath(const ImmutableDBOptions* db_options);
|
||||
|
||||
extern IOStatus GenerateOneFileChecksum(
|
||||
FileSystem* fs, const std::string& file_path,
|
||||
FileChecksumGenFactory* checksum_factory, std::string* file_checksum,
|
||||
std::string* file_checksum_func_name,
|
||||
size_t verify_checksums_readahead_size, bool allow_mmap_reads);
|
||||
|
||||
inline IOStatus PrepareIOFromReadOptions(const ReadOptions& ro, Env* env,
|
||||
IOOptions& opts) {
|
||||
if (!env) {
|
||||
|
||||
@@ -132,7 +132,7 @@ class RandomAccessFileReader {
|
||||
|
||||
FSRandomAccessFile* file() { return file_.get(); }
|
||||
|
||||
std::string file_name() const { return file_name_; }
|
||||
const std::string& file_name() const { return file_name_; }
|
||||
|
||||
bool use_direct_io() const { return file_->use_direct_io(); }
|
||||
|
||||
|
||||
+2
-6
@@ -162,10 +162,7 @@ class HdfsEnv : public Env {
|
||||
return posixEnv->TimeToString(number);
|
||||
}
|
||||
|
||||
static uint64_t gettid() {
|
||||
assert(sizeof(pthread_t) <= sizeof(uint64_t));
|
||||
return (uint64_t)pthread_self();
|
||||
}
|
||||
static uint64_t gettid() { return Env::Default()->GetThreadID(); }
|
||||
|
||||
uint64_t GetThreadID() const override { return HdfsEnv::gettid(); }
|
||||
|
||||
@@ -209,8 +206,7 @@ class HdfsEnv : public Env {
|
||||
std::string portStr = (rem == 0 ? remaining :
|
||||
remaining.substr(0, rem));
|
||||
|
||||
tPort port;
|
||||
port = atoi(portStr.c_str());
|
||||
tPort port = static_cast<tPort>(atoi(portStr.c_str()));
|
||||
if (port == 0) {
|
||||
throw HdfsFatalException("Bad host-port for hdfs " + uri);
|
||||
}
|
||||
|
||||
@@ -111,10 +111,16 @@ struct RangePtr {
|
||||
RangePtr(const Slice* s, const Slice* l) : start(s), limit(l) {}
|
||||
};
|
||||
|
||||
// It is valid that files_checksums and files_checksum_func_names are both
|
||||
// empty (no checksum informaiton is provided for ingestion). Otherwise,
|
||||
// their sizes should be the same as external_files. The file order should
|
||||
// be the same in three vectors and guaranteed by the caller.
|
||||
struct IngestExternalFileArg {
|
||||
ColumnFamilyHandle* column_family = nullptr;
|
||||
std::vector<std::string> external_files;
|
||||
IngestExternalFileOptions options;
|
||||
std::vector<std::string> files_checksums;
|
||||
std::vector<std::string> files_checksum_func_names;
|
||||
};
|
||||
|
||||
struct GetMergeOperandsOptions {
|
||||
@@ -1538,6 +1544,13 @@ class DB {
|
||||
// Returns Status::OK if identity could be set properly
|
||||
virtual Status GetDbIdentity(std::string& identity) const = 0;
|
||||
|
||||
// Return a unique identifier for each DB object that is opened
|
||||
// This DB session ID should be unique among all open DB instances on all
|
||||
// hosts, and should be unique among re-openings of the same or other DBs.
|
||||
// (Two open DBs have the same identity from other function GetDbIdentity when
|
||||
// one is physically copied from the other.)
|
||||
virtual Status GetDbSessionId(std::string& session_id) const = 0;
|
||||
|
||||
// Returns default column family handle
|
||||
virtual ColumnFamilyHandle* DefaultColumnFamily() const = 0;
|
||||
|
||||
|
||||
+20
-2
@@ -61,6 +61,13 @@ class FileSystem;
|
||||
|
||||
const size_t kDefaultPageSize = 4 * 1024;
|
||||
|
||||
enum class CpuPriority {
|
||||
kIdle = 0,
|
||||
kLow = 1,
|
||||
kNormal = 2,
|
||||
kHigh = 3,
|
||||
};
|
||||
|
||||
// Options while opening a file to read/write
|
||||
struct EnvOptions {
|
||||
// Construct with default Options
|
||||
@@ -474,6 +481,13 @@ class Env {
|
||||
// Lower IO priority for threads from the specified pool.
|
||||
virtual void LowerThreadPoolIOPriority(Priority /*pool*/ = LOW) {}
|
||||
|
||||
// Lower CPU priority for threads from the specified pool.
|
||||
virtual Status LowerThreadPoolCPUPriority(Priority /*pool*/,
|
||||
CpuPriority /*pri*/) {
|
||||
return Status::NotSupported(
|
||||
"Env::LowerThreadPoolCPUPriority(Priority, CpuPriority) not supported");
|
||||
}
|
||||
|
||||
// Lower CPU priority for threads from the specified pool.
|
||||
virtual void LowerThreadPoolCPUPriority(Priority /*pool*/ = LOW) {}
|
||||
|
||||
@@ -1355,14 +1369,18 @@ class EnvWrapper : public Env {
|
||||
return target_->IncBackgroundThreadsIfNeeded(num, pri);
|
||||
}
|
||||
|
||||
void LowerThreadPoolIOPriority(Priority pool = LOW) override {
|
||||
void LowerThreadPoolIOPriority(Priority pool) override {
|
||||
target_->LowerThreadPoolIOPriority(pool);
|
||||
}
|
||||
|
||||
void LowerThreadPoolCPUPriority(Priority pool = LOW) override {
|
||||
void LowerThreadPoolCPUPriority(Priority pool) override {
|
||||
target_->LowerThreadPoolCPUPriority(pool);
|
||||
}
|
||||
|
||||
Status LowerThreadPoolCPUPriority(Priority pool, CpuPriority pri) override {
|
||||
return target_->LowerThreadPoolCPUPriority(pool, pri);
|
||||
}
|
||||
|
||||
std::string TimeToString(uint64_t time) override {
|
||||
return target_->TimeToString(time);
|
||||
}
|
||||
|
||||
@@ -169,8 +169,10 @@ class CTREncryptionProvider : public EncryptionProvider {
|
||||
virtual ~CTREncryptionProvider() {}
|
||||
|
||||
// GetPrefixLength returns the length of the prefix that is added to every
|
||||
// file and used for storing encryption options. For optimal performance, the
|
||||
// prefix length should be a multiple of the page size.
|
||||
// file
|
||||
// and used for storing encryption options.
|
||||
// For optimal performance, the prefix length should be a multiple of
|
||||
// the page size.
|
||||
virtual size_t GetPrefixLength() override;
|
||||
|
||||
// CreateNewPrefix initialized an allocated block of prefix memory
|
||||
@@ -194,13 +196,243 @@ class CTREncryptionProvider : public EncryptionProvider {
|
||||
size_t blockSize);
|
||||
|
||||
// CreateCipherStreamFromPrefix creates a block access cipher stream for a
|
||||
// file given given name and options. The given prefix is already decrypted.
|
||||
// file given
|
||||
// given name and options. The given prefix is already decrypted.
|
||||
virtual Status CreateCipherStreamFromPrefix(
|
||||
const std::string& fname, const EnvOptions& options,
|
||||
uint64_t initialCounter, const Slice& iv, const Slice& prefix,
|
||||
std::unique_ptr<BlockAccessCipherStream>* result);
|
||||
};
|
||||
|
||||
class EncryptedSequentialFile : public SequentialFile {
|
||||
protected:
|
||||
std::unique_ptr<SequentialFile> file_;
|
||||
std::unique_ptr<BlockAccessCipherStream> stream_;
|
||||
uint64_t offset_;
|
||||
size_t prefixLength_;
|
||||
|
||||
public:
|
||||
// Default ctor. Given underlying sequential file is supposed to be at
|
||||
// offset == prefixLength.
|
||||
EncryptedSequentialFile(std::unique_ptr<SequentialFile>&& f,
|
||||
std::unique_ptr<BlockAccessCipherStream>&& s,
|
||||
size_t prefixLength)
|
||||
: file_(std::move(f)),
|
||||
stream_(std::move(s)),
|
||||
offset_(prefixLength),
|
||||
prefixLength_(prefixLength) {}
|
||||
|
||||
// Read up to "n" bytes from the file. "scratch[0..n-1]" may be
|
||||
// written by this routine. Sets "*result" to the data that was
|
||||
// read (including if fewer than "n" bytes were successfully read).
|
||||
// May set "*result" to point at data in "scratch[0..n-1]", so
|
||||
// "scratch[0..n-1]" must be live when "*result" is used.
|
||||
// If an error was encountered, returns a non-OK status.
|
||||
//
|
||||
// REQUIRES: External synchronization
|
||||
virtual Status Read(size_t n, Slice* result, char* scratch) override;
|
||||
|
||||
// Skip "n" bytes from the file. This is guaranteed to be no
|
||||
// slower that reading the same data, but may be faster.
|
||||
//
|
||||
// If end of file is reached, skipping will stop at the end of the
|
||||
// file, and Skip will return OK.
|
||||
//
|
||||
// REQUIRES: External synchronization
|
||||
virtual Status Skip(uint64_t n) override;
|
||||
|
||||
// Indicates the upper layers if the current SequentialFile implementation
|
||||
// uses direct IO.
|
||||
virtual bool use_direct_io() const override;
|
||||
|
||||
// Use the returned alignment value to allocate
|
||||
// aligned buffer for Direct I/O
|
||||
virtual size_t GetRequiredBufferAlignment() const override;
|
||||
|
||||
// Remove any kind of caching of data from the offset to offset+length
|
||||
// of this file. If the length is 0, then it refers to the end of file.
|
||||
// If the system is not caching the file contents, then this is a noop.
|
||||
virtual Status InvalidateCache(size_t offset, size_t length) override;
|
||||
|
||||
// Positioned Read for direct I/O
|
||||
// If Direct I/O enabled, offset, n, and scratch should be properly aligned
|
||||
virtual Status PositionedRead(uint64_t offset, size_t n, Slice* result,
|
||||
char* scratch) override;
|
||||
};
|
||||
|
||||
// A file abstraction for randomly reading the contents of a file.
|
||||
class EncryptedRandomAccessFile : public RandomAccessFile {
|
||||
protected:
|
||||
std::unique_ptr<RandomAccessFile> file_;
|
||||
std::unique_ptr<BlockAccessCipherStream> stream_;
|
||||
size_t prefixLength_;
|
||||
|
||||
public:
|
||||
EncryptedRandomAccessFile(std::unique_ptr<RandomAccessFile>&& f,
|
||||
std::unique_ptr<BlockAccessCipherStream>&& s,
|
||||
size_t prefixLength)
|
||||
: file_(std::move(f)),
|
||||
stream_(std::move(s)),
|
||||
prefixLength_(prefixLength) {}
|
||||
|
||||
// Read up to "n" bytes from the file starting at "offset".
|
||||
// "scratch[0..n-1]" may be written by this routine. Sets "*result"
|
||||
// to the data that was read (including if fewer than "n" bytes were
|
||||
// successfully read). May set "*result" to point at data in
|
||||
// "scratch[0..n-1]", so "scratch[0..n-1]" must be live when
|
||||
// "*result" is used. If an error was encountered, returns a non-OK
|
||||
// status.
|
||||
//
|
||||
// Safe for concurrent use by multiple threads.
|
||||
// If Direct I/O enabled, offset, n, and scratch should be aligned properly.
|
||||
virtual Status Read(uint64_t offset, size_t n, Slice* result,
|
||||
char* scratch) const override;
|
||||
|
||||
// Readahead the file starting from offset by n bytes for caching.
|
||||
virtual Status Prefetch(uint64_t offset, size_t n) override;
|
||||
|
||||
// Tries to get an unique ID for this file that will be the same each time
|
||||
// the file is opened (and will stay the same while the file is open).
|
||||
// Furthermore, it tries to make this ID at most "max_size" bytes. If such an
|
||||
// ID can be created this function returns the length of the ID and places it
|
||||
// in "id"; otherwise, this function returns 0, in which case "id"
|
||||
// may not have been modified.
|
||||
//
|
||||
// This function guarantees, for IDs from a given environment, two unique ids
|
||||
// cannot be made equal to each other by adding arbitrary bytes to one of
|
||||
// them. That is, no unique ID is the prefix of another.
|
||||
//
|
||||
// This function guarantees that the returned ID will not be interpretable as
|
||||
// a single varint.
|
||||
//
|
||||
// Note: these IDs are only valid for the duration of the process.
|
||||
virtual size_t GetUniqueId(char* id, size_t max_size) const override;
|
||||
|
||||
virtual void Hint(AccessPattern pattern) override;
|
||||
|
||||
// Indicates the upper layers if the current RandomAccessFile implementation
|
||||
// uses direct IO.
|
||||
virtual bool use_direct_io() const override;
|
||||
|
||||
// Use the returned alignment value to allocate
|
||||
// aligned buffer for Direct I/O
|
||||
virtual size_t GetRequiredBufferAlignment() const override;
|
||||
|
||||
// Remove any kind of caching of data from the offset to offset+length
|
||||
// of this file. If the length is 0, then it refers to the end of file.
|
||||
// If the system is not caching the file contents, then this is a noop.
|
||||
virtual Status InvalidateCache(size_t offset, size_t length) override;
|
||||
};
|
||||
|
||||
// A file abstraction for sequential writing. The implementation
|
||||
// must provide buffering since callers may append small fragments
|
||||
// at a time to the file.
|
||||
class EncryptedWritableFile : public WritableFileWrapper {
|
||||
protected:
|
||||
std::unique_ptr<WritableFile> file_;
|
||||
std::unique_ptr<BlockAccessCipherStream> stream_;
|
||||
size_t prefixLength_;
|
||||
|
||||
public:
|
||||
// Default ctor. Prefix is assumed to be written already.
|
||||
EncryptedWritableFile(std::unique_ptr<WritableFile>&& f,
|
||||
std::unique_ptr<BlockAccessCipherStream>&& s,
|
||||
size_t prefixLength)
|
||||
: WritableFileWrapper(f.get()),
|
||||
file_(std::move(f)),
|
||||
stream_(std::move(s)),
|
||||
prefixLength_(prefixLength) {}
|
||||
|
||||
Status Append(const Slice& data) override;
|
||||
|
||||
Status PositionedAppend(const Slice& data, uint64_t offset) override;
|
||||
|
||||
// Indicates the upper layers if the current WritableFile implementation
|
||||
// uses direct IO.
|
||||
virtual bool use_direct_io() const override;
|
||||
|
||||
// Use the returned alignment value to allocate
|
||||
// aligned buffer for Direct I/O
|
||||
virtual size_t GetRequiredBufferAlignment() const override;
|
||||
|
||||
/*
|
||||
* Get the size of valid data in the file.
|
||||
*/
|
||||
virtual uint64_t GetFileSize() override;
|
||||
|
||||
// Truncate is necessary to trim the file to the correct size
|
||||
// before closing. It is not always possible to keep track of the file
|
||||
// size due to whole pages writes. The behavior is undefined if called
|
||||
// with other writes to follow.
|
||||
virtual Status Truncate(uint64_t size) override;
|
||||
|
||||
// Remove any kind of caching of data from the offset to offset+length
|
||||
// of this file. If the length is 0, then it refers to the end of file.
|
||||
// If the system is not caching the file contents, then this is a noop.
|
||||
// This call has no effect on dirty pages in the cache.
|
||||
virtual Status InvalidateCache(size_t offset, size_t length) override;
|
||||
|
||||
// Sync a file range with disk.
|
||||
// offset is the starting byte of the file range to be synchronized.
|
||||
// nbytes specifies the length of the range to be synchronized.
|
||||
// This asks the OS to initiate flushing the cached data to disk,
|
||||
// without waiting for completion.
|
||||
// Default implementation does nothing.
|
||||
virtual Status RangeSync(uint64_t offset, uint64_t nbytes) override;
|
||||
|
||||
// PrepareWrite performs any necessary preparation for a write
|
||||
// before the write actually occurs. This allows for pre-allocation
|
||||
// of space on devices where it can result in less file
|
||||
// fragmentation and/or less waste from over-zealous filesystem
|
||||
// pre-allocation.
|
||||
virtual void PrepareWrite(size_t offset, size_t len) override;
|
||||
|
||||
// Pre-allocates space for a file.
|
||||
virtual Status Allocate(uint64_t offset, uint64_t len) override;
|
||||
};
|
||||
|
||||
// A file abstraction for random reading and writing.
|
||||
class EncryptedRandomRWFile : public RandomRWFile {
|
||||
protected:
|
||||
std::unique_ptr<RandomRWFile> file_;
|
||||
std::unique_ptr<BlockAccessCipherStream> stream_;
|
||||
size_t prefixLength_;
|
||||
|
||||
public:
|
||||
EncryptedRandomRWFile(std::unique_ptr<RandomRWFile>&& f,
|
||||
std::unique_ptr<BlockAccessCipherStream>&& s,
|
||||
size_t prefixLength)
|
||||
: file_(std::move(f)),
|
||||
stream_(std::move(s)),
|
||||
prefixLength_(prefixLength) {}
|
||||
|
||||
// Indicates if the class makes use of direct I/O
|
||||
// If false you must pass aligned buffer to Write()
|
||||
virtual bool use_direct_io() const override;
|
||||
|
||||
// Use the returned alignment value to allocate
|
||||
// aligned buffer for Direct I/O
|
||||
virtual size_t GetRequiredBufferAlignment() const override;
|
||||
|
||||
// Write bytes in `data` at offset `offset`, Returns Status::OK() on success.
|
||||
// Pass aligned buffer when use_direct_io() returns true.
|
||||
virtual Status Write(uint64_t offset, const Slice& data) override;
|
||||
|
||||
// Read up to `n` bytes starting from offset `offset` and store them in
|
||||
// result, provided `scratch` size should be at least `n`.
|
||||
// Returns Status::OK() on success.
|
||||
virtual Status Read(uint64_t offset, size_t n, Slice* result,
|
||||
char* scratch) const override;
|
||||
|
||||
virtual Status Flush() override;
|
||||
|
||||
virtual Status Sync() override;
|
||||
|
||||
virtual Status Fsync() override;
|
||||
|
||||
virtual Status Close() override;
|
||||
};
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
#endif // !defined(ROCKSDB_LITE)
|
||||
@@ -51,13 +51,6 @@ class InternalKeyComparator;
|
||||
class WalFilter;
|
||||
class FileSystem;
|
||||
|
||||
enum class CpuPriority {
|
||||
kIdle = 0,
|
||||
kLow = 1,
|
||||
kNormal = 2,
|
||||
kHigh = 3,
|
||||
};
|
||||
|
||||
// DB contents are stored in a set of blocks, each of which holds a
|
||||
// sequence of key,value pairs. Each block may be compressed before
|
||||
// being stored in a file. The following enum describes which
|
||||
@@ -1571,6 +1564,26 @@ struct IngestExternalFileOptions {
|
||||
// Using a large readahead size (> 2MB) can typically improve the performance
|
||||
// of forward iteration on spinning disks.
|
||||
size_t verify_checksums_readahead_size = 0;
|
||||
// Set to TRUE if user wants to verify the sst file checksum of ingested
|
||||
// files. The DB checksum function will generate the checksum of each
|
||||
// ingested file (if file_checksum_gen_factory is set) and compare the
|
||||
// checksum function name and checksum with the ingested checksum information.
|
||||
//
|
||||
// If this option is set to True: 1) if DB does not enable checksum
|
||||
// (file_checksum_gen_factory == nullptr), the ingested checksum information
|
||||
// will be ignored; 2) If DB enable the checksum function, we calculate the
|
||||
// sst file checksum after the file is moved or copied and compare the
|
||||
// checksum and checksum name. If checksum or checksum function name does
|
||||
// not match, ingestion will be failed. If the verification is sucessful,
|
||||
// checksum and checksum function name will be stored in Manifest.
|
||||
// If this option is set to FALSE, 1) if DB does not enable checksum,
|
||||
// the ingested checksum information will be ignored; 2) if DB enable the
|
||||
// checksum, we only verify the ingested checksum function name and we
|
||||
// trust the ingested checksum. If the checksum function name matches, we
|
||||
// store the checksum in Manifest. DB does not calculate the checksum during
|
||||
// ingestion. However, if no checksum information is provided with the
|
||||
// ingested files, DB will generate the checksum and store in the Manifest.
|
||||
bool verify_file_checksum = true;
|
||||
};
|
||||
|
||||
enum TraceFilterType : uint64_t {
|
||||
|
||||
@@ -56,6 +56,12 @@ class PersistentCache {
|
||||
virtual StatsType Stats() = 0;
|
||||
|
||||
virtual std::string GetPrintableOptions() const = 0;
|
||||
|
||||
// Return a new numeric id. May be used by multiple clients who are
|
||||
// sharding the same persistent cache to partition the key space. Typically
|
||||
// the client will allocate a new id at startup and prepend the id to its
|
||||
// cache keys.
|
||||
virtual uint64_t NewId() = 0;
|
||||
};
|
||||
|
||||
// Factor method to create a new persistent cache
|
||||
|
||||
@@ -200,6 +200,40 @@ struct BlockBasedTableOptions {
|
||||
// incompatible with block-based filters.
|
||||
bool partition_filters = false;
|
||||
|
||||
// EXPERIMENTAL Option to generate Bloom filters that minimize memory
|
||||
// internal fragmentation.
|
||||
//
|
||||
// When false, malloc_usable_size is not available, or format_version < 5,
|
||||
// filters are generated without regard to internal fragmentation when
|
||||
// loaded into memory (historical behavior). When true (and
|
||||
// malloc_usable_size is available and format_version >= 5), then Bloom
|
||||
// filters are generated to "round up" and "round down" their sizes to
|
||||
// minimize internal fragmentation when loaded into memory, assuming the
|
||||
// reading DB has the same memory allocation characteristics as the
|
||||
// generating DB. This option does not break forward or backward
|
||||
// compatibility.
|
||||
//
|
||||
// While individual filters will vary in bits/key and false positive rate
|
||||
// when setting is true, the implementation attempts to maintain a weighted
|
||||
// average FP rate for filters consistent with this option set to false.
|
||||
//
|
||||
// With Jemalloc for example, this setting is expected to save about 10% of
|
||||
// the memory footprint and block cache charge of filters, while increasing
|
||||
// disk usage of filters by about 1-2% due to encoding efficiency losses
|
||||
// with variance in bits/key.
|
||||
//
|
||||
// NOTE: Because some memory counted by block cache might be unmapped pages
|
||||
// within internal fragmentation, this option can increase observed RSS
|
||||
// memory usage. With cache_index_and_filter_blocks=true, this option makes
|
||||
// the block cache better at using space it is allowed.
|
||||
//
|
||||
// NOTE: Do not set to true if you do not trust malloc_usable_size. With
|
||||
// this option, RocksDB might access an allocated memory object beyond its
|
||||
// original size if malloc_usable_size says it is safe to do so. While this
|
||||
// can be considered bad practice, it should not produce undefined behavior
|
||||
// unless malloc_usable_size is buggy or broken.
|
||||
bool optimize_filters_for_memory = false;
|
||||
|
||||
// Use delta encoding to compress keys in blocks.
|
||||
// ReadOptions::pin_data requires this option to be disabled.
|
||||
//
|
||||
|
||||
@@ -30,6 +30,8 @@ typedef std::map<std::string, std::string> UserCollectedProperties;
|
||||
|
||||
// table properties' human-readable names in the property block.
|
||||
struct TablePropertiesNames {
|
||||
static const std::string kDbId;
|
||||
static const std::string kDbSessionId;
|
||||
static const std::string kDataSize;
|
||||
static const std::string kIndexSize;
|
||||
static const std::string kIndexPartitions;
|
||||
@@ -193,6 +195,17 @@ struct TableProperties {
|
||||
// Actual SST file creation time. 0 means unknown.
|
||||
uint64_t file_creation_time = 0;
|
||||
|
||||
// DB identity
|
||||
// db_id is an identifier generated the first time the DB is created
|
||||
// If DB identity is unset or unassigned, `db_id` will be an empty string.
|
||||
std::string db_id;
|
||||
|
||||
// DB session identity
|
||||
// db_session_id is an identifier that gets reset every time the DB is opened
|
||||
// If DB session identity is unset or unassigned, `db_session_id` will be an
|
||||
// empty string.
|
||||
std::string db_session_id;
|
||||
|
||||
// Name of the column family with which this SST file is associated.
|
||||
// If column family is unknown, `column_family_name` will be an empty string.
|
||||
std::string column_family_name;
|
||||
|
||||
@@ -400,6 +400,10 @@ class StackableDB : public DB {
|
||||
return db_->GetDbIdentity(identity);
|
||||
}
|
||||
|
||||
virtual Status GetDbSessionId(std::string& session_id) const override {
|
||||
return db_->GetDbSessionId(session_id);
|
||||
}
|
||||
|
||||
using DB::SetOptions;
|
||||
virtual Status SetOptions(ColumnFamilyHandle* column_family_handle,
|
||||
const std::unordered_map<std::string, std::string>&
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
#pragma once
|
||||
|
||||
#define ROCKSDB_MAJOR 6
|
||||
#define ROCKSDB_MINOR 10
|
||||
#define ROCKSDB_MINOR 11
|
||||
#define ROCKSDB_PATCH 0
|
||||
|
||||
// Do not use these. We made the mistake of declaring macros starting with
|
||||
|
||||
@@ -656,7 +656,7 @@ Status GetStringFromStruct(
|
||||
std::string* opt_string) {
|
||||
assert(opt_string);
|
||||
opt_string->clear();
|
||||
for (const auto iter : type_info) {
|
||||
for (const auto& iter : type_info) {
|
||||
const auto& opt_info = iter.second;
|
||||
// If the option is no longer used in rocksdb and marked as deprecated,
|
||||
// we skip it in the serialization.
|
||||
|
||||
@@ -42,10 +42,10 @@ const char kSpecialChar = 'z';
|
||||
typedef std::vector<std::pair<size_t, size_t>> OffsetGap;
|
||||
|
||||
void FillWithSpecialChar(char* start_ptr, size_t total_size,
|
||||
const OffsetGap& blacklist,
|
||||
const OffsetGap& excluded,
|
||||
char special_char = kSpecialChar) {
|
||||
size_t offset = 0;
|
||||
for (auto& pair : blacklist) {
|
||||
for (auto& pair : excluded) {
|
||||
std::memset(start_ptr + offset, special_char, pair.first - offset);
|
||||
offset = pair.first + pair.second;
|
||||
}
|
||||
@@ -53,10 +53,10 @@ void FillWithSpecialChar(char* start_ptr, size_t total_size,
|
||||
}
|
||||
|
||||
int NumUnsetBytes(char* start_ptr, size_t total_size,
|
||||
const OffsetGap& blacklist) {
|
||||
const OffsetGap& excluded) {
|
||||
int total_unset_bytes_base = 0;
|
||||
size_t offset = 0;
|
||||
for (auto& pair : blacklist) {
|
||||
for (auto& pair : excluded) {
|
||||
for (char* ptr = start_ptr + offset; ptr < start_ptr + pair.first; ptr++) {
|
||||
if (*ptr == kSpecialChar) {
|
||||
total_unset_bytes_base++;
|
||||
@@ -72,11 +72,11 @@ int NumUnsetBytes(char* start_ptr, size_t total_size,
|
||||
return total_unset_bytes_base;
|
||||
}
|
||||
|
||||
// Return true iff two structs are the same except blacklist fields.
|
||||
// Return true iff two structs are the same except excluded fields.
|
||||
bool CompareBytes(char* start_ptr1, char* start_ptr2, size_t total_size,
|
||||
const OffsetGap& blacklist) {
|
||||
const OffsetGap& excluded) {
|
||||
size_t offset = 0;
|
||||
for (auto& pair : blacklist) {
|
||||
for (auto& pair : excluded) {
|
||||
for (; offset < pair.first; offset++) {
|
||||
if (*(start_ptr1 + offset) != *(start_ptr2 + offset)) {
|
||||
return false;
|
||||
@@ -99,11 +99,11 @@ bool CompareBytes(char* start_ptr1, char* start_ptr2, size_t total_size,
|
||||
// GetBlockBasedTableOptionsFromString() and add the option to the input string
|
||||
// passed to the GetBlockBasedTableOptionsFromString() in this test.
|
||||
// If it is a complicated type, you also need to add the field to
|
||||
// kBbtoBlacklist, and maybe add customized verification for it.
|
||||
// kBbtoExcluded, and maybe add customized verification for it.
|
||||
TEST_F(OptionsSettableTest, BlockBasedTableOptionsAllFieldsSettable) {
|
||||
// Items in the form of <offset, size>. Need to be in ascending order
|
||||
// and not overlapping. Need to updated if new pointer-option is added.
|
||||
const OffsetGap kBbtoBlacklist = {
|
||||
const OffsetGap kBbtoExcluded = {
|
||||
{offsetof(struct BlockBasedTableOptions, flush_block_policy_factory),
|
||||
sizeof(std::shared_ptr<FlushBlockPolicyFactory>)},
|
||||
{offsetof(struct BlockBasedTableOptions, block_cache),
|
||||
@@ -128,20 +128,20 @@ TEST_F(OptionsSettableTest, BlockBasedTableOptionsAllFieldsSettable) {
|
||||
// copy a well constructed struct to this memory and see how many special
|
||||
// bytes left.
|
||||
BlockBasedTableOptions* bbto = new (bbto_ptr) BlockBasedTableOptions();
|
||||
FillWithSpecialChar(bbto_ptr, sizeof(BlockBasedTableOptions), kBbtoBlacklist);
|
||||
FillWithSpecialChar(bbto_ptr, sizeof(BlockBasedTableOptions), kBbtoExcluded);
|
||||
// It based on the behavior of compiler that padding bytes are not changed
|
||||
// when copying the struct. It's prone to failure when compiler behavior
|
||||
// changes. We verify there is unset bytes to detect the case.
|
||||
*bbto = BlockBasedTableOptions();
|
||||
int unset_bytes_base =
|
||||
NumUnsetBytes(bbto_ptr, sizeof(BlockBasedTableOptions), kBbtoBlacklist);
|
||||
NumUnsetBytes(bbto_ptr, sizeof(BlockBasedTableOptions), kBbtoExcluded);
|
||||
ASSERT_GT(unset_bytes_base, 0);
|
||||
bbto->~BlockBasedTableOptions();
|
||||
|
||||
// Construct the base option passed into
|
||||
// GetBlockBasedTableOptionsFromString().
|
||||
bbto = new (bbto_ptr) BlockBasedTableOptions();
|
||||
FillWithSpecialChar(bbto_ptr, sizeof(BlockBasedTableOptions), kBbtoBlacklist);
|
||||
FillWithSpecialChar(bbto_ptr, sizeof(BlockBasedTableOptions), kBbtoExcluded);
|
||||
// This option is not setable:
|
||||
bbto->use_delta_encoding = true;
|
||||
|
||||
@@ -149,7 +149,7 @@ TEST_F(OptionsSettableTest, BlockBasedTableOptionsAllFieldsSettable) {
|
||||
BlockBasedTableOptions* new_bbto =
|
||||
new (new_bbto_ptr) BlockBasedTableOptions();
|
||||
FillWithSpecialChar(new_bbto_ptr, sizeof(BlockBasedTableOptions),
|
||||
kBbtoBlacklist);
|
||||
kBbtoExcluded);
|
||||
|
||||
// Need to update the option string if a new option is added.
|
||||
ASSERT_OK(GetBlockBasedTableOptionsFromString(
|
||||
@@ -167,6 +167,7 @@ TEST_F(OptionsSettableTest, BlockBasedTableOptionsAllFieldsSettable) {
|
||||
"block_size_deviation=8;block_restart_interval=4; "
|
||||
"metadata_block_size=1024;"
|
||||
"partition_filters=false;"
|
||||
"optimize_filters_for_memory=true;"
|
||||
"index_block_restart_interval=4;"
|
||||
"filter_policy=bloomfilter:4:true;whole_key_filtering=1;"
|
||||
"format_version=1;"
|
||||
@@ -178,7 +179,7 @@ TEST_F(OptionsSettableTest, BlockBasedTableOptionsAllFieldsSettable) {
|
||||
|
||||
ASSERT_EQ(unset_bytes_base,
|
||||
NumUnsetBytes(new_bbto_ptr, sizeof(BlockBasedTableOptions),
|
||||
kBbtoBlacklist));
|
||||
kBbtoExcluded));
|
||||
|
||||
ASSERT_TRUE(new_bbto->block_cache.get() != nullptr);
|
||||
ASSERT_TRUE(new_bbto->block_cache_compressed.get() != nullptr);
|
||||
@@ -198,9 +199,9 @@ TEST_F(OptionsSettableTest, BlockBasedTableOptionsAllFieldsSettable) {
|
||||
// GetDBOptionsFromString() and add the option to the input string passed to
|
||||
// DBOptionsFromString()in this test.
|
||||
// If it is a complicated type, you also need to add the field to
|
||||
// kDBOptionsBlacklist, and maybe add customized verification for it.
|
||||
// kDBOptionsExcluded, and maybe add customized verification for it.
|
||||
TEST_F(OptionsSettableTest, DBOptionsAllFieldsSettable) {
|
||||
const OffsetGap kDBOptionsBlacklist = {
|
||||
const OffsetGap kDBOptionsExcluded = {
|
||||
{offsetof(struct DBOptions, env), sizeof(Env*)},
|
||||
{offsetof(struct DBOptions, rate_limiter),
|
||||
sizeof(std::shared_ptr<RateLimiter>)},
|
||||
@@ -228,22 +229,22 @@ TEST_F(OptionsSettableTest, DBOptionsAllFieldsSettable) {
|
||||
// copy a well constructed struct to this memory and see how many special
|
||||
// bytes left.
|
||||
DBOptions* options = new (options_ptr) DBOptions();
|
||||
FillWithSpecialChar(options_ptr, sizeof(DBOptions), kDBOptionsBlacklist);
|
||||
FillWithSpecialChar(options_ptr, sizeof(DBOptions), kDBOptionsExcluded);
|
||||
// It based on the behavior of compiler that padding bytes are not changed
|
||||
// when copying the struct. It's prone to failure when compiler behavior
|
||||
// changes. We verify there is unset bytes to detect the case.
|
||||
*options = DBOptions();
|
||||
int unset_bytes_base =
|
||||
NumUnsetBytes(options_ptr, sizeof(DBOptions), kDBOptionsBlacklist);
|
||||
NumUnsetBytes(options_ptr, sizeof(DBOptions), kDBOptionsExcluded);
|
||||
ASSERT_GT(unset_bytes_base, 0);
|
||||
options->~DBOptions();
|
||||
|
||||
options = new (options_ptr) DBOptions();
|
||||
FillWithSpecialChar(options_ptr, sizeof(DBOptions), kDBOptionsBlacklist);
|
||||
FillWithSpecialChar(options_ptr, sizeof(DBOptions), kDBOptionsExcluded);
|
||||
|
||||
char* new_options_ptr = new char[sizeof(DBOptions)];
|
||||
DBOptions* new_options = new (new_options_ptr) DBOptions();
|
||||
FillWithSpecialChar(new_options_ptr, sizeof(DBOptions), kDBOptionsBlacklist);
|
||||
FillWithSpecialChar(new_options_ptr, sizeof(DBOptions), kDBOptionsExcluded);
|
||||
|
||||
// Need to update the option string if a new option is added.
|
||||
ASSERT_OK(
|
||||
@@ -327,7 +328,7 @@ TEST_F(OptionsSettableTest, DBOptionsAllFieldsSettable) {
|
||||
new_options));
|
||||
|
||||
ASSERT_EQ(unset_bytes_base, NumUnsetBytes(new_options_ptr, sizeof(DBOptions),
|
||||
kDBOptionsBlacklist));
|
||||
kDBOptionsExcluded));
|
||||
|
||||
options->~DBOptions();
|
||||
new_options->~DBOptions();
|
||||
@@ -349,12 +350,12 @@ inline int offset_of(T1 T2::*member) {
|
||||
// GetColumnFamilyOptionsFromString() and add the option to the input
|
||||
// string passed to GetColumnFamilyOptionsFromString()in this test.
|
||||
// If it is a complicated type, you also need to add the field to
|
||||
// kColumnFamilyOptionsBlacklist, and maybe add customized verification
|
||||
// kColumnFamilyOptionsExcluded, and maybe add customized verification
|
||||
// for it.
|
||||
TEST_F(OptionsSettableTest, ColumnFamilyOptionsAllFieldsSettable) {
|
||||
// options in the blacklist need to appear in the same order as in
|
||||
// options in the excluded set need to appear in the same order as in
|
||||
// ColumnFamilyOptions.
|
||||
const OffsetGap kColumnFamilyOptionsBlacklist = {
|
||||
const OffsetGap kColumnFamilyOptionsExcluded = {
|
||||
{offset_of(&ColumnFamilyOptions::inplace_callback),
|
||||
sizeof(UpdateStatus(*)(char*, uint32_t*, Slice, std::string*))},
|
||||
{offset_of(
|
||||
@@ -393,7 +394,7 @@ TEST_F(OptionsSettableTest, ColumnFamilyOptionsAllFieldsSettable) {
|
||||
// bytes left.
|
||||
ColumnFamilyOptions* options = new (options_ptr) ColumnFamilyOptions();
|
||||
FillWithSpecialChar(options_ptr, sizeof(ColumnFamilyOptions),
|
||||
kColumnFamilyOptionsBlacklist);
|
||||
kColumnFamilyOptionsExcluded);
|
||||
|
||||
// It based on the behavior of compiler that padding bytes are not changed
|
||||
// when copying the struct. It's prone to failure when compiler behavior
|
||||
@@ -405,13 +406,13 @@ TEST_F(OptionsSettableTest, ColumnFamilyOptionsAllFieldsSettable) {
|
||||
options->max_mem_compaction_level = 0;
|
||||
|
||||
int unset_bytes_base = NumUnsetBytes(options_ptr, sizeof(ColumnFamilyOptions),
|
||||
kColumnFamilyOptionsBlacklist);
|
||||
kColumnFamilyOptionsExcluded);
|
||||
ASSERT_GT(unset_bytes_base, 0);
|
||||
options->~ColumnFamilyOptions();
|
||||
|
||||
options = new (options_ptr) ColumnFamilyOptions();
|
||||
FillWithSpecialChar(options_ptr, sizeof(ColumnFamilyOptions),
|
||||
kColumnFamilyOptionsBlacklist);
|
||||
kColumnFamilyOptionsExcluded);
|
||||
|
||||
// Following options are not settable through
|
||||
// GetColumnFamilyOptionsFromString():
|
||||
@@ -427,7 +428,7 @@ TEST_F(OptionsSettableTest, ColumnFamilyOptionsAllFieldsSettable) {
|
||||
ColumnFamilyOptions* new_options =
|
||||
new (new_options_ptr) ColumnFamilyOptions();
|
||||
FillWithSpecialChar(new_options_ptr, sizeof(ColumnFamilyOptions),
|
||||
kColumnFamilyOptionsBlacklist);
|
||||
kColumnFamilyOptionsExcluded);
|
||||
|
||||
// Need to update the option string if a new option is added.
|
||||
ASSERT_OK(GetColumnFamilyOptionsFromString(
|
||||
@@ -490,7 +491,7 @@ TEST_F(OptionsSettableTest, ColumnFamilyOptionsAllFieldsSettable) {
|
||||
|
||||
ASSERT_EQ(unset_bytes_base,
|
||||
NumUnsetBytes(new_options_ptr, sizeof(ColumnFamilyOptions),
|
||||
kColumnFamilyOptionsBlacklist));
|
||||
kColumnFamilyOptionsExcluded));
|
||||
|
||||
ColumnFamilyOptions rnd_filled_options = *new_options;
|
||||
|
||||
@@ -502,7 +503,7 @@ TEST_F(OptionsSettableTest, ColumnFamilyOptionsAllFieldsSettable) {
|
||||
|
||||
// Test copying to mutabable and immutable options and copy back the mutable
|
||||
// part.
|
||||
const OffsetGap kMutableCFOptionsBlacklist = {
|
||||
const OffsetGap kMutableCFOptionsExcluded = {
|
||||
{offset_of(&MutableCFOptions::prefix_extractor),
|
||||
sizeof(std::shared_ptr<const SliceTransform>)},
|
||||
{offset_of(&MutableCFOptions::max_bytes_for_level_multiplier_additional),
|
||||
@@ -517,16 +518,16 @@ TEST_F(OptionsSettableTest, ColumnFamilyOptionsAllFieldsSettable) {
|
||||
const char kMySpecialChar = 'x';
|
||||
char* mcfo1_ptr = new char[sizeof(MutableCFOptions)];
|
||||
FillWithSpecialChar(mcfo1_ptr, sizeof(MutableCFOptions),
|
||||
kMutableCFOptionsBlacklist, kMySpecialChar);
|
||||
kMutableCFOptionsExcluded, kMySpecialChar);
|
||||
char* mcfo2_ptr = new char[sizeof(MutableCFOptions)];
|
||||
FillWithSpecialChar(mcfo2_ptr, sizeof(MutableCFOptions),
|
||||
kMutableCFOptionsBlacklist, kMySpecialChar);
|
||||
kMutableCFOptionsExcluded, kMySpecialChar);
|
||||
|
||||
// A clean column family options is constructed after filling the same special
|
||||
// char as the initial one. So that the padding bytes are the same.
|
||||
char* cfo_clean_ptr = new char[sizeof(ColumnFamilyOptions)];
|
||||
FillWithSpecialChar(cfo_clean_ptr, sizeof(ColumnFamilyOptions),
|
||||
kColumnFamilyOptionsBlacklist);
|
||||
kColumnFamilyOptionsExcluded);
|
||||
rnd_filled_options.num_levels = 66;
|
||||
ColumnFamilyOptions* cfo_clean = new (cfo_clean_ptr) ColumnFamilyOptions();
|
||||
|
||||
@@ -536,7 +537,7 @@ TEST_F(OptionsSettableTest, ColumnFamilyOptionsAllFieldsSettable) {
|
||||
MutableCFOptions* mcfo2 = new (mcfo2_ptr) MutableCFOptions(cfo_back);
|
||||
|
||||
ASSERT_TRUE(CompareBytes(mcfo1_ptr, mcfo2_ptr, sizeof(MutableCFOptions),
|
||||
kMutableCFOptionsBlacklist));
|
||||
kMutableCFOptionsExcluded));
|
||||
|
||||
cfo_clean->~ColumnFamilyOptions();
|
||||
mcfo1->~MutableCFOptions();
|
||||
|
||||
+1
-1
@@ -36,7 +36,7 @@ namespace port {
|
||||
|
||||
namespace {
|
||||
|
||||
#if defined(OS_LINUX) || defined(OS_FREEBSD)
|
||||
#if defined(OS_LINUX) || defined(OS_FREEBSD) || defined(OS_GNU_KFREEBSD)
|
||||
const char* GetExecutableName() {
|
||||
static char name[1024];
|
||||
|
||||
|
||||
@@ -176,6 +176,7 @@ LIB_SOURCES = \
|
||||
tools/dump/db_dump_tool.cc \
|
||||
trace_replay/trace_replay.cc \
|
||||
trace_replay/block_cache_tracer.cc \
|
||||
trace_replay/io_tracer.cc \
|
||||
util/build_version.cc \
|
||||
util/coding.cc \
|
||||
util/compaction_job_stats_impl.cc \
|
||||
@@ -437,8 +438,9 @@ MAIN_SOURCES = \
|
||||
tools/ldb_cmd_test.cc \
|
||||
tools/reduce_levels_test.cc \
|
||||
tools/sst_dump_test.cc \
|
||||
tools/trace_analyzer_test.cc \
|
||||
tools/trace_analyzer_test.cc \
|
||||
trace_replay/block_cache_tracer_test.cc \
|
||||
trace_replay/io_tracer_test.cc \
|
||||
util/autovector_test.cc \
|
||||
util/bloom_test.cc \
|
||||
util/coding_test.cc \
|
||||
|
||||
+91
-28
@@ -128,17 +128,14 @@ struct DecodeKeyV4 {
|
||||
};
|
||||
|
||||
void DataBlockIter::Next() {
|
||||
assert(Valid());
|
||||
ParseNextDataKey<DecodeEntry>();
|
||||
}
|
||||
|
||||
void DataBlockIter::NextOrReport() {
|
||||
assert(Valid());
|
||||
ParseNextDataKey<CheckAndDecodeEntry>();
|
||||
}
|
||||
|
||||
void IndexBlockIter::Next() {
|
||||
assert(Valid());
|
||||
ParseNextIndexKey();
|
||||
}
|
||||
|
||||
@@ -248,18 +245,14 @@ void DataBlockIter::Seek(const Slice& target) {
|
||||
return;
|
||||
}
|
||||
uint32_t index = 0;
|
||||
bool skip_linear_scan = false;
|
||||
bool ok = BinarySeek<DecodeKey>(seek_key, 0, num_restarts_ - 1, &index,
|
||||
comparator_);
|
||||
&skip_linear_scan, comparator_);
|
||||
|
||||
if (!ok) {
|
||||
return;
|
||||
}
|
||||
SeekToRestartPoint(index);
|
||||
|
||||
// Linear search (within restart block) for first key >= target
|
||||
while (ParseNextDataKey<DecodeEntry>() &&
|
||||
comparator_->Compare(applied_key_.UpdateAndGetKey(), seek_key) < 0) {
|
||||
}
|
||||
FindKeyAfterBinarySeek(seek_key, index, skip_linear_scan, comparator_);
|
||||
}
|
||||
|
||||
// Optimized Seek for point lookup for an internal key `target`
|
||||
@@ -393,6 +386,7 @@ void IndexBlockIter::Seek(const Slice& target) {
|
||||
}
|
||||
status_ = Status::OK();
|
||||
uint32_t index = 0;
|
||||
bool skip_linear_scan = false;
|
||||
bool ok = false;
|
||||
if (prefix_index_) {
|
||||
bool prefix_may_exist = true;
|
||||
@@ -404,23 +398,21 @@ void IndexBlockIter::Seek(const Slice& target) {
|
||||
current_ = restarts_;
|
||||
status_ = Status::NotFound();
|
||||
}
|
||||
// restart interval must be one when hash search is enabled so the binary
|
||||
// search simply lands at the right place.
|
||||
skip_linear_scan = true;
|
||||
} else if (value_delta_encoded_) {
|
||||
ok = BinarySeek<DecodeKeyV4>(seek_key, 0, num_restarts_ - 1, &index,
|
||||
comparator_);
|
||||
&skip_linear_scan, comparator_);
|
||||
} else {
|
||||
ok = BinarySeek<DecodeKey>(seek_key, 0, num_restarts_ - 1, &index,
|
||||
comparator_);
|
||||
&skip_linear_scan, comparator_);
|
||||
}
|
||||
|
||||
if (!ok) {
|
||||
return;
|
||||
}
|
||||
SeekToRestartPoint(index);
|
||||
|
||||
// Linear search (within restart block) for first key >= target
|
||||
while (ParseNextIndexKey() &&
|
||||
comparator_->Compare(applied_key_.UpdateAndGetKey(), seek_key) < 0) {
|
||||
}
|
||||
FindKeyAfterBinarySeek(seek_key, index, skip_linear_scan, comparator_);
|
||||
}
|
||||
|
||||
void DataBlockIter::SeekForPrev(const Slice& target) {
|
||||
@@ -430,18 +422,15 @@ void DataBlockIter::SeekForPrev(const Slice& target) {
|
||||
return;
|
||||
}
|
||||
uint32_t index = 0;
|
||||
bool skip_linear_scan = false;
|
||||
bool ok = BinarySeek<DecodeKey>(seek_key, 0, num_restarts_ - 1, &index,
|
||||
comparator_);
|
||||
&skip_linear_scan, comparator_);
|
||||
|
||||
if (!ok) {
|
||||
return;
|
||||
}
|
||||
SeekToRestartPoint(index);
|
||||
FindKeyAfterBinarySeek(seek_key, index, skip_linear_scan, comparator_);
|
||||
|
||||
// Linear search (within restart block) for first key >= seek_key
|
||||
while (ParseNextDataKey<DecodeEntry>() &&
|
||||
comparator_->Compare(applied_key_.UpdateAndGetKey(), seek_key) < 0) {
|
||||
}
|
||||
if (!Valid()) {
|
||||
SeekToLast();
|
||||
} else {
|
||||
@@ -659,17 +648,70 @@ void IndexBlockIter::DecodeCurrentValue(uint32_t shared) {
|
||||
}
|
||||
}
|
||||
|
||||
// Binary search in restart array to find the first restart point that
|
||||
// is either the last restart point with a key less than target,
|
||||
// which means the key of next restart point is larger than target, or
|
||||
// the first restart point with a key = target
|
||||
template <class TValue>
|
||||
void BlockIter<TValue>::FindKeyAfterBinarySeek(const Slice& target,
|
||||
uint32_t index,
|
||||
bool skip_linear_scan,
|
||||
const Comparator* comp) {
|
||||
// SeekToRestartPoint() only does the lookup in the restart block. We need
|
||||
// to follow it up with Next() to position the iterator at the restart key.
|
||||
SeekToRestartPoint(index);
|
||||
Next();
|
||||
|
||||
if (!skip_linear_scan) {
|
||||
// Linear search (within restart block) for first key >= target
|
||||
uint32_t max_offset;
|
||||
if (index + 1 < num_restarts_) {
|
||||
// We are in a non-last restart interval. Since `BinarySeek()` guarantees
|
||||
// the next restart key is strictly greater than `target`, we can
|
||||
// terminate upon reaching it without any additional key comparison.
|
||||
max_offset = GetRestartPoint(index + 1);
|
||||
} else {
|
||||
// We are in the last restart interval. The while-loop will terminate by
|
||||
// `Valid()` returning false upon advancing past the block's last key.
|
||||
max_offset = port::kMaxUint32;
|
||||
}
|
||||
while (true) {
|
||||
Next();
|
||||
if (!Valid()) {
|
||||
break;
|
||||
}
|
||||
if (current_ == max_offset) {
|
||||
assert(comp->Compare(applied_key_.UpdateAndGetKey(), target) > 0);
|
||||
break;
|
||||
} else if (comp->Compare(applied_key_.UpdateAndGetKey(), target) >= 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Binary searches in restart array to find the starting restart point for the
|
||||
// linear scan, and stores it in `*index`. Assumes restart array does not
|
||||
// contain duplicate keys. It is guaranteed that the restart key at `*index + 1`
|
||||
// is strictly greater than `target` or does not exist (this can be used to
|
||||
// elide a comparison when linear scan reaches all the way to the next restart
|
||||
// key). Furthermore, `*skip_linear_scan` is set to indicate whether the
|
||||
// `*index`th restart key is the final result so that key does not need to be
|
||||
// compared again later.
|
||||
template <class TValue>
|
||||
template <typename DecodeKeyFunc>
|
||||
bool BlockIter<TValue>::BinarySeek(const Slice& target, uint32_t left,
|
||||
uint32_t right, uint32_t* index,
|
||||
bool* skip_linear_scan,
|
||||
const Comparator* comp) {
|
||||
assert(left <= right);
|
||||
if (restarts_ == 0) {
|
||||
// SST files dedicated to range tombstones are written with index blocks
|
||||
// that have no keys while also having `num_restarts_ == 1`. This would
|
||||
// cause a problem for `BinarySeek()` as it'd try to access the first key
|
||||
// which does not exist. We identify such blocks by the offset at which
|
||||
// their restarts are stored, and return false to prevent any attempted
|
||||
// key accesses.
|
||||
return false;
|
||||
}
|
||||
|
||||
*skip_linear_scan = false;
|
||||
while (left < right) {
|
||||
uint32_t mid = (left + right + 1) / 2;
|
||||
uint32_t region_offset = GetRestartPoint(mid);
|
||||
@@ -692,11 +734,32 @@ bool BlockIter<TValue>::BinarySeek(const Slice& target, uint32_t left,
|
||||
// after "mid" are uninteresting.
|
||||
right = mid - 1;
|
||||
} else {
|
||||
*skip_linear_scan = true;
|
||||
left = right = mid;
|
||||
}
|
||||
}
|
||||
|
||||
assert(left == right);
|
||||
*index = left;
|
||||
if (*index == 0) {
|
||||
// Special case as we land at zero as long as restart key at index 1 is >
|
||||
// "target". We need to compare the restart key at index 0 so we can set
|
||||
// `*skip_linear_scan` when the 0th restart key is >= "target".
|
||||
//
|
||||
// GetRestartPoint() is always zero for restart key zero; skip the restart
|
||||
// block access.
|
||||
uint32_t shared, non_shared;
|
||||
const char* key_ptr =
|
||||
DecodeKeyFunc()(data_, data_ + restarts_, &shared, &non_shared);
|
||||
if (key_ptr == nullptr || (shared != 0)) {
|
||||
CorruptionError();
|
||||
return false;
|
||||
}
|
||||
Slice first_key(key_ptr, non_shared);
|
||||
raw_key_.SetKey(first_key, false /* copy */);
|
||||
int cmp = comp->Compare(applied_key_.UpdateAndGetKey(), target);
|
||||
*skip_linear_scan = cmp >= 0;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -343,6 +343,8 @@ class BlockIter : public InternalIteratorBase<TValue> {
|
||||
|
||||
Cache::Handle* cache_handle() { return cache_handle_; }
|
||||
|
||||
virtual void Next() override = 0;
|
||||
|
||||
protected:
|
||||
// Note: The type could be changed to InternalKeyComparator but we see a weird
|
||||
// performance drop by that.
|
||||
@@ -403,9 +405,14 @@ class BlockIter : public InternalIteratorBase<TValue> {
|
||||
|
||||
void CorruptionError();
|
||||
|
||||
protected:
|
||||
template <typename DecodeKeyFunc>
|
||||
inline bool BinarySeek(const Slice& target, uint32_t left, uint32_t right,
|
||||
uint32_t* index, const Comparator* comp);
|
||||
uint32_t* index, bool* is_index_key_result,
|
||||
const Comparator* comp);
|
||||
|
||||
void FindKeyAfterBinarySeek(const Slice& target, uint32_t index,
|
||||
bool is_index_key_result, const Comparator* comp);
|
||||
};
|
||||
|
||||
class DataBlockIter final : public BlockIter<Slice> {
|
||||
|
||||
@@ -374,6 +374,10 @@ struct BlockBasedTableBuilder::Rep {
|
||||
const uint64_t target_file_size;
|
||||
uint64_t file_creation_time = 0;
|
||||
|
||||
// DB IDs
|
||||
const std::string db_id;
|
||||
const std::string db_session_id;
|
||||
|
||||
std::vector<std::unique_ptr<IntTblPropCollector>> table_properties_collectors;
|
||||
|
||||
std::unique_ptr<ParallelCompressionRep> pc_rep;
|
||||
@@ -447,7 +451,8 @@ struct BlockBasedTableBuilder::Rep {
|
||||
const CompressionOptions& _compression_opts, const bool skip_filters,
|
||||
const int _level_at_creation, const std::string& _column_family_name,
|
||||
const uint64_t _creation_time, const uint64_t _oldest_key_time,
|
||||
const uint64_t _target_file_size, const uint64_t _file_creation_time)
|
||||
const uint64_t _target_file_size, const uint64_t _file_creation_time,
|
||||
const std::string& _db_id, const std::string& _db_session_id)
|
||||
: ioptions(_ioptions),
|
||||
moptions(_moptions),
|
||||
table_options(table_opt),
|
||||
@@ -488,7 +493,9 @@ struct BlockBasedTableBuilder::Rep {
|
||||
creation_time(_creation_time),
|
||||
oldest_key_time(_oldest_key_time),
|
||||
target_file_size(_target_file_size),
|
||||
file_creation_time(_file_creation_time) {
|
||||
file_creation_time(_file_creation_time),
|
||||
db_id(_db_id),
|
||||
db_session_id(_db_session_id) {
|
||||
for (uint32_t i = 0; i < compression_opts.parallel_threads; i++) {
|
||||
compression_ctxs[i].reset(new CompressionContext(compression_type));
|
||||
}
|
||||
@@ -698,7 +705,8 @@ BlockBasedTableBuilder::BlockBasedTableBuilder(
|
||||
const CompressionOptions& compression_opts, const bool skip_filters,
|
||||
const std::string& column_family_name, const int level_at_creation,
|
||||
const uint64_t creation_time, const uint64_t oldest_key_time,
|
||||
const uint64_t target_file_size, const uint64_t file_creation_time) {
|
||||
const uint64_t target_file_size, const uint64_t file_creation_time,
|
||||
const std::string& db_id, const std::string& db_session_id) {
|
||||
BlockBasedTableOptions sanitized_table_options(table_options);
|
||||
if (sanitized_table_options.format_version == 0 &&
|
||||
sanitized_table_options.checksum != kCRC32c) {
|
||||
@@ -711,18 +719,18 @@ BlockBasedTableBuilder::BlockBasedTableBuilder(
|
||||
sanitized_table_options.format_version = 1;
|
||||
}
|
||||
|
||||
rep_ = new Rep(ioptions, moptions, sanitized_table_options,
|
||||
internal_comparator, int_tbl_prop_collector_factories,
|
||||
column_family_id, file, compression_type,
|
||||
sample_for_compression, compression_opts, skip_filters,
|
||||
level_at_creation, column_family_name, creation_time,
|
||||
oldest_key_time, target_file_size, file_creation_time);
|
||||
rep_ = new Rep(
|
||||
ioptions, moptions, sanitized_table_options, internal_comparator,
|
||||
int_tbl_prop_collector_factories, column_family_id, file,
|
||||
compression_type, sample_for_compression, compression_opts, skip_filters,
|
||||
level_at_creation, column_family_name, creation_time, oldest_key_time,
|
||||
target_file_size, file_creation_time, db_id, db_session_id);
|
||||
|
||||
if (rep_->filter_builder != nullptr) {
|
||||
rep_->filter_builder->StartBlock(0);
|
||||
}
|
||||
if (table_options.block_cache_compressed.get() != nullptr) {
|
||||
BlockBasedTable::GenerateCachePrefix(
|
||||
BlockBasedTable::GenerateCachePrefix<Cache, FSWritableFile>(
|
||||
table_options.block_cache_compressed.get(), file->writable_file(),
|
||||
&rep_->compressed_cache_key_prefix[0],
|
||||
&rep_->compressed_cache_key_prefix_size);
|
||||
@@ -1085,42 +1093,43 @@ void BlockBasedTableBuilder::WriteRawBlock(const Slice& block_contents,
|
||||
if (io_s.ok()) {
|
||||
char trailer[kBlockTrailerSize];
|
||||
trailer[0] = type;
|
||||
char* trailer_without_type = trailer + 1;
|
||||
uint32_t checksum = 0;
|
||||
switch (r->table_options.checksum) {
|
||||
case kNoChecksum:
|
||||
EncodeFixed32(trailer_without_type, 0);
|
||||
break;
|
||||
case kCRC32c: {
|
||||
auto crc = crc32c::Value(block_contents.data(), block_contents.size());
|
||||
crc = crc32c::Extend(crc, trailer, 1); // Extend to cover block type
|
||||
EncodeFixed32(trailer_without_type, crc32c::Mask(crc));
|
||||
uint32_t crc =
|
||||
crc32c::Value(block_contents.data(), block_contents.size());
|
||||
// Extend to cover compression type
|
||||
crc = crc32c::Extend(crc, trailer, 1);
|
||||
checksum = crc32c::Mask(crc);
|
||||
break;
|
||||
}
|
||||
case kxxHash: {
|
||||
XXH32_state_t* const state = XXH32_createState();
|
||||
XXH32_reset(state, 0);
|
||||
XXH32_update(state, block_contents.data(),
|
||||
static_cast<uint32_t>(block_contents.size()));
|
||||
XXH32_update(state, trailer, 1); // Extend to cover block type
|
||||
EncodeFixed32(trailer_without_type, XXH32_digest(state));
|
||||
XXH32_update(state, block_contents.data(), block_contents.size());
|
||||
// Extend to cover compression type
|
||||
XXH32_update(state, trailer, 1);
|
||||
checksum = XXH32_digest(state);
|
||||
XXH32_freeState(state);
|
||||
break;
|
||||
}
|
||||
case kxxHash64: {
|
||||
XXH64_state_t* const state = XXH64_createState();
|
||||
XXH64_reset(state, 0);
|
||||
XXH64_update(state, block_contents.data(),
|
||||
static_cast<uint32_t>(block_contents.size()));
|
||||
XXH64_update(state, trailer, 1); // Extend to cover block type
|
||||
EncodeFixed32(
|
||||
trailer_without_type,
|
||||
static_cast<uint32_t>(XXH64_digest(state) & // lower 32 bits
|
||||
uint64_t{0xffffffff}));
|
||||
XXH64_update(state, block_contents.data(), block_contents.size());
|
||||
// Extend to cover compression type
|
||||
XXH64_update(state, trailer, 1);
|
||||
checksum = Lower32of64(XXH64_digest(state));
|
||||
XXH64_freeState(state);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
assert(false);
|
||||
break;
|
||||
}
|
||||
|
||||
EncodeFixed32(trailer + 1, checksum);
|
||||
assert(io_s.ok());
|
||||
TEST_SYNC_POINT_CALLBACK(
|
||||
"BlockBasedTableBuilder::WriteRawBlock:TamperWithChecksum",
|
||||
@@ -1445,6 +1454,8 @@ void BlockBasedTableBuilder::WritePropertiesBlock(
|
||||
rep_->props.creation_time = rep_->creation_time;
|
||||
rep_->props.oldest_key_time = rep_->oldest_key_time;
|
||||
rep_->props.file_creation_time = rep_->file_creation_time;
|
||||
rep_->props.db_id = rep_->db_id;
|
||||
rep_->props.db_session_id = rep_->db_session_id;
|
||||
|
||||
// Add basic properties
|
||||
property_block_builder.AddTableProperty(rep_->props);
|
||||
|
||||
@@ -51,7 +51,8 @@ class BlockBasedTableBuilder : public TableBuilder {
|
||||
const std::string& column_family_name, const int level_at_creation,
|
||||
const uint64_t creation_time = 0, const uint64_t oldest_key_time = 0,
|
||||
const uint64_t target_file_size = 0,
|
||||
const uint64_t file_creation_time = 0);
|
||||
const uint64_t file_creation_time = 0, const std::string& db_id = "",
|
||||
const std::string& db_session_id = "");
|
||||
|
||||
// No copying allowed
|
||||
BlockBasedTableBuilder(const BlockBasedTableBuilder&) = delete;
|
||||
|
||||
@@ -268,6 +268,10 @@ static std::unordered_map<std::string, OptionTypeInfo>
|
||||
{offsetof(struct BlockBasedTableOptions, partition_filters),
|
||||
OptionType::kBoolean, OptionVerificationType::kNormal,
|
||||
OptionTypeFlags::kNone, 0}},
|
||||
{"optimize_filters_for_memory",
|
||||
{offsetof(struct BlockBasedTableOptions, optimize_filters_for_memory),
|
||||
OptionType::kBoolean, OptionVerificationType::kNormal,
|
||||
OptionTypeFlags::kNone, 0}},
|
||||
{"filter_policy",
|
||||
{offsetof(struct BlockBasedTableOptions, filter_policy),
|
||||
OptionType::kUnknown, OptionVerificationType::kByNameAllowFromNull,
|
||||
@@ -439,7 +443,8 @@ TableBuilder* BlockBasedTableFactory::NewTableBuilder(
|
||||
table_builder_options.creation_time,
|
||||
table_builder_options.oldest_key_time,
|
||||
table_builder_options.target_file_size,
|
||||
table_builder_options.file_creation_time);
|
||||
table_builder_options.file_creation_time, table_builder_options.db_id,
|
||||
table_builder_options.db_session_id);
|
||||
|
||||
return table_builder;
|
||||
}
|
||||
|
||||
@@ -452,44 +452,21 @@ void BlockBasedTable::SetupCacheKeyPrefix(Rep* rep) {
|
||||
rep->cache_key_prefix_size = 0;
|
||||
rep->compressed_cache_key_prefix_size = 0;
|
||||
if (rep->table_options.block_cache != nullptr) {
|
||||
GenerateCachePrefix(rep->table_options.block_cache.get(), rep->file->file(),
|
||||
&rep->cache_key_prefix[0], &rep->cache_key_prefix_size);
|
||||
GenerateCachePrefix<Cache, FSRandomAccessFile>(
|
||||
rep->table_options.block_cache.get(), rep->file->file(),
|
||||
&rep->cache_key_prefix[0], &rep->cache_key_prefix_size);
|
||||
}
|
||||
if (rep->table_options.persistent_cache != nullptr) {
|
||||
GenerateCachePrefix(/*cache=*/nullptr, rep->file->file(),
|
||||
&rep->persistent_cache_key_prefix[0],
|
||||
&rep->persistent_cache_key_prefix_size);
|
||||
GenerateCachePrefix<PersistentCache, FSRandomAccessFile>(
|
||||
rep->table_options.persistent_cache.get(), rep->file->file(),
|
||||
&rep->persistent_cache_key_prefix[0],
|
||||
&rep->persistent_cache_key_prefix_size);
|
||||
}
|
||||
if (rep->table_options.block_cache_compressed != nullptr) {
|
||||
GenerateCachePrefix(rep->table_options.block_cache_compressed.get(),
|
||||
rep->file->file(), &rep->compressed_cache_key_prefix[0],
|
||||
&rep->compressed_cache_key_prefix_size);
|
||||
}
|
||||
}
|
||||
|
||||
void BlockBasedTable::GenerateCachePrefix(Cache* cc, FSRandomAccessFile* file,
|
||||
char* buffer, size_t* size) {
|
||||
// generate an id from the file
|
||||
*size = file->GetUniqueId(buffer, kMaxCacheKeyPrefixSize);
|
||||
|
||||
// If the prefix wasn't generated or was too long,
|
||||
// create one from the cache.
|
||||
if (cc != nullptr && *size == 0) {
|
||||
char* end = EncodeVarint64(buffer, cc->NewId());
|
||||
*size = static_cast<size_t>(end - buffer);
|
||||
}
|
||||
}
|
||||
|
||||
void BlockBasedTable::GenerateCachePrefix(Cache* cc, FSWritableFile* file,
|
||||
char* buffer, size_t* size) {
|
||||
// generate an id from the file
|
||||
*size = file->GetUniqueId(buffer, kMaxCacheKeyPrefixSize);
|
||||
|
||||
// If the prefix wasn't generated or was too long,
|
||||
// create one from the cache.
|
||||
if (cc != nullptr && *size == 0) {
|
||||
char* end = EncodeVarint64(buffer, cc->NewId());
|
||||
*size = static_cast<size_t>(end - buffer);
|
||||
GenerateCachePrefix<Cache, FSRandomAccessFile>(
|
||||
rep->table_options.block_cache_compressed.get(), rep->file->file(),
|
||||
&rep->compressed_cache_key_prefix[0],
|
||||
&rep->compressed_cache_key_prefix_size);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -799,9 +776,9 @@ Status BlockBasedTable::TryReadPropertiesWithGlobalSeqno(
|
||||
EncodeFixed64(
|
||||
tmp_buf.get() + global_seqno_offset - props_block_handle.offset(), 0);
|
||||
}
|
||||
uint32_t value = DecodeFixed32(tmp_buf.get() + block_size + 1);
|
||||
s = ROCKSDB_NAMESPACE::VerifyChecksum(rep_->footer.checksum(),
|
||||
tmp_buf.get(), block_size + 1, value);
|
||||
s = ROCKSDB_NAMESPACE::VerifyBlockChecksum(
|
||||
rep_->footer.checksum(), tmp_buf.get(), block_size,
|
||||
rep_->file->file_name(), props_block_handle.offset());
|
||||
}
|
||||
return s;
|
||||
}
|
||||
@@ -1426,10 +1403,8 @@ Status BlockBasedTable::MaybeReadBlockAndLoadToCache(
|
||||
assert(block_entry != nullptr);
|
||||
const bool no_io = (ro.read_tier == kBlockCacheTier);
|
||||
Cache* block_cache = rep_->table_options.block_cache.get();
|
||||
// No point to cache compressed blocks if it never goes away
|
||||
Cache* block_cache_compressed =
|
||||
rep_->immortal_table ? nullptr
|
||||
: rep_->table_options.block_cache_compressed.get();
|
||||
rep_->table_options.block_cache_compressed.get();
|
||||
|
||||
// First, try to get the block from the cache
|
||||
//
|
||||
@@ -1753,15 +1728,14 @@ void BlockBasedTable::RetrieveMultipleBlocks(
|
||||
if (options.verify_checksums) {
|
||||
PERF_TIMER_GUARD(block_checksum_time);
|
||||
const char* data = req.result.data();
|
||||
uint32_t expected =
|
||||
DecodeFixed32(data + req_offset + handle.size() + 1);
|
||||
// Since the scratch might be shared. the offset of the data block in
|
||||
// Since the scratch might be shared, the offset of the data block in
|
||||
// the buffer might not be 0. req.result.data() only point to the
|
||||
// begin address of each read request, we need to add the offset
|
||||
// in each read request. Checksum is stored in the block trailer,
|
||||
// which is handle.size() + 1.
|
||||
s = ROCKSDB_NAMESPACE::VerifyChecksum(
|
||||
footer.checksum(), data + req_offset, handle.size() + 1, expected);
|
||||
// beyond the payload size.
|
||||
s = ROCKSDB_NAMESPACE::VerifyBlockChecksum(
|
||||
footer.checksum(), data + req_offset, handle.size(),
|
||||
rep_->file->file_name(), handle.offset());
|
||||
TEST_SYNC_POINT_CALLBACK("RetrieveMultipleBlocks:VerifyChecksum", &s);
|
||||
}
|
||||
} else if (!use_shared_buffer) {
|
||||
|
||||
@@ -439,10 +439,19 @@ class BlockBasedTable : public TableReader {
|
||||
static void SetupCacheKeyPrefix(Rep* rep);
|
||||
|
||||
// Generate a cache key prefix from the file
|
||||
static void GenerateCachePrefix(Cache* cc, FSRandomAccessFile* file,
|
||||
char* buffer, size_t* size);
|
||||
static void GenerateCachePrefix(Cache* cc, FSWritableFile* file, char* buffer,
|
||||
size_t* size);
|
||||
template <typename TCache, typename TFile>
|
||||
static void GenerateCachePrefix(TCache* cc, TFile* file, char* buffer,
|
||||
size_t* size) {
|
||||
// generate an id from the file
|
||||
*size = file->GetUniqueId(buffer, kMaxCacheKeyPrefixSize);
|
||||
|
||||
// If the prefix wasn't generated or was too long,
|
||||
// create one from the cache.
|
||||
if (cc != nullptr && *size == 0) {
|
||||
char* end = EncodeVarint64(buffer, cc->NewId());
|
||||
*size = static_cast<size_t>(end - buffer);
|
||||
}
|
||||
}
|
||||
|
||||
// Size of all data blocks, maybe approximate
|
||||
uint64_t GetApproximateDataSize();
|
||||
|
||||
@@ -28,9 +28,12 @@ namespace {
|
||||
// See description in FastLocalBloomImpl
|
||||
class FastLocalBloomBitsBuilder : public BuiltinFilterBitsBuilder {
|
||||
public:
|
||||
explicit FastLocalBloomBitsBuilder(const int millibits_per_key)
|
||||
// Non-null aggregate_rounding_balance implies optimize_filters_for_memory
|
||||
explicit FastLocalBloomBitsBuilder(
|
||||
const int millibits_per_key,
|
||||
std::atomic<int64_t>* aggregate_rounding_balance)
|
||||
: millibits_per_key_(millibits_per_key),
|
||||
num_probes_(FastLocalBloomImpl::ChooseNumProbes(millibits_per_key_)) {
|
||||
aggregate_rounding_balance_(aggregate_rounding_balance) {
|
||||
assert(millibits_per_key >= 1000);
|
||||
}
|
||||
|
||||
@@ -48,33 +51,36 @@ class FastLocalBloomBitsBuilder : public BuiltinFilterBitsBuilder {
|
||||
}
|
||||
|
||||
virtual Slice Finish(std::unique_ptr<const char[]>* buf) override {
|
||||
size_t num_entry = hash_entries_.size();
|
||||
std::unique_ptr<char[]> mutable_buf;
|
||||
uint32_t len_with_metadata =
|
||||
CalculateSpace(static_cast<uint32_t>(hash_entries_.size()));
|
||||
char* data = new char[len_with_metadata];
|
||||
memset(data, 0, len_with_metadata);
|
||||
CalculateAndAllocate(num_entry, &mutable_buf, /*update_balance*/ true);
|
||||
|
||||
assert(data);
|
||||
assert(mutable_buf);
|
||||
assert(len_with_metadata >= 5);
|
||||
|
||||
// Compute num_probes after any rounding / adjustments
|
||||
int num_probes = GetNumProbes(num_entry, len_with_metadata);
|
||||
|
||||
uint32_t len = len_with_metadata - 5;
|
||||
if (len > 0) {
|
||||
AddAllEntries(data, len);
|
||||
AddAllEntries(mutable_buf.get(), len, num_probes);
|
||||
}
|
||||
|
||||
assert(hash_entries_.empty());
|
||||
|
||||
// See BloomFilterPolicy::GetBloomBitsReader re: metadata
|
||||
// -1 = Marker for newer Bloom implementations
|
||||
data[len] = static_cast<char>(-1);
|
||||
mutable_buf[len] = static_cast<char>(-1);
|
||||
// 0 = Marker for this sub-implementation
|
||||
data[len + 1] = static_cast<char>(0);
|
||||
mutable_buf[len + 1] = static_cast<char>(0);
|
||||
// num_probes (and 0 in upper bits for 64-byte block size)
|
||||
data[len + 2] = static_cast<char>(num_probes_);
|
||||
mutable_buf[len + 2] = static_cast<char>(num_probes);
|
||||
// rest of metadata stays zero
|
||||
|
||||
const char* const_data = data;
|
||||
buf->reset(const_data);
|
||||
assert(hash_entries_.empty());
|
||||
|
||||
return Slice(data, len_with_metadata);
|
||||
Slice rv(mutable_buf.get(), len_with_metadata);
|
||||
*buf = std::move(mutable_buf);
|
||||
return rv;
|
||||
}
|
||||
|
||||
int CalculateNumEntry(const uint32_t bytes) override {
|
||||
@@ -84,26 +90,163 @@ class FastLocalBloomBitsBuilder : public BuiltinFilterBitsBuilder {
|
||||
}
|
||||
|
||||
uint32_t CalculateSpace(const int num_entry) override {
|
||||
uint32_t num_cache_lines = 0;
|
||||
if (millibits_per_key_ > 0 && num_entry > 0) {
|
||||
num_cache_lines = static_cast<uint32_t>(
|
||||
(int64_t{num_entry} * millibits_per_key_ + 511999) / 512000);
|
||||
}
|
||||
return num_cache_lines * 64 + /*metadata*/ 5;
|
||||
// NB: the BuiltinFilterBitsBuilder API presumes len fits in uint32_t.
|
||||
return static_cast<uint32_t>(
|
||||
CalculateAndAllocate(static_cast<size_t>(num_entry),
|
||||
/* buf */ nullptr,
|
||||
/*update_balance*/ false));
|
||||
}
|
||||
|
||||
double EstimatedFpRate(size_t keys, size_t bytes) override {
|
||||
return FastLocalBloomImpl::EstimatedFpRate(keys, bytes - /*metadata*/ 5,
|
||||
num_probes_, /*hash bits*/ 64);
|
||||
// To choose size using malloc_usable_size, we have to actually allocate.
|
||||
uint32_t CalculateAndAllocate(size_t num_entry, std::unique_ptr<char[]>* buf,
|
||||
bool update_balance) {
|
||||
std::unique_ptr<char[]> tmpbuf;
|
||||
|
||||
// If not for cache line blocks in the filter, what would the target
|
||||
// length in bytes be?
|
||||
size_t raw_target_len = static_cast<size_t>(
|
||||
(uint64_t{num_entry} * millibits_per_key_ + 7999) / 8000);
|
||||
|
||||
if (raw_target_len >= size_t{0xffffffc0}) {
|
||||
// Max supported for this data structure implementation
|
||||
raw_target_len = size_t{0xffffffc0};
|
||||
}
|
||||
|
||||
// Round up to nearest multiple of 64 (block size). This adjustment is
|
||||
// used for target FP rate only so that we don't receive complaints about
|
||||
// lower FP rate vs. historic Bloom filter behavior.
|
||||
uint32_t target_len =
|
||||
static_cast<uint32_t>(raw_target_len + 63) & ~uint32_t{63};
|
||||
|
||||
// Return value set to a default; overwritten in some cases
|
||||
uint32_t rv = target_len + /* metadata */ 5;
|
||||
#ifdef ROCKSDB_MALLOC_USABLE_SIZE
|
||||
if (aggregate_rounding_balance_ != nullptr) {
|
||||
// Do optimize_filters_for_memory, using malloc_usable_size.
|
||||
// Approach: try to keep FP rate balance better than or on
|
||||
// target (negative aggregate_rounding_balance_). We can then select a
|
||||
// lower bound filter size (within reasonable limits) that gets us as
|
||||
// close to on target as possible. We request allocation for that filter
|
||||
// size and use malloc_usable_size to "round up" to the actual
|
||||
// allocation size.
|
||||
|
||||
// Although it can be considered bad practice to use malloc_usable_size
|
||||
// to access an object beyond its original size, this approach should
|
||||
// quite general: working for all allocators that properly support
|
||||
// malloc_usable_size.
|
||||
|
||||
// Race condition on balance is OK because it can only cause temporary
|
||||
// skew in rounding up vs. rounding down, as long as updates are atomic
|
||||
// and relative.
|
||||
int64_t balance = aggregate_rounding_balance_->load();
|
||||
|
||||
double target_fp_rate = EstimatedFpRate(num_entry, target_len + 5);
|
||||
double rv_fp_rate = target_fp_rate;
|
||||
|
||||
if (balance < 0) {
|
||||
// See formula for BloomFilterPolicy::aggregate_rounding_balance_
|
||||
double for_balance_fp_rate =
|
||||
-balance / double{0x100000000} + target_fp_rate;
|
||||
|
||||
// To simplify, we just try a few modified smaller sizes. This also
|
||||
// caps how much we vary filter size vs. target, to avoid outlier
|
||||
// behavior from excessive variance.
|
||||
for (uint64_t maybe_len64 :
|
||||
{uint64_t{3} * target_len / 4, uint64_t{13} * target_len / 16,
|
||||
uint64_t{7} * target_len / 8, uint64_t{15} * target_len / 16}) {
|
||||
uint32_t maybe_len =
|
||||
static_cast<uint32_t>(maybe_len64) & ~uint32_t{63};
|
||||
double maybe_fp_rate = EstimatedFpRate(num_entry, maybe_len + 5);
|
||||
if (maybe_fp_rate <= for_balance_fp_rate) {
|
||||
rv = maybe_len + /* metadata */ 5;
|
||||
rv_fp_rate = maybe_fp_rate;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Filter blocks are loaded into block cache with their block trailer.
|
||||
// We need to make sure that's accounted for in choosing a
|
||||
// fragmentation-friendly size.
|
||||
const uint32_t kExtraPadding = kBlockTrailerSize;
|
||||
size_t requested = rv + kExtraPadding;
|
||||
|
||||
// Allocate and get usable size
|
||||
tmpbuf.reset(new char[requested]);
|
||||
size_t usable = malloc_usable_size(tmpbuf.get());
|
||||
|
||||
if (usable - usable / 4 > requested) {
|
||||
// Ratio greater than 4/3 is too much for utilizing, if it's
|
||||
// not a buggy or mislinked malloc_usable_size implementation.
|
||||
// Non-linearity of FP rates with bits/key means rapidly
|
||||
// diminishing returns in overall accuracy for additional
|
||||
// storage on disk.
|
||||
// Nothing to do, except assert that the result is accurate about
|
||||
// the usable size. (Assignment never used.)
|
||||
assert(tmpbuf[usable - 1] = 'x');
|
||||
} else if (usable > requested) {
|
||||
// Adjust for reasonably larger usable size
|
||||
size_t usable_len = (usable - kExtraPadding - /* metadata */ 5);
|
||||
if (usable_len >= size_t{0xffffffc0}) {
|
||||
// Max supported for this data structure implementation
|
||||
usable_len = size_t{0xffffffc0};
|
||||
}
|
||||
|
||||
rv = (static_cast<uint32_t>(usable_len) & ~uint32_t{63}) +
|
||||
/* metadata */ 5;
|
||||
rv_fp_rate = EstimatedFpRate(num_entry, rv);
|
||||
} else {
|
||||
// Too small means bad malloc_usable_size
|
||||
assert(usable == requested);
|
||||
}
|
||||
memset(tmpbuf.get(), 0, rv);
|
||||
|
||||
if (update_balance) {
|
||||
int64_t diff = static_cast<int64_t>((rv_fp_rate - target_fp_rate) *
|
||||
double{0x100000000});
|
||||
*aggregate_rounding_balance_ += diff;
|
||||
}
|
||||
}
|
||||
#else
|
||||
(void)update_balance;
|
||||
#endif // ROCKSDB_MALLOC_USABLE_SIZE
|
||||
if (buf) {
|
||||
if (tmpbuf) {
|
||||
*buf = std::move(tmpbuf);
|
||||
} else {
|
||||
buf->reset(new char[rv]());
|
||||
}
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
double EstimatedFpRate(size_t keys, size_t len_with_metadata) override {
|
||||
int num_probes = GetNumProbes(keys, len_with_metadata);
|
||||
return FastLocalBloomImpl::EstimatedFpRate(
|
||||
keys, len_with_metadata - /*metadata*/ 5, num_probes, /*hash bits*/ 64);
|
||||
}
|
||||
|
||||
private:
|
||||
void AddAllEntries(char* data, uint32_t len) {
|
||||
// Compute num_probes after any rounding / adjustments
|
||||
int GetNumProbes(size_t keys, size_t len_with_metadata) {
|
||||
uint64_t millibits = uint64_t{len_with_metadata - 5} * 8000;
|
||||
int actual_millibits_per_key =
|
||||
static_cast<int>(millibits / std::max(keys, size_t{1}));
|
||||
// BEGIN XXX/TODO(peterd): preserving old/default behavior for now to
|
||||
// minimize unit test churn. Remove this some time.
|
||||
if (!aggregate_rounding_balance_) {
|
||||
actual_millibits_per_key = millibits_per_key_;
|
||||
}
|
||||
// END XXX/TODO
|
||||
return FastLocalBloomImpl::ChooseNumProbes(actual_millibits_per_key);
|
||||
}
|
||||
|
||||
void AddAllEntries(char* data, uint32_t len, int num_probes) {
|
||||
// Simple version without prefetching:
|
||||
//
|
||||
// for (auto h : hash_entries_) {
|
||||
// FastLocalBloomImpl::AddHash(Lower32of64(h), Upper32of64(h), len,
|
||||
// num_probes_, data);
|
||||
// num_probes, data);
|
||||
// }
|
||||
|
||||
const size_t num_entries = hash_entries_.size();
|
||||
@@ -129,7 +272,7 @@ class FastLocalBloomBitsBuilder : public BuiltinFilterBitsBuilder {
|
||||
uint32_t& hash_ref = hashes[i & kBufferMask];
|
||||
uint32_t& byte_offset_ref = byte_offsets[i & kBufferMask];
|
||||
// Process (add)
|
||||
FastLocalBloomImpl::AddHashPrepared(hash_ref, num_probes_,
|
||||
FastLocalBloomImpl::AddHashPrepared(hash_ref, num_probes,
|
||||
data + byte_offset_ref);
|
||||
// And buffer
|
||||
uint64_t h = hash_entries_.front();
|
||||
@@ -141,13 +284,16 @@ class FastLocalBloomBitsBuilder : public BuiltinFilterBitsBuilder {
|
||||
|
||||
// Finish processing
|
||||
for (i = 0; i <= kBufferMask && i < num_entries; ++i) {
|
||||
FastLocalBloomImpl::AddHashPrepared(hashes[i], num_probes_,
|
||||
FastLocalBloomImpl::AddHashPrepared(hashes[i], num_probes,
|
||||
data + byte_offsets[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// Target allocation per added key, in thousandths of a bit.
|
||||
int millibits_per_key_;
|
||||
int num_probes_;
|
||||
// See BloomFilterPolicy::aggregate_rounding_balance_. If nullptr,
|
||||
// always "round up" like historic behavior.
|
||||
std::atomic<int64_t>* aggregate_rounding_balance_;
|
||||
// A deque avoids unnecessary copying of already-saved values
|
||||
// and has near-minimal peak memory use.
|
||||
std::deque<uint64_t> hash_entries_;
|
||||
@@ -457,7 +603,7 @@ const std::vector<BloomFilterPolicy::Mode> BloomFilterPolicy::kAllUserModes = {
|
||||
};
|
||||
|
||||
BloomFilterPolicy::BloomFilterPolicy(double bits_per_key, Mode mode)
|
||||
: mode_(mode), warned_(false) {
|
||||
: mode_(mode), warned_(false), aggregate_rounding_balance_(0) {
|
||||
// Sanitize bits_per_key
|
||||
if (bits_per_key < 1.0) {
|
||||
bits_per_key = 1.0;
|
||||
@@ -549,6 +695,7 @@ FilterBitsBuilder* BloomFilterPolicy::GetFilterBitsBuilder() const {
|
||||
FilterBitsBuilder* BloomFilterPolicy::GetBuilderWithContext(
|
||||
const FilterBuildingContext& context) const {
|
||||
Mode cur = mode_;
|
||||
bool offm = context.table_options.optimize_filters_for_memory;
|
||||
// Unusual code construction so that we can have just
|
||||
// one exhaustive switch without (risky) recursion
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
@@ -563,7 +710,8 @@ FilterBitsBuilder* BloomFilterPolicy::GetBuilderWithContext(
|
||||
case kDeprecatedBlock:
|
||||
return nullptr;
|
||||
case kFastLocalBloom:
|
||||
return new FastLocalBloomBitsBuilder(millibits_per_key_);
|
||||
return new FastLocalBloomBitsBuilder(
|
||||
millibits_per_key_, offm ? &aggregate_rounding_balance_ : nullptr);
|
||||
case kLegacyBloom:
|
||||
if (whole_bits_per_key_ >= 14 && context.info_log &&
|
||||
!warned_.load(std::memory_order_relaxed)) {
|
||||
|
||||
@@ -135,6 +135,16 @@ class BloomFilterPolicy : public FilterPolicy {
|
||||
// only report once per BloomFilterPolicy instance, to keep the noise down.)
|
||||
mutable std::atomic<bool> warned_;
|
||||
|
||||
// State for implementing optimize_filters_for_memory. Essentially, this
|
||||
// tracks a surplus or deficit in total FP rate of filters generated by
|
||||
// builders under this policy vs. what would have been generated without
|
||||
// optimize_filters_for_memory.
|
||||
//
|
||||
// To avoid floating point weirdness, the actual value is
|
||||
// Sum over all generated filters f:
|
||||
// (predicted_fp_rate(f) - predicted_fp_rate(f|o_f_f_m=false)) * 2^32
|
||||
mutable std::atomic<int64_t> aggregate_rounding_balance_;
|
||||
|
||||
// For newer Bloom filter implementation(s)
|
||||
FilterBitsReader* GetBloomBitsReader(const Slice& contents) const;
|
||||
};
|
||||
|
||||
@@ -8,7 +8,11 @@
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
#include "table/block_based/reader_common.h"
|
||||
|
||||
#include "monitoring/perf_context_imp.h"
|
||||
#include "util/coding.h"
|
||||
#include "util/crc32c.h"
|
||||
#include "util/hash.h"
|
||||
#include "util/string_util.h"
|
||||
#include "util/xxhash.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
@@ -18,29 +22,42 @@ void ForceReleaseCachedEntry(void* arg, void* h) {
|
||||
cache->Release(handle, true /* force_erase */);
|
||||
}
|
||||
|
||||
Status VerifyChecksum(const ChecksumType type, const char* buf, size_t len,
|
||||
uint32_t expected) {
|
||||
Status VerifyBlockChecksum(ChecksumType type, const char* data,
|
||||
size_t block_size, const std::string& file_name,
|
||||
uint64_t offset) {
|
||||
PERF_TIMER_GUARD(block_checksum_time);
|
||||
// After block_size bytes is compression type (1 byte), which is part of
|
||||
// the checksummed section.
|
||||
size_t len = block_size + 1;
|
||||
// And then the stored checksum value (4 bytes).
|
||||
uint32_t stored = DecodeFixed32(data + len);
|
||||
|
||||
Status s;
|
||||
uint32_t actual = 0;
|
||||
uint32_t computed = 0;
|
||||
switch (type) {
|
||||
case kNoChecksum:
|
||||
break;
|
||||
case kCRC32c:
|
||||
expected = crc32c::Unmask(expected);
|
||||
actual = crc32c::Value(buf, len);
|
||||
stored = crc32c::Unmask(stored);
|
||||
computed = crc32c::Value(data, len);
|
||||
break;
|
||||
case kxxHash:
|
||||
actual = XXH32(buf, static_cast<int>(len), 0);
|
||||
computed = XXH32(data, len, 0);
|
||||
break;
|
||||
case kxxHash64:
|
||||
actual = static_cast<uint32_t>(XXH64(buf, static_cast<int>(len), 0) &
|
||||
uint64_t{0xffffffff});
|
||||
computed = Lower32of64(XXH64(data, len, 0));
|
||||
break;
|
||||
default:
|
||||
s = Status::Corruption("unknown checksum type");
|
||||
s = Status::Corruption(
|
||||
"unknown checksum type " + ToString(type) + " from footer of " +
|
||||
file_name + ", while checking block at offset " + ToString(offset) +
|
||||
" size " + ToString(block_size));
|
||||
}
|
||||
if (s.ok() && actual != expected) {
|
||||
s = Status::Corruption("properties block checksum mismatched");
|
||||
if (s.ok() && stored != computed) {
|
||||
s = Status::Corruption(
|
||||
"block checksum mismatch: stored = " + ToString(stored) +
|
||||
", computed = " + ToString(computed) + " in " + file_name +
|
||||
" offset " + ToString(offset) + " size " + ToString(block_size));
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
@@ -28,6 +28,10 @@ inline MemoryAllocator* GetMemoryAllocatorForCompressedBlock(
|
||||
: nullptr;
|
||||
}
|
||||
|
||||
extern Status VerifyChecksum(const ChecksumType type, const char* buf,
|
||||
size_t len, uint32_t expected);
|
||||
// Assumes block has a trailer as in format.h. file_name and offset provided
|
||||
// for generating a diagnostic message in returned status.
|
||||
extern Status VerifyBlockChecksum(ChecksumType type, const char* data,
|
||||
size_t block_size,
|
||||
const std::string& file_name,
|
||||
uint64_t offset);
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
+4
-35
@@ -19,51 +19,20 @@
|
||||
#include "rocksdb/env.h"
|
||||
#include "table/block_based/block.h"
|
||||
#include "table/block_based/block_based_table_reader.h"
|
||||
#include "table/block_based/reader_common.h"
|
||||
#include "table/format.h"
|
||||
#include "table/persistent_cache_helper.h"
|
||||
#include "util/coding.h"
|
||||
#include "util/compression.h"
|
||||
#include "util/crc32c.h"
|
||||
#include "util/stop_watch.h"
|
||||
#include "util/string_util.h"
|
||||
#include "util/xxhash.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
inline void BlockFetcher::CheckBlockChecksum() {
|
||||
// Check the crc of the type and the block contents
|
||||
if (read_options_.verify_checksums) {
|
||||
const char* data = slice_.data(); // Pointer to where Read put the data
|
||||
PERF_TIMER_GUARD(block_checksum_time);
|
||||
uint32_t value = DecodeFixed32(data + block_size_ + 1);
|
||||
uint32_t actual = 0;
|
||||
switch (footer_.checksum()) {
|
||||
case kNoChecksum:
|
||||
break;
|
||||
case kCRC32c:
|
||||
value = crc32c::Unmask(value);
|
||||
actual = crc32c::Value(data, block_size_ + 1);
|
||||
break;
|
||||
case kxxHash:
|
||||
actual = XXH32(data, static_cast<int>(block_size_) + 1, 0);
|
||||
break;
|
||||
case kxxHash64:
|
||||
actual = static_cast<uint32_t>(
|
||||
XXH64(data, static_cast<int>(block_size_) + 1, 0) &
|
||||
uint64_t{0xffffffff});
|
||||
break;
|
||||
default:
|
||||
status_ = Status::Corruption(
|
||||
"unknown checksum type " + ToString(footer_.checksum()) + " in " +
|
||||
file_->file_name() + " offset " + ToString(handle_.offset()) +
|
||||
" size " + ToString(block_size_));
|
||||
}
|
||||
if (status_.ok() && actual != value) {
|
||||
status_ = Status::Corruption(
|
||||
"block checksum mismatch: expected " + ToString(actual) + ", got " +
|
||||
ToString(value) + " in " + file_->file_name() + " offset " +
|
||||
ToString(handle_.offset()) + " size " + ToString(block_size_));
|
||||
}
|
||||
status_ = ROCKSDB_NAMESPACE::VerifyBlockChecksum(
|
||||
footer_.checksum(), slice_.data(), block_size_, file_->file_name(),
|
||||
handle_.offset());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -53,7 +53,8 @@ CuckooTableBuilder::CuckooTableBuilder(
|
||||
const Comparator* user_comparator, uint32_t cuckoo_block_size,
|
||||
bool use_module_hash, bool identity_as_first_hash,
|
||||
uint64_t (*get_slice_hash)(const Slice&, uint32_t, uint64_t),
|
||||
uint32_t column_family_id, const std::string& column_family_name)
|
||||
uint32_t column_family_id, const std::string& column_family_name,
|
||||
const std::string& db_id, const std::string& db_session_id)
|
||||
: num_hash_func_(2),
|
||||
file_(file),
|
||||
max_hash_table_ratio_(max_hash_table_ratio),
|
||||
@@ -79,6 +80,8 @@ CuckooTableBuilder::CuckooTableBuilder(
|
||||
properties_.filter_size = 0;
|
||||
properties_.column_family_id = column_family_id;
|
||||
properties_.column_family_name = column_family_name;
|
||||
properties_.db_id = db_id;
|
||||
properties_.db_session_id = db_session_id;
|
||||
}
|
||||
|
||||
void CuckooTableBuilder::Add(const Slice& key, const Slice& value) {
|
||||
|
||||
@@ -22,15 +22,14 @@ namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
class CuckooTableBuilder: public TableBuilder {
|
||||
public:
|
||||
CuckooTableBuilder(WritableFileWriter* file, double max_hash_table_ratio,
|
||||
uint32_t max_num_hash_func, uint32_t max_search_depth,
|
||||
const Comparator* user_comparator,
|
||||
uint32_t cuckoo_block_size, bool use_module_hash,
|
||||
bool identity_as_first_hash,
|
||||
uint64_t (*get_slice_hash)(const Slice&, uint32_t,
|
||||
uint64_t),
|
||||
uint32_t column_family_id,
|
||||
const std::string& column_family_name);
|
||||
CuckooTableBuilder(
|
||||
WritableFileWriter* file, double max_hash_table_ratio,
|
||||
uint32_t max_num_hash_func, uint32_t max_search_depth,
|
||||
const Comparator* user_comparator, uint32_t cuckoo_block_size,
|
||||
bool use_module_hash, bool identity_as_first_hash,
|
||||
uint64_t (*get_slice_hash)(const Slice&, uint32_t, uint64_t),
|
||||
uint32_t column_family_id, const std::string& column_family_name,
|
||||
const std::string& db_id = "", const std::string& db_session_id = "");
|
||||
// No copying allowed
|
||||
CuckooTableBuilder(const CuckooTableBuilder&) = delete;
|
||||
void operator=(const CuckooTableBuilder&) = delete;
|
||||
|
||||
@@ -40,7 +40,8 @@ TableBuilder* CuckooTableFactory::NewTableBuilder(
|
||||
table_builder_options.internal_comparator.user_comparator(),
|
||||
table_options_.cuckoo_block_size, table_options_.use_module_hash,
|
||||
table_options_.identity_as_first_hash, nullptr /* get_slice_hash */,
|
||||
column_family_id, table_builder_options.column_family_name);
|
||||
column_family_id, table_builder_options.column_family_name,
|
||||
table_builder_options.db_id, table_builder_options.db_session_id);
|
||||
}
|
||||
|
||||
std::string CuckooTableFactory::GetPrintableTableOptions() const {
|
||||
|
||||
+1
-1
@@ -220,7 +220,7 @@ Status ReadFooterFromFile(RandomAccessFileReader* file,
|
||||
uint64_t file_size, Footer* footer,
|
||||
uint64_t enforce_table_magic_number = 0);
|
||||
|
||||
// 1-byte type + 32-bit crc
|
||||
// 1-byte compression type + 32-bit checksum
|
||||
static const size_t kBlockTrailerSize = 5;
|
||||
|
||||
// Make block size calculation for IO less error prone
|
||||
|
||||
@@ -96,6 +96,8 @@ void PropertyBlockBuilder::AddTableProperty(const TableProperties& props) {
|
||||
if (props.file_creation_time > 0) {
|
||||
Add(TablePropertiesNames::kFileCreationTime, props.file_creation_time);
|
||||
}
|
||||
Add(TablePropertiesNames::kDbId, props.db_id);
|
||||
Add(TablePropertiesNames::kDbSessionId, props.db_session_id);
|
||||
|
||||
if (!props.filter_policy_name.empty()) {
|
||||
Add(TablePropertiesNames::kFilterPolicy, props.filter_policy_name);
|
||||
@@ -311,6 +313,10 @@ Status ReadProperties(const Slice& handle_value, RandomAccessFileReader* file,
|
||||
continue;
|
||||
}
|
||||
*(pos->second) = val;
|
||||
} else if (key == TablePropertiesNames::kDbId) {
|
||||
new_table_properties->db_id = raw_val.ToString();
|
||||
} else if (key == TablePropertiesNames::kDbSessionId) {
|
||||
new_table_properties->db_session_id = raw_val.ToString();
|
||||
} else if (key == TablePropertiesNames::kFilterPolicy) {
|
||||
new_table_properties->filter_policy_name = raw_val.ToString();
|
||||
} else if (key == TablePropertiesNames::kColumnFamilyName) {
|
||||
|
||||
@@ -64,7 +64,8 @@ PlainTableBuilder::PlainTableBuilder(
|
||||
EncodingType encoding_type, size_t index_sparseness,
|
||||
uint32_t bloom_bits_per_key, const std::string& column_family_name,
|
||||
uint32_t num_probes, size_t huge_page_tlb_size, double hash_table_ratio,
|
||||
bool store_index_in_file)
|
||||
bool store_index_in_file, const std::string& db_id,
|
||||
const std::string& db_session_id)
|
||||
: ioptions_(ioptions),
|
||||
moptions_(moptions),
|
||||
bloom_block_(num_probes),
|
||||
@@ -97,6 +98,8 @@ PlainTableBuilder::PlainTableBuilder(
|
||||
properties_.format_version = (encoding_type == kPlain) ? 0 : 1;
|
||||
properties_.column_family_id = column_family_id;
|
||||
properties_.column_family_name = column_family_name;
|
||||
properties_.db_id = db_id;
|
||||
properties_.db_session_id = db_session_id;
|
||||
properties_.prefix_extractor_name = moptions_.prefix_extractor != nullptr
|
||||
? moptions_.prefix_extractor->Name()
|
||||
: "nullptr";
|
||||
|
||||
@@ -45,7 +45,8 @@ class PlainTableBuilder: public TableBuilder {
|
||||
size_t index_sparseness, uint32_t bloom_bits_per_key,
|
||||
const std::string& column_family_name, uint32_t num_probes = 6,
|
||||
size_t huge_page_tlb_size = 0, double hash_table_ratio = 0,
|
||||
bool store_index_in_file = false);
|
||||
bool store_index_in_file = false, const std::string& db_id = "",
|
||||
const std::string& db_session_id = "");
|
||||
// No copying allowed
|
||||
PlainTableBuilder(const PlainTableBuilder&) = delete;
|
||||
void operator=(const PlainTableBuilder&) = delete;
|
||||
|
||||
Loaded 100 of 123 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user