mirror of
https://github.com/facebook/rocksdb.git
synced 2026-11-06 11:02:36 -05:00
Compare commits
53
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c509dd3249 | ||
|
|
19c8025386 | ||
|
|
63462c3a94 | ||
|
|
62f110f527 | ||
|
|
878b3a41de | ||
|
|
f36f44c5e6 | ||
|
|
8d70e53114 | ||
|
|
5a4b8005a7 | ||
|
|
4cd964946a | ||
|
|
48bfca38f6 | ||
|
|
c8b9556cb6 | ||
|
|
30bfa2a44e | ||
|
|
3e1eb99ab7 | ||
|
|
3514cf51fa | ||
|
|
dd63f04c83 | ||
|
|
115c9113ca | ||
|
|
07e2ca100a | ||
|
|
9912ebe1b5 | ||
|
|
0fdd019f62 | ||
|
|
43107e0a9a | ||
|
|
c0a8d89c27 | ||
|
|
85189fd64f | ||
|
|
1910560c2c | ||
|
|
61cc9ef76f | ||
|
|
f5d4dbbeef | ||
|
|
84dcfe1a5f | ||
|
|
a818699f2f | ||
|
|
97a69f4372 | ||
|
|
af58d92760 | ||
|
|
83833637c1 | ||
|
|
717749f4c0 | ||
|
|
d854abad78 | ||
|
|
722ebba834 | ||
|
|
b3585a11b4 | ||
|
|
fbe2d259cb | ||
|
|
e6be168aa5 | ||
|
|
02db03af8d | ||
|
|
5abda3bb8b | ||
|
|
edf74d1cb1 | ||
|
|
6a8ddd374d | ||
|
|
1fb3593f25 | ||
|
|
f5e649453c | ||
|
|
119b26fac0 | ||
|
|
fb08330f74 | ||
|
|
3020df9df5 | ||
|
|
f8c2e5a608 | ||
|
|
8988f831d8 | ||
|
|
f941adef88 | ||
|
|
23e446a157 | ||
|
|
2e7070b194 | ||
|
|
98b0cbea88 | ||
|
|
1bee0fca05 | ||
|
|
2677bd5967 |
No files matched your search
+76
-1
@@ -11,7 +11,59 @@ executors:
|
||||
shell: bash.exe
|
||||
|
||||
jobs:
|
||||
build:
|
||||
build-linux:
|
||||
machine:
|
||||
image: ubuntu-1604:201903-01
|
||||
resource_class: 2xlarge
|
||||
steps:
|
||||
- checkout # check out the code in the project directory
|
||||
- run: pyenv global 3.5.2
|
||||
- run: sudo apt-get update -y
|
||||
- run: sudo apt-get install -y libgflags-dev
|
||||
- run: SKIP_FORMAT_BUCK_CHECKS=1 PRINT_PARALLEL_OUTPUTS=1 make J=32 all check -j32
|
||||
|
||||
build-linux-release:
|
||||
machine:
|
||||
image: ubuntu-1604:201903-01
|
||||
resource_class: 2xlarge
|
||||
steps:
|
||||
- checkout # check out the code in the project directory
|
||||
- run: make release -j32
|
||||
|
||||
build-linux-lite:
|
||||
machine:
|
||||
image: ubuntu-1604:201903-01
|
||||
resource_class: 2xlarge
|
||||
steps:
|
||||
- checkout # check out the code in the project directory
|
||||
- run: pyenv global 3.5.2
|
||||
- run: SKIP_FORMAT_BUCK_CHECKS=1 PRINT_PARALLEL_OUTPUTS=1 LITE=1 make J=32 all check -j32
|
||||
|
||||
build-linux-lite-release:
|
||||
machine:
|
||||
image: ubuntu-1604:201903-01
|
||||
resource_class: large
|
||||
steps:
|
||||
- checkout # check out the code in the project directory
|
||||
- run: make release -j32
|
||||
|
||||
build-linux-clang-no-test:
|
||||
machine:
|
||||
image: ubuntu-1604:201903-01
|
||||
resource_class: 2xlarge
|
||||
steps:
|
||||
- checkout # check out the code in the project directory
|
||||
- run: USE_CLANG=1 make all -j32
|
||||
|
||||
build-linux-cmake:
|
||||
machine:
|
||||
image: ubuntu-1604:201903-01
|
||||
resource_class: 2xlarge
|
||||
steps:
|
||||
- checkout # check out the code in the project directory
|
||||
- run: mkdir build && cd build && cmake -DWITH_GFLAGS=0 .. && make -j32
|
||||
|
||||
build-windows:
|
||||
executor: windows-2xlarge
|
||||
|
||||
environment:
|
||||
@@ -54,3 +106,26 @@ jobs:
|
||||
shell: powershell.exe
|
||||
command: |
|
||||
build_tools\run_ci_db_test.ps1 -SuiteRun db_basic_test,db_test,db_test2,env_basic_test,env_test,db_merge_operand_test -Concurrency 16
|
||||
|
||||
workflows:
|
||||
build-linux:
|
||||
jobs:
|
||||
- build-linux
|
||||
build-linux-lite:
|
||||
jobs:
|
||||
- build-linux-lite
|
||||
build-linux-release:
|
||||
jobs:
|
||||
- build-linux-release
|
||||
build-linux-lite-release:
|
||||
jobs:
|
||||
- build-linux-lite-release
|
||||
build-linux-clang-no-test:
|
||||
jobs:
|
||||
- build-linux-clang-no-test
|
||||
build-linux-cmake:
|
||||
jobs:
|
||||
- build-linux-cmake
|
||||
build-windows:
|
||||
jobs:
|
||||
- build-windows
|
||||
+4
-3
@@ -539,6 +539,9 @@ set(SOURCES
|
||||
db/blob/blob_file_addition.cc
|
||||
db/blob/blob_file_garbage.cc
|
||||
db/blob/blob_file_meta.cc
|
||||
db/blob/blob_log_format.cc
|
||||
db/blob/blob_log_reader.cc
|
||||
db/blob/blob_log_writer.cc
|
||||
db/builder.cc
|
||||
db/c.cc
|
||||
db/column_family.cc
|
||||
@@ -730,9 +733,6 @@ set(SOURCES
|
||||
utilities/blob_db/blob_db_impl_filesnapshot.cc
|
||||
utilities/blob_db/blob_dump_tool.cc
|
||||
utilities/blob_db/blob_file.cc
|
||||
utilities/blob_db/blob_log_reader.cc
|
||||
utilities/blob_db/blob_log_writer.cc
|
||||
utilities/blob_db/blob_log_format.cc
|
||||
utilities/cassandra/cassandra_compaction_filter.cc
|
||||
utilities/cassandra/format.cc
|
||||
utilities/cassandra/merge_operator.cc
|
||||
@@ -1090,6 +1090,7 @@ if(WITH_TESTS)
|
||||
table/sst_file_reader_test.cc
|
||||
table/table_test.cc
|
||||
table/block_fetcher_test.cc
|
||||
test_util/testutil_test.cc
|
||||
tools/block_cache_analyzer/block_cache_trace_analyzer_test.cc
|
||||
tools/ldb_cmd_test.cc
|
||||
tools/reduce_levels_test.cc
|
||||
|
||||
+45
-4
@@ -1,8 +1,39 @@
|
||||
# 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.
|
||||
## 6.11.7 (11/15/2020)
|
||||
### Bug Fixes
|
||||
* Fix a bug of encoding and parsing BlockBasedTableOptions::read_amp_bytes_per_bit as a 64-bit integer.
|
||||
* Fixed the logic of populating native data structure for `read_amp_bytes_per_bit` during OPTIONS file parsing on big-endian architecture. Without this fix, original code introduced in PR7659, when running on big-endian machine, can mistakenly store read_amp_bytes_per_bit (an uint32) in little endian format. Future access to `read_amp_bytes_per_bit` will give wrong values. Little endian architecture is not affected.
|
||||
|
||||
## 6.11.6 (10/12/2020)
|
||||
### Bug Fixes
|
||||
* Fixed a bug in the following combination of features: indexes with user keys (`format_version >= 3`), indexes are partitioned (`index_type == kTwoLevelIndexSearch`), and some index partitions are pinned in memory (`BlockBasedTableOptions::pin_l0_filter_and_index_blocks_in_cache`). The bug could cause keys to be truncated when read from the index leading to wrong read results or other unexpected behavior.
|
||||
* Fixed a bug when indexes are partitioned (`index_type == kTwoLevelIndexSearch`), some index partitions are pinned in memory (`BlockBasedTableOptions::pin_l0_filter_and_index_blocks_in_cache`), and partitions reads could be mixed between block cache and directly from the file (e.g., with `enable_index_compression == 1` and `mmap_read == 1`, partitions that were stored uncompressed due to poor compression ratio would be read directly from the file via mmap, while partitions that were stored compressed would be read from block cache). The bug could cause index partitions to be mistakenly considered empty during reads leading to wrong read results.
|
||||
|
||||
## 6.11.5 (7/23/2020)
|
||||
### Bug Fixes
|
||||
* Memtable lookup should report unrecognized value_type as corruption (#7121).
|
||||
|
||||
## 6.11.4 (7/15/2020)
|
||||
### Bug Fixes
|
||||
* Make compaction report InternalKey corruption while iterating over the input.
|
||||
|
||||
## 6.11.3 (7/9/2020)
|
||||
### Bug Fixes
|
||||
* Fix a bug when index_type == kTwoLevelIndexSearch in PartitionedIndexBuilder to update FlushPolicy to point to internal key partitioner when it changes from user-key mode to internal-key mode in index partition.
|
||||
* Disable file deletion after MANIFEST write/sync failure until db re-open or Resume() so that subsequent re-open will not see MANIFEST referencing deleted SSTs.
|
||||
|
||||
## 6.11.1 (6/23/2020)
|
||||
### Bug Fixes
|
||||
* 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.
|
||||
* Fail recovery and report once hitting a physical log record checksum mismatch, while reading MANIFEST. RocksDB should not continue processing the MANIFEST any further.
|
||||
* Fix a bug of wrong iterator result if another thread finishes an update and a DB flush between two statement.
|
||||
|
||||
### Public API Change
|
||||
* `DB::OpenForReadOnly()` now returns `Status::NotFound` when the specified DB directory does not exist. Previously the error returned depended on the underlying `Env`.
|
||||
|
||||
## 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.
|
||||
@@ -12,6 +43,9 @@
|
||||
* Fix corrupt key read from ingested file when iterator direction switches from reverse to forward at a key that is a prefix of another key in the same file. It is only possible in files with a non-zero global seqno.
|
||||
* Fix abnormally large estimate from GetApproximateSizes when a range starts near the end of one SST file and near the beginning of another. Now GetApproximateSizes consistently and fairly includes the size of SST metadata in addition to data blocks, attributing metadata proportionally among the data blocks based on their size.
|
||||
* Fix potential file descriptor leakage in PosixEnv's IsDirectory() and NewRandomAccessFile().
|
||||
* Fix false negative from the VerifyChecksum() API when there is a checksum mismatch in an index partition block in a BlockBasedTable format table file (index_type is kTwoLevelIndexSearch).
|
||||
* Fix sst_dump to return non-zero exit code if the specified file is not a recognized SST file or fails requested checks.
|
||||
* Fix incorrect results from batched MultiGet for duplicate keys, when the duplicate key matches the largest key of an SST file and the value type for the key in the file is a merge value.
|
||||
|
||||
### Public API Change
|
||||
* Flush(..., column_family) may return Status::ColumnFamilyDropped() instead of Status::InvalidArgument() if column_family is dropped while processing the flush request.
|
||||
@@ -20,11 +54,18 @@
|
||||
* 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.
|
||||
|
||||
### 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
|
||||
|
||||
@@ -9,7 +9,9 @@
|
||||
BASH_EXISTS := $(shell which bash)
|
||||
SHELL := $(shell which bash)
|
||||
# Default to python3. Some distros like CentOS 8 do not have `python`.
|
||||
PYTHON?=$(shell which python3 || which python || echo python3)
|
||||
ifeq ($(origin PYTHON), undefined)
|
||||
PYTHON := $(shell which python3 || which python || echo python3)
|
||||
endif
|
||||
export PYTHON
|
||||
|
||||
CLEAN_FILES = # deliberately empty, so we can append below.
|
||||
@@ -639,6 +641,7 @@ TESTS = \
|
||||
blob_file_garbage_test \
|
||||
timer_test \
|
||||
db_with_timestamp_compaction_test \
|
||||
testutil_test \
|
||||
|
||||
PARALLEL_TEST = \
|
||||
backupable_db_test \
|
||||
@@ -975,6 +978,12 @@ J ?= 100%
|
||||
# Use this regexp to select the subset of tests whose names match.
|
||||
tests-regexp = .
|
||||
|
||||
ifeq ($(PRINT_PARALLEL_OUTPUTS), 1)
|
||||
parallel_com = '{}'
|
||||
else
|
||||
parallel_com = '{} >& t/log-{/}'
|
||||
endif
|
||||
|
||||
.PHONY: check_0
|
||||
check_0:
|
||||
$(AM_V_GEN)export TEST_TMPDIR=$(TMPD); \
|
||||
@@ -988,7 +997,10 @@ check_0:
|
||||
} \
|
||||
| $(prioritize_long_running_tests) \
|
||||
| grep -E '$(tests-regexp)' \
|
||||
| build_tools/gnu_parallel -j$(J) --plain --joblog=LOG $$eta --gnu '{} >& t/log-{/}'
|
||||
| build_tools/gnu_parallel -j$(J) --plain --joblog=LOG $$eta --gnu $(parallel_com) ; \
|
||||
parallel_retcode=$$? ; \
|
||||
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
|
||||
|
||||
@@ -1047,9 +1059,11 @@ 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
|
||||
endif
|
||||
|
||||
# TODO add ldb_tests
|
||||
check_some: $(SUBSET)
|
||||
@@ -1866,6 +1880,9 @@ blob_file_garbage_test: db/blob/blob_file_garbage_test.o $(LIBOBJECTS) $(TESTHAR
|
||||
timer_test: util/timer_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
testutil_test: test_util/testutil_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
#-------------------------------------------------
|
||||
# make install related stuff
|
||||
INSTALL_PATH ?= /usr/local
|
||||
|
||||
@@ -78,7 +78,6 @@ ROCKSDB_PREPROCESSOR_FLAGS = [
|
||||
# Directories with files for #include
|
||||
"-I" + REPO_PATH + "include/",
|
||||
"-I" + REPO_PATH,
|
||||
"-I" + REPO_PATH + "third-party/gtest-1.8.1/fused-src/",
|
||||
]
|
||||
|
||||
ROCKSDB_ARCH_PREPROCESSOR_FLAGS = {
|
||||
@@ -125,6 +124,9 @@ cpp_library(
|
||||
"db/blob/blob_file_addition.cc",
|
||||
"db/blob/blob_file_garbage.cc",
|
||||
"db/blob/blob_file_meta.cc",
|
||||
"db/blob/blob_log_format.cc",
|
||||
"db/blob/blob_log_reader.cc",
|
||||
"db/blob/blob_log_writer.cc",
|
||||
"db/builder.cc",
|
||||
"db/c.cc",
|
||||
"db/column_family.cc",
|
||||
@@ -319,9 +321,6 @@ cpp_library(
|
||||
"utilities/blob_db/blob_db_impl_filesnapshot.cc",
|
||||
"utilities/blob_db/blob_dump_tool.cc",
|
||||
"utilities/blob_db/blob_file.cc",
|
||||
"utilities/blob_db/blob_log_format.cc",
|
||||
"utilities/blob_db/blob_log_reader.cc",
|
||||
"utilities/blob_db/blob_log_writer.cc",
|
||||
"utilities/cassandra/cassandra_compaction_filter.cc",
|
||||
"utilities/cassandra/format.cc",
|
||||
"utilities/cassandra/merge_operator.cc",
|
||||
@@ -398,11 +397,10 @@ cpp_library(
|
||||
os_deps = ROCKSDB_OS_DEPS,
|
||||
os_preprocessor_flags = ROCKSDB_OS_PREPROCESSOR_FLAGS,
|
||||
preprocessor_flags = ROCKSDB_PREPROCESSOR_FLAGS,
|
||||
deps = [
|
||||
":rocksdb_lib",
|
||||
":rocksdb_third_party_gtest",
|
||||
deps = [":rocksdb_lib"],
|
||||
external_deps = ROCKSDB_EXTERNAL_DEPS + [
|
||||
("googletest", None, "gtest"),
|
||||
],
|
||||
external_deps = ROCKSDB_EXTERNAL_DEPS,
|
||||
)
|
||||
|
||||
cpp_library(
|
||||
@@ -449,18 +447,26 @@ cpp_library(
|
||||
external_deps = ROCKSDB_EXTERNAL_DEPS,
|
||||
)
|
||||
|
||||
cpp_library(
|
||||
name = "rocksdb_third_party_gtest",
|
||||
srcs = ["third-party/gtest-1.8.1/fused-src/gtest/gtest-all.cc"],
|
||||
headers = ["third-party/gtest-1.8.1/fused-src/gtest/gtest.h"],
|
||||
arch_preprocessor_flags = ROCKSDB_ARCH_PREPROCESSOR_FLAGS,
|
||||
compiler_flags = ROCKSDB_COMPILER_FLAGS,
|
||||
os_deps = ROCKSDB_OS_DEPS,
|
||||
os_preprocessor_flags = ROCKSDB_OS_PREPROCESSOR_FLAGS,
|
||||
preprocessor_flags = ROCKSDB_PREPROCESSOR_FLAGS,
|
||||
deps = [],
|
||||
external_deps = ROCKSDB_EXTERNAL_DEPS,
|
||||
)
|
||||
if not is_opt_mode:
|
||||
cpp_binary(
|
||||
name = "c_test_bin",
|
||||
srcs = ["db/c_test.c"],
|
||||
arch_preprocessor_flags = ROCKSDB_ARCH_PREPROCESSOR_FLAGS,
|
||||
os_preprocessor_flags = ROCKSDB_OS_PREPROCESSOR_FLAGS,
|
||||
compiler_flags = ROCKSDB_COMPILER_FLAGS,
|
||||
preprocessor_flags = ROCKSDB_PREPROCESSOR_FLAGS,
|
||||
deps = [":rocksdb_test_lib"],
|
||||
)
|
||||
|
||||
if not is_opt_mode:
|
||||
custom_unittest(
|
||||
"c_test",
|
||||
command = [
|
||||
native.package_name() + "/buckifier/rocks_test_runner.sh",
|
||||
"$(location :{})".format("c_test_bin"),
|
||||
],
|
||||
type = "simple",
|
||||
)
|
||||
|
||||
cpp_library(
|
||||
name = "env_basic_test_lib",
|
||||
@@ -575,13 +581,6 @@ ROCKS_TESTS = [
|
||||
[],
|
||||
[],
|
||||
],
|
||||
[
|
||||
"c_test",
|
||||
"db/c_test.c",
|
||||
"serial",
|
||||
[],
|
||||
[],
|
||||
],
|
||||
[
|
||||
"cache_simulator_test",
|
||||
"utilities/simulator_cache/cache_simulator_test.cc",
|
||||
@@ -1485,6 +1484,13 @@ ROCKS_TESTS = [
|
||||
[],
|
||||
[],
|
||||
],
|
||||
[
|
||||
"testutil_test",
|
||||
"test_util/testutil_test.cc",
|
||||
"serial",
|
||||
[],
|
||||
[],
|
||||
],
|
||||
[
|
||||
"thread_list_test",
|
||||
"util/thread_list_test.cc",
|
||||
@@ -1638,18 +1644,17 @@ ROCKS_TESTS = [
|
||||
# Do not build the tests in opt mode, since SyncPoint and other test code
|
||||
# will not be included.
|
||||
[
|
||||
test_binary(
|
||||
extra_compiler_flags = extra_compiler_flags,
|
||||
extra_deps = extra_deps,
|
||||
parallelism = parallelism,
|
||||
rocksdb_arch_preprocessor_flags = ROCKSDB_ARCH_PREPROCESSOR_FLAGS,
|
||||
rocksdb_compiler_flags = ROCKSDB_COMPILER_FLAGS,
|
||||
rocksdb_external_deps = ROCKSDB_EXTERNAL_DEPS,
|
||||
rocksdb_os_deps = ROCKSDB_OS_DEPS,
|
||||
rocksdb_os_preprocessor_flags = ROCKSDB_OS_PREPROCESSOR_FLAGS,
|
||||
rocksdb_preprocessor_flags = ROCKSDB_PREPROCESSOR_FLAGS,
|
||||
test_cc = test_cc,
|
||||
test_name = test_name,
|
||||
cpp_unittest(
|
||||
name = test_name,
|
||||
srcs = [test_cc],
|
||||
arch_preprocessor_flags = ROCKSDB_ARCH_PREPROCESSOR_FLAGS,
|
||||
os_preprocessor_flags = ROCKSDB_OS_PREPROCESSOR_FLAGS,
|
||||
compiler_flags = ROCKSDB_COMPILER_FLAGS,
|
||||
preprocessor_flags = ROCKSDB_PREPROCESSOR_FLAGS,
|
||||
deps = [":rocksdb_test_lib"] + extra_deps,
|
||||
external_deps = ROCKSDB_EXTERNAL_DEPS + [
|
||||
("googletest", None, "gtest"),
|
||||
],
|
||||
)
|
||||
for test_name, test_cc, parallelism, extra_deps, extra_compiler_flags in ROCKS_TESTS
|
||||
if not is_opt_mode
|
||||
|
||||
@@ -64,8 +64,6 @@ def get_cc_files(repo_path):
|
||||
continue
|
||||
for filename in fnmatch.filter(filenames, '*.cc'):
|
||||
cc_files.append(os.path.join(root, filename))
|
||||
for filename in fnmatch.filter(filenames, '*.c'):
|
||||
cc_files.append(os.path.join(root, filename))
|
||||
return cc_files
|
||||
|
||||
|
||||
@@ -108,16 +106,6 @@ def get_tests(repo_path):
|
||||
|
||||
return tests
|
||||
|
||||
|
||||
# Get gtest dir from Makefile
|
||||
def get_gtest_dir(repo_path):
|
||||
for line in open(repo_path + "/Makefile"):
|
||||
if line.strip().startswith("GTEST_DIR ="):
|
||||
return line.split("=")[1].strip()
|
||||
# if not found
|
||||
exit_with_error("Unable to find GTEST_DIR in Makefile")
|
||||
|
||||
|
||||
# Parse extra dependencies passed by user from command line
|
||||
def get_dependencies():
|
||||
deps_map = {
|
||||
@@ -151,13 +139,11 @@ def generate_targets(repo_path, deps_map):
|
||||
cc_files = get_cc_files(repo_path)
|
||||
# get tests from Makefile
|
||||
tests = get_tests(repo_path)
|
||||
# get gtest dir
|
||||
gtest_dir = get_gtest_dir(repo_path) + "/"
|
||||
|
||||
if src_mk is None or cc_files is None or tests is None:
|
||||
return False
|
||||
|
||||
TARGETS = TARGETSBuilder("%s/TARGETS" % repo_path, gtest_dir)
|
||||
TARGETS = TARGETSBuilder("%s/TARGETS" % repo_path)
|
||||
|
||||
# rocksdb_lib
|
||||
TARGETS.add_library(
|
||||
@@ -171,7 +157,10 @@ def generate_targets(repo_path, deps_map):
|
||||
src_mk.get("TEST_LIB_SOURCES", []) +
|
||||
src_mk.get("EXP_LIB_SOURCES", []) +
|
||||
src_mk.get("ANALYZER_LIB_SOURCES", []),
|
||||
[":rocksdb_lib", ":rocksdb_third_party_gtest"])
|
||||
[":rocksdb_lib"],
|
||||
extra_external_deps=""" + [
|
||||
("googletest", None, "gtest"),
|
||||
]""")
|
||||
# rocksdb_tools_lib
|
||||
TARGETS.add_library(
|
||||
"rocksdb_tools_lib",
|
||||
@@ -185,18 +174,20 @@ def generate_targets(repo_path, deps_map):
|
||||
src_mk.get("ANALYZER_LIB_SOURCES", [])
|
||||
+ src_mk.get('STRESS_LIB_SOURCES', [])
|
||||
+ ["test_util/testutil.cc"])
|
||||
# rocksdb_third_party_gtest
|
||||
TARGETS.add_library(
|
||||
"rocksdb_third_party_gtest",
|
||||
[gtest_dir + "gtest/gtest-all.cc"],
|
||||
[],
|
||||
[gtest_dir + "gtest/gtest.h"])
|
||||
|
||||
print("Extra dependencies:\n{0}".format(json.dumps(deps_map)))
|
||||
# test for every test we found in the Makefile
|
||||
|
||||
# c_test.c is added through TARGETS.add_c_test(). If there
|
||||
# are more than one .c test file, we need to extend
|
||||
# TARGETS.add_c_test() to include other C tests too.
|
||||
TARGETS.add_c_test()
|
||||
|
||||
# test for every .cc test we found in the Makefile
|
||||
for target_alias, deps in deps_map.items():
|
||||
for test in sorted(tests):
|
||||
match_src = [src for src in cc_files if ("/%s.c" % test) in src]
|
||||
if test == 'c_test':
|
||||
continue
|
||||
match_src = [src for src in cc_files if ("/%s.cc" % test) in src]
|
||||
if len(match_src) == 0:
|
||||
print(ColorString.warning("Cannot find .cc file for %s" % test))
|
||||
continue
|
||||
|
||||
@@ -25,11 +25,10 @@ def pretty_list(lst, indent=8):
|
||||
|
||||
|
||||
class TARGETSBuilder(object):
|
||||
def __init__(self, path, gtest_dir):
|
||||
def __init__(self, path):
|
||||
self.path = path
|
||||
self.targets_file = open(path, 'w')
|
||||
header = targets_cfg.rocksdb_target_header_template.format(
|
||||
gtest_dir=gtest_dir)
|
||||
header = targets_cfg.rocksdb_target_header_template
|
||||
self.targets_file.write(header)
|
||||
self.total_lib = 0
|
||||
self.total_bin = 0
|
||||
@@ -39,7 +38,8 @@ class TARGETSBuilder(object):
|
||||
def __del__(self):
|
||||
self.targets_file.close()
|
||||
|
||||
def add_library(self, name, srcs, deps=None, headers=None):
|
||||
def add_library(self, name, srcs, deps=None, headers=None,
|
||||
extra_external_deps=""):
|
||||
headers_attr_prefix = ""
|
||||
if headers is None:
|
||||
headers_attr_prefix = "auto_"
|
||||
@@ -51,7 +51,8 @@ class TARGETSBuilder(object):
|
||||
srcs=pretty_list(srcs),
|
||||
headers_attr_prefix=headers_attr_prefix,
|
||||
headers=headers,
|
||||
deps=pretty_list(deps)))
|
||||
deps=pretty_list(deps),
|
||||
extra_external_deps=extra_external_deps))
|
||||
self.total_lib = self.total_lib + 1
|
||||
|
||||
def add_rocksdb_library(self, name, srcs, headers=None):
|
||||
@@ -75,6 +76,30 @@ class TARGETSBuilder(object):
|
||||
pretty_list(deps)))
|
||||
self.total_bin = self.total_bin + 1
|
||||
|
||||
def add_c_test(self):
|
||||
self.targets_file.write("""
|
||||
if not is_opt_mode:
|
||||
cpp_binary(
|
||||
name = "c_test_bin",
|
||||
srcs = ["db/c_test.c"],
|
||||
arch_preprocessor_flags = ROCKSDB_ARCH_PREPROCESSOR_FLAGS,
|
||||
os_preprocessor_flags = ROCKSDB_OS_PREPROCESSOR_FLAGS,
|
||||
compiler_flags = ROCKSDB_COMPILER_FLAGS,
|
||||
preprocessor_flags = ROCKSDB_PREPROCESSOR_FLAGS,
|
||||
deps = [":rocksdb_test_lib"],
|
||||
)
|
||||
|
||||
if not is_opt_mode:
|
||||
custom_unittest(
|
||||
"c_test",
|
||||
command = [
|
||||
native.package_name() + "/buckifier/rocks_test_runner.sh",
|
||||
"$(location :{})".format("c_test_bin"),
|
||||
],
|
||||
type = "simple",
|
||||
)
|
||||
""")
|
||||
|
||||
def register_test(self,
|
||||
test_name,
|
||||
src,
|
||||
|
||||
+14
-16
@@ -85,14 +85,13 @@ ROCKSDB_PREPROCESSOR_FLAGS = [
|
||||
# Directories with files for #include
|
||||
"-I" + REPO_PATH + "include/",
|
||||
"-I" + REPO_PATH,
|
||||
"-I" + REPO_PATH + "{gtest_dir}",
|
||||
]
|
||||
|
||||
ROCKSDB_ARCH_PREPROCESSOR_FLAGS = {{
|
||||
ROCKSDB_ARCH_PREPROCESSOR_FLAGS = {
|
||||
"x86_64": [
|
||||
"-DHAVE_PCLMUL",
|
||||
],
|
||||
}}
|
||||
}
|
||||
|
||||
build_mode = read_config("fbcode", "build_mode")
|
||||
|
||||
@@ -134,7 +133,7 @@ cpp_library(
|
||||
os_preprocessor_flags = ROCKSDB_OS_PREPROCESSOR_FLAGS,
|
||||
preprocessor_flags = ROCKSDB_PREPROCESSOR_FLAGS,
|
||||
deps = [{deps}],
|
||||
external_deps = ROCKSDB_EXTERNAL_DEPS,
|
||||
external_deps = ROCKSDB_EXTERNAL_DEPS{extra_external_deps},
|
||||
)
|
||||
"""
|
||||
|
||||
@@ -183,18 +182,17 @@ ROCKS_TESTS = [
|
||||
# Do not build the tests in opt mode, since SyncPoint and other test code
|
||||
# will not be included.
|
||||
[
|
||||
test_binary(
|
||||
extra_compiler_flags = extra_compiler_flags,
|
||||
extra_deps = extra_deps,
|
||||
parallelism = parallelism,
|
||||
rocksdb_arch_preprocessor_flags = ROCKSDB_ARCH_PREPROCESSOR_FLAGS,
|
||||
rocksdb_compiler_flags = ROCKSDB_COMPILER_FLAGS,
|
||||
rocksdb_external_deps = ROCKSDB_EXTERNAL_DEPS,
|
||||
rocksdb_os_deps = ROCKSDB_OS_DEPS,
|
||||
rocksdb_os_preprocessor_flags = ROCKSDB_OS_PREPROCESSOR_FLAGS,
|
||||
rocksdb_preprocessor_flags = ROCKSDB_PREPROCESSOR_FLAGS,
|
||||
test_cc = test_cc,
|
||||
test_name = test_name,
|
||||
cpp_unittest(
|
||||
name = test_name,
|
||||
srcs = [test_cc],
|
||||
arch_preprocessor_flags = ROCKSDB_ARCH_PREPROCESSOR_FLAGS,
|
||||
os_preprocessor_flags = ROCKSDB_OS_PREPROCESSOR_FLAGS,
|
||||
compiler_flags = ROCKSDB_COMPILER_FLAGS,
|
||||
preprocessor_flags = ROCKSDB_PREPROCESSOR_FLAGS,
|
||||
deps = [":rocksdb_test_lib"] + extra_deps,
|
||||
external_deps = ROCKSDB_EXTERNAL_DEPS + [
|
||||
("googletest", None, "gtest"),
|
||||
],
|
||||
)
|
||||
for test_name, test_cc, parallelism, extra_deps, extra_compiler_flags in ROCKS_TESTS
|
||||
if not is_opt_mode
|
||||
|
||||
@@ -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_;
|
||||
};
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
//
|
||||
#ifndef ROCKSDB_LITE
|
||||
|
||||
#include "utilities/blob_db/blob_log_format.h"
|
||||
#include "db/blob/blob_log_format.h"
|
||||
|
||||
#include "util/coding.h"
|
||||
#include "util/crc32c.h"
|
||||
@@ -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>;
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
//
|
||||
#ifndef ROCKSDB_LITE
|
||||
|
||||
#include "utilities/blob_db/blob_log_reader.h"
|
||||
#include "db/blob/blob_log_reader.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
@@ -10,12 +10,12 @@
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "db/blob/blob_log_format.h"
|
||||
#include "file/random_access_file_reader.h"
|
||||
#include "rocksdb/env.h"
|
||||
#include "rocksdb/slice.h"
|
||||
#include "rocksdb/statistics.h"
|
||||
#include "rocksdb/status.h"
|
||||
#include "utilities/blob_db/blob_log_format.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
@@ -4,17 +4,17 @@
|
||||
// (found in the LICENSE.Apache file in the root directory).
|
||||
#ifndef ROCKSDB_LITE
|
||||
|
||||
#include "utilities/blob_db/blob_log_writer.h"
|
||||
#include "db/blob/blob_log_writer.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
#include "db/blob/blob_log_format.h"
|
||||
#include "file/writable_file_writer.h"
|
||||
#include "monitoring/statistics.h"
|
||||
#include "rocksdb/env.h"
|
||||
#include "util/coding.h"
|
||||
#include "util/stop_watch.h"
|
||||
#include "utilities/blob_db/blob_log_format.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
namespace blob_db {
|
||||
@@ -10,12 +10,12 @@
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "db/blob/blob_log_format.h"
|
||||
#include "rocksdb/env.h"
|
||||
#include "rocksdb/slice.h"
|
||||
#include "rocksdb/statistics.h"
|
||||
#include "rocksdb/status.h"
|
||||
#include "rocksdb/types.h"
|
||||
#include "utilities/blob_db/blob_log_format.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
+3
-1
@@ -239,7 +239,9 @@ Status BuildTable(
|
||||
(internal_stats == nullptr) ? nullptr
|
||||
: internal_stats->GetFileReadHist(0),
|
||||
TableReaderCaller::kFlush, /*arena=*/nullptr,
|
||||
/*skip_filter=*/false, level, /*smallest_compaction_key=*/nullptr,
|
||||
/*skip_filter=*/false, level,
|
||||
MaxFileSizeForL0MetaPin(mutable_cf_options),
|
||||
/*smallest_compaction_key=*/nullptr,
|
||||
/*largest_compaction_key*/ nullptr,
|
||||
/*allow_unprepared_value*/ false));
|
||||
s = it->status();
|
||||
|
||||
@@ -246,6 +246,7 @@ void CompactionIterator::NextFromInput() {
|
||||
iter_stats_.num_input_records++;
|
||||
|
||||
if (!ParseInternalKey(key_, &ikey_)) {
|
||||
iter_stats_.num_input_corrupt_records++;
|
||||
// If `expect_valid_internal_key_` is false, return the corrupted key
|
||||
// and let the caller decide what to do with it.
|
||||
// TODO(noetzli): We should have a more elegant solution for this.
|
||||
@@ -258,7 +259,6 @@ void CompactionIterator::NextFromInput() {
|
||||
has_current_user_key_ = false;
|
||||
current_user_key_sequence_ = kMaxSequenceNumber;
|
||||
current_user_key_snapshot_ = 0;
|
||||
iter_stats_.num_input_corrupt_records++;
|
||||
valid_ = true;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -654,6 +654,8 @@ Status CompactionJob::Run() {
|
||||
compact_->compaction->output_level()),
|
||||
TableReaderCaller::kCompactionRefill, /*arena=*/nullptr,
|
||||
/*skip_filters=*/false, compact_->compaction->output_level(),
|
||||
MaxFileSizeForL0MetaPin(
|
||||
*compact_->compaction->mutable_cf_options()),
|
||||
/*smallest_compaction_key=*/nullptr,
|
||||
/*largest_compaction_key=*/nullptr,
|
||||
/*allow_unprepared_value=*/false);
|
||||
@@ -719,7 +721,6 @@ Status CompactionJob::Install(const MutableCFOptions& mutable_cf_options) {
|
||||
cfd->internal_stats()->AddCompactionStats(
|
||||
compact_->compaction->output_level(), thread_pri_, compaction_stats_);
|
||||
|
||||
versions_->SetIOStatusOK();
|
||||
if (status.ok()) {
|
||||
status = InstallCompactionResults(mutable_cf_options);
|
||||
}
|
||||
@@ -894,9 +895,10 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
|
||||
sub_compact->c_iter.reset(new CompactionIterator(
|
||||
input.get(), cfd->user_comparator(), &merge, versions_->LastSequence(),
|
||||
&existing_snapshots_, earliest_write_conflict_snapshot_,
|
||||
snapshot_checker_, env_, ShouldReportDetailedTime(env_, stats_), false,
|
||||
&range_del_agg, sub_compact->compaction, compaction_filter,
|
||||
shutting_down_, preserve_deletes_seqnum_, manual_compaction_paused_,
|
||||
snapshot_checker_, env_, ShouldReportDetailedTime(env_, stats_),
|
||||
/*expect_valid_internal_key=*/true, &range_del_agg,
|
||||
sub_compact->compaction, compaction_filter, shutting_down_,
|
||||
preserve_deletes_seqnum_, manual_compaction_paused_,
|
||||
db_options_.info_log));
|
||||
auto c_iter = sub_compact->c_iter.get();
|
||||
c_iter->SeekToFirst();
|
||||
|
||||
@@ -395,7 +395,7 @@ TEST_F(CompactionJobTest, Simple) {
|
||||
RunCompaction({ files }, expected_results);
|
||||
}
|
||||
|
||||
TEST_F(CompactionJobTest, SimpleCorrupted) {
|
||||
TEST_F(CompactionJobTest, DISABLED_SimpleCorrupted) {
|
||||
NewDB();
|
||||
|
||||
auto expected_results = CreateTwoFiles(true);
|
||||
@@ -989,7 +989,7 @@ TEST_F(CompactionJobTest, MultiSingleDelete) {
|
||||
// single deletion and the (single) deletion gets removed while the corrupt key
|
||||
// gets written out. TODO(noetzli): We probably want a better way to treat
|
||||
// corrupt keys.
|
||||
TEST_F(CompactionJobTest, CorruptionAfterDeletion) {
|
||||
TEST_F(CompactionJobTest, DISABLED_CorruptionAfterDeletion) {
|
||||
NewDB();
|
||||
|
||||
auto file1 =
|
||||
|
||||
+3
-39
@@ -157,42 +157,6 @@ class CorruptionTest : public testing::Test {
|
||||
ASSERT_GE(max_expected, correct);
|
||||
}
|
||||
|
||||
void CorruptFile(const std::string& fname, int offset, int bytes_to_corrupt) {
|
||||
struct stat sbuf;
|
||||
if (stat(fname.c_str(), &sbuf) != 0) {
|
||||
const char* msg = strerror(errno);
|
||||
FAIL() << fname << ": " << msg;
|
||||
}
|
||||
|
||||
if (offset < 0) {
|
||||
// Relative to end of file; make it absolute
|
||||
if (-offset > sbuf.st_size) {
|
||||
offset = 0;
|
||||
} else {
|
||||
offset = static_cast<int>(sbuf.st_size + offset);
|
||||
}
|
||||
}
|
||||
if (offset > sbuf.st_size) {
|
||||
offset = static_cast<int>(sbuf.st_size);
|
||||
}
|
||||
if (offset + bytes_to_corrupt > sbuf.st_size) {
|
||||
bytes_to_corrupt = static_cast<int>(sbuf.st_size - offset);
|
||||
}
|
||||
|
||||
// Do it
|
||||
std::string contents;
|
||||
Status s = ReadFileToString(Env::Default(), fname, &contents);
|
||||
ASSERT_TRUE(s.ok()) << s.ToString();
|
||||
for (int i = 0; i < bytes_to_corrupt; i++) {
|
||||
contents[i + offset] ^= 0x80;
|
||||
}
|
||||
s = WriteStringToFile(Env::Default(), contents, fname);
|
||||
ASSERT_TRUE(s.ok()) << s.ToString();
|
||||
Options options;
|
||||
EnvOptions env_options;
|
||||
ASSERT_NOK(VerifySstFileChecksum(options, env_options, fname));
|
||||
}
|
||||
|
||||
void Corrupt(FileType filetype, int offset, int bytes_to_corrupt) {
|
||||
// Pick file to corrupt
|
||||
std::vector<std::string> filenames;
|
||||
@@ -211,7 +175,7 @@ class CorruptionTest : public testing::Test {
|
||||
}
|
||||
ASSERT_TRUE(!fname.empty()) << filetype;
|
||||
|
||||
CorruptFile(fname, offset, bytes_to_corrupt);
|
||||
test::CorruptFile(fname, offset, bytes_to_corrupt);
|
||||
}
|
||||
|
||||
// corrupts exactly one file at level `level`. if no file found at level,
|
||||
@@ -221,7 +185,7 @@ class CorruptionTest : public testing::Test {
|
||||
db_->GetLiveFilesMetaData(&metadata);
|
||||
for (const auto& m : metadata) {
|
||||
if (m.level == level) {
|
||||
CorruptFile(dbname_ + "/" + m.name, offset, bytes_to_corrupt);
|
||||
test::CorruptFile(dbname_ + "/" + m.name, offset, bytes_to_corrupt);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -556,7 +520,7 @@ TEST_F(CorruptionTest, RangeDeletionCorrupted) {
|
||||
ImmutableCFOptions(options_), kRangeDelBlock, &range_del_handle));
|
||||
|
||||
ASSERT_OK(TryReopen());
|
||||
CorruptFile(filename, static_cast<int>(range_del_handle.offset()), 1);
|
||||
test::CorruptFile(filename, static_cast<int>(range_del_handle.offset()), 1);
|
||||
ASSERT_TRUE(TryReopen().IsCorruption());
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
|
||||
#include "db/db_test_util.h"
|
||||
#include "port/stack_trace.h"
|
||||
#include "rocksdb/merge_operator.h"
|
||||
#include "rocksdb/perf_context.h"
|
||||
#include "rocksdb/utilities/debug.h"
|
||||
#include "table/block_based/block_based_table_reader.h"
|
||||
@@ -17,6 +18,8 @@
|
||||
#if !defined(ROCKSDB_LITE)
|
||||
#include "test_util/sync_point.h"
|
||||
#endif
|
||||
#include "utilities/merge_operators.h"
|
||||
#include "utilities/merge_operators/string_append/stringappend.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
@@ -1340,6 +1343,57 @@ TEST_F(DBBasicTest, MultiGetBatchedSortedMultiFile) {
|
||||
} while (ChangeOptions());
|
||||
}
|
||||
|
||||
TEST_F(DBBasicTest, MultiGetBatchedDuplicateKeys) {
|
||||
Options opts = CurrentOptions();
|
||||
opts.merge_operator = MergeOperators::CreateStringAppendOperator();
|
||||
CreateAndReopenWithCF({"pikachu"}, opts);
|
||||
SetPerfLevel(kEnableCount);
|
||||
// To expand the power of this test, generate > 1 table file and
|
||||
// mix with memtable
|
||||
ASSERT_OK(Merge(1, "k1", "v1"));
|
||||
ASSERT_OK(Merge(1, "k2", "v2"));
|
||||
Flush(1);
|
||||
MoveFilesToLevel(2, 1);
|
||||
ASSERT_OK(Merge(1, "k3", "v3"));
|
||||
ASSERT_OK(Merge(1, "k4", "v4"));
|
||||
Flush(1);
|
||||
MoveFilesToLevel(2, 1);
|
||||
ASSERT_OK(Merge(1, "k4", "v4_2"));
|
||||
ASSERT_OK(Merge(1, "k6", "v6"));
|
||||
Flush(1);
|
||||
MoveFilesToLevel(2, 1);
|
||||
ASSERT_OK(Merge(1, "k7", "v7"));
|
||||
ASSERT_OK(Merge(1, "k8", "v8"));
|
||||
Flush(1);
|
||||
MoveFilesToLevel(2, 1);
|
||||
|
||||
get_perf_context()->Reset();
|
||||
|
||||
std::vector<Slice> keys({"k8", "k8", "k8", "k4", "k4", "k1", "k3"});
|
||||
std::vector<PinnableSlice> values(keys.size());
|
||||
std::vector<ColumnFamilyHandle*> cfs(keys.size(), handles_[1]);
|
||||
std::vector<Status> s(keys.size());
|
||||
|
||||
db_->MultiGet(ReadOptions(), handles_[1], keys.size(), keys.data(),
|
||||
values.data(), s.data(), false);
|
||||
|
||||
ASSERT_EQ(values.size(), keys.size());
|
||||
ASSERT_EQ(std::string(values[0].data(), values[0].size()), "v8");
|
||||
ASSERT_EQ(std::string(values[1].data(), values[1].size()), "v8");
|
||||
ASSERT_EQ(std::string(values[2].data(), values[2].size()), "v8");
|
||||
ASSERT_EQ(std::string(values[3].data(), values[3].size()), "v4,v4_2");
|
||||
ASSERT_EQ(std::string(values[4].data(), values[4].size()), "v4,v4_2");
|
||||
ASSERT_EQ(std::string(values[5].data(), values[5].size()), "v1");
|
||||
ASSERT_EQ(std::string(values[6].data(), values[6].size()), "v3");
|
||||
ASSERT_EQ(24, (int)get_perf_context()->multiget_read_bytes);
|
||||
|
||||
for (Status& status : s) {
|
||||
ASSERT_OK(status);
|
||||
}
|
||||
|
||||
SetPerfLevel(kDisable);
|
||||
}
|
||||
|
||||
TEST_F(DBBasicTest, MultiGetBatchedMultiLevel) {
|
||||
Options options = CurrentOptions();
|
||||
options.disable_auto_compactions = true;
|
||||
@@ -2145,6 +2199,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);
|
||||
@@ -2185,6 +2268,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,
|
||||
@@ -2913,6 +3023,33 @@ TEST_F(DBBasicTestMultiGetDeadline, MultiGetDeadlineExceeded) {
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(DBBasicTest, ManifestWriteFailure) {
|
||||
Options options = GetDefaultOptions();
|
||||
options.create_if_missing = true;
|
||||
options.disable_auto_compactions = true;
|
||||
options.env = env_;
|
||||
DestroyAndReopen(options);
|
||||
ASSERT_OK(Put("foo", "bar"));
|
||||
ASSERT_OK(Flush());
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"VersionSet::ProcessManifestWrites:AfterSyncManifest", [&](void* arg) {
|
||||
ASSERT_NE(nullptr, arg);
|
||||
auto* s = reinterpret_cast<Status*>(arg);
|
||||
ASSERT_OK(*s);
|
||||
// Manually overwrite return status
|
||||
*s = Status::IOError();
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
ASSERT_OK(Put("key", "value"));
|
||||
ASSERT_NOK(Flush());
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
Reopen(options);
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
#ifdef ROCKSDB_UNITTESTS_WITH_CUSTOM_OBJECTS_FROM_STATIC_LIBS
|
||||
|
||||
@@ -3184,6 +3184,15 @@ TEST_P(DBCompactionTestWithParam, IntraL0Compaction) {
|
||||
options.level0_file_num_compaction_trigger = 5;
|
||||
options.max_background_compactions = 2;
|
||||
options.max_subcompactions = max_subcompactions_;
|
||||
options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics();
|
||||
options.write_buffer_size = 2 << 20; // 2MB
|
||||
|
||||
BlockBasedTableOptions table_options;
|
||||
table_options.block_cache = NewLRUCache(64 << 20); // 64MB
|
||||
table_options.cache_index_and_filter_blocks = true;
|
||||
table_options.pin_l0_filter_and_index_blocks_in_cache = true;
|
||||
options.table_factory.reset(new BlockBasedTableFactory(table_options));
|
||||
|
||||
DestroyAndReopen(options);
|
||||
|
||||
const size_t kValueSize = 1 << 20;
|
||||
@@ -3214,6 +3223,7 @@ TEST_P(DBCompactionTestWithParam, IntraL0Compaction) {
|
||||
ASSERT_OK(Put(Key(i + 1), value));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_EQ(i + 1, TestGetTickerCount(options, BLOCK_CACHE_INDEX_MISS));
|
||||
}
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
|
||||
@@ -3228,6 +3238,16 @@ TEST_P(DBCompactionTestWithParam, IntraL0Compaction) {
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
ASSERT_GE(level_to_files[0][i].fd.file_size, 1 << 21);
|
||||
}
|
||||
|
||||
// The index/filter in the file produced by intra-L0 should not be pinned.
|
||||
// That means clearing unref'd entries in block cache and re-accessing the
|
||||
// file produced by intra-L0 should bump the index block miss count.
|
||||
uint64_t prev_index_misses =
|
||||
TestGetTickerCount(options, BLOCK_CACHE_INDEX_MISS);
|
||||
table_options.block_cache->EraseUnRefEntries();
|
||||
ASSERT_EQ("", Get(Key(0)));
|
||||
ASSERT_EQ(prev_index_misses + 1,
|
||||
TestGetTickerCount(options, BLOCK_CACHE_INDEX_MISS));
|
||||
}
|
||||
|
||||
TEST_P(DBCompactionTestWithParam, IntraL0CompactionDoesNotObsoleteDeletions) {
|
||||
|
||||
@@ -23,57 +23,6 @@
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
Status DBImpl::DisableFileDeletions() {
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
++disable_delete_obsolete_files_;
|
||||
if (disable_delete_obsolete_files_ == 1) {
|
||||
ROCKS_LOG_INFO(immutable_db_options_.info_log, "File Deletions Disabled");
|
||||
} else {
|
||||
ROCKS_LOG_WARN(immutable_db_options_.info_log,
|
||||
"File Deletions Disabled, but already disabled. Counter: %d",
|
||||
disable_delete_obsolete_files_);
|
||||
}
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status DBImpl::EnableFileDeletions(bool force) {
|
||||
// Job id == 0 means that this is not our background process, but rather
|
||||
// user thread
|
||||
JobContext job_context(0);
|
||||
bool file_deletion_enabled = false;
|
||||
{
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
if (force) {
|
||||
// if force, we need to enable file deletions right away
|
||||
disable_delete_obsolete_files_ = 0;
|
||||
} else if (disable_delete_obsolete_files_ > 0) {
|
||||
--disable_delete_obsolete_files_;
|
||||
}
|
||||
if (disable_delete_obsolete_files_ == 0) {
|
||||
file_deletion_enabled = true;
|
||||
FindObsoleteFiles(&job_context, true);
|
||||
bg_cv_.SignalAll();
|
||||
}
|
||||
}
|
||||
if (file_deletion_enabled) {
|
||||
ROCKS_LOG_INFO(immutable_db_options_.info_log, "File Deletions Enabled");
|
||||
if (job_context.HaveSomethingToDelete()) {
|
||||
PurgeObsoleteFiles(job_context);
|
||||
}
|
||||
} else {
|
||||
ROCKS_LOG_WARN(immutable_db_options_.info_log,
|
||||
"File Deletions Enable, but not really enabled. Counter: %d",
|
||||
disable_delete_obsolete_files_);
|
||||
}
|
||||
job_context.Clean();
|
||||
LogFlush(immutable_db_options_.info_log);
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
int DBImpl::IsFileDeletionsEnabled() const {
|
||||
return !disable_delete_obsolete_files_;
|
||||
}
|
||||
|
||||
Status DBImpl::GetLiveFiles(std::vector<std::string>& ret,
|
||||
uint64_t* manifest_file_size,
|
||||
bool flush_memtable) {
|
||||
|
||||
+73
-12
@@ -312,8 +312,36 @@ Status DBImpl::ResumeImpl() {
|
||||
}
|
||||
|
||||
// Make sure the IO Status stored in version set is set to OK.
|
||||
bool file_deletion_disabled = !IsFileDeletionsEnabled();
|
||||
if (s.ok()) {
|
||||
versions_->SetIOStatusOK();
|
||||
IOStatus io_s = versions_->io_status();
|
||||
if (io_s.IsIOError()) {
|
||||
// If resuming from IOError resulted from MANIFEST write, then assert
|
||||
// that we must have already set the MANIFEST writer to nullptr during
|
||||
// clean-up phase MANIFEST writing. We must have also disabled file
|
||||
// deletions.
|
||||
assert(!versions_->descriptor_log_);
|
||||
assert(file_deletion_disabled);
|
||||
// Since we are trying to recover from MANIFEST write error, we need to
|
||||
// switch to a new MANIFEST anyway. The old MANIFEST can be corrupted.
|
||||
// Therefore, force writing a dummy version edit because we do not know
|
||||
// whether there are flush jobs with non-empty data to flush, triggering
|
||||
// appends to MANIFEST.
|
||||
VersionEdit edit;
|
||||
auto cfh = reinterpret_cast<ColumnFamilyHandleImpl*>(default_cf_handle_);
|
||||
assert(cfh);
|
||||
ColumnFamilyData* cfd = cfh->cfd();
|
||||
const MutableCFOptions& cf_opts = *cfd->GetLatestMutableCFOptions();
|
||||
s = versions_->LogAndApply(cfd, cf_opts, &edit, &mutex_,
|
||||
directories_.GetDbDir());
|
||||
if (!s.ok()) {
|
||||
io_s = versions_->io_status();
|
||||
if (!io_s.ok()) {
|
||||
s = error_handler_.SetBGError(io_s,
|
||||
BackgroundErrorReason::kManifestWrite);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We cannot guarantee consistency of the WAL. So force flush Memtables of
|
||||
@@ -364,6 +392,13 @@ Status DBImpl::ResumeImpl() {
|
||||
job_context.Clean();
|
||||
|
||||
if (s.ok()) {
|
||||
assert(versions_->io_status().ok());
|
||||
// If we reach here, we should re-enable file deletions if it was disabled
|
||||
// during previous error handling.
|
||||
if (file_deletion_disabled) {
|
||||
// Always return ok
|
||||
EnableFileDeletions(/*force=*/true);
|
||||
}
|
||||
ROCKS_LOG_INFO(immutable_db_options_.info_log, "Successfully resumed DB");
|
||||
}
|
||||
mutex_.Lock();
|
||||
@@ -2670,7 +2705,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 +2727,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 +2744,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:
|
||||
// +-------------------------------+
|
||||
@@ -4180,7 +4235,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 +4247,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);
|
||||
}
|
||||
@@ -4253,10 +4310,6 @@ Status DBImpl::IngestExternalFiles(
|
||||
"cannot ingest an external file into a dropped CF");
|
||||
break;
|
||||
}
|
||||
if (args[i].options.skip_memtable_flush) {
|
||||
need_flush[i] = false;
|
||||
continue;
|
||||
}
|
||||
bool tmp = false;
|
||||
status = ingestion_jobs[i].NeedsFlush(&tmp, cfd->GetSuperVersion());
|
||||
need_flush[i] = tmp;
|
||||
@@ -4371,6 +4424,14 @@ Status DBImpl::IngestExternalFiles(
|
||||
#endif // !NDEBUG
|
||||
}
|
||||
}
|
||||
} else if (versions_->io_status().IsIOError()) {
|
||||
// Error while writing to MANIFEST.
|
||||
// In fact, versions_->io_status() can also be the result of renaming
|
||||
// CURRENT file. With current code, it's just difficult to tell. So just
|
||||
// be pessimistic and try write to a new MANIFEST.
|
||||
// TODO: distinguish between MANIFEST write and CURRENT renaming
|
||||
const IOStatus& io_s = versions_->io_status();
|
||||
error_handler_.SetBGError(io_s, BackgroundErrorReason::kManifestWrite);
|
||||
}
|
||||
|
||||
// Resume writes to the DB
|
||||
|
||||
@@ -356,6 +356,12 @@ class DBImpl : public DB {
|
||||
|
||||
virtual Status Close() override;
|
||||
|
||||
virtual Status DisableFileDeletions() override;
|
||||
|
||||
virtual Status EnableFileDeletions(bool force) override;
|
||||
|
||||
virtual bool IsFileDeletionsEnabled() const;
|
||||
|
||||
Status GetStatsHistory(
|
||||
uint64_t start_time, uint64_t end_time,
|
||||
std::unique_ptr<StatsHistoryIterator>* stats_iterator) override;
|
||||
@@ -363,9 +369,6 @@ class DBImpl : public DB {
|
||||
#ifndef ROCKSDB_LITE
|
||||
using DB::ResetStats;
|
||||
virtual Status ResetStats() override;
|
||||
virtual Status DisableFileDeletions() override;
|
||||
virtual Status EnableFileDeletions(bool force) override;
|
||||
virtual int IsFileDeletionsEnabled() const;
|
||||
// All the returned filenames start with "/"
|
||||
virtual Status GetLiveFiles(std::vector<std::string>&,
|
||||
uint64_t* manifest_file_size,
|
||||
@@ -477,6 +480,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,
|
||||
@@ -1779,6 +1783,8 @@ class DBImpl : public DB {
|
||||
SuperVersion* sv, SequenceNumber snap_seqnum, ReadCallback* callback,
|
||||
bool* is_blob_index);
|
||||
|
||||
Status DisableFileDeletionsWithLock();
|
||||
|
||||
// table_cache_ provides its own synchronization
|
||||
std::shared_ptr<Cache> table_cache_;
|
||||
|
||||
|
||||
@@ -210,7 +210,15 @@ Status DBImpl::FlushMemTableToOutputFile(
|
||||
if (!s.ok() && !s.IsShutdownInProgress() && !s.IsColumnFamilyDropped()) {
|
||||
if (!io_s.ok() && !io_s.IsShutdownInProgress() &&
|
||||
!io_s.IsColumnFamilyDropped()) {
|
||||
error_handler_.SetBGError(io_s, BackgroundErrorReason::kFlush);
|
||||
// Error while writing to MANIFEST.
|
||||
// In fact, versions_->io_status() can also be the result of renaming
|
||||
// CURRENT file. With current code, it's just difficult to tell. So just
|
||||
// be pessimistic and try write to a new MANIFEST.
|
||||
// TODO: distinguish between MANIFEST write and CURRENT renaming
|
||||
auto err_reason = versions_->io_status().ok()
|
||||
? BackgroundErrorReason::kFlush
|
||||
: BackgroundErrorReason::kManifestWrite;
|
||||
error_handler_.SetBGError(io_s, err_reason);
|
||||
} else {
|
||||
Status new_bg_error = s;
|
||||
error_handler_.SetBGError(new_bg_error, BackgroundErrorReason::kFlush);
|
||||
@@ -574,7 +582,15 @@ Status DBImpl::AtomicFlushMemTablesToOutputFiles(
|
||||
// it is not because of CF drop.
|
||||
if (!s.ok() && !s.IsColumnFamilyDropped()) {
|
||||
if (!io_s.ok() && !io_s.IsColumnFamilyDropped()) {
|
||||
error_handler_.SetBGError(io_s, BackgroundErrorReason::kFlush);
|
||||
// Error while writing to MANIFEST.
|
||||
// In fact, versions_->io_status() can also be the result of renaming
|
||||
// CURRENT file. With current code, it's just difficult to tell. So just
|
||||
// be pessimistic and try write to a new MANIFEST.
|
||||
// TODO: distinguish between MANIFEST write and CURRENT renaming
|
||||
auto err_reason = versions_->io_status().ok()
|
||||
? BackgroundErrorReason::kFlush
|
||||
: BackgroundErrorReason::kManifestWrite;
|
||||
error_handler_.SetBGError(io_s, err_reason);
|
||||
} else {
|
||||
Status new_bg_error = s;
|
||||
error_handler_.SetBGError(new_bg_error, BackgroundErrorReason::kFlush);
|
||||
@@ -2687,7 +2703,6 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
|
||||
for (const auto& f : *c->inputs(0)) {
|
||||
c->edit()->DeleteFile(c->level(), f->fd.GetNumber());
|
||||
}
|
||||
versions_->SetIOStatusOK();
|
||||
status = versions_->LogAndApply(c->column_family_data(),
|
||||
*c->mutable_cf_options(), c->edit(),
|
||||
&mutex_, directories_.GetDbDir());
|
||||
@@ -2745,7 +2760,6 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
|
||||
}
|
||||
}
|
||||
|
||||
versions_->SetIOStatusOK();
|
||||
status = versions_->LogAndApply(c->column_family_data(),
|
||||
*c->mutable_cf_options(), c->edit(),
|
||||
&mutex_, directories_.GetDbDir());
|
||||
@@ -2877,7 +2891,15 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
|
||||
ROCKS_LOG_WARN(immutable_db_options_.info_log, "Compaction error: %s",
|
||||
status.ToString().c_str());
|
||||
if (!io_s.ok()) {
|
||||
error_handler_.SetBGError(io_s, BackgroundErrorReason::kCompaction);
|
||||
// Error while writing to MANIFEST.
|
||||
// In fact, versions_->io_status() can also be the result of renaming
|
||||
// CURRENT file. With current code, it's just difficult to tell. So just
|
||||
// be pessimistic and try write to a new MANIFEST.
|
||||
// TODO: distinguish between MANIFEST write and CURRENT renaming
|
||||
auto err_reason = versions_->io_status().ok()
|
||||
? BackgroundErrorReason::kCompaction
|
||||
: BackgroundErrorReason::kManifestWrite;
|
||||
error_handler_.SetBGError(io_s, err_reason);
|
||||
} else {
|
||||
error_handler_.SetBGError(status, BackgroundErrorReason::kCompaction);
|
||||
}
|
||||
|
||||
@@ -36,6 +36,62 @@ uint64_t DBImpl::MinObsoleteSstNumberToKeep() {
|
||||
return std::numeric_limits<uint64_t>::max();
|
||||
}
|
||||
|
||||
Status DBImpl::DisableFileDeletions() {
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
return DisableFileDeletionsWithLock();
|
||||
}
|
||||
|
||||
Status DBImpl::DisableFileDeletionsWithLock() {
|
||||
mutex_.AssertHeld();
|
||||
++disable_delete_obsolete_files_;
|
||||
if (disable_delete_obsolete_files_ == 1) {
|
||||
ROCKS_LOG_INFO(immutable_db_options_.info_log, "File Deletions Disabled");
|
||||
} else {
|
||||
ROCKS_LOG_WARN(immutable_db_options_.info_log,
|
||||
"File Deletions Disabled, but already disabled. Counter: %d",
|
||||
disable_delete_obsolete_files_);
|
||||
}
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status DBImpl::EnableFileDeletions(bool force) {
|
||||
// Job id == 0 means that this is not our background process, but rather
|
||||
// user thread
|
||||
JobContext job_context(0);
|
||||
bool file_deletion_enabled = false;
|
||||
{
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
if (force) {
|
||||
// if force, we need to enable file deletions right away
|
||||
disable_delete_obsolete_files_ = 0;
|
||||
} else if (disable_delete_obsolete_files_ > 0) {
|
||||
--disable_delete_obsolete_files_;
|
||||
}
|
||||
if (disable_delete_obsolete_files_ == 0) {
|
||||
file_deletion_enabled = true;
|
||||
FindObsoleteFiles(&job_context, true);
|
||||
bg_cv_.SignalAll();
|
||||
}
|
||||
}
|
||||
if (file_deletion_enabled) {
|
||||
ROCKS_LOG_INFO(immutable_db_options_.info_log, "File Deletions Enabled");
|
||||
if (job_context.HaveSomethingToDelete()) {
|
||||
PurgeObsoleteFiles(job_context);
|
||||
}
|
||||
} else {
|
||||
ROCKS_LOG_WARN(immutable_db_options_.info_log,
|
||||
"File Deletions Enable, but not really enabled. Counter: %d",
|
||||
disable_delete_obsolete_files_);
|
||||
}
|
||||
job_context.Clean();
|
||||
LogFlush(immutable_db_options_.info_log);
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
bool DBImpl::IsFileDeletionsEnabled() const {
|
||||
return 0 == disable_delete_obsolete_files_;
|
||||
}
|
||||
|
||||
// * Returns the list of live files in 'sst_live' and 'blob_live'.
|
||||
// If it's doing full scan:
|
||||
// * Returns the list of all files in the filesystem in
|
||||
|
||||
@@ -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;
|
||||
@@ -1569,6 +1604,12 @@ Status DBImpl::Open(const DBOptions& db_options, const std::string& dbname,
|
||||
auto sfm = static_cast<SstFileManagerImpl*>(
|
||||
impl->immutable_db_options_.sst_file_manager.get());
|
||||
if (s.ok() && sfm) {
|
||||
// Set Statistics ptr for SstFileManager to dump the stats of
|
||||
// DeleteScheduler.
|
||||
sfm->SetStatisticsPtr(impl->immutable_db_options_.statistics);
|
||||
ROCKS_LOG_INFO(impl->immutable_db_options_.info_log,
|
||||
"SstFileManager instance %p", sfm);
|
||||
|
||||
// Notify SstFileManager about all sst files that already exist in
|
||||
// db_paths[0] and cf_paths[0] when the DB is opened.
|
||||
|
||||
|
||||
@@ -376,6 +376,7 @@ TEST_F(DBSSTTest, RateLimitedDelete) {
|
||||
env_->SetTimeElapseOnlySleep(&options);
|
||||
options.disable_auto_compactions = true;
|
||||
options.env = env_;
|
||||
options.statistics = CreateDBStatistics();
|
||||
|
||||
int64_t rate_bytes_per_sec = 1024 * 10; // 10 Kbs / Sec
|
||||
Status s;
|
||||
@@ -425,6 +426,9 @@ TEST_F(DBSSTTest, RateLimitedDelete) {
|
||||
}
|
||||
ASSERT_GT(time_spent_deleting, expected_penlty * 0.9);
|
||||
ASSERT_LT(time_spent_deleting, expected_penlty * 1.1);
|
||||
ASSERT_EQ(4, options.statistics->getAndResetTickerCount(FILES_MARKED_TRASH));
|
||||
ASSERT_EQ(
|
||||
0, options.statistics->getAndResetTickerCount(FILES_DELETED_IMMEDIATELY));
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
@@ -816,6 +820,12 @@ TEST_F(DBSSTTest, CancellingCompactionsWorks) {
|
||||
ASSERT_EQ(sfm->GetCompactionsReservedSize(), 0);
|
||||
// Make sure the stat is bumped
|
||||
ASSERT_GT(dbfull()->immutable_db_options().statistics.get()->getTickerCount(COMPACTION_CANCELLED), 0);
|
||||
ASSERT_EQ(0,
|
||||
dbfull()->immutable_db_options().statistics.get()->getTickerCount(
|
||||
FILES_MARKED_TRASH));
|
||||
ASSERT_EQ(4,
|
||||
dbfull()->immutable_db_options().statistics.get()->getTickerCount(
|
||||
FILES_DELETED_IMMEDIATELY));
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
|
||||
|
||||
+2
-1
@@ -2990,10 +2990,11 @@ class ModelDB : public DB {
|
||||
|
||||
Status SyncWAL() override { return Status::OK(); }
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
Status DisableFileDeletions() override { return Status::OK(); }
|
||||
|
||||
Status EnableFileDeletions(bool /*force*/) override { return Status::OK(); }
|
||||
#ifndef ROCKSDB_LITE
|
||||
|
||||
Status GetLiveFiles(std::vector<std::string>&, uint64_t* /*size*/,
|
||||
bool /*flush_memtable*/ = true) override {
|
||||
return Status::OK();
|
||||
|
||||
+315
-102
@@ -25,6 +25,7 @@ class DBTest2 : public DBTestBase {
|
||||
DBTest2() : DBTestBase("/db_test2") {}
|
||||
};
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
TEST_F(DBTest2, OpenForReadOnly) {
|
||||
DB* db_ptr = nullptr;
|
||||
std::string dbname = test::PerThreadDBPath("db_readonly");
|
||||
@@ -86,6 +87,97 @@ TEST_F(DBTest2, OpenForReadOnlyWithColumnFamilies) {
|
||||
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()));
|
||||
|
||||
class PartitionedIndexTestListener : public EventListener {
|
||||
public:
|
||||
void OnFlushCompleted(DB* /*db*/, const FlushJobInfo& info) override {
|
||||
ASSERT_GT(info.table_properties.index_partitions, 1);
|
||||
ASSERT_EQ(info.table_properties.index_key_is_user_key, 0);
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(DBTest2, PartitionedIndexUserToInternalKey) {
|
||||
BlockBasedTableOptions table_options;
|
||||
Options options = CurrentOptions();
|
||||
table_options.index_type = BlockBasedTableOptions::kTwoLevelIndexSearch;
|
||||
PartitionedIndexTestListener* listener = new PartitionedIndexTestListener();
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
options.listeners.emplace_back(listener);
|
||||
std::vector<const Snapshot*> snapshots;
|
||||
Reopen(options);
|
||||
Random rnd(301);
|
||||
|
||||
for (int i = 0; i < 3000; i++) {
|
||||
int j = i % 30;
|
||||
std::string value = RandomString(&rnd, 10500);
|
||||
ASSERT_OK(Put("keykey_" + std::to_string(j), value));
|
||||
snapshots.push_back(db_->GetSnapshot());
|
||||
}
|
||||
Flush();
|
||||
for (auto s : snapshots) {
|
||||
db_->ReleaseSnapshot(s);
|
||||
}
|
||||
}
|
||||
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
class PrefixFullBloomWithReverseComparator
|
||||
: public DBTestBase,
|
||||
public ::testing::WithParamInterface<bool> {
|
||||
@@ -1329,7 +1421,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;
|
||||
@@ -1343,50 +1470,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;
|
||||
|
||||
@@ -1397,74 +1515,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");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2888,6 +2987,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"));
|
||||
|
||||
@@ -4917,6 +5104,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
|
||||
|
||||
+33
-13
@@ -51,9 +51,19 @@ std::map<std::tuple<BackgroundErrorReason, Status::Code, Status::SubCode, bool>,
|
||||
Status::Code::kIOError, Status::SubCode::kNoSpace,
|
||||
false),
|
||||
Status::Severity::kHardError},
|
||||
// Errors during MANIFEST write
|
||||
{std::make_tuple(BackgroundErrorReason::kManifestWrite,
|
||||
Status::Code::kIOError, Status::SubCode::kNoSpace,
|
||||
true),
|
||||
Status::Severity::kHardError},
|
||||
{std::make_tuple(BackgroundErrorReason::kManifestWrite,
|
||||
Status::Code::kIOError, Status::SubCode::kNoSpace,
|
||||
false),
|
||||
Status::Severity::kHardError},
|
||||
};
|
||||
|
||||
std::map<std::tuple<BackgroundErrorReason, Status::Code, bool>, Status::Severity>
|
||||
std::map<std::tuple<BackgroundErrorReason, Status::Code, bool>,
|
||||
Status::Severity>
|
||||
DefaultErrorSeverityMap = {
|
||||
// Errors during BG compaction
|
||||
{std::make_tuple(BackgroundErrorReason::kCompaction,
|
||||
@@ -75,11 +85,11 @@ std::map<std::tuple<BackgroundErrorReason, Status::Code, bool>, Status::Severity
|
||||
{std::make_tuple(BackgroundErrorReason::kFlush,
|
||||
Status::Code::kCorruption, false),
|
||||
Status::Severity::kNoError},
|
||||
{std::make_tuple(BackgroundErrorReason::kFlush,
|
||||
Status::Code::kIOError, true),
|
||||
{std::make_tuple(BackgroundErrorReason::kFlush, Status::Code::kIOError,
|
||||
true),
|
||||
Status::Severity::kFatalError},
|
||||
{std::make_tuple(BackgroundErrorReason::kFlush,
|
||||
Status::Code::kIOError, false),
|
||||
{std::make_tuple(BackgroundErrorReason::kFlush, Status::Code::kIOError,
|
||||
false),
|
||||
Status::Severity::kNoError},
|
||||
// Errors during Write
|
||||
{std::make_tuple(BackgroundErrorReason::kWriteCallback,
|
||||
@@ -94,30 +104,36 @@ std::map<std::tuple<BackgroundErrorReason, Status::Code, bool>, Status::Severity
|
||||
{std::make_tuple(BackgroundErrorReason::kWriteCallback,
|
||||
Status::Code::kIOError, false),
|
||||
Status::Severity::kNoError},
|
||||
{std::make_tuple(BackgroundErrorReason::kManifestWrite,
|
||||
Status::Code::kIOError, true),
|
||||
Status::Severity::kFatalError},
|
||||
{std::make_tuple(BackgroundErrorReason::kManifestWrite,
|
||||
Status::Code::kIOError, false),
|
||||
Status::Severity::kFatalError},
|
||||
};
|
||||
|
||||
std::map<std::tuple<BackgroundErrorReason, bool>, Status::Severity>
|
||||
DefaultReasonMap = {
|
||||
// Errors during BG compaction
|
||||
{std::make_tuple(BackgroundErrorReason::kCompaction, true),
|
||||
Status::Severity::kFatalError},
|
||||
Status::Severity::kFatalError},
|
||||
{std::make_tuple(BackgroundErrorReason::kCompaction, false),
|
||||
Status::Severity::kNoError},
|
||||
Status::Severity::kNoError},
|
||||
// Errors during BG flush
|
||||
{std::make_tuple(BackgroundErrorReason::kFlush, true),
|
||||
Status::Severity::kFatalError},
|
||||
Status::Severity::kFatalError},
|
||||
{std::make_tuple(BackgroundErrorReason::kFlush, false),
|
||||
Status::Severity::kNoError},
|
||||
Status::Severity::kNoError},
|
||||
// Errors during Write
|
||||
{std::make_tuple(BackgroundErrorReason::kWriteCallback, true),
|
||||
Status::Severity::kFatalError},
|
||||
Status::Severity::kFatalError},
|
||||
{std::make_tuple(BackgroundErrorReason::kWriteCallback, false),
|
||||
Status::Severity::kFatalError},
|
||||
Status::Severity::kFatalError},
|
||||
// Errors during Memtable update
|
||||
{std::make_tuple(BackgroundErrorReason::kMemTable, true),
|
||||
Status::Severity::kFatalError},
|
||||
Status::Severity::kFatalError},
|
||||
{std::make_tuple(BackgroundErrorReason::kMemTable, false),
|
||||
Status::Severity::kFatalError},
|
||||
Status::Severity::kFatalError},
|
||||
};
|
||||
|
||||
void ErrorHandler::CancelErrorRecovery() {
|
||||
@@ -247,6 +263,10 @@ Status ErrorHandler::SetBGError(const IOStatus& bg_io_err,
|
||||
if (recovery_in_prog_ && recovery_error_.ok()) {
|
||||
recovery_error_ = bg_io_err;
|
||||
}
|
||||
if (BackgroundErrorReason::kManifestWrite == reason) {
|
||||
// Always returns ok
|
||||
db_->DisableFileDeletionsWithLock();
|
||||
}
|
||||
Status new_bg_io_err = bg_io_err;
|
||||
Status s;
|
||||
if (bg_io_err.GetDataLoss()) {
|
||||
|
||||
@@ -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
|
||||
@@ -84,9 +84,9 @@ class ForwardLevelIterator : public InternalIterator {
|
||||
prefix_extractor_, /*table_reader_ptr=*/nullptr,
|
||||
/*file_read_hist=*/nullptr, TableReaderCaller::kUserIterator,
|
||||
/*arena=*/nullptr, /*skip_filters=*/false, /*level=*/-1,
|
||||
/*max_file_size_for_l0_meta_pin=*/0,
|
||||
/*smallest_compaction_key=*/nullptr,
|
||||
/*largest_compaction_key=*/nullptr,
|
||||
allow_unprepared_value_);
|
||||
/*largest_compaction_key=*/nullptr, allow_unprepared_value_);
|
||||
file_iter_->SetPinnedItersMgr(pinned_iters_mgr_);
|
||||
valid_ = false;
|
||||
if (!range_del_agg.IsEmpty()) {
|
||||
@@ -686,9 +686,9 @@ void ForwardIterator::RebuildIterators(bool refresh_sv) {
|
||||
/*table_reader_ptr=*/nullptr, /*file_read_hist=*/nullptr,
|
||||
TableReaderCaller::kUserIterator, /*arena=*/nullptr,
|
||||
/*skip_filters=*/false, /*level=*/-1,
|
||||
MaxFileSizeForL0MetaPin(sv_->mutable_cf_options),
|
||||
/*smallest_compaction_key=*/nullptr,
|
||||
/*largest_compaction_key=*/nullptr,
|
||||
allow_unprepared_value_));
|
||||
/*largest_compaction_key=*/nullptr, allow_unprepared_value_));
|
||||
}
|
||||
BuildLevelIterators(vstorage);
|
||||
current_ = nullptr;
|
||||
@@ -764,9 +764,9 @@ void ForwardIterator::RenewIterators() {
|
||||
/*table_reader_ptr=*/nullptr, /*file_read_hist=*/nullptr,
|
||||
TableReaderCaller::kUserIterator, /*arena=*/nullptr,
|
||||
/*skip_filters=*/false, /*level=*/-1,
|
||||
MaxFileSizeForL0MetaPin(svnew->mutable_cf_options),
|
||||
/*smallest_compaction_key=*/nullptr,
|
||||
/*largest_compaction_key=*/nullptr,
|
||||
allow_unprepared_value_));
|
||||
/*largest_compaction_key=*/nullptr, allow_unprepared_value_));
|
||||
}
|
||||
|
||||
for (auto* f : l0_iters_) {
|
||||
@@ -830,9 +830,9 @@ void ForwardIterator::ResetIncompleteIterators() {
|
||||
/*table_reader_ptr=*/nullptr, /*file_read_hist=*/nullptr,
|
||||
TableReaderCaller::kUserIterator, /*arena=*/nullptr,
|
||||
/*skip_filters=*/false, /*level=*/-1,
|
||||
MaxFileSizeForL0MetaPin(sv_->mutable_cf_options),
|
||||
/*smallest_compaction_key=*/nullptr,
|
||||
/*largest_compaction_key=*/nullptr,
|
||||
allow_unprepared_value_);
|
||||
/*largest_compaction_key=*/nullptr, allow_unprepared_value_);
|
||||
l0_iters_[i]->SetPinnedItersMgr(pinned_iters_mgr_);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -798,7 +798,7 @@ bool InternalStats::HandleCurrentSuperVersionNumber(uint64_t* value,
|
||||
|
||||
bool InternalStats::HandleIsFileDeletionsEnabled(uint64_t* value, DBImpl* db,
|
||||
Version* /*version*/) {
|
||||
*value = db->IsFileDeletionsEnabled();
|
||||
*value = db->IsFileDeletionsEnabled() ? 1 : 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
+7
-1
@@ -766,7 +766,13 @@ static bool SaveValue(void* arg, const char* entry) {
|
||||
}
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
default: {
|
||||
std::string msg("Unrecognized value type: " +
|
||||
std::to_string(static_cast<int>(type)) + ". ");
|
||||
msg.append("User key: " + user_key_slice.ToString(/*hex=*/true) + ". ");
|
||||
msg.append("seq: " + std::to_string(seq) + ".");
|
||||
*(s->status) = Status::Corruption(msg.c_str());
|
||||
}
|
||||
assert(false);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -470,7 +470,6 @@ Status MemTableList::TryInstallMemtableFlushResults(
|
||||
}
|
||||
|
||||
// this can release and reacquire the mutex.
|
||||
vset->SetIOStatusOK();
|
||||
s = vset->LogAndApply(cfd, mutable_cf_options, edit_list, mu,
|
||||
db_directory);
|
||||
*io_s = vset->io_status();
|
||||
|
||||
+2
-1
@@ -528,7 +528,8 @@ class Repairer {
|
||||
cfd->GetLatestMutableCFOptions()->prefix_extractor.get(),
|
||||
/*table_reader_ptr=*/nullptr, /*file_read_hist=*/nullptr,
|
||||
TableReaderCaller::kRepair, /*arena=*/nullptr, /*skip_filters=*/false,
|
||||
/*level=*/-1, /*smallest_compaction_key=*/nullptr,
|
||||
/*level=*/-1, /*max_file_size_for_l0_meta_pin=*/0,
|
||||
/*smallest_compaction_key=*/nullptr,
|
||||
/*largest_compaction_key=*/nullptr,
|
||||
/*allow_unprepared_value=*/false);
|
||||
ParsedInternalKey parsed;
|
||||
|
||||
+19
-11
@@ -97,7 +97,8 @@ Status TableCache::GetTableReader(
|
||||
bool sequential_mode, bool record_read_stats, HistogramImpl* file_read_hist,
|
||||
std::unique_ptr<TableReader>* table_reader,
|
||||
const SliceTransform* prefix_extractor, bool skip_filters, int level,
|
||||
bool prefetch_index_and_filter_in_cache) {
|
||||
bool prefetch_index_and_filter_in_cache,
|
||||
size_t max_file_size_for_l0_meta_pin) {
|
||||
std::string fname =
|
||||
TableFileName(ioptions_.cf_paths, fd.GetNumber(), fd.GetPathId());
|
||||
std::unique_ptr<FSRandomAccessFile> file;
|
||||
@@ -124,7 +125,8 @@ Status TableCache::GetTableReader(
|
||||
TableReaderOptions(ioptions_, prefix_extractor, file_options,
|
||||
internal_comparator, skip_filters, immortal_tables_,
|
||||
false /* force_direct_prefetch */, level,
|
||||
fd.largest_seqno, block_cache_tracer_),
|
||||
fd.largest_seqno, block_cache_tracer_,
|
||||
max_file_size_for_l0_meta_pin),
|
||||
std::move(file_reader), fd.GetFileSize(), table_reader,
|
||||
prefetch_index_and_filter_in_cache);
|
||||
TEST_SYNC_POINT("TableCache::GetTableReader:0");
|
||||
@@ -145,8 +147,8 @@ Status TableCache::FindTable(const FileOptions& file_options,
|
||||
const SliceTransform* prefix_extractor,
|
||||
const bool no_io, bool record_read_stats,
|
||||
HistogramImpl* file_read_hist, bool skip_filters,
|
||||
int level,
|
||||
bool prefetch_index_and_filter_in_cache) {
|
||||
int level, bool prefetch_index_and_filter_in_cache,
|
||||
size_t max_file_size_for_l0_meta_pin) {
|
||||
PERF_TIMER_GUARD_WITH_ENV(find_table_nanos, ioptions_.env);
|
||||
Status s;
|
||||
uint64_t number = fd.GetNumber();
|
||||
@@ -170,7 +172,8 @@ Status TableCache::FindTable(const FileOptions& file_options,
|
||||
s = GetTableReader(file_options, internal_comparator, fd,
|
||||
false /* sequential mode */, record_read_stats,
|
||||
file_read_hist, &table_reader, prefix_extractor,
|
||||
skip_filters, level, prefetch_index_and_filter_in_cache);
|
||||
skip_filters, level, prefetch_index_and_filter_in_cache,
|
||||
max_file_size_for_l0_meta_pin);
|
||||
if (!s.ok()) {
|
||||
assert(table_reader == nullptr);
|
||||
RecordTick(ioptions_.statistics, NO_FILE_ERRORS);
|
||||
@@ -194,6 +197,7 @@ InternalIterator* TableCache::NewIterator(
|
||||
RangeDelAggregator* range_del_agg, const SliceTransform* prefix_extractor,
|
||||
TableReader** table_reader_ptr, HistogramImpl* file_read_hist,
|
||||
TableReaderCaller caller, Arena* arena, bool skip_filters, int level,
|
||||
size_t max_file_size_for_l0_meta_pin,
|
||||
const InternalKey* smallest_compaction_key,
|
||||
const InternalKey* largest_compaction_key, bool allow_unprepared_value) {
|
||||
PERF_TIMER_GUARD(new_table_iterator_nanos);
|
||||
@@ -211,7 +215,9 @@ InternalIterator* TableCache::NewIterator(
|
||||
s = FindTable(file_options, icomparator, fd, &handle, prefix_extractor,
|
||||
options.read_tier == kBlockCacheTier /* no_io */,
|
||||
!for_compaction /* record_read_stats */, file_read_hist,
|
||||
skip_filters, level);
|
||||
skip_filters, level,
|
||||
true /* prefetch_index_and_filter_in_cache */,
|
||||
max_file_size_for_l0_meta_pin);
|
||||
if (s.ok()) {
|
||||
table_reader = GetTableReaderFromHandle(handle);
|
||||
}
|
||||
@@ -372,7 +378,7 @@ Status TableCache::Get(const ReadOptions& options,
|
||||
GetContext* get_context,
|
||||
const SliceTransform* prefix_extractor,
|
||||
HistogramImpl* file_read_hist, bool skip_filters,
|
||||
int level) {
|
||||
int level, size_t max_file_size_for_l0_meta_pin) {
|
||||
auto& fd = file_meta.fd;
|
||||
std::string* row_cache_entry = nullptr;
|
||||
bool done = false;
|
||||
@@ -397,10 +403,12 @@ Status TableCache::Get(const ReadOptions& options,
|
||||
Cache::Handle* handle = nullptr;
|
||||
if (!done && s.ok()) {
|
||||
if (t == nullptr) {
|
||||
s = FindTable(
|
||||
file_options_, internal_comparator, fd, &handle, prefix_extractor,
|
||||
options.read_tier == kBlockCacheTier /* no_io */,
|
||||
true /* record_read_stats */, file_read_hist, skip_filters, level);
|
||||
s = FindTable(file_options_, internal_comparator, fd, &handle,
|
||||
prefix_extractor,
|
||||
options.read_tier == kBlockCacheTier /* no_io */,
|
||||
true /* record_read_stats */, file_read_hist, skip_filters,
|
||||
level, true /* prefetch_index_and_filter_in_cache */,
|
||||
max_file_size_for_l0_meta_pin);
|
||||
if (s.ok()) {
|
||||
t = GetTableReaderFromHandle(handle);
|
||||
}
|
||||
|
||||
+7
-4
@@ -72,7 +72,8 @@ class TableCache {
|
||||
const FileMetaData& file_meta, RangeDelAggregator* range_del_agg,
|
||||
const SliceTransform* prefix_extractor, TableReader** table_reader_ptr,
|
||||
HistogramImpl* file_read_hist, TableReaderCaller caller, Arena* arena,
|
||||
bool skip_filters, int level, const InternalKey* smallest_compaction_key,
|
||||
bool skip_filters, int level, size_t max_file_size_for_l0_meta_pin,
|
||||
const InternalKey* smallest_compaction_key,
|
||||
const InternalKey* largest_compaction_key, bool allow_unprepared_value);
|
||||
|
||||
// If a seek to internal key "k" in specified file finds an entry,
|
||||
@@ -91,7 +92,7 @@ class TableCache {
|
||||
GetContext* get_context,
|
||||
const SliceTransform* prefix_extractor = nullptr,
|
||||
HistogramImpl* file_read_hist = nullptr, bool skip_filters = false,
|
||||
int level = -1);
|
||||
int level = -1, size_t max_file_size_for_l0_meta_pin = 0);
|
||||
|
||||
// Return the range delete tombstone iterator of the file specified by
|
||||
// `file_meta`.
|
||||
@@ -135,7 +136,8 @@ class TableCache {
|
||||
const bool no_io = false, bool record_read_stats = true,
|
||||
HistogramImpl* file_read_hist = nullptr,
|
||||
bool skip_filters = false, int level = -1,
|
||||
bool prefetch_index_and_filter_in_cache = true);
|
||||
bool prefetch_index_and_filter_in_cache = true,
|
||||
size_t max_file_size_for_l0_meta_pin = 0);
|
||||
|
||||
// Get TableReader from a cache handle.
|
||||
TableReader* GetTableReaderFromHandle(Cache::Handle* handle);
|
||||
@@ -200,7 +202,8 @@ class TableCache {
|
||||
std::unique_ptr<TableReader>* table_reader,
|
||||
const SliceTransform* prefix_extractor = nullptr,
|
||||
bool skip_filters = false, int level = -1,
|
||||
bool prefetch_index_and_filter_in_cache = true);
|
||||
bool prefetch_index_and_filter_in_cache = true,
|
||||
size_t max_file_size_for_l0_meta_pin = 0);
|
||||
|
||||
// Create a key prefix for looking up the row cache. The prefix is of the
|
||||
// format row_cache_id + fd_number + seq_no. Later, the user key can be
|
||||
|
||||
+208
-94
@@ -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];
|
||||
@@ -278,7 +310,6 @@ class VersionBuilder::Rep {
|
||||
TEST_SYNC_POINT_CALLBACK("VersionBuilder::CheckConsistency0", &pair);
|
||||
#endif
|
||||
if (!level_zero_cmp_(f1, f2)) {
|
||||
fprintf(stderr, "L0 files are not sorted properly");
|
||||
return Status::Corruption("L0 files are not sorted properly");
|
||||
}
|
||||
|
||||
@@ -287,11 +318,6 @@ class VersionBuilder::Rep {
|
||||
SequenceNumber external_file_seqno = f2->fd.smallest_seqno;
|
||||
if (!(external_file_seqno < f1->fd.largest_seqno ||
|
||||
external_file_seqno == 0)) {
|
||||
fprintf(stderr,
|
||||
"L0 file with seqno %" PRIu64 " %" PRIu64
|
||||
" vs. file with global_seqno %" PRIu64 "\n",
|
||||
f1->fd.smallest_seqno, f1->fd.largest_seqno,
|
||||
external_file_seqno);
|
||||
return Status::Corruption(
|
||||
"L0 file with seqno " +
|
||||
NumberToString(f1->fd.smallest_seqno) + " " +
|
||||
@@ -301,11 +327,6 @@ class VersionBuilder::Rep {
|
||||
NumberToString(f1->fd.GetNumber()));
|
||||
}
|
||||
} else if (f1->fd.smallest_seqno <= f2->fd.smallest_seqno) {
|
||||
fprintf(stderr,
|
||||
"L0 files seqno %" PRIu64 " %" PRIu64 " vs. %" PRIu64
|
||||
" %" PRIu64 "\n",
|
||||
f1->fd.smallest_seqno, f1->fd.largest_seqno,
|
||||
f2->fd.smallest_seqno, f2->fd.largest_seqno);
|
||||
return Status::Corruption(
|
||||
"L0 files seqno " + NumberToString(f1->fd.smallest_seqno) +
|
||||
" " + NumberToString(f1->fd.largest_seqno) + " " +
|
||||
@@ -320,7 +341,6 @@ class VersionBuilder::Rep {
|
||||
TEST_SYNC_POINT_CALLBACK("VersionBuilder::CheckConsistency1", &pair);
|
||||
#endif
|
||||
if (!level_nonzero_cmp_(f1, f2)) {
|
||||
fprintf(stderr, "L%d files are not sorted properly", level);
|
||||
return Status::Corruption("L" + NumberToString(level) +
|
||||
" files are not sorted properly");
|
||||
}
|
||||
@@ -328,9 +348,6 @@ class VersionBuilder::Rep {
|
||||
// Make sure there is no overlap in levels > 0
|
||||
if (vstorage->InternalComparator()->Compare(f1->largest,
|
||||
f2->smallest) >= 0) {
|
||||
fprintf(stderr, "L%d have overlapping ranges %s vs. %s\n", level,
|
||||
(f1->largest).DebugString(true).c_str(),
|
||||
(f2->smallest).DebugString(true).c_str());
|
||||
return Status::Corruption(
|
||||
"L" + NumberToString(level) + " have overlapping ranges " +
|
||||
(f1->largest).DebugString(true) + " vs. " +
|
||||
@@ -343,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;
|
||||
@@ -445,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());
|
||||
|
||||
@@ -478,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;
|
||||
@@ -485,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();
|
||||
|
||||
@@ -529,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;
|
||||
@@ -552,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()) {
|
||||
@@ -590,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;
|
||||
}
|
||||
@@ -619,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());
|
||||
|
||||
@@ -633,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);
|
||||
}
|
||||
}
|
||||
@@ -769,7 +865,8 @@ class VersionBuilder::Rep {
|
||||
Status LoadTableHandlers(InternalStats* internal_stats, int max_threads,
|
||||
bool prefetch_index_and_filter_in_cache,
|
||||
bool is_initial_load,
|
||||
const SliceTransform* prefix_extractor) {
|
||||
const SliceTransform* prefix_extractor,
|
||||
size_t max_file_size_for_l0_meta_pin) {
|
||||
assert(table_cache_ != nullptr);
|
||||
|
||||
size_t table_cache_capacity = table_cache_->get_cache()->GetCapacity();
|
||||
@@ -838,7 +935,7 @@ class VersionBuilder::Rep {
|
||||
file_meta->fd, &file_meta->table_reader_handle, prefix_extractor,
|
||||
false /*no_io */, true /* record_read_stats */,
|
||||
internal_stats->GetFileReadHist(level), false, level,
|
||||
prefetch_index_and_filter_in_cache);
|
||||
prefetch_index_and_filter_in_cache, max_file_size_for_l0_meta_pin);
|
||||
if (file_meta->table_reader_handle != nullptr) {
|
||||
// Load table_reader
|
||||
file_meta->fd.table_reader = table_cache_->GetTableReaderFromHandle(
|
||||
@@ -864,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);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -897,10 +1010,11 @@ Status VersionBuilder::SaveTo(VersionStorageInfo* vstorage) {
|
||||
Status VersionBuilder::LoadTableHandlers(
|
||||
InternalStats* internal_stats, int max_threads,
|
||||
bool prefetch_index_and_filter_in_cache, bool is_initial_load,
|
||||
const SliceTransform* prefix_extractor) {
|
||||
return rep_->LoadTableHandlers(internal_stats, max_threads,
|
||||
prefetch_index_and_filter_in_cache,
|
||||
is_initial_load, prefix_extractor);
|
||||
const SliceTransform* prefix_extractor,
|
||||
size_t max_file_size_for_l0_meta_pin) {
|
||||
return rep_->LoadTableHandlers(
|
||||
internal_stats, max_threads, prefetch_index_and_filter_in_cache,
|
||||
is_initial_load, prefix_extractor, max_file_size_for_l0_meta_pin);
|
||||
}
|
||||
|
||||
BaseReferencedVersionBuilder::BaseReferencedVersionBuilder(
|
||||
|
||||
@@ -42,7 +42,8 @@ class VersionBuilder {
|
||||
Status LoadTableHandlers(InternalStats* internal_stats, int max_threads,
|
||||
bool prefetch_index_and_filter_in_cache,
|
||||
bool is_initial_load,
|
||||
const SliceTransform* prefix_extractor);
|
||||
const SliceTransform* prefix_extractor,
|
||||
size_t max_file_size_for_l0_meta_pin);
|
||||
|
||||
private:
|
||||
class Rep;
|
||||
|
||||
+262
-26
@@ -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));
|
||||
}
|
||||
@@ -422,8 +427,19 @@ TEST_F(VersionBuilderTest, ApplyFileDeletionAndAddition) {
|
||||
constexpr uint64_t file_number = 2345;
|
||||
constexpr char smallest[] = "bar";
|
||||
constexpr char largest[] = "foo";
|
||||
constexpr uint64_t file_size = 10000;
|
||||
constexpr uint32_t path_id = 0;
|
||||
constexpr SequenceNumber smallest_seq = 100;
|
||||
constexpr SequenceNumber largest_seq = 500;
|
||||
constexpr uint64_t num_entries = 0;
|
||||
constexpr uint64_t num_deletions = 0;
|
||||
constexpr bool sampled = false;
|
||||
constexpr SequenceNumber smallest_seqno = 1;
|
||||
constexpr SequenceNumber largest_seqno = 1000;
|
||||
|
||||
Add(level, file_number, smallest, largest);
|
||||
Add(level, file_number, smallest, largest, file_size, path_id, smallest_seq,
|
||||
largest_seq, num_entries, num_deletions, sampled, smallest_seqno,
|
||||
largest_seqno);
|
||||
|
||||
EnvOptions env_options;
|
||||
constexpr TableCache* table_cache = nullptr;
|
||||
@@ -440,18 +456,14 @@ TEST_F(VersionBuilderTest, ApplyFileDeletionAndAddition) {
|
||||
|
||||
VersionEdit addition;
|
||||
|
||||
constexpr uint32_t path_id = 0;
|
||||
constexpr uint64_t file_size = 10000;
|
||||
constexpr SequenceNumber smallest_seqno = 100;
|
||||
constexpr SequenceNumber largest_seqno = 1000;
|
||||
constexpr bool marked_for_compaction = false;
|
||||
|
||||
addition.AddFile(level, file_number, path_id, file_size,
|
||||
GetInternalKey(smallest), GetInternalKey(largest),
|
||||
smallest_seqno, largest_seqno, marked_for_compaction,
|
||||
kInvalidBlobFileNumber, kUnknownOldestAncesterTime,
|
||||
kUnknownFileCreationTime, kUnknownFileChecksum,
|
||||
kUnknownFileChecksumFuncName);
|
||||
GetInternalKey(smallest, smallest_seq),
|
||||
GetInternalKey(largest, largest_seq), smallest_seqno,
|
||||
largest_seqno, marked_for_compaction, kInvalidBlobFileNumber,
|
||||
kUnknownOldestAncesterTime, kUnknownFileCreationTime,
|
||||
kUnknownFileChecksum, kUnknownFileChecksumFuncName);
|
||||
|
||||
ASSERT_OK(builder.Apply(&addition));
|
||||
|
||||
@@ -635,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);
|
||||
}
|
||||
@@ -651,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;
|
||||
@@ -710,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);
|
||||
@@ -752,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(),
|
||||
@@ -809,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);
|
||||
}
|
||||
@@ -849,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;
|
||||
@@ -939,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();
|
||||
@@ -988,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,
|
||||
@@ -1002,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();
|
||||
@@ -1022,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);
|
||||
}
|
||||
@@ -1042,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();
|
||||
@@ -1068,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();
|
||||
|
||||
@@ -18,10 +18,6 @@
|
||||
#include "util/string_util.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
// The unknown file checksum.
|
||||
const std::string kUnknownFileChecksum("");
|
||||
// The unknown sst file checksum function name.
|
||||
const std::string kUnknownFileChecksumFuncName("Unknown");
|
||||
|
||||
namespace {
|
||||
|
||||
|
||||
@@ -29,9 +29,6 @@ constexpr uint64_t kFileNumberMask = 0x3FFFFFFFFFFFFFFF;
|
||||
constexpr uint64_t kUnknownOldestAncesterTime = 0;
|
||||
constexpr uint64_t kUnknownFileCreationTime = 0;
|
||||
|
||||
extern const std::string kUnknownFileChecksum;
|
||||
extern const std::string kUnknownFileChecksumFuncName;
|
||||
|
||||
extern uint64_t PackFileNumberAndPathId(uint64_t number, uint64_t path_id);
|
||||
|
||||
// A copyable structure contains information needed to read data from an SST
|
||||
|
||||
@@ -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,17 +411,17 @@ 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,
|
||||
cfd->GetLatestMutableCFOptions()->prefix_extractor.get());
|
||||
cfd->GetLatestMutableCFOptions()->prefix_extractor.get(),
|
||||
MaxFileSizeForL0MetaPin(*cfd->GetLatestMutableCFOptions()));
|
||||
if ((s.IsPathNotFound() || s.IsCorruption()) &&
|
||||
no_error_if_table_files_missing_) {
|
||||
s = Status::OK();
|
||||
@@ -550,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_ ||
|
||||
@@ -557,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_; }
|
||||
|
||||
|
||||
+112
-73
@@ -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"
|
||||
@@ -365,6 +368,7 @@ class FilePickerMultiGet {
|
||||
range_(range),
|
||||
batch_iter_(range->begin()),
|
||||
batch_iter_prev_(range->begin()),
|
||||
upper_key_(range->begin()),
|
||||
maybe_repeat_key_(false),
|
||||
current_level_range_(*range, range->begin(), range->end()),
|
||||
current_file_range_(*range, range->begin(), range->end()),
|
||||
@@ -481,9 +485,19 @@ class FilePickerMultiGet {
|
||||
}
|
||||
if (cmp_largest == 0) {
|
||||
// cmp_largest is 0, which means the next key will not be in this
|
||||
// file, so stop looking further. Also don't increment megt_iter_
|
||||
// as we may have to look for this key in the next file if we don't
|
||||
// find it in this one
|
||||
// file, so stop looking further. However, its possible there are
|
||||
// duplicates in the batch, so find the upper bound for the batch
|
||||
// in this file (upper_key_) by skipping past the duplicates. We
|
||||
// leave batch_iter_ as is since we may have to pick up from there
|
||||
// for the next file, if this file has a merge value rather than
|
||||
// final value
|
||||
upper_key_ = batch_iter_;
|
||||
++upper_key_;
|
||||
while (upper_key_ != current_level_range_.end() &&
|
||||
user_comparator_->Compare(batch_iter_->ukey, upper_key_->ukey) ==
|
||||
0) {
|
||||
++upper_key_;
|
||||
}
|
||||
break;
|
||||
} else {
|
||||
if (curr_level_ == 0) {
|
||||
@@ -503,6 +517,12 @@ class FilePickerMultiGet {
|
||||
*fd = f;
|
||||
*file_index = curr_file_index;
|
||||
*is_last_key_in_file = cmp_largest == 0;
|
||||
if (!*is_last_key_in_file) {
|
||||
// If the largest key in the batch overlapping the file is not the
|
||||
// largest key in the file, upper_ley_ would not have been updated so
|
||||
// update it here
|
||||
upper_key_ = batch_iter_;
|
||||
}
|
||||
return file_hit;
|
||||
}
|
||||
|
||||
@@ -524,7 +544,7 @@ class FilePickerMultiGet {
|
||||
// file regardless for all keys not found yet
|
||||
if (current_level_range_.CheckKeyDone(batch_iter_) ||
|
||||
curr_level_ == 0) {
|
||||
++batch_iter_;
|
||||
batch_iter_ = upper_key_;
|
||||
}
|
||||
}
|
||||
// batch_iter_prev_ will become the start key for the next file
|
||||
@@ -544,18 +564,20 @@ class FilePickerMultiGet {
|
||||
&is_last_key_in_file)) {
|
||||
search_ended_ = !PrepareNextLevel();
|
||||
} else {
|
||||
MultiGetRange::Iterator upper_key = batch_iter_;
|
||||
if (is_last_key_in_file) {
|
||||
// Since cmp_largest is 0, batch_iter_ still points to the last key
|
||||
// that falls in this file, instead of the next one. Increment
|
||||
// upper_key so we can set the range properly for SST MultiGet
|
||||
++upper_key;
|
||||
++(fp_ctx_array_[batch_iter_.index()].curr_index_in_curr_level);
|
||||
// the file index for all keys between batch_iter_ and upper_key_
|
||||
auto tmp_iter = batch_iter_;
|
||||
while (tmp_iter != upper_key_) {
|
||||
++(fp_ctx_array_[tmp_iter.index()].curr_index_in_curr_level);
|
||||
++tmp_iter;
|
||||
}
|
||||
maybe_repeat_key_ = true;
|
||||
}
|
||||
// Set the range for this file
|
||||
current_file_range_ =
|
||||
MultiGetRange(next_file_range, batch_iter_prev_, upper_key);
|
||||
MultiGetRange(next_file_range, batch_iter_prev_, upper_key_);
|
||||
returned_file_level_ = curr_level_;
|
||||
hit_file_level_ = curr_level_;
|
||||
is_hit_file_last_in_level_ =
|
||||
@@ -607,6 +629,7 @@ class FilePickerMultiGet {
|
||||
// key found in the previous SST file, in order to serve as the start of
|
||||
// the batch key range for the next SST file
|
||||
MultiGetRange::Iterator batch_iter_prev_;
|
||||
MultiGetRange::Iterator upper_key_;
|
||||
bool maybe_repeat_key_;
|
||||
MultiGetRange current_level_range_;
|
||||
MultiGetRange current_file_range_;
|
||||
@@ -626,7 +649,7 @@ class FilePickerMultiGet {
|
||||
if (fp_ctx_array_[mget_iter.index()].curr_index_in_curr_level <
|
||||
curr_file_level_->num_files) {
|
||||
batch_iter_prev_ = current_level_range_.begin();
|
||||
batch_iter_ = current_level_range_.begin();
|
||||
upper_key_ = batch_iter_ = current_level_range_.begin();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -721,7 +744,7 @@ class FilePickerMultiGet {
|
||||
}
|
||||
if (level_contains_keys) {
|
||||
batch_iter_prev_ = current_level_range_.begin();
|
||||
batch_iter_ = current_level_range_.begin();
|
||||
upper_key_ = batch_iter_ = current_level_range_.begin();
|
||||
return true;
|
||||
}
|
||||
curr_level_++;
|
||||
@@ -984,7 +1007,8 @@ class LevelIterator final : public InternalIterator {
|
||||
read_options_, file_options_, icomparator_, *file_meta.file_metadata,
|
||||
range_del_agg_, prefix_extractor_,
|
||||
nullptr /* don't need reference to table */, file_read_hist_, caller_,
|
||||
/*arena=*/nullptr, skip_filters_, level_, smallest_compaction_key,
|
||||
/*arena=*/nullptr, skip_filters_, level_,
|
||||
/*max_file_size_for_l0_meta_pin=*/0, smallest_compaction_key,
|
||||
largest_compaction_key, allow_unprepared_value_);
|
||||
}
|
||||
|
||||
@@ -1579,10 +1603,9 @@ void Version::AddIteratorsForLevel(const ReadOptions& read_options,
|
||||
mutable_cf_options_.prefix_extractor.get(), nullptr,
|
||||
cfd_->internal_stats()->GetFileReadHist(0),
|
||||
TableReaderCaller::kUserIterator, arena,
|
||||
/*skip_filters=*/false, /*level=*/0,
|
||||
/*skip_filters=*/false, /*level=*/0, max_file_size_for_l0_meta_pin_,
|
||||
/*smallest_compaction_key=*/nullptr,
|
||||
/*largest_compaction_key=*/nullptr,
|
||||
allow_unprepared_value));
|
||||
/*largest_compaction_key=*/nullptr, allow_unprepared_value));
|
||||
}
|
||||
if (should_sample) {
|
||||
// Count ones for every L0 files. This is done per iterator creation
|
||||
@@ -1604,8 +1627,8 @@ void Version::AddIteratorsForLevel(const ReadOptions& read_options,
|
||||
mutable_cf_options_.prefix_extractor.get(), should_sample_file_read(),
|
||||
cfd_->internal_stats()->GetFileReadHist(level),
|
||||
TableReaderCaller::kUserIterator, IsFilterSkipped(level), level,
|
||||
range_del_agg, /*largest_compaction_key=*/nullptr,
|
||||
allow_unprepared_value));
|
||||
range_del_agg,
|
||||
/*compaction_boundaries=*/nullptr, allow_unprepared_value));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1639,7 +1662,7 @@ Status Version::OverlapWithLevelIterator(const ReadOptions& read_options,
|
||||
mutable_cf_options_.prefix_extractor.get(), nullptr,
|
||||
cfd_->internal_stats()->GetFileReadHist(0),
|
||||
TableReaderCaller::kUserIterator, &arena,
|
||||
/*skip_filters=*/false, /*level=*/0,
|
||||
/*skip_filters=*/false, /*level=*/0, max_file_size_for_l0_meta_pin_,
|
||||
/*smallest_compaction_key=*/nullptr,
|
||||
/*largest_compaction_key=*/nullptr,
|
||||
/*allow_unprepared_value=*/false));
|
||||
@@ -1743,6 +1766,8 @@ Version::Version(ColumnFamilyData* column_family_data, VersionSet* vset,
|
||||
refs_(0),
|
||||
file_options_(file_opt),
|
||||
mutable_cf_options_(mutable_cf_options),
|
||||
max_file_size_for_l0_meta_pin_(
|
||||
MaxFileSizeForL0MetaPin(mutable_cf_options_)),
|
||||
version_number_(version_number) {}
|
||||
|
||||
void Version::Get(const ReadOptions& read_options, const LookupKey& k,
|
||||
@@ -1806,7 +1831,7 @@ void Version::Get(const ReadOptions& read_options, const LookupKey& k,
|
||||
cfd_->internal_stats()->GetFileReadHist(fp.GetHitFileLevel()),
|
||||
IsFilterSkipped(static_cast<int>(fp.GetHitFileLevel()),
|
||||
fp.IsHitFileLastInLevel()),
|
||||
fp.GetHitFileLevel());
|
||||
fp.GetHitFileLevel(), max_file_size_for_l0_meta_pin_);
|
||||
// TODO: examine the behavior for corrupted key
|
||||
if (timer_enabled) {
|
||||
PERF_COUNTER_BY_LEVEL_ADD(get_from_table_nanos, timer.ElapsedNanos(),
|
||||
@@ -3853,10 +3878,6 @@ Status VersionSet::ProcessManifestWrites(
|
||||
}
|
||||
#endif // NDEBUG
|
||||
|
||||
uint64_t new_manifest_file_size = 0;
|
||||
Status s;
|
||||
IOStatus io_s;
|
||||
|
||||
assert(pending_manifest_file_number_ == 0);
|
||||
if (!descriptor_log_ ||
|
||||
manifest_file_size_ > db_options_->max_manifest_file_size) {
|
||||
@@ -3886,6 +3907,9 @@ Status VersionSet::ProcessManifestWrites(
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t new_manifest_file_size = 0;
|
||||
Status s;
|
||||
IOStatus io_s;
|
||||
{
|
||||
FileOptions opt_file_opts = fs_->OptimizeForManifestWrite(file_options_);
|
||||
mu->Unlock();
|
||||
@@ -3902,7 +3926,8 @@ Status VersionSet::ProcessManifestWrites(
|
||||
cfd->internal_stats(), 1 /* max_threads */,
|
||||
true /* prefetch_index_and_filter_in_cache */,
|
||||
false /* is_initial_load */,
|
||||
mutable_cf_options_ptrs[i]->prefix_extractor.get());
|
||||
mutable_cf_options_ptrs[i]->prefix_extractor.get(),
|
||||
MaxFileSizeForL0MetaPin(*mutable_cf_options_ptrs[i]));
|
||||
if (!s.ok()) {
|
||||
if (db_options_->paranoid_checks) {
|
||||
break;
|
||||
@@ -3921,9 +3946,9 @@ Status VersionSet::ProcessManifestWrites(
|
||||
std::string descriptor_fname =
|
||||
DescriptorFileName(dbname_, pending_manifest_file_number_);
|
||||
std::unique_ptr<FSWritableFile> descriptor_file;
|
||||
s = NewWritableFile(fs_, descriptor_fname, &descriptor_file,
|
||||
opt_file_opts);
|
||||
if (s.ok()) {
|
||||
io_s = NewWritableFile(fs_, descriptor_fname, &descriptor_file,
|
||||
opt_file_opts);
|
||||
if (io_s.ok()) {
|
||||
descriptor_file->SetPreallocationBlockSize(
|
||||
db_options_->manifest_preallocation_size);
|
||||
|
||||
@@ -3932,7 +3957,10 @@ Status VersionSet::ProcessManifestWrites(
|
||||
nullptr, db_options_->listeners));
|
||||
descriptor_log_.reset(
|
||||
new log::Writer(std::move(file_writer), 0, false));
|
||||
s = WriteCurrentStateToManifest(curr_state, descriptor_log_.get());
|
||||
s = WriteCurrentStateToManifest(curr_state, descriptor_log_.get(),
|
||||
io_s);
|
||||
} else {
|
||||
s = io_s;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3968,16 +3996,16 @@ Status VersionSet::ProcessManifestWrites(
|
||||
#endif /* !NDEBUG */
|
||||
io_s = descriptor_log_->AddRecord(record);
|
||||
if (!io_s.ok()) {
|
||||
io_status_ = io_s;
|
||||
s = io_s;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (s.ok()) {
|
||||
io_s = SyncManifest(env_, db_options_, descriptor_log_->file());
|
||||
TEST_SYNC_POINT_CALLBACK(
|
||||
"VersionSet::ProcessManifestWrites:AfterSyncManifest", &io_s);
|
||||
}
|
||||
if (!io_s.ok()) {
|
||||
io_status_ = io_s;
|
||||
s = io_s;
|
||||
ROCKS_LOG_ERROR(db_options_->info_log, "MANIFEST write %s\n",
|
||||
s.ToString().c_str());
|
||||
@@ -3990,7 +4018,6 @@ Status VersionSet::ProcessManifestWrites(
|
||||
io_s = SetCurrentFile(fs_, dbname_, pending_manifest_file_number_,
|
||||
db_directory);
|
||||
if (!io_s.ok()) {
|
||||
io_status_ = io_s;
|
||||
s = io_s;
|
||||
}
|
||||
TEST_SYNC_POINT("VersionSet::ProcessManifestWrites:AfterNewManifest");
|
||||
@@ -4012,6 +4039,14 @@ Status VersionSet::ProcessManifestWrites(
|
||||
mu->Lock();
|
||||
}
|
||||
|
||||
if (!io_s.ok()) {
|
||||
if (io_status_.ok()) {
|
||||
io_status_ = io_s;
|
||||
}
|
||||
} else if (!io_status_.ok()) {
|
||||
io_status_ = io_s;
|
||||
}
|
||||
|
||||
// Append the old manifest file to the obsolete_manifest_ list to be deleted
|
||||
// by PurgeObsoleteFiles later.
|
||||
if (s.ok() && new_descriptor_log) {
|
||||
@@ -4421,24 +4456,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()) {
|
||||
@@ -4482,6 +4519,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();
|
||||
@@ -4528,8 +4568,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);
|
||||
@@ -4551,12 +4590,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);
|
||||
@@ -4629,7 +4669,8 @@ Status VersionSet::Recover(
|
||||
cfd->internal_stats(), db_options_->max_file_opening_threads,
|
||||
false /* prefetch_index_and_filter_in_cache */,
|
||||
true /* is_initial_load */,
|
||||
cfd->GetLatestMutableCFOptions()->prefix_extractor.get());
|
||||
cfd->GetLatestMutableCFOptions()->prefix_extractor.get(),
|
||||
MaxFileSizeForL0MetaPin(*cfd->GetLatestMutableCFOptions()));
|
||||
if (!s.ok()) {
|
||||
if (db_options_->paranoid_checks) {
|
||||
return s;
|
||||
@@ -4821,21 +4862,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,
|
||||
@@ -5260,7 +5300,7 @@ void VersionSet::MarkMinLogNumberToKeep2PC(uint64_t number) {
|
||||
|
||||
Status VersionSet::WriteCurrentStateToManifest(
|
||||
const std::unordered_map<uint32_t, MutableCFState>& curr_state,
|
||||
log::Writer* log) {
|
||||
log::Writer* log, IOStatus& io_s) {
|
||||
// TODO: Break up into multiple records to reduce memory usage on recovery?
|
||||
|
||||
// WARNING: This method doesn't hold a mutex!!
|
||||
@@ -5269,6 +5309,7 @@ Status VersionSet::WriteCurrentStateToManifest(
|
||||
// LogAndApply. Column family manipulations can only happen within LogAndApply
|
||||
// (the same single thread), so we're safe to iterate.
|
||||
|
||||
assert(io_s.ok());
|
||||
if (db_options_->write_dbid_to_manifest) {
|
||||
VersionEdit edit_for_db_id;
|
||||
assert(!db_id_.empty());
|
||||
@@ -5278,10 +5319,9 @@ Status VersionSet::WriteCurrentStateToManifest(
|
||||
return Status::Corruption("Unable to Encode VersionEdit:" +
|
||||
edit_for_db_id.DebugString(true));
|
||||
}
|
||||
IOStatus io_s = log->AddRecord(db_id_record);
|
||||
io_s = log->AddRecord(db_id_record);
|
||||
if (!io_s.ok()) {
|
||||
io_status_ = io_s;
|
||||
return std::move(io_s);
|
||||
return io_s;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5308,10 +5348,9 @@ Status VersionSet::WriteCurrentStateToManifest(
|
||||
return Status::Corruption(
|
||||
"Unable to Encode VersionEdit:" + edit.DebugString(true));
|
||||
}
|
||||
IOStatus io_s = log->AddRecord(record);
|
||||
io_s = log->AddRecord(record);
|
||||
if (!io_s.ok()) {
|
||||
io_status_ = io_s;
|
||||
return std::move(io_s);
|
||||
return io_s;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5361,10 +5400,9 @@ Status VersionSet::WriteCurrentStateToManifest(
|
||||
return Status::Corruption(
|
||||
"Unable to Encode VersionEdit:" + edit.DebugString(true));
|
||||
}
|
||||
IOStatus io_s = log->AddRecord(record);
|
||||
io_s = log->AddRecord(record);
|
||||
if (!io_s.ok()) {
|
||||
io_status_ = io_s;
|
||||
return std::move(io_s);
|
||||
return io_s;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5666,13 +5704,14 @@ InternalIterator* VersionSet::MakeInputIterator(
|
||||
for (size_t i = 0; i < flevel->num_files; i++) {
|
||||
list[num++] = cfd->table_cache()->NewIterator(
|
||||
read_options, file_options_compactions,
|
||||
cfd->internal_comparator(),
|
||||
*flevel->files[i].file_metadata, range_del_agg,
|
||||
c->mutable_cf_options()->prefix_extractor.get(),
|
||||
cfd->internal_comparator(), *flevel->files[i].file_metadata,
|
||||
range_del_agg, c->mutable_cf_options()->prefix_extractor.get(),
|
||||
/*table_reader_ptr=*/nullptr,
|
||||
/*file_read_hist=*/nullptr, TableReaderCaller::kCompaction,
|
||||
/*arena=*/nullptr,
|
||||
/*skip_filters=*/false, /*level=*/static_cast<int>(c->level(which)),
|
||||
/*skip_filters=*/false,
|
||||
/*level=*/static_cast<int>(c->level(which)),
|
||||
MaxFileSizeForL0MetaPin(*c->mutable_cf_options()),
|
||||
/*smallest_compaction_key=*/nullptr,
|
||||
/*largest_compaction_key=*/nullptr,
|
||||
/*allow_unprepared_value=*/false);
|
||||
@@ -5955,8 +5994,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>(
|
||||
@@ -5964,7 +6002,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();
|
||||
@@ -5973,10 +6011,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_ &&
|
||||
@@ -6003,7 +6040,8 @@ Status ReactiveVersionSet::Recover(
|
||||
cfd->internal_stats(), db_options_->max_file_opening_threads,
|
||||
false /* prefetch_index_and_filter_in_cache */,
|
||||
true /* is_initial_load */,
|
||||
cfd->GetLatestMutableCFOptions()->prefix_extractor.get());
|
||||
cfd->GetLatestMutableCFOptions()->prefix_extractor.get(),
|
||||
MaxFileSizeForL0MetaPin(*cfd->GetLatestMutableCFOptions()));
|
||||
if (!s.ok()) {
|
||||
enough = false;
|
||||
if (s.IsPathNotFound()) {
|
||||
@@ -6279,7 +6317,8 @@ Status ReactiveVersionSet::ApplyOneVersionEditToBuilder(
|
||||
cfd->internal_stats(), db_options_->max_file_opening_threads,
|
||||
false /* prefetch_index_and_filter_in_cache */,
|
||||
false /* is_initial_load */,
|
||||
cfd->GetLatestMutableCFOptions()->prefix_extractor.get());
|
||||
cfd->GetLatestMutableCFOptions()->prefix_extractor.get(),
|
||||
MaxFileSizeForL0MetaPin(*cfd->GetLatestMutableCFOptions()));
|
||||
TEST_SYNC_POINT_CALLBACK(
|
||||
"ReactiveVersionSet::ApplyOneVersionEditToBuilder:"
|
||||
"AfterLoadTableHandlers",
|
||||
|
||||
+26
-10
@@ -324,6 +324,17 @@ class VersionStorageInfo {
|
||||
return it->second;
|
||||
}
|
||||
|
||||
// REQUIRES: This version has been saved (see VersionSet::SaveTo)
|
||||
FileMetaData* GetFileMetaDataByNumber(uint64_t file_number) const {
|
||||
auto location = GetFileLocation(file_number);
|
||||
|
||||
if (!location.IsValid()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return files_[location.GetLevel()][location.GetPosition()];
|
||||
}
|
||||
|
||||
// REQUIRES: This version has been saved (see VersionSet::SaveTo)
|
||||
using BlobFiles = std::map<uint64_t, std::shared_ptr<BlobFileMetaData>>;
|
||||
const BlobFiles& GetBlobFiles() const { return blob_files_; }
|
||||
@@ -792,6 +803,8 @@ class Version {
|
||||
int refs_; // Number of live refs to this version
|
||||
const FileOptions file_options_;
|
||||
const MutableCFOptions mutable_cf_options_;
|
||||
// Cached value to avoid recomputing it on every read.
|
||||
const size_t max_file_size_for_l0_meta_pin_;
|
||||
|
||||
// A version number that uniquely represents this version. This is
|
||||
// used for debugging and logging purposes only.
|
||||
@@ -1146,12 +1159,13 @@ class VersionSet {
|
||||
static uint64_t GetTotalSstFilesSize(Version* dummy_versions);
|
||||
|
||||
// Get the IO Status returned by written Manifest.
|
||||
IOStatus io_status() const { return io_status_; }
|
||||
|
||||
// Set the IO Status to OK. Called before Manifest write if needed.
|
||||
void SetIOStatusOK() { io_status_ = IOStatus::OK(); }
|
||||
const IOStatus& io_status() const { return io_status_; }
|
||||
|
||||
protected:
|
||||
using VersionBuilderMap =
|
||||
std::unordered_map<uint32_t,
|
||||
std::unique_ptr<BaseReferencedVersionBuilder>>;
|
||||
|
||||
struct ManifestWriter;
|
||||
|
||||
friend class Version;
|
||||
@@ -1163,7 +1177,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;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1186,7 +1202,7 @@ class VersionSet {
|
||||
// Save current contents to *log
|
||||
Status WriteCurrentStateToManifest(
|
||||
const std::unordered_map<uint32_t, MutableCFState>& curr_state,
|
||||
log::Writer* log);
|
||||
log::Writer* log, IOStatus& io_s);
|
||||
|
||||
void AppendVersion(ColumnFamilyData* column_family_data, Version* v);
|
||||
|
||||
@@ -1194,13 +1210,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(
|
||||
@@ -1329,8 +1346,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.
|
||||
|
||||
@@ -421,7 +421,7 @@ TEST_F(VersionStorageInfoTest, GetOverlappingInputs) {
|
||||
1, {"i", 0, kTypeValue}, {"j", 0, kTypeValue}));
|
||||
}
|
||||
|
||||
TEST_F(VersionStorageInfoTest, FileLocation) {
|
||||
TEST_F(VersionStorageInfoTest, FileLocationAndMetaDataByNumber) {
|
||||
Add(0, 11U, "1", "2", 5000U);
|
||||
Add(0, 12U, "1", "2", 5000U);
|
||||
|
||||
@@ -429,13 +429,18 @@ TEST_F(VersionStorageInfoTest, FileLocation) {
|
||||
|
||||
ASSERT_EQ(vstorage_.GetFileLocation(11U),
|
||||
VersionStorageInfo::FileLocation(0, 0));
|
||||
ASSERT_NE(vstorage_.GetFileMetaDataByNumber(11U), nullptr);
|
||||
|
||||
ASSERT_EQ(vstorage_.GetFileLocation(12U),
|
||||
VersionStorageInfo::FileLocation(0, 1));
|
||||
ASSERT_NE(vstorage_.GetFileMetaDataByNumber(12U), nullptr);
|
||||
|
||||
ASSERT_EQ(vstorage_.GetFileLocation(7U),
|
||||
VersionStorageInfo::FileLocation(2, 0));
|
||||
ASSERT_NE(vstorage_.GetFileMetaDataByNumber(7U), nullptr);
|
||||
|
||||
ASSERT_FALSE(vstorage_.GetFileLocation(999U).IsValid());
|
||||
ASSERT_EQ(vstorage_.GetFileMetaDataByNumber(999U), nullptr);
|
||||
}
|
||||
|
||||
class VersionStorageInfoTimestampTest : public VersionStorageInfoTestBase {
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
#include "file/delete_scheduler.h"
|
||||
|
||||
#include <cinttypes>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
@@ -65,6 +66,12 @@ Status DeleteScheduler::DeleteFile(const std::string& file_path,
|
||||
s = fs_->DeleteFile(file_path, IOOptions(), nullptr);
|
||||
if (s.ok()) {
|
||||
sst_file_manager_->OnDeleteFile(file_path);
|
||||
ROCKS_LOG_INFO(info_log_,
|
||||
"Deleted file %s immediately, rate_bytes_per_sec %" PRIi64
|
||||
", total_trash_size %" PRIu64 " max_trash_db_ratio %lf",
|
||||
file_path.c_str(), rate_bytes_per_sec_.load(),
|
||||
total_trash_size_.load(), max_trash_db_ratio_.load());
|
||||
RecordTick(stats_.get(), FILES_DELETED_IMMEDIATELY);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
@@ -72,6 +79,8 @@ Status DeleteScheduler::DeleteFile(const std::string& file_path,
|
||||
// Move file to trash
|
||||
std::string trash_file;
|
||||
s = MarkAsTrash(file_path, &trash_file);
|
||||
ROCKS_LOG_INFO(info_log_, "Mark file: %s as trash -- %s", trash_file.c_str(),
|
||||
s.ToString().c_str());
|
||||
|
||||
if (!s.ok()) {
|
||||
ROCKS_LOG_ERROR(info_log_, "Failed to mark %s as trash -- %s",
|
||||
@@ -79,10 +88,14 @@ Status DeleteScheduler::DeleteFile(const std::string& file_path,
|
||||
s = fs_->DeleteFile(file_path, IOOptions(), nullptr);
|
||||
if (s.ok()) {
|
||||
sst_file_manager_->OnDeleteFile(file_path);
|
||||
ROCKS_LOG_INFO(info_log_, "Deleted file %s immediately",
|
||||
trash_file.c_str());
|
||||
RecordTick(stats_.get(), FILES_DELETED_IMMEDIATELY);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
RecordTick(stats_.get(), FILES_MARKED_TRASH);
|
||||
// Update the total trash size
|
||||
uint64_t trash_file_size = 0;
|
||||
fs_->GetFileSize(trash_file, IOOptions(), &trash_file_size, nullptr);
|
||||
@@ -210,6 +223,8 @@ void DeleteScheduler::BackgroundEmptyTrash() {
|
||||
current_delete_rate = rate_bytes_per_sec_.load();
|
||||
start_time = env_->NowMicros();
|
||||
total_deleted_bytes = 0;
|
||||
ROCKS_LOG_INFO(info_log_, "rate_bytes_per_sec is changed to %" PRIi64,
|
||||
current_delete_rate);
|
||||
}
|
||||
|
||||
// Get new file to delete
|
||||
@@ -233,19 +248,27 @@ void DeleteScheduler::BackgroundEmptyTrash() {
|
||||
bg_errors_[path_in_trash] = s;
|
||||
}
|
||||
|
||||
// Apply penlty if necessary
|
||||
uint64_t total_penlty;
|
||||
// Apply penalty if necessary
|
||||
uint64_t total_penalty;
|
||||
if (current_delete_rate > 0) {
|
||||
// rate limiting is enabled
|
||||
total_penlty =
|
||||
total_penalty =
|
||||
((total_deleted_bytes * kMicrosInSecond) / current_delete_rate);
|
||||
while (!closing_ && !cv_.TimedWait(start_time + total_penlty)) {}
|
||||
ROCKS_LOG_INFO(info_log_,
|
||||
"Rate limiting is enabled with penalty %" PRIu64
|
||||
"after deleting file %s",
|
||||
total_penalty, path_in_trash.c_str());
|
||||
while (!closing_ && !cv_.TimedWait(start_time + total_penalty)) {
|
||||
}
|
||||
} else {
|
||||
// rate limiting is disabled
|
||||
total_penlty = 0;
|
||||
total_penalty = 0;
|
||||
ROCKS_LOG_INFO(info_log_,
|
||||
"Rate limiting is disabled after deleting file %s",
|
||||
path_in_trash.c_str());
|
||||
}
|
||||
TEST_SYNC_POINT_CALLBACK("DeleteScheduler::BackgroundEmptyTrash:Wait",
|
||||
&total_penlty);
|
||||
&total_penalty);
|
||||
|
||||
if (is_complete) {
|
||||
pending_files_--;
|
||||
@@ -353,9 +376,13 @@ void DeleteScheduler::WaitForEmptyTrash() {
|
||||
}
|
||||
|
||||
void DeleteScheduler::MaybeCreateBackgroundThread() {
|
||||
if(bg_thread_ == nullptr && rate_bytes_per_sec_.load() > 0) {
|
||||
if (bg_thread_ == nullptr && rate_bytes_per_sec_.load() > 0) {
|
||||
bg_thread_.reset(
|
||||
new port::Thread(&DeleteScheduler::BackgroundEmptyTrash, this));
|
||||
ROCKS_LOG_INFO(info_log_,
|
||||
"Created background thread for deletion scheduler with "
|
||||
"rate_bytes_per_sec: %" PRIi64,
|
||||
rate_bytes_per_sec_.load());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -83,6 +83,10 @@ class DeleteScheduler {
|
||||
static Status CleanupDirectory(Env* env, SstFileManagerImpl* sfm,
|
||||
const std::string& path);
|
||||
|
||||
void SetStatisticsPtr(const std::shared_ptr<Statistics>& stats) {
|
||||
stats_ = stats;
|
||||
}
|
||||
|
||||
private:
|
||||
Status MarkAsTrash(const std::string& file_path, std::string* path_in_trash);
|
||||
|
||||
@@ -137,6 +141,7 @@ class DeleteScheduler {
|
||||
// immediately
|
||||
std::atomic<double> max_trash_db_ratio_;
|
||||
static const uint64_t kMicrosInSecond = 1000 * 1000LL;
|
||||
std::shared_ptr<Statistics> stats_;
|
||||
};
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
@@ -32,6 +32,7 @@ class DeleteSchedulerTest : public testing::Test {
|
||||
ToString(i));
|
||||
DestroyAndCreateDir(dummy_files_dirs_.back());
|
||||
}
|
||||
stats_ = ROCKSDB_NAMESPACE::CreateDBStatistics();
|
||||
}
|
||||
|
||||
~DeleteSchedulerTest() override {
|
||||
@@ -99,6 +100,7 @@ class DeleteSchedulerTest : public testing::Test {
|
||||
new SstFileManagerImpl(env_, fs, nullptr, rate_bytes_per_sec_,
|
||||
/* max_trash_db_ratio= */ 1.1, 128 * 1024));
|
||||
delete_scheduler_ = sst_file_mgr_->delete_scheduler();
|
||||
sst_file_mgr_->SetStatisticsPtr(stats_);
|
||||
}
|
||||
|
||||
Env* env_;
|
||||
@@ -106,6 +108,7 @@ class DeleteSchedulerTest : public testing::Test {
|
||||
int64_t rate_bytes_per_sec_;
|
||||
DeleteScheduler* delete_scheduler_;
|
||||
std::unique_ptr<SstFileManagerImpl> sst_file_mgr_;
|
||||
std::shared_ptr<Statistics> stats_;
|
||||
};
|
||||
|
||||
// Test the basic functionality of DeleteScheduler (Rate Limiting).
|
||||
@@ -182,6 +185,8 @@ TEST_F(DeleteSchedulerTest, BasicRateLimiting) {
|
||||
ASSERT_EQ(num_files, dir_synced);
|
||||
|
||||
ASSERT_EQ(CountTrashFiles(), 0);
|
||||
ASSERT_EQ(num_files, stats_->getAndResetTickerCount(FILES_MARKED_TRASH));
|
||||
ASSERT_EQ(0, stats_->getAndResetTickerCount(FILES_DELETED_IMMEDIATELY));
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
}
|
||||
@@ -219,6 +224,9 @@ TEST_F(DeleteSchedulerTest, MultiDirectoryDeletionsScheduled) {
|
||||
ASSERT_EQ(0, CountTrashFiles(i));
|
||||
}
|
||||
|
||||
ASSERT_EQ(kNumFiles, stats_->getAndResetTickerCount(FILES_MARKED_TRASH));
|
||||
ASSERT_EQ(0, stats_->getAndResetTickerCount(FILES_DELETED_IMMEDIATELY));
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
|
||||
@@ -301,6 +309,10 @@ TEST_F(DeleteSchedulerTest, RateLimitingMultiThreaded) {
|
||||
|
||||
ASSERT_EQ(CountNormalFiles(), 0);
|
||||
ASSERT_EQ(CountTrashFiles(), 0);
|
||||
ASSERT_EQ(num_files * thread_cnt,
|
||||
stats_->getAndResetTickerCount(FILES_MARKED_TRASH));
|
||||
ASSERT_EQ(0, stats_->getAndResetTickerCount(FILES_DELETED_IMMEDIATELY));
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
}
|
||||
@@ -318,8 +330,9 @@ TEST_F(DeleteSchedulerTest, DisableRateLimiting) {
|
||||
|
||||
rate_bytes_per_sec_ = 0;
|
||||
NewDeleteScheduler();
|
||||
constexpr int num_files = 10;
|
||||
|
||||
for (int i = 0; i < 10; i++) {
|
||||
for (int i = 0; i < num_files; i++) {
|
||||
// Every file we delete will be deleted immediately
|
||||
std::string dummy_file = NewDummyFile("dummy.data");
|
||||
ASSERT_OK(delete_scheduler_->DeleteFile(dummy_file, ""));
|
||||
@@ -329,6 +342,9 @@ TEST_F(DeleteSchedulerTest, DisableRateLimiting) {
|
||||
}
|
||||
|
||||
ASSERT_EQ(bg_delete_file, 0);
|
||||
ASSERT_EQ(0, stats_->getAndResetTickerCount(FILES_MARKED_TRASH));
|
||||
ASSERT_EQ(num_files,
|
||||
stats_->getAndResetTickerCount(FILES_DELETED_IMMEDIATELY));
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
@@ -365,6 +381,8 @@ TEST_F(DeleteSchedulerTest, ConflictNames) {
|
||||
|
||||
auto bg_errors = delete_scheduler_->GetBackgroundErrors();
|
||||
ASSERT_EQ(bg_errors.size(), 0);
|
||||
ASSERT_EQ(10, stats_->getAndResetTickerCount(FILES_MARKED_TRASH));
|
||||
ASSERT_EQ(0, stats_->getAndResetTickerCount(FILES_DELETED_IMMEDIATELY));
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
@@ -439,9 +457,12 @@ TEST_F(DeleteSchedulerTest, StartBGEmptyTrashMultipleTimes) {
|
||||
|
||||
auto bg_errors = delete_scheduler_->GetBackgroundErrors();
|
||||
ASSERT_EQ(bg_errors.size(), 0);
|
||||
ASSERT_EQ(10, stats_->getAndResetTickerCount(FILES_MARKED_TRASH));
|
||||
ASSERT_EQ(0, stats_->getAndResetTickerCount(FILES_DELETED_IMMEDIATELY));
|
||||
}
|
||||
|
||||
ASSERT_EQ(bg_delete_file, 50);
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
|
||||
}
|
||||
|
||||
@@ -653,6 +674,8 @@ TEST_F(DeleteSchedulerTest, ImmediateDeleteOn25PercDBSize) {
|
||||
// When we end up with 26 files in trash we will start
|
||||
// deleting new files immediately
|
||||
ASSERT_EQ(fg_delete_file, 74);
|
||||
ASSERT_EQ(26, stats_->getAndResetTickerCount(FILES_MARKED_TRASH));
|
||||
ASSERT_EQ(74, stats_->getAndResetTickerCount(FILES_DELETED_IMMEDIATELY));
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -135,6 +135,11 @@ class SstFileManagerImpl : public SstFileManager {
|
||||
// once in the object's lifetime, and before the destructor
|
||||
void Close();
|
||||
|
||||
void SetStatisticsPtr(const std::shared_ptr<Statistics>& stats) override {
|
||||
stats_ = stats;
|
||||
delete_scheduler_.SetStatisticsPtr(stats);
|
||||
}
|
||||
|
||||
private:
|
||||
// REQUIRES: mutex locked
|
||||
void OnAddFileImpl(const std::string& file_path, uint64_t file_size,
|
||||
@@ -190,6 +195,7 @@ class SstFileManagerImpl : public SstFileManager {
|
||||
std::list<ErrorHandler*> error_handler_list_;
|
||||
// Pointer to ErrorHandler instance that is currently processing recovery
|
||||
ErrorHandler* cur_instance_;
|
||||
std::shared_ptr<Statistics> stats_;
|
||||
};
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
@@ -236,7 +236,7 @@ const char* WritableFileWriter::GetFileChecksumFuncName() const {
|
||||
if (checksum_generator_ != nullptr) {
|
||||
return checksum_generator_->Name();
|
||||
} else {
|
||||
return kUnknownFileChecksumFuncName.c_str();
|
||||
return kUnknownFileChecksumFuncName;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
@@ -1260,8 +1266,6 @@ class DB {
|
||||
// updated, false if user attempted to call if with seqnum <= current value.
|
||||
virtual bool SetPreserveDeletesSequenceNumber(SequenceNumber seqnum) = 0;
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
|
||||
// Prevent file deletions. Compactions will continue to occur,
|
||||
// but no obsolete files will be deleted. Calling this multiple
|
||||
// times have the same effect as calling it once.
|
||||
@@ -1278,6 +1282,7 @@ class DB {
|
||||
// threads call EnableFileDeletions()
|
||||
virtual Status EnableFileDeletions(bool force = true) = 0;
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
// GetLiveFiles followed by GetSortedWalFiles can generate a lossless backup
|
||||
|
||||
// Retrieve the list of all files in the database. The files are
|
||||
|
||||
@@ -18,6 +18,11 @@
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
// The unknown file checksum.
|
||||
constexpr char kUnknownFileChecksum[] = "";
|
||||
// The unknown sst file checksum function name.
|
||||
constexpr char kUnknownFileChecksumFuncName[] = "Unknown";
|
||||
|
||||
struct FileChecksumGenContext {
|
||||
std::string file_name;
|
||||
};
|
||||
|
||||
@@ -117,6 +117,7 @@ enum class BackgroundErrorReason {
|
||||
kCompaction,
|
||||
kWriteCallback,
|
||||
kMemTable,
|
||||
kManifestWrite,
|
||||
};
|
||||
|
||||
enum class WriteStallCondition {
|
||||
|
||||
@@ -1571,11 +1571,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 you are sure although the key ranges may overlap,
|
||||
// the memtable and the file being ingested
|
||||
// do not have the same keys, or not care about that.
|
||||
// The memtable flushing may block for hundreds of milliseconds.
|
||||
bool skip_memtable_flush = false;
|
||||
// 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 {
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
class SSTDumpTool {
|
||||
public:
|
||||
int Run(int argc, char** argv, Options options = Options());
|
||||
int Run(int argc, char const* const* argv, Options options = Options());
|
||||
};
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include <vector>
|
||||
|
||||
#include "rocksdb/file_system.h"
|
||||
#include "rocksdb/statistics.h"
|
||||
#include "rocksdb/status.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
@@ -80,6 +81,9 @@ class SstFileManager {
|
||||
// Return the total size of trash files
|
||||
// thread-safe
|
||||
virtual uint64_t GetTotalTrashSize() = 0;
|
||||
|
||||
// Set the statistics ptr to dump the stat information
|
||||
virtual void SetStatisticsPtr(const std::shared_ptr<Statistics>& stats) = 0;
|
||||
};
|
||||
|
||||
// Create a new SstFileManager that can be shared among multiple RocksDB
|
||||
|
||||
@@ -360,6 +360,12 @@ enum Tickers : uint32_t {
|
||||
// <= BLOCK_CACHE_COMPRESSION_DICT_ADD
|
||||
BLOCK_CACHE_COMPRESSION_DICT_ADD_REDUNDANT,
|
||||
|
||||
// # of files marked as trash by sst file manager and will be deleted
|
||||
// later by background thread.
|
||||
FILES_MARKED_TRASH,
|
||||
// # of files deleted immediately by sst file manger through delete scheduler.
|
||||
FILES_DELETED_IMMEDIATELY,
|
||||
|
||||
TICKER_ENUM_MAX
|
||||
};
|
||||
|
||||
|
||||
@@ -78,7 +78,7 @@ class LDBCommand {
|
||||
SelectCommand);
|
||||
|
||||
static LDBCommand* InitFromCmdLineArgs(
|
||||
int argc, char** argv, const Options& options,
|
||||
int argc, char const* const* argv, const Options& options,
|
||||
const LDBOptions& ldb_options,
|
||||
const std::vector<ColumnFamilyDescriptor>* column_families);
|
||||
|
||||
@@ -269,11 +269,13 @@ class LDBCommand {
|
||||
|
||||
class LDBCommandRunner {
|
||||
public:
|
||||
static void PrintHelp(const LDBOptions& ldb_options, const char* exec_name);
|
||||
static void PrintHelp(const LDBOptions& ldb_options, const char* exec_name,
|
||||
bool to_stderr = true);
|
||||
|
||||
// Returns the status code to return. 0 is no error.
|
||||
static int RunCommand(
|
||||
int argc, char** argv, Options options, const LDBOptions& ldb_options,
|
||||
int argc, char const* const* argv, Options options,
|
||||
const LDBOptions& ldb_options,
|
||||
const std::vector<ColumnFamilyDescriptor>* column_families);
|
||||
};
|
||||
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
#pragma once
|
||||
|
||||
#define ROCKSDB_MAJOR 6
|
||||
#define ROCKSDB_MINOR 10
|
||||
#define ROCKSDB_PATCH 0
|
||||
#define ROCKSDB_MINOR 11
|
||||
#define ROCKSDB_PATCH 7
|
||||
|
||||
// Do not use these. We made the mistake of declaring macros starting with
|
||||
// double underscore. Now we have to live with our choice. We'll deprecate these
|
||||
|
||||
+12
-3
@@ -1292,9 +1292,9 @@ class ByteBufferJni : public JavaClass {
|
||||
return constructWith(env, direct, nullptr, capacity, jbytebuffer_clazz);
|
||||
}
|
||||
|
||||
static jobject constructWith(
|
||||
JNIEnv* env, const bool direct, const char* buf, const size_t capacity,
|
||||
jclass jbytebuffer_clazz = nullptr) {
|
||||
static jobject constructWith(JNIEnv* env, const bool direct, const char* buf,
|
||||
const size_t capacity,
|
||||
jclass jbytebuffer_clazz = nullptr) {
|
||||
if (direct) {
|
||||
bool allocated = false;
|
||||
if (buf == nullptr) {
|
||||
@@ -4945,6 +4945,11 @@ class TickerTypeJni {
|
||||
return -0x0C;
|
||||
case ROCKSDB_NAMESPACE::Tickers::TXN_GET_TRY_AGAIN:
|
||||
return -0x0D;
|
||||
case ROCKSDB_NAMESPACE::Tickers::FILES_MARKED_TRASH:
|
||||
return -0x0E;
|
||||
case ROCKSDB_NAMESPACE::Tickers::FILES_DELETED_IMMEDIATELY:
|
||||
return -0X0F;
|
||||
|
||||
case ROCKSDB_NAMESPACE::Tickers::TICKER_ENUM_MAX:
|
||||
// 0x5F for backwards compatibility on current minor version.
|
||||
return 0x5F;
|
||||
@@ -5240,6 +5245,10 @@ class TickerTypeJni {
|
||||
return ROCKSDB_NAMESPACE::Tickers::TXN_SNAPSHOT_MUTEX_OVERHEAD;
|
||||
case -0x0D:
|
||||
return ROCKSDB_NAMESPACE::Tickers::TXN_GET_TRY_AGAIN;
|
||||
case -0x0E:
|
||||
return ROCKSDB_NAMESPACE::Tickers::FILES_MARKED_TRASH;
|
||||
case -0x0F:
|
||||
return ROCKSDB_NAMESPACE::Tickers::FILES_DELETED_IMMEDIATELY;
|
||||
case 0x5F:
|
||||
// 0x5F for backwards compatibility on current minor version.
|
||||
return ROCKSDB_NAMESPACE::Tickers::TICKER_ENUM_MAX;
|
||||
|
||||
@@ -722,6 +722,16 @@ public enum TickerType {
|
||||
*/
|
||||
TXN_GET_TRY_AGAIN((byte) -0x0D),
|
||||
|
||||
/**
|
||||
* # of files marked as trash by delete scheduler
|
||||
*/
|
||||
FILES_MARKED_TRASH((byte) -0x0E),
|
||||
|
||||
/**
|
||||
* # of files deleted immediately by delete scheduler
|
||||
*/
|
||||
FILES_DELETED_IMMEDIATELY((byte) -0x0f),
|
||||
|
||||
TICKER_ENUM_MAX((byte) 0x5F);
|
||||
|
||||
private final byte value;
|
||||
|
||||
@@ -184,6 +184,8 @@ const std::vector<std::pair<Tickers, std::string>> TickersNameMap = {
|
||||
{BLOCK_CACHE_DATA_ADD_REDUNDANT, "rocksdb.block.cache.data.add.redundant"},
|
||||
{BLOCK_CACHE_COMPRESSION_DICT_ADD_REDUNDANT,
|
||||
"rocksdb.block.cache.compression.dict.add.redundant"},
|
||||
{FILES_MARKED_TRASH, "rocksdb.files.marked.trash"},
|
||||
{FILES_DELETED_IMMEDIATELY, "rocksdb.files.deleted.immediately"},
|
||||
};
|
||||
|
||||
const std::vector<std::pair<Histograms, std::string>> HistogramsNameMap = {
|
||||
|
||||
@@ -720,6 +720,17 @@ uint64_t MaxFileSizeForLevel(const MutableCFOptions& cf_options,
|
||||
}
|
||||
}
|
||||
|
||||
size_t MaxFileSizeForL0MetaPin(const MutableCFOptions& cf_options) {
|
||||
// We do not want to pin meta-blocks that almost certainly came from intra-L0
|
||||
// or a former larger `write_buffer_size` value to avoid surprising users with
|
||||
// pinned memory usage. We use a factor of 1.5 to account for overhead
|
||||
// introduced during flush in most cases.
|
||||
if (port::kMaxSizet / 3 < cf_options.write_buffer_size / 2) {
|
||||
return port::kMaxSizet;
|
||||
}
|
||||
return cf_options.write_buffer_size / 2 * 3;
|
||||
}
|
||||
|
||||
void MutableCFOptions::RefreshDerivedOptions(int num_levels,
|
||||
CompactionStyle compaction_style) {
|
||||
max_file_size.resize(num_levels);
|
||||
|
||||
@@ -268,4 +268,9 @@ uint64_t MultiplyCheckOverflow(uint64_t op1, double op2);
|
||||
uint64_t MaxFileSizeForLevel(const MutableCFOptions& cf_options,
|
||||
int level, CompactionStyle compaction_style, int base_level = 1,
|
||||
bool level_compaction_dynamic_level_bytes = false);
|
||||
|
||||
// Get the max size of an L0 file for which we will pin its meta-blocks when
|
||||
// `pin_l0_filter_and_index_blocks_in_cache` is set.
|
||||
size_t MaxFileSizeForL0MetaPin(const MutableCFOptions& cf_options);
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
@@ -7,7 +7,6 @@
|
||||
|
||||
#include <cinttypes>
|
||||
|
||||
#include "db/version_edit.h"
|
||||
#include "logging/logging.h"
|
||||
#include "options/options_helper.h"
|
||||
#include "port/port.h"
|
||||
@@ -616,9 +615,8 @@ void ImmutableDBOptions::Dump(Logger* log) const {
|
||||
log, " Options.log_readahead_size: %" ROCKSDB_PRIszt,
|
||||
log_readahead_size);
|
||||
ROCKS_LOG_HEADER(log, " Options.file_checksum_gen_factory: %s",
|
||||
file_checksum_gen_factory
|
||||
? file_checksum_gen_factory->Name()
|
||||
: kUnknownFileChecksumFuncName.c_str());
|
||||
file_checksum_gen_factory ? file_checksum_gen_factory->Name()
|
||||
: kUnknownFileChecksumFuncName);
|
||||
ROCKS_LOG_HEADER(log, " Options.best_efforts_recovery: %d",
|
||||
static_cast<int>(best_efforts_recovery));
|
||||
}
|
||||
|
||||
+14
-6
@@ -647,7 +647,12 @@ TEST_F(OptionsTest, GetBlockBasedTableOptionsFromString) {
|
||||
"block_cache=1M;block_cache_compressed=1k;block_size=1024;"
|
||||
"block_size_deviation=8;block_restart_interval=4;"
|
||||
"format_version=5;whole_key_filtering=1;"
|
||||
"filter_policy=bloomfilter:4.567:false;",
|
||||
"filter_policy=bloomfilter:4.567:false;"
|
||||
// A bug caused read_amp_bytes_per_bit to be a large integer in OPTIONS
|
||||
// file generated by 6.10 to 6.14. Though bug is fixed in these releases,
|
||||
// we need to handle the case of loading OPTIONS file generated before the
|
||||
// fix.
|
||||
"read_amp_bytes_per_bit=17179869185;",
|
||||
&new_opt));
|
||||
ASSERT_TRUE(new_opt.cache_index_and_filter_blocks);
|
||||
ASSERT_EQ(new_opt.index_type, BlockBasedTableOptions::kHashSearch);
|
||||
@@ -668,6 +673,9 @@ TEST_F(OptionsTest, GetBlockBasedTableOptionsFromString) {
|
||||
dynamic_cast<const BloomFilterPolicy&>(*new_opt.filter_policy);
|
||||
EXPECT_EQ(bfp.GetMillibitsPerKey(), 4567);
|
||||
EXPECT_EQ(bfp.GetWholeBitsPerKey(), 5);
|
||||
// Verify that only the lower 32bits are stored in
|
||||
// new_opt.read_amp_bytes_per_bit.
|
||||
EXPECT_EQ(1U, new_opt.read_amp_bytes_per_bit);
|
||||
|
||||
// unknown option
|
||||
ASSERT_NOK(GetBlockBasedTableOptionsFromString(
|
||||
@@ -1279,7 +1287,7 @@ TEST_F(OptionsTest, ConvertOptionsTest) {
|
||||
// This test suite tests the old APIs into the Configure options methods.
|
||||
// Once those APIs are officially deprecated, this test suite can be deleted.
|
||||
class OptionsOldApiTest : public testing::Test {};
|
||||
|
||||
|
||||
TEST_F(OptionsOldApiTest, GetOptionsFromMapTest) {
|
||||
std::unordered_map<std::string, std::string> cf_options_map = {
|
||||
{"write_buffer_size", "1"},
|
||||
@@ -1744,7 +1752,7 @@ TEST_F(OptionsOldApiTest, GetColumnFamilyOptionsFromStringTest) {
|
||||
ASSERT_TRUE(new_cf_opt.memtable_factory != nullptr);
|
||||
ASSERT_EQ(std::string(new_cf_opt.memtable_factory->Name()), "SkipListFactory");
|
||||
}
|
||||
|
||||
|
||||
TEST_F(OptionsOldApiTest, GetBlockBasedTableOptionsFromString) {
|
||||
BlockBasedTableOptions table_opt;
|
||||
BlockBasedTableOptions new_opt;
|
||||
@@ -1919,7 +1927,7 @@ TEST_F(OptionsOldApiTest, GetBlockBasedTableOptionsFromString) {
|
||||
->GetHighPriPoolRatio(),
|
||||
0.5);
|
||||
}
|
||||
|
||||
|
||||
TEST_F(OptionsOldApiTest, GetPlainTableOptionsFromString) {
|
||||
PlainTableOptions table_opt;
|
||||
PlainTableOptions new_opt;
|
||||
@@ -1950,7 +1958,7 @@ TEST_F(OptionsOldApiTest, GetPlainTableOptionsFromString) {
|
||||
"encoding_type=kPrefixXX",
|
||||
&new_opt));
|
||||
}
|
||||
|
||||
|
||||
TEST_F(OptionsOldApiTest, GetOptionsFromStringTest) {
|
||||
Options base_options, new_options;
|
||||
base_options.write_buffer_size = 20;
|
||||
@@ -2508,7 +2516,7 @@ TEST_F(OptionsParserTest, Readahead) {
|
||||
uint64_t file_size = 0;
|
||||
ASSERT_OK(env_->GetFileSize(kOptionsFileName, &file_size));
|
||||
assert(file_size > 0);
|
||||
|
||||
|
||||
RocksDBOptionsParser parser;
|
||||
|
||||
env_->num_seq_file_read_ = 0;
|
||||
|
||||
@@ -8,6 +8,9 @@ LIB_SOURCES = \
|
||||
db/blob/blob_file_addition.cc \
|
||||
db/blob/blob_file_garbage.cc \
|
||||
db/blob/blob_file_meta.cc \
|
||||
db/blob/blob_log_format.cc \
|
||||
db/blob/blob_log_reader.cc \
|
||||
db/blob/blob_log_writer.cc \
|
||||
db/builder.cc \
|
||||
db/c.cc \
|
||||
db/column_family.cc \
|
||||
@@ -198,9 +201,6 @@ LIB_SOURCES = \
|
||||
utilities/blob_db/blob_db_impl.cc \
|
||||
utilities/blob_db/blob_db_impl_filesnapshot.cc \
|
||||
utilities/blob_db/blob_file.cc \
|
||||
utilities/blob_db/blob_log_format.cc \
|
||||
utilities/blob_db/blob_log_reader.cc \
|
||||
utilities/blob_db/blob_log_writer.cc \
|
||||
utilities/cassandra/cassandra_compaction_filter.cc \
|
||||
utilities/cassandra/format.cc \
|
||||
utilities/cassandra/merge_operator.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> {
|
||||
|
||||
@@ -1781,7 +1781,7 @@ const char* BlockBasedTableBuilder::GetFileChecksumFuncName() const {
|
||||
if (rep_->file != nullptr) {
|
||||
return rep_->file->GetFileChecksumFuncName();
|
||||
} else {
|
||||
return kUnknownFileChecksumFuncName.c_str();
|
||||
return kUnknownFileChecksumFuncName;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -327,8 +327,24 @@ static std::unordered_map<std::string, OptionTypeInfo>
|
||||
OptionTypeFlags::kNone, 0}},
|
||||
{"read_amp_bytes_per_bit",
|
||||
{offsetof(struct BlockBasedTableOptions, read_amp_bytes_per_bit),
|
||||
OptionType::kSizeT, OptionVerificationType::kNormal,
|
||||
OptionTypeFlags::kNone, 0}},
|
||||
OptionType::kUInt32T, OptionVerificationType::kNormal,
|
||||
OptionTypeFlags::kNone, 0,
|
||||
[](const ConfigOptions& /*opts*/, const std::string& /*name*/,
|
||||
const std::string& value, char* addr) {
|
||||
// A workaround to fix a bug in 6.10, 6.11, 6.12, 6.13
|
||||
// and 6.14. The bug will write out 8 bytes to OPTIONS file from the
|
||||
// starting address of BlockBasedTableOptions.read_amp_bytes_per_bit
|
||||
// which is actually a uint32. Consequently, the value of
|
||||
// read_amp_bytes_per_bit written in the OPTIONS file is wrong.
|
||||
// From 6.15, RocksDB will try to parse the read_amp_bytes_per_bit
|
||||
// from OPTIONS file as a uint32. To be able to load OPTIONS file
|
||||
// generated by affected releases before the fix, we need to
|
||||
// manually parse read_amp_bytes_per_bit with this special hack.
|
||||
uint64_t read_amp_bytes_per_bit = ParseUint64(value);
|
||||
*(reinterpret_cast<uint32_t*>(addr)) =
|
||||
static_cast<uint32_t>(read_amp_bytes_per_bit);
|
||||
return Status::OK();
|
||||
}}},
|
||||
{"enable_index_compression",
|
||||
{offsetof(struct BlockBasedTableOptions, enable_index_compression),
|
||||
OptionType::kBoolean, OptionVerificationType::kNormal,
|
||||
@@ -420,7 +436,8 @@ Status BlockBasedTableFactory::NewTableReader(
|
||||
table_reader_options.level, table_reader_options.immortal,
|
||||
table_reader_options.largest_seqno,
|
||||
table_reader_options.force_direct_prefetch, &tail_prefetch_stats_,
|
||||
table_reader_options.block_cache_tracer);
|
||||
table_reader_options.block_cache_tracer,
|
||||
table_reader_options.max_file_size_for_l0_meta_pin);
|
||||
}
|
||||
|
||||
TableBuilder* BlockBasedTableFactory::NewTableBuilder(
|
||||
|
||||
@@ -610,7 +610,8 @@ Status BlockBasedTable::Open(
|
||||
const int level, const bool immortal_table,
|
||||
const SequenceNumber largest_seqno, const bool force_direct_prefetch,
|
||||
TailPrefetchStats* tail_prefetch_stats,
|
||||
BlockCacheTracer* const block_cache_tracer) {
|
||||
BlockCacheTracer* const block_cache_tracer,
|
||||
size_t max_file_size_for_l0_meta_pin) {
|
||||
table_reader->reset();
|
||||
|
||||
Status s;
|
||||
@@ -706,7 +707,8 @@ Status BlockBasedTable::Open(
|
||||
}
|
||||
s = new_table->PrefetchIndexAndFilterBlocks(
|
||||
prefetch_buffer.get(), metaindex_iter.get(), new_table.get(),
|
||||
prefetch_all, table_options, level, &lookup_context);
|
||||
prefetch_all, table_options, level, file_size,
|
||||
max_file_size_for_l0_meta_pin, &lookup_context);
|
||||
|
||||
if (s.ok()) {
|
||||
// Update tail prefetch stats
|
||||
@@ -943,6 +945,7 @@ Status BlockBasedTable::PrefetchIndexAndFilterBlocks(
|
||||
FilePrefetchBuffer* prefetch_buffer, InternalIterator* meta_iter,
|
||||
BlockBasedTable* new_table, bool prefetch_all,
|
||||
const BlockBasedTableOptions& table_options, const int level,
|
||||
size_t file_size, size_t max_file_size_for_l0_meta_pin,
|
||||
BlockCacheLookupContext* lookup_context) {
|
||||
Status s;
|
||||
|
||||
@@ -987,9 +990,10 @@ Status BlockBasedTable::PrefetchIndexAndFilterBlocks(
|
||||
|
||||
const bool use_cache = table_options.cache_index_and_filter_blocks;
|
||||
|
||||
// pin both index and filters, down to all partitions
|
||||
// pin both index and filters, down to all partitions.
|
||||
const bool pin_all =
|
||||
rep_->table_options.pin_l0_filter_and_index_blocks_in_cache && level == 0;
|
||||
rep_->table_options.pin_l0_filter_and_index_blocks_in_cache &&
|
||||
level == 0 && file_size <= max_file_size_for_l0_meta_pin;
|
||||
|
||||
// prefetch the first level of index
|
||||
const bool prefetch_index =
|
||||
@@ -1422,10 +1426,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
|
||||
//
|
||||
@@ -2837,6 +2839,12 @@ Status BlockBasedTable::VerifyChecksumInBlocks(
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (s.ok()) {
|
||||
// In the case of two level indexes, we would have exited the above loop
|
||||
// by checking index_iter->Valid(), but Valid() might have returned false
|
||||
// due to an IO error. So check the index_iter status
|
||||
s = index_iter->status();
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
|
||||
@@ -101,7 +101,8 @@ class BlockBasedTable : public TableReader {
|
||||
const SequenceNumber largest_seqno = 0,
|
||||
bool force_direct_prefetch = false,
|
||||
TailPrefetchStats* tail_prefetch_stats = nullptr,
|
||||
BlockCacheTracer* const block_cache_tracer = nullptr);
|
||||
BlockCacheTracer* const block_cache_tracer = nullptr,
|
||||
size_t max_file_size_for_l0_meta_pin = 0);
|
||||
|
||||
bool PrefixMayMatch(const Slice& internal_key,
|
||||
const ReadOptions& read_options,
|
||||
@@ -255,6 +256,7 @@ class BlockBasedTable : public TableReader {
|
||||
|
||||
private:
|
||||
friend class MockedBlockBasedTable;
|
||||
friend class BlockBasedTableReaderTestVerifyChecksum_ChecksumMismatch_Test;
|
||||
static std::atomic<uint64_t> next_cache_key_id_;
|
||||
BlockCacheTracer* const block_cache_tracer_;
|
||||
|
||||
@@ -420,6 +422,7 @@ class BlockBasedTable : public TableReader {
|
||||
FilePrefetchBuffer* prefetch_buffer, InternalIterator* meta_iter,
|
||||
BlockBasedTable* new_table, bool prefetch_all,
|
||||
const BlockBasedTableOptions& table_options, const int level,
|
||||
size_t file_size, size_t max_file_size_for_l0_meta_pin,
|
||||
BlockCacheLookupContext* lookup_context);
|
||||
|
||||
static BlockType GetBlockTypeForMetaBlockByName(const Slice& meta_block_name);
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
// (found in the LICENSE.Apache file in the root directory).
|
||||
|
||||
#include "table/block_based/block_based_table_reader.h"
|
||||
#include "rocksdb/file_system.h"
|
||||
#include "table/block_based/partitioned_index_iterator.h"
|
||||
|
||||
#include "db/table_properties_collector.h"
|
||||
#include "options/options_helper.h"
|
||||
@@ -19,19 +21,29 @@ namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
class BlockBasedTableReaderTest
|
||||
: public testing::Test,
|
||||
public testing::WithParamInterface<std::tuple<CompressionType, bool>> {
|
||||
public testing::WithParamInterface<std::tuple<
|
||||
CompressionType, bool, BlockBasedTableOptions::IndexType, bool>> {
|
||||
protected:
|
||||
CompressionType compression_type_;
|
||||
bool use_direct_reads_;
|
||||
|
||||
void SetUp() override {
|
||||
std::tie(compression_type_, use_direct_reads_) = GetParam();
|
||||
BlockBasedTableOptions::IndexType index_type;
|
||||
bool no_block_cache;
|
||||
std::tie(compression_type_, use_direct_reads_, index_type, no_block_cache) =
|
||||
GetParam();
|
||||
|
||||
test::SetupSyncPointsToMockDirectIO();
|
||||
test_dir_ = test::PerThreadDBPath("block_based_table_reader_test");
|
||||
env_ = Env::Default();
|
||||
fs_ = FileSystem::Default();
|
||||
ASSERT_OK(fs_->CreateDir(test_dir_, IOOptions(), nullptr));
|
||||
|
||||
BlockBasedTableOptions opts;
|
||||
opts.index_type = index_type;
|
||||
opts.no_block_cache = no_block_cache;
|
||||
table_factory_.reset(
|
||||
static_cast<BlockBasedTableFactory*>(NewBlockBasedTableFactory(opts)));
|
||||
}
|
||||
|
||||
void TearDown() override { EXPECT_OK(test::DestroyDir(env_, test_dir_)); }
|
||||
@@ -50,7 +62,7 @@ class BlockBasedTableReaderTest
|
||||
ColumnFamilyOptions cf_options;
|
||||
MutableCFOptions moptions(cf_options);
|
||||
std::vector<std::unique_ptr<IntTblPropCollectorFactory>> factories;
|
||||
std::unique_ptr<TableBuilder> table_builder(table_factory_.NewTableBuilder(
|
||||
std::unique_ptr<TableBuilder> table_builder(table_factory_->NewTableBuilder(
|
||||
TableBuilderOptions(ioptions, moptions, comparator, &factories,
|
||||
compression_type, 0 /* sample_for_compression */,
|
||||
CompressionOptions(), false /* skip_filters */,
|
||||
@@ -79,19 +91,21 @@ class BlockBasedTableReaderTest
|
||||
|
||||
std::unique_ptr<TableReader> table_reader;
|
||||
ASSERT_OK(BlockBasedTable::Open(ioptions, EnvOptions(),
|
||||
table_factory_.table_options(), comparator,
|
||||
table_factory_->table_options(), comparator,
|
||||
std::move(file), file_size, &table_reader));
|
||||
|
||||
table->reset(reinterpret_cast<BlockBasedTable*>(table_reader.release()));
|
||||
}
|
||||
|
||||
std::string Path(const std::string& fname) { return test_dir_ + "/" + fname; }
|
||||
|
||||
const std::shared_ptr<FileSystem>& fs() const { return fs_; }
|
||||
|
||||
private:
|
||||
std::string test_dir_;
|
||||
Env* env_;
|
||||
std::shared_ptr<FileSystem> fs_;
|
||||
BlockBasedTableFactory table_factory_;
|
||||
|
||||
std::string Path(const std::string& fname) { return test_dir_ + "/" + fname; }
|
||||
std::unique_ptr<BlockBasedTableFactory> table_factory_;
|
||||
|
||||
void WriteToFile(const std::string& content, const std::string& filename) {
|
||||
std::unique_ptr<FSWritableFile> f;
|
||||
@@ -219,20 +233,104 @@ TEST_P(BlockBasedTableReaderTest, MultiGet) {
|
||||
}
|
||||
}
|
||||
|
||||
class BlockBasedTableReaderTestVerifyChecksum
|
||||
: public BlockBasedTableReaderTest {
|
||||
public:
|
||||
BlockBasedTableReaderTestVerifyChecksum() : BlockBasedTableReaderTest() {}
|
||||
};
|
||||
|
||||
TEST_P(BlockBasedTableReaderTestVerifyChecksum, ChecksumMismatch) {
|
||||
// Prepare key-value pairs to occupy multiple blocks.
|
||||
// Each value is 256B, every 16 pairs constitute 1 block.
|
||||
// Adjacent blocks contain values with different compression complexity:
|
||||
// human readable strings are easier to compress than random strings.
|
||||
Random rnd(101);
|
||||
std::map<std::string, std::string> kv;
|
||||
{
|
||||
uint32_t key = 0;
|
||||
for (int block = 0; block < 800; block++) {
|
||||
for (int i = 0; i < 16; i++) {
|
||||
char k[9] = {0};
|
||||
// Internal key is constructed directly from this key,
|
||||
// and internal key size is required to be >= 8 bytes,
|
||||
// so use %08u as the format string.
|
||||
sprintf(k, "%08u", key);
|
||||
std::string v;
|
||||
test::RandomString(&rnd, 256, &v);
|
||||
kv[std::string(k)] = v;
|
||||
key++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string table_name =
|
||||
"BlockBasedTableReaderTest" + CompressionTypeToString(compression_type_);
|
||||
CreateTable(table_name, compression_type_, kv);
|
||||
|
||||
std::unique_ptr<BlockBasedTable> table;
|
||||
Options options;
|
||||
ImmutableCFOptions ioptions(options);
|
||||
FileOptions foptions;
|
||||
foptions.use_direct_reads = use_direct_reads_;
|
||||
InternalKeyComparator comparator(options.comparator);
|
||||
NewBlockBasedTableReader(foptions, ioptions, comparator, table_name, &table);
|
||||
|
||||
// Use the top level iterator to find the offset/size of the first
|
||||
// 2nd level index block and corrupt the block
|
||||
IndexBlockIter iiter_on_stack;
|
||||
BlockCacheLookupContext context{TableReaderCaller::kUserVerifyChecksum};
|
||||
InternalIteratorBase<IndexValue>* iiter = table->NewIndexIterator(
|
||||
ReadOptions(), /*disable_prefix_seek=*/false, &iiter_on_stack,
|
||||
/*get_context=*/nullptr, &context);
|
||||
std::unique_ptr<InternalIteratorBase<IndexValue>> iiter_unique_ptr;
|
||||
if (iiter != &iiter_on_stack) {
|
||||
iiter_unique_ptr = std::unique_ptr<InternalIteratorBase<IndexValue>>(iiter);
|
||||
}
|
||||
ASSERT_OK(iiter->status());
|
||||
iiter->SeekToFirst();
|
||||
BlockHandle handle = static_cast<ParititionedIndexIterator*>(iiter)
|
||||
->index_iter_->value()
|
||||
.handle;
|
||||
table.reset();
|
||||
|
||||
// Corrupt the block pointed to by handle
|
||||
test::CorruptFile(Path(table_name), static_cast<int>(handle.offset()), 128);
|
||||
|
||||
NewBlockBasedTableReader(foptions, ioptions, comparator, table_name, &table);
|
||||
Status s = table->VerifyChecksum(ReadOptions(),
|
||||
TableReaderCaller::kUserVerifyChecksum);
|
||||
ASSERT_EQ(s.code(), Status::kCorruption);
|
||||
}
|
||||
|
||||
// Param 1: compression type
|
||||
// Param 2: whether to use direct reads
|
||||
// Param 3: Block Based Table Index type
|
||||
// Param 4: BBTO no_block_cache option
|
||||
#ifdef ROCKSDB_LITE
|
||||
// Skip direct I/O tests in lite mode since direct I/O is unsupported.
|
||||
INSTANTIATE_TEST_CASE_P(
|
||||
MultiGet, BlockBasedTableReaderTest,
|
||||
::testing::Combine(::testing::ValuesIn(GetSupportedCompressions()),
|
||||
::testing::Values(false)));
|
||||
::testing::Combine(
|
||||
::testing::ValuesIn(GetSupportedCompressions()),
|
||||
::testing::Values(false),
|
||||
::testing::Values(BlockBasedTableOptions::IndexType::kBinarySearch),
|
||||
::testing::Values(false)));
|
||||
#else // ROCKSDB_LITE
|
||||
INSTANTIATE_TEST_CASE_P(
|
||||
MultiGet, BlockBasedTableReaderTest,
|
||||
::testing::Combine(::testing::ValuesIn(GetSupportedCompressions()),
|
||||
::testing::Bool()));
|
||||
::testing::Combine(
|
||||
::testing::ValuesIn(GetSupportedCompressions()), ::testing::Bool(),
|
||||
::testing::Values(BlockBasedTableOptions::IndexType::kBinarySearch),
|
||||
::testing::Values(false)));
|
||||
#endif // ROCKSDB_LITE
|
||||
INSTANTIATE_TEST_CASE_P(
|
||||
VerifyChecksum, BlockBasedTableReaderTestVerifyChecksum,
|
||||
::testing::Combine(
|
||||
::testing::ValuesIn(GetSupportedCompressions()),
|
||||
::testing::Values(false),
|
||||
::testing::Values(
|
||||
BlockBasedTableOptions::IndexType::kTwoLevelIndexSearch),
|
||||
::testing::Values(true)));
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
|
||||
@@ -104,6 +104,15 @@ void PartitionedIndexBuilder::MakeNewSubIndexBuilder() {
|
||||
comparator_, table_opt_.index_block_restart_interval,
|
||||
table_opt_.format_version, use_value_delta_encoding_,
|
||||
table_opt_.index_shortening, /* include_first_key */ false);
|
||||
|
||||
// Set sub_index_builder_->seperator_is_key_plus_seq_ to true if
|
||||
// seperator_is_key_plus_seq_ is true (internal-key mode) (set to false by
|
||||
// default on Creation) so that flush policy can point to
|
||||
// sub_index_builder_->index_block_builder_
|
||||
if (seperator_is_key_plus_seq_) {
|
||||
sub_index_builder_->seperator_is_key_plus_seq_ = true;
|
||||
}
|
||||
|
||||
flush_policy_.reset(FlushBlockBySizePolicyFactory::NewFlushBlockPolicy(
|
||||
table_opt_.metadata_block_size, table_opt_.block_size_deviation,
|
||||
// Note: this is sub-optimal since sub_index_builder_ could later reset
|
||||
@@ -129,9 +138,15 @@ void PartitionedIndexBuilder::AddIndexEntry(
|
||||
}
|
||||
sub_index_builder_->AddIndexEntry(last_key_in_current_block,
|
||||
first_key_in_next_block, block_handle);
|
||||
if (sub_index_builder_->seperator_is_key_plus_seq_) {
|
||||
// then we need to apply it to all sub-index builders
|
||||
if (!seperator_is_key_plus_seq_ &&
|
||||
sub_index_builder_->seperator_is_key_plus_seq_) {
|
||||
// then we need to apply it to all sub-index builders and reset
|
||||
// flush_policy to point to Block Builder of sub_index_builder_ that store
|
||||
// internal keys.
|
||||
seperator_is_key_plus_seq_ = true;
|
||||
flush_policy_.reset(FlushBlockBySizePolicyFactory::NewFlushBlockPolicy(
|
||||
table_opt_.metadata_block_size, table_opt_.block_size_deviation,
|
||||
sub_index_builder_->index_block_builder_));
|
||||
}
|
||||
sub_index_last_key_ = std::string(*last_key_in_current_block);
|
||||
entries_.push_back(
|
||||
@@ -161,9 +176,15 @@ void PartitionedIndexBuilder::AddIndexEntry(
|
||||
sub_index_builder_->AddIndexEntry(last_key_in_current_block,
|
||||
first_key_in_next_block, block_handle);
|
||||
sub_index_last_key_ = std::string(*last_key_in_current_block);
|
||||
if (sub_index_builder_->seperator_is_key_plus_seq_) {
|
||||
// then we need to apply it to all sub-index builders
|
||||
if (!seperator_is_key_plus_seq_ &&
|
||||
sub_index_builder_->seperator_is_key_plus_seq_) {
|
||||
// then we need to apply it to all sub-index builders and reset
|
||||
// flush_policy to point to Block Builder of sub_index_builder_ that store
|
||||
// internal keys.
|
||||
seperator_is_key_plus_seq_ = true;
|
||||
flush_policy_.reset(FlushBlockBySizePolicyFactory::NewFlushBlockPolicy(
|
||||
table_opt_.metadata_block_size, table_opt_.block_size_deviation,
|
||||
sub_index_builder_->index_block_builder_));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,6 +122,7 @@ class ParititionedIndexIterator : public InternalIteratorBase<IndexValue> {
|
||||
}
|
||||
|
||||
private:
|
||||
friend class BlockBasedTableReaderTestVerifyChecksum_ChecksumMismatch_Test;
|
||||
const BlockBasedTable* table_;
|
||||
const ReadOptions read_options_;
|
||||
#ifndef NDEBUG
|
||||
|
||||
@@ -167,7 +167,7 @@ void PartitionIndexReader::CacheDependencies(bool pin) {
|
||||
|
||||
assert(s.ok() || block.GetValue() == nullptr);
|
||||
if (s.ok() && block.GetValue() != nullptr) {
|
||||
if (block.IsCached()) {
|
||||
if (block.IsCached() || block.GetOwnValue()) {
|
||||
if (pin) {
|
||||
partition_map_[handle.offset()] = std::move(block);
|
||||
}
|
||||
|
||||
@@ -528,7 +528,7 @@ const char* CuckooTableBuilder::GetFileChecksumFuncName() const {
|
||||
if (file_ != nullptr) {
|
||||
return file_->GetFileChecksumFuncName();
|
||||
} else {
|
||||
return kUnknownFileChecksumFuncName.c_str();
|
||||
return kUnknownFileChecksumFuncName;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -142,6 +142,11 @@ class IteratorWrapperBase {
|
||||
return result_.value_prepared;
|
||||
}
|
||||
|
||||
Slice user_key() const {
|
||||
assert(Valid());
|
||||
return iter_->user_key();
|
||||
}
|
||||
|
||||
private:
|
||||
void Update() {
|
||||
valid_ = iter_->Valid();
|
||||
|
||||
+1
-1
@@ -163,7 +163,7 @@ class MockTableBuilder : public TableBuilder {
|
||||
std::string GetFileChecksum() const override { return kUnknownFileChecksum; }
|
||||
// Get file checksum function name
|
||||
const char* GetFileChecksumFuncName() const override {
|
||||
return kUnknownFileChecksumFuncName.c_str();
|
||||
return kUnknownFileChecksumFuncName;
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
@@ -313,7 +313,7 @@ const char* PlainTableBuilder::GetFileChecksumFuncName() const {
|
||||
if (file_ != nullptr) {
|
||||
return file_->GetFileChecksumFuncName();
|
||||
} else {
|
||||
return kUnknownFileChecksumFuncName.c_str();
|
||||
return kUnknownFileChecksumFuncName;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+11
-4
@@ -34,11 +34,13 @@ struct TableReaderOptions {
|
||||
const InternalKeyComparator& _internal_comparator,
|
||||
bool _skip_filters = false, bool _immortal = false,
|
||||
bool _force_direct_prefetch = false, int _level = -1,
|
||||
BlockCacheTracer* const _block_cache_tracer = nullptr)
|
||||
BlockCacheTracer* const _block_cache_tracer = nullptr,
|
||||
size_t _max_file_size_for_l0_meta_pin = 0)
|
||||
: TableReaderOptions(_ioptions, _prefix_extractor, _env_options,
|
||||
_internal_comparator, _skip_filters, _immortal,
|
||||
_force_direct_prefetch, _level,
|
||||
0 /* _largest_seqno */, _block_cache_tracer) {}
|
||||
0 /* _largest_seqno */, _block_cache_tracer,
|
||||
_max_file_size_for_l0_meta_pin) {}
|
||||
|
||||
// @param skip_filters Disables loading/accessing the filter block
|
||||
TableReaderOptions(const ImmutableCFOptions& _ioptions,
|
||||
@@ -48,7 +50,8 @@ struct TableReaderOptions {
|
||||
bool _skip_filters, bool _immortal,
|
||||
bool _force_direct_prefetch, int _level,
|
||||
SequenceNumber _largest_seqno,
|
||||
BlockCacheTracer* const _block_cache_tracer)
|
||||
BlockCacheTracer* const _block_cache_tracer,
|
||||
size_t _max_file_size_for_l0_meta_pin)
|
||||
: ioptions(_ioptions),
|
||||
prefix_extractor(_prefix_extractor),
|
||||
env_options(_env_options),
|
||||
@@ -58,7 +61,8 @@ struct TableReaderOptions {
|
||||
force_direct_prefetch(_force_direct_prefetch),
|
||||
level(_level),
|
||||
largest_seqno(_largest_seqno),
|
||||
block_cache_tracer(_block_cache_tracer) {}
|
||||
block_cache_tracer(_block_cache_tracer),
|
||||
max_file_size_for_l0_meta_pin(_max_file_size_for_l0_meta_pin) {}
|
||||
|
||||
const ImmutableCFOptions& ioptions;
|
||||
const SliceTransform* prefix_extractor;
|
||||
@@ -77,6 +81,9 @@ struct TableReaderOptions {
|
||||
// largest seqno in the table
|
||||
SequenceNumber largest_seqno;
|
||||
BlockCacheTracer* const block_cache_tracer;
|
||||
// Largest L0 file size whose meta-blocks may be pinned (can be zero when
|
||||
// unknown).
|
||||
const size_t max_file_size_for_l0_meta_pin;
|
||||
};
|
||||
|
||||
struct TableBuilderOptions {
|
||||
|
||||
+6
-23
@@ -378,7 +378,8 @@ class TableConstructor: public Constructor {
|
||||
return ioptions.table_factory->NewTableReader(
|
||||
TableReaderOptions(ioptions, moptions.prefix_extractor.get(), soptions,
|
||||
internal_comparator, !kSkipFilters, !kImmortal,
|
||||
false, level_, largest_seqno_, &block_cache_tracer_),
|
||||
false, level_, largest_seqno_, &block_cache_tracer_,
|
||||
moptions.write_buffer_size),
|
||||
std::move(file_reader_), TEST_GetSink()->contents().size(),
|
||||
&table_reader_);
|
||||
}
|
||||
@@ -3265,17 +3266,9 @@ TEST_P(BlockBasedTableTest, NoFileChecksum) {
|
||||
BlockBasedTableOptions table_options = GetBlockBasedTableOptions();
|
||||
std::unique_ptr<InternalKeyComparator> comparator(
|
||||
new InternalKeyComparator(BytewiseComparator()));
|
||||
SequenceNumber largest_seqno = 0;
|
||||
int level = 0;
|
||||
std::vector<std::unique_ptr<IntTblPropCollectorFactory>>
|
||||
int_tbl_prop_collector_factories;
|
||||
|
||||
if (largest_seqno != 0) {
|
||||
// Pretend that it's an external file written by SstFileWriter.
|
||||
int_tbl_prop_collector_factories.emplace_back(
|
||||
new SstFileWriterPropertiesCollectorFactory(2 /* version */,
|
||||
0 /* global_seqno*/));
|
||||
}
|
||||
std::string column_family_name;
|
||||
|
||||
FileChecksumTestHelper f(true);
|
||||
@@ -3292,9 +3285,8 @@ TEST_P(BlockBasedTableTest, NoFileChecksum) {
|
||||
f.ResetTableBuilder(std::move(builder));
|
||||
f.AddKVtoKVMap(1000);
|
||||
f.WriteKVAndFlushTable();
|
||||
ASSERT_STREQ(f.GetFileChecksumFuncName(),
|
||||
kUnknownFileChecksumFuncName.c_str());
|
||||
ASSERT_STREQ(f.GetFileChecksum().c_str(), kUnknownFileChecksum.c_str());
|
||||
ASSERT_STREQ(f.GetFileChecksumFuncName(), kUnknownFileChecksumFuncName);
|
||||
ASSERT_STREQ(f.GetFileChecksum().c_str(), kUnknownFileChecksum);
|
||||
}
|
||||
|
||||
TEST_P(BlockBasedTableTest, Crc32cFileChecksum) {
|
||||
@@ -3307,17 +3299,9 @@ TEST_P(BlockBasedTableTest, Crc32cFileChecksum) {
|
||||
BlockBasedTableOptions table_options = GetBlockBasedTableOptions();
|
||||
std::unique_ptr<InternalKeyComparator> comparator(
|
||||
new InternalKeyComparator(BytewiseComparator()));
|
||||
SequenceNumber largest_seqno = 0;
|
||||
int level = 0;
|
||||
std::vector<std::unique_ptr<IntTblPropCollectorFactory>>
|
||||
int_tbl_prop_collector_factories;
|
||||
|
||||
if (largest_seqno != 0) {
|
||||
// Pretend that it's an external file written by SstFileWriter.
|
||||
int_tbl_prop_collector_factories.emplace_back(
|
||||
new SstFileWriterPropertiesCollectorFactory(2 /* version */,
|
||||
0 /* global_seqno*/));
|
||||
}
|
||||
std::string column_family_name;
|
||||
|
||||
FileChecksumGenContext gen_context;
|
||||
@@ -3446,9 +3430,8 @@ TEST_F(PlainTableTest, NoFileChecksum) {
|
||||
f.ResetTableBuilder(std::move(builder));
|
||||
f.AddKVtoKVMap(1000);
|
||||
f.WriteKVAndFlushTable();
|
||||
ASSERT_STREQ(f.GetFileChecksumFuncName(),
|
||||
kUnknownFileChecksumFuncName.c_str());
|
||||
EXPECT_EQ(f.GetFileChecksum(), kUnknownFileChecksum.c_str());
|
||||
ASSERT_STREQ(f.GetFileChecksumFuncName(), kUnknownFileChecksumFuncName);
|
||||
EXPECT_EQ(f.GetFileChecksum(), kUnknownFileChecksum);
|
||||
}
|
||||
|
||||
TEST_F(PlainTableTest, Crc32cFileChecksum) {
|
||||
|
||||
@@ -43,6 +43,10 @@ class TwoLevelIndexIterator : public InternalIteratorBase<IndexValue> {
|
||||
assert(Valid());
|
||||
return second_level_iter_.key();
|
||||
}
|
||||
Slice user_key() const override {
|
||||
assert(Valid());
|
||||
return second_level_iter_.user_key();
|
||||
}
|
||||
IndexValue value() const override {
|
||||
assert(Valid());
|
||||
return second_level_iter_.value();
|
||||
|
||||
+52
-1
@@ -10,6 +10,7 @@
|
||||
#include "test_util/testutil.h"
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <sys/stat.h>
|
||||
#include <array>
|
||||
#include <cctype>
|
||||
#include <fstream>
|
||||
@@ -464,7 +465,16 @@ Status DestroyDir(Env* env, const std::string& dir) {
|
||||
if (file_in_dir == "." || file_in_dir == "..") {
|
||||
continue;
|
||||
}
|
||||
s = env->DeleteFile(dir + "/" + file_in_dir);
|
||||
std::string path = dir + "/" + file_in_dir;
|
||||
bool is_dir = false;
|
||||
s = env->IsDirectory(path, &is_dir);
|
||||
if (s.ok()) {
|
||||
if (is_dir) {
|
||||
s = DestroyDir(env, path);
|
||||
} else {
|
||||
s = env->DeleteFile(path);
|
||||
}
|
||||
}
|
||||
if (!s.ok()) {
|
||||
break;
|
||||
}
|
||||
@@ -527,5 +537,46 @@ void SetupSyncPointsToMockDirectIO() {
|
||||
#endif
|
||||
}
|
||||
|
||||
void CorruptFile(const std::string& fname, int offset, int bytes_to_corrupt) {
|
||||
struct stat sbuf;
|
||||
if (stat(fname.c_str(), &sbuf) != 0) {
|
||||
// strerror is not thread-safe so should not be used in the "passing" path
|
||||
// of unit tests (sometimes parallelized) but is OK here where test fails
|
||||
const char* msg = strerror(errno);
|
||||
fprintf(stderr, "%s:%s\n", fname.c_str(), msg);
|
||||
assert(false);
|
||||
}
|
||||
|
||||
if (offset < 0) {
|
||||
// Relative to end of file; make it absolute
|
||||
if (-offset > sbuf.st_size) {
|
||||
offset = 0;
|
||||
} else {
|
||||
offset = static_cast<int>(sbuf.st_size + offset);
|
||||
}
|
||||
}
|
||||
if (offset > sbuf.st_size) {
|
||||
offset = static_cast<int>(sbuf.st_size);
|
||||
}
|
||||
if (offset + bytes_to_corrupt > sbuf.st_size) {
|
||||
bytes_to_corrupt = static_cast<int>(sbuf.st_size - offset);
|
||||
}
|
||||
|
||||
// Do it
|
||||
std::string contents;
|
||||
Status s = ReadFileToString(Env::Default(), fname, &contents);
|
||||
assert(s.ok());
|
||||
for (int i = 0; i < bytes_to_corrupt; i++) {
|
||||
contents[i + offset] ^= 0x80;
|
||||
}
|
||||
s = WriteStringToFile(Env::Default(), contents, fname);
|
||||
assert(s.ok());
|
||||
Options options;
|
||||
EnvOptions env_options;
|
||||
#ifndef ROCKSDB_LITE
|
||||
assert(!VerifySstFileChecksum(options, env_options, fname).ok());
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace test
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
@@ -812,5 +812,7 @@ void ResetTmpDirForDirectIO();
|
||||
// to the file system.
|
||||
void SetupSyncPointsToMockDirectIO();
|
||||
|
||||
void CorruptFile(const std::string& fname, int offset, int bytes_to_corrupt);
|
||||
|
||||
} // namespace test
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
Loaded 100 of 112 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user